exprforge 0.3.1 → 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/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.1",
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 };