exprforge 0.4.0 → 0.5.0
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 +545 -99
- package/ast.js +190 -2
- package/emitters/base.js +36 -4
- package/emitters/cobol.js +21 -2
- package/emitters/registry.js +12 -1
- package/evaluate.js +47 -14
- package/expr.js +61 -6
- package/fn.js +23 -4
- package/index.js +141 -8
- package/load-expr.js +112 -0
- package/macros.js +707 -0
- package/math/index.js +21 -0
- package/package.json +4 -1
- package/primitives.js +24 -0
- package/samples/macro-demo.js +56 -0
package/math/index.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
// itself (see package.json's "exports" map) — additive, not merged into the
|
|
12
12
|
// core barrel.
|
|
13
13
|
const { num, v, call, add, mul, sub, div, letIn, cmp, select } = require("../ast.js");
|
|
14
|
+
const { loadMacro } = require("../macros.js");
|
|
14
15
|
|
|
15
16
|
// Shared epsilon for all near-zero guards below. Exposed so callers can
|
|
16
17
|
// reuse it in their own cmp() calls for consistency with safeDiv/normalize3,
|
|
@@ -110,4 +111,24 @@ function clamp(val, lo, hi) {
|
|
|
110
111
|
return select(cmp(val, "<", lo), lo, select(cmp(val, ">", hi), hi, val));
|
|
111
112
|
}
|
|
112
113
|
|
|
114
|
+
// Also registered as macros (see macros.js/issue #21 ask 3), usable
|
|
115
|
+
// directly inside fn`...`/expr`...` template TEXT, not just from
|
|
116
|
+
// JS-authoring -- e.g. `fn`rodrigues(...): let b = cross3(ax, ay, az,
|
|
117
|
+
// bx, by, bz); let bLen = sqrt(b.rx^2 + b.ry^2 + b.rz^2); ...``. Every
|
|
118
|
+
// one of these already has exactly the signature loadMacro() wants
|
|
119
|
+
// ((...argNodes) => Node | {field: Node}) with no wrapping needed --
|
|
120
|
+
// this IS the "safe, inline-expanded, built from existing primitives"
|
|
121
|
+
// tier's worked example, not a separate mechanism layered on top of it.
|
|
122
|
+
// `clamp` is deliberately excluded: it's a 3-argument (val, lo, hi) helper
|
|
123
|
+
// whose own doc comment already flags `val` as re-evaluated three times
|
|
124
|
+
// if it's not cheap -- fine for JS-authoring callers who control that,
|
|
125
|
+
// but not offered as a macro name here since a fn`...` author has no
|
|
126
|
+
// equivalent "pass an already-let-bound reference" convention to reach
|
|
127
|
+
// for if they trip over the same cost.
|
|
128
|
+
loadMacro("dot3", dot3);
|
|
129
|
+
loadMacro("len3", len3);
|
|
130
|
+
loadMacro("cross3", cross3);
|
|
131
|
+
loadMacro("normalize3", normalize3);
|
|
132
|
+
loadMacro("safeDiv", safeDiv);
|
|
133
|
+
|
|
113
134
|
module.exports = { EPS, safeDiv, dot3, len3, cross3, normalize3, clamp };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "exprforge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
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",
|
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
"expr.js",
|
|
17
17
|
"fn.js",
|
|
18
18
|
"evaluate.js",
|
|
19
|
+
"macros.js",
|
|
20
|
+
"primitives.js",
|
|
21
|
+
"load-expr.js",
|
|
19
22
|
"build.js",
|
|
20
23
|
"emitters/",
|
|
21
24
|
"samples/",
|
package/primitives.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// exprforge/primitives.js
|
|
2
|
+
//
|
|
3
|
+
// The fixed, non-extensible set of built-in Math primitives (see ast.js's
|
|
4
|
+
// call() node, evaluate.js's CALLS table, and every emitters/<lang>.js's
|
|
5
|
+
// own `calls` table -- all three already have to agree on this same set
|
|
6
|
+
// of names, per evaluate.js's own header comment) plus each one's exact,
|
|
7
|
+
// required argument count.
|
|
8
|
+
//
|
|
9
|
+
// A standalone module with no dependencies of its own, specifically so
|
|
10
|
+
// evaluate.js, emitters/base.js, and macros.js can all require it without
|
|
11
|
+
// risking a cycle (macros.js already can't require evaluate.js back,
|
|
12
|
+
// since evaluate.js requires macros.js -- see macros.js's own comment on
|
|
13
|
+
// this). Update this table too if a new built-in primitive is ever
|
|
14
|
+
// added -- same "two lists have to stay in sync" precedent evaluate.js's
|
|
15
|
+
// own header comment already documents for CALLS vs. every emitter's
|
|
16
|
+
// `calls` table; this is a third.
|
|
17
|
+
const PRIMITIVE_ARITY = {
|
|
18
|
+
sqrt: 1, abs: 1, sin: 1, cos: 1, tan: 1, asin: 1, acos: 1, atan: 1,
|
|
19
|
+
log: 1, log2: 1, log10: 1, exp: 1, floor: 1, ceil: 1, round: 1,
|
|
20
|
+
trunc: 1, sign: 1,
|
|
21
|
+
pow: 2, atan2: 2, min: 2, max: 2, hypot: 2,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
module.exports = { PRIMITIVE_ARITY };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// exprforge/samples/macro-demo.js
|
|
2
|
+
// Not a worked example -- a conformance-test fixture for macros.js,
|
|
3
|
+
// same role samples/kitchen-sink.js plays for the core Math primitives
|
|
4
|
+
// and samples/math-demo.js plays for exprforge/math: exists purely so
|
|
5
|
+
// test/conformance.test.js can prove macro-expanded code agrees across
|
|
6
|
+
// every real target, not just evaluate() (which is blind to codegen --
|
|
7
|
+
// see the two macros registered below, both AST-fn-def-shaped
|
|
8
|
+
// (fn`...`-based via loadMacro), NOT plain-JS-function macros like
|
|
9
|
+
// math/index.js's (already covered by mathDemo). That distinction is
|
|
10
|
+
// the whole point of this file: an AST-fn-def macro's own "let"
|
|
11
|
+
// statements get automatically alpha-renamed on every call (see
|
|
12
|
+
// macros.js's toMacro/substituteAndRename), and a gensym'd name that
|
|
13
|
+
// isn't a valid identifier in every target once slipped past every
|
|
14
|
+
// check here and only broke Fortran (a leading underscore -- see
|
|
15
|
+
// substituteAndRename's own comment). hypotSq is called TWICE in one
|
|
16
|
+
// function specifically to prove two different calls' gensym'd names
|
|
17
|
+
// don't collide with each other either.
|
|
18
|
+
const { v, letIn, call, field, add } = require("../ast.js");
|
|
19
|
+
const { fn } = require("../fn.js");
|
|
20
|
+
const { loadMacro } = require("../macros.js");
|
|
21
|
+
|
|
22
|
+
// Single-value macro with its own internal "let" -- the gensym path.
|
|
23
|
+
loadMacro("hypotSq", fn(["hypotSq(a, b): let sq = a * a + b * b; return sq;"]));
|
|
24
|
+
|
|
25
|
+
// Multi-output macro whose outputs() is wrapped in its own let-chain --
|
|
26
|
+
// the exact shape that was originally misdetected as a single value
|
|
27
|
+
// before that got fixed (see toMacro's own comment in macros.js).
|
|
28
|
+
// Field access (.rx/.ry) on its result is exercised below.
|
|
29
|
+
loadMacro(
|
|
30
|
+
"rotate90",
|
|
31
|
+
fn([
|
|
32
|
+
`rotate90(x, y):
|
|
33
|
+
let rx = 0 - y;
|
|
34
|
+
let ry = x;
|
|
35
|
+
return { rx, ry };
|
|
36
|
+
`,
|
|
37
|
+
]),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
// "byy", not "by": BY is a reserved COBOL keyword -- same reason
|
|
41
|
+
// math-demo.js's MATH_DEMO_PARAMS uses it, see that file's own comment.
|
|
42
|
+
const macroDemoAst = {
|
|
43
|
+
name: "macroDemo",
|
|
44
|
+
params: ["ax", "ay", "bx", "byy"],
|
|
45
|
+
body: letIn(
|
|
46
|
+
"hSq1",
|
|
47
|
+
call("hypotSq", v("ax"), v("ay")),
|
|
48
|
+
letIn(
|
|
49
|
+
"hSq2",
|
|
50
|
+
call("hypotSq", v("bx"), v("byy")),
|
|
51
|
+
letIn("r", call("rotate90", v("ax"), v("ay")), add(v("hSq1"), v("hSq2"), field(v("r"), "rx"), field(v("r"), "ry"))),
|
|
52
|
+
),
|
|
53
|
+
),
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
module.exports = { macroDemoAst };
|