exprforge 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -0
- package/expr.js +11 -0
- package/fn.js +55 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -355,6 +355,35 @@ deferred-to-`collectLets` behavior every hand-built `letIn`/`letChain`
|
|
|
355
355
|
already has. A `fn` body with no `let` statements at all is just
|
|
356
356
|
`return expr;`, equivalent to a bare `expr` call.
|
|
357
357
|
|
|
358
|
+
### Optional signature line
|
|
359
|
+
|
|
360
|
+
Writing `name`/`params` separately, next to the body, is fine for a
|
|
361
|
+
one-off — but `fn` can carry them too, with a leading `name(params):`
|
|
362
|
+
line:
|
|
363
|
+
|
|
364
|
+
```js
|
|
365
|
+
const { fn, emitAll, evaluate } = require("exprforge");
|
|
366
|
+
|
|
367
|
+
const normalize2 = fn`
|
|
368
|
+
normalize2(x, y):
|
|
369
|
+
let mag = sqrt(x^2 + y^2);
|
|
370
|
+
return { nx: x / mag, ny: y / mag };
|
|
371
|
+
`;
|
|
372
|
+
// normalize2 is now the full {name, params, body} shape directly --
|
|
373
|
+
// no wrapping object needed.
|
|
374
|
+
|
|
375
|
+
evaluate(normalize2, [3, 4]); // { nx: 0.6, ny: 0.8 }
|
|
376
|
+
emitAll(normalize2).rust.source; // ready to use immediately
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
This changes `fn`'s return type based on what you wrote, deliberately:
|
|
380
|
+
no signature → a bare `Node`, exactly as above and fully backward
|
|
381
|
+
compatible; a signature present → the full `{name, params, body}`
|
|
382
|
+
object. `let`/`return` still can't be used as a function name — a
|
|
383
|
+
signature is told apart from a statement by the same rule that tells
|
|
384
|
+
`let`/`return` apart from any other identifier, so naming a function
|
|
385
|
+
`let` just parses as (and fails as) a `let` statement instead.
|
|
386
|
+
|
|
358
387
|
## Printing an AST back out, and a native evaluator
|
|
359
388
|
|
|
360
389
|
Two things that fall out of `fn` existing: `emitters.expr` is a real,
|
package/expr.js
CHANGED
|
@@ -136,6 +136,17 @@ class Parser {
|
|
|
136
136
|
return this.tokens[this.i];
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
// One token beyond peek() -- always safe, since `tokens` always ends
|
|
140
|
+
// with an EOF token appended once by expr()/fn() before the parser
|
|
141
|
+
// ever runs, so there's always something to look at even right at
|
|
142
|
+
// the end of input. Added for fn.js's signature-line lookahead
|
|
143
|
+
// ("IDENT followed by '(' -- is this a signature, or the start of a
|
|
144
|
+
// statement?"), which expr()'s own single-token-lookahead grammar
|
|
145
|
+
// never needed.
|
|
146
|
+
peekNext() {
|
|
147
|
+
return this.tokens[this.i + 1];
|
|
148
|
+
}
|
|
149
|
+
|
|
139
150
|
next() {
|
|
140
151
|
return this.tokens[this.i++];
|
|
141
152
|
}
|
package/fn.js
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
// statement-sequence wrapper around that, lowering to the real ast.js
|
|
11
11
|
// builders (letChain, outputs), never a new node shape:
|
|
12
12
|
//
|
|
13
|
-
// program := stmt* returnStmt
|
|
13
|
+
// program := signature? stmt* returnStmt
|
|
14
|
+
// signature := IDENT "(" (IDENT ("," IDENT)*)? ")" ":"
|
|
14
15
|
// stmt := "let" IDENT "=" expression ";"
|
|
15
16
|
// returnStmt := "return" expression ";"
|
|
16
17
|
// | "return" "{" IDENT ":" expression ("," IDENT ":" expression)* "}" ";"
|
|
@@ -24,7 +25,21 @@
|
|
|
24
25
|
// Duplicate let-names are deliberately NOT checked here -- letChain()
|
|
25
26
|
// doesn't check either (ast.js); collectLets() already does, at
|
|
26
27
|
// emission time. Same "defer semantic validation to emission" precedent
|
|
27
|
-
// expr.js itself follows for function/call names.
|
|
28
|
+
// expr.js itself follows for function/call names. Duplicate param names
|
|
29
|
+
// go unchecked for the same reason, and because nothing else in this
|
|
30
|
+
// project validates that today either -- {name, params, body} objects
|
|
31
|
+
// were always hand-built plain JS before this, with no infrastructure
|
|
32
|
+
// for it.
|
|
33
|
+
//
|
|
34
|
+
// The signature line is entirely optional, which makes fn's return type
|
|
35
|
+
// conditional on what you actually wrote: no signature -> a bare Node,
|
|
36
|
+
// exactly as before this was added (fully backward compatible -- every
|
|
37
|
+
// fn`...` template written before this feature existed still parses
|
|
38
|
+
// identically); a signature present -> the full {name, params, body}
|
|
39
|
+
// shape, usable directly in emitAll()/evaluate() with no wrapping. This
|
|
40
|
+
// was a deliberate API choice, not an oversight -- see the GitHub issue
|
|
41
|
+
// this shipped from for the alternative considered (a separate,
|
|
42
|
+
// always-full-definition tag) and why this was preferred.
|
|
28
43
|
const { letChain, outputs } = require("./ast.js");
|
|
29
44
|
const { Parser, tokenizeSegment } = require("./expr.js");
|
|
30
45
|
|
|
@@ -77,7 +92,42 @@ function parseReturnStatement(parser) {
|
|
|
77
92
|
return node;
|
|
78
93
|
}
|
|
79
94
|
|
|
95
|
+
// Signature lookahead needs 2 tokens, not 1: an IDENT that isn't "let"/
|
|
96
|
+
// "return" (those always start a statement instead), immediately
|
|
97
|
+
// followed by "(". That's a complete, unambiguous rule given the
|
|
98
|
+
// grammar above -- a fn`...` program only ever starts with a signature,
|
|
99
|
+
// a "let", or a "return", so there's no fourth case this could be
|
|
100
|
+
// confused with. (A malformed body missing its "let"/"return" entirely
|
|
101
|
+
// -- e.g. a bare `` fn`sqrt(x)` `` someone forgot the "return" on --
|
|
102
|
+
// still ends up an error either way, just reported as a missing ":"
|
|
103
|
+
// rather than a missing "return"; not worth deeper lookahead to improve
|
|
104
|
+
// one malformed-input error message.)
|
|
105
|
+
function looksLikeSignature(parser) {
|
|
106
|
+
const t = parser.peek();
|
|
107
|
+
if (t.type !== "IDENT" || t.value === "let" || t.value === "return") return false;
|
|
108
|
+
const next = parser.peekNext();
|
|
109
|
+
return next.type === "OP" && next.value === "(";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function parseSignature(parser) {
|
|
113
|
+
const name = expectIdent(parser, "as the function name starting a fn`...` signature");
|
|
114
|
+
parser.expectOp("(");
|
|
115
|
+
const params = [];
|
|
116
|
+
if (!parser.isOp(")")) {
|
|
117
|
+
params.push(expectIdent(parser, "as a parameter name in a fn`...` signature"));
|
|
118
|
+
while (parser.isOp(",")) {
|
|
119
|
+
parser.next();
|
|
120
|
+
params.push(expectIdent(parser, "as a parameter name in a fn`...` signature"));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
parser.expectOp(")");
|
|
124
|
+
parser.expectOp(":");
|
|
125
|
+
return { name, params };
|
|
126
|
+
}
|
|
127
|
+
|
|
80
128
|
function parseProgram(parser) {
|
|
129
|
+
const signature = looksLikeSignature(parser) ? parseSignature(parser) : null;
|
|
130
|
+
|
|
81
131
|
const bindings = [];
|
|
82
132
|
while (isKeyword(parser, "let")) {
|
|
83
133
|
bindings.push(parseLetStatement(parser));
|
|
@@ -86,7 +136,9 @@ function parseProgram(parser) {
|
|
|
86
136
|
parser.error('expected "return" (a fn`...` body is zero or more "let" statements followed by a "return")');
|
|
87
137
|
}
|
|
88
138
|
const body = parseReturnStatement(parser);
|
|
89
|
-
|
|
139
|
+
const result = bindings.length > 0 ? letChain(bindings, body) : body;
|
|
140
|
+
|
|
141
|
+
return signature ? { name: signature.name, params: signature.params, body: result } : result;
|
|
90
142
|
}
|
|
91
143
|
|
|
92
144
|
// Same token-splicing loop expr() uses in expr.js -- see that file's
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "exprforge",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Author a math expression once as an AST (or readable infix text via expr/fn), emit identical-behavior implementations in JS, TypeScript, Python, C#, Lua, QB64, C, Java, Go, Rust, Perl, PHP, Julia, Fortran, Zig, Scheme, and COBOL, plus a native evaluator and its own readable syntax printer.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "commonjs",
|