subscript 10.3.6 → 10.4.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.
Files changed (93) hide show
  1. package/README.md +9 -2
  2. package/eval/access.js +57 -0
  3. package/eval/accessor.js +19 -0
  4. package/eval/async.js +13 -0
  5. package/eval/class.js +35 -0
  6. package/eval/collection.js +41 -0
  7. package/eval/function.js +37 -0
  8. package/eval/if.js +7 -0
  9. package/eval/jessie.js +19 -0
  10. package/eval/justin.js +21 -0
  11. package/eval/loop.js +108 -0
  12. package/eval/module.js +8 -0
  13. package/eval/op/arithmetic.js +12 -0
  14. package/eval/op/arrow.js +35 -0
  15. package/eval/op/assign-logical.js +23 -0
  16. package/eval/op/assignment.js +15 -0
  17. package/eval/op/bitwise-unsigned.js +8 -0
  18. package/eval/op/bitwise.js +9 -0
  19. package/eval/op/comparison.js +7 -0
  20. package/eval/op/defer.js +4 -0
  21. package/eval/op/equality.js +5 -0
  22. package/eval/op/identity.js +5 -0
  23. package/eval/op/increment.js +13 -0
  24. package/eval/op/logical.js +6 -0
  25. package/eval/op/membership.js +4 -0
  26. package/eval/op/nullish.js +4 -0
  27. package/eval/op/optional.js +38 -0
  28. package/eval/op/pow.js +8 -0
  29. package/eval/op/range.js +13 -0
  30. package/eval/op/spread.js +4 -0
  31. package/eval/op/ternary.js +4 -0
  32. package/eval/op/type.js +5 -0
  33. package/eval/op/unary.js +24 -0
  34. package/eval/prop.js +16 -0
  35. package/eval/regex.js +7 -0
  36. package/eval/seq.js +10 -0
  37. package/eval/subscript.js +15 -0
  38. package/eval/switch.js +27 -0
  39. package/eval/template.js +14 -0
  40. package/eval/try.js +36 -0
  41. package/eval/unit.js +10 -0
  42. package/eval/var.js +69 -0
  43. package/feature/access.js +2 -58
  44. package/feature/accessor.js +2 -21
  45. package/feature/asi.js +55 -11
  46. package/feature/async.js +2 -14
  47. package/feature/class.js +2 -35
  48. package/feature/collection.js +2 -43
  49. package/feature/function.js +2 -39
  50. package/feature/if.js +2 -8
  51. package/feature/jessie.js +32 -0
  52. package/feature/justin.js +36 -0
  53. package/feature/loop.js +2 -109
  54. package/feature/module.js +1 -10
  55. package/feature/op/arithmetic.js +2 -13
  56. package/feature/op/arrow.js +2 -38
  57. package/feature/op/assign-logical.js +3 -24
  58. package/feature/op/assignment.js +2 -18
  59. package/feature/op/bitwise-unsigned.js +2 -8
  60. package/feature/op/bitwise.js +2 -14
  61. package/feature/op/comparison.js +2 -8
  62. package/feature/op/defer.js +2 -7
  63. package/feature/op/equality.js +2 -7
  64. package/feature/op/identity.js +2 -6
  65. package/feature/op/increment.js +2 -14
  66. package/feature/op/logical.js +2 -8
  67. package/feature/op/membership.js +2 -7
  68. package/feature/op/nullish.js +2 -5
  69. package/feature/op/optional.js +2 -41
  70. package/feature/op/pow.js +2 -10
  71. package/feature/op/range.js +2 -16
  72. package/feature/op/spread.js +2 -7
  73. package/feature/op/ternary.js +2 -7
  74. package/feature/op/type.js +2 -8
  75. package/feature/op/unary.js +2 -27
  76. package/feature/prop.js +2 -17
  77. package/feature/regex.js +3 -12
  78. package/feature/seq.js +2 -11
  79. package/feature/shebang.js +7 -0
  80. package/feature/subscript.js +23 -0
  81. package/feature/switch.js +2 -28
  82. package/feature/template.js +4 -16
  83. package/feature/try.js +4 -38
  84. package/feature/unit.js +4 -7
  85. package/feature/var.js +2 -70
  86. package/jessie.js +4 -25
  87. package/jessie.min.js +9 -8
  88. package/justin.js +4 -30
  89. package/justin.min.js +8 -8
  90. package/package.json +9 -1
  91. package/parse.js +27 -29
  92. package/subscript.js +7 -16
  93. package/subscript.min.js +5 -5
package/README.md CHANGED
@@ -58,7 +58,7 @@ let fn = jessie(`
58
58
  fn({}) // 120
59
59
  ```
60
60
 
61
- See [docs](./docs.md#presets) for full description.
61
+ Each preset has a parse-only entry — `subscript/feature/{subscript,justin,jessie}` — that drops the runtime (~50% smaller) for consumers that only need the AST. See [docs](./docs.md#presets) for full description.
62
62
 
63
63
 
64
64
  ## Syntax tree
@@ -83,7 +83,7 @@ See [spec.md](./spec.md).
83
83
 
84
84
  ## Extension
85
85
 
86
- Add operators, literals or custom syntax:
86
+ Add operators, literals or custom syntax. `binary`/`unary`/`token`/`keyword` register parsers; `operator` registers compilers — pair them as needed:
87
87
 
88
88
  ```js
89
89
  import justin, { binary, operator, compile } from 'subscript/justin.js'
@@ -98,6 +98,13 @@ operator('∩', (a, b) => ( // register compiler
98
98
  justin('[1,2,3] ∩ [2,3,4]')({}) // [2, 3]
99
99
  ```
100
100
 
101
+ For parse-only consumers, register only the parse half:
102
+
103
+ ```js
104
+ import { binary } from 'subscript/feature/justin.js'
105
+ binary('∩', 80)
106
+ ```
107
+
101
108
  See [docs.md](./docs.md) for full API.
102
109
 
103
110
 
package/eval/access.js ADDED
@@ -0,0 +1,57 @@
1
+ // Property access - eval half
2
+ import { operator, compile } from '../parse.js';
3
+
4
+ // Block prototype chain attacks
5
+ export const unsafe = k => k?.[0] === '_' && k[1] === '_' || k === 'constructor' || k === 'prototype';
6
+
7
+ const err = msg => { throw Error(msg) };
8
+ operator('[]', (a, b) => {
9
+ // Array literal: [1,2,3] - b is strictly undefined (AST length 2)
10
+ if (b === undefined) {
11
+ a = !a ? [] : a[0] === ',' ? a.slice(1) : [a];
12
+ a = a.map(a => a == null ? (() => undefined) : a[0] === '...' ? (a = compile(a[1]), ctx => a(ctx)) : (a = compile(a), ctx => [a(ctx)]));
13
+ return ctx => a.flatMap(a => a(ctx));
14
+ }
15
+ // Member access: a[b]
16
+ if (b == null) err('Missing index');
17
+ a = compile(a); b = compile(b);
18
+ return ctx => { const k = b(ctx); return unsafe(k) ? undefined : a(ctx)[k]; };
19
+ });
20
+ operator('.', (a, b) => (a = compile(a), b = !b[0] ? b[1] : b, unsafe(b) ? () => undefined : ctx => a(ctx)[b]));
21
+ operator('()', (a, b) => {
22
+ // Group: (expr) - no second argument means grouping, not call
23
+ if (b === undefined) return a == null ? err('Empty ()') : compile(a);
24
+ // Validate: no sparse arguments in calls
25
+ const hasSparse = n => n?.[0] === ',' && n.slice(1).some(a => a == null || hasSparse(a));
26
+ if (hasSparse(b)) err('Empty argument');
27
+ const args = !b ? () => [] :
28
+ b[0] === ',' ? (b = b.slice(1).map(compile), ctx => b.map(arg => arg(ctx))) :
29
+ (b = compile(b), ctx => [b(ctx)]);
30
+ // Inline call handling for x(), a.b(), a[b](), (x)()
31
+ return call(a, (obj, path, ctx) => obj[path](...args(ctx)));
32
+ });
33
+
34
+ // Left-value check (valid assignment target)
35
+ export const isLval = n =>
36
+ typeof n === 'string' ||
37
+ (Array.isArray(n) && (
38
+ n[0] === '.' || n[0] === '?.' ||
39
+ (n[0] === '[]' && n.length === 3) || n[0] === '?.[]' ||
40
+ (n[0] === '()' && n.length === 2 && isLval(n[1])) ||
41
+ n[0] === '{}'
42
+ ));
43
+
44
+ // Simple call helper (no optional chaining) - handles x(), a.b(), a[b](), (x)()
45
+ const call = (a, fn, obj, path) => (
46
+ a == null ? err('Empty ()') :
47
+ a[0] === '()' && a.length == 2 ? call(a[1], fn) :
48
+ typeof a === 'string' ? ctx => fn(ctx, a, ctx) :
49
+ a[0] === '.' ? (obj = compile(a[1]), path = a[2], ctx => fn(obj(ctx), path, ctx)) :
50
+ a[0] === '?.' ? (obj = compile(a[1]), path = a[2], ctx => { const o = obj(ctx); return o == null ? undefined : fn(o, path, ctx); }) :
51
+ a[0] === '[]' && a.length === 3 ? (obj = compile(a[1]), path = compile(a[2]), ctx => fn(obj(ctx), path(ctx), ctx)) :
52
+ a[0] === '?.[]' ? (obj = compile(a[1]), path = compile(a[2]), ctx => { const o = obj(ctx); return o == null ? undefined : fn(o, path(ctx), ctx); }) :
53
+ (a = compile(a), ctx => fn([a(ctx)], 0, ctx))
54
+ );
55
+
56
+ // Export as prop for backward compatibility with other features
57
+ export const prop = call;
@@ -0,0 +1,19 @@
1
+ // Object accessor properties (getters/setters) - eval half
2
+ import { operator, compile } from '../parse.js';
3
+
4
+ // Accessor marker for object property definitions
5
+ export const ACC = Symbol('accessor');
6
+
7
+ operator('get', (name, body) => {
8
+ body = body ? compile(body) : () => {};
9
+ return ctx => [[ACC, name, {
10
+ get: function() { const s = Object.create(ctx || {}); s.this = this; return body(s); }
11
+ }]];
12
+ });
13
+
14
+ operator('set', (name, param, body) => {
15
+ body = body ? compile(body) : () => {};
16
+ return ctx => [[ACC, name, {
17
+ set: function(v) { const s = Object.create(ctx || {}); s.this = this; s[param] = v; body(s); }
18
+ }]];
19
+ });
package/eval/async.js ADDED
@@ -0,0 +1,13 @@
1
+ // Async/await/yield - eval half
2
+ import { operator, compile } from '../parse.js';
3
+
4
+ operator('async', fn => {
5
+ const inner = compile(fn);
6
+ return ctx => {
7
+ const f = inner(ctx);
8
+ return async function(...args) { return f(...args); };
9
+ };
10
+ });
11
+ operator('await', a => (a = compile(a), async ctx => await a(ctx)));
12
+ operator('yield', a => (a = a ? compile(a) : null, ctx => { throw { __yield__: a ? a(ctx) : undefined }; }));
13
+ operator('yield*', a => (a = compile(a), ctx => { throw { __yield_all__: a(ctx) }; }));
package/eval/class.js ADDED
@@ -0,0 +1,35 @@
1
+ // Class declarations and expressions - eval half
2
+ import { operator, compile } from '../parse.js';
3
+
4
+ const STATIC = Symbol('static');
5
+ const err = msg => { throw Error(msg) };
6
+
7
+ operator('instanceof', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) instanceof b(ctx)));
8
+ operator('class', (name, base, body) => {
9
+ base = base ? compile(base) : null;
10
+ body = body ? compile(body) : null;
11
+ return ctx => {
12
+ const Parent = base ? base(ctx) : Object;
13
+ const cls = function(...args) {
14
+ if (!(this instanceof cls)) return err('Class constructor must be called with new');
15
+ const instance = base ? Reflect.construct(Parent, args, cls) : this;
16
+ if (cls.prototype.__constructor__) cls.prototype.__constructor__.apply(instance, args);
17
+ return instance;
18
+ };
19
+ Object.setPrototypeOf(cls.prototype, Parent.prototype);
20
+ Object.setPrototypeOf(cls, Parent);
21
+ if (body) {
22
+ const methods = Object.create(ctx);
23
+ methods['super'] = Parent;
24
+ const entries = body(methods);
25
+ const items = Array.isArray(entries) && typeof entries[0]?.[0] === 'string' ? entries : [];
26
+ for (const [k, v] of items) {
27
+ if (k === 'constructor') cls.prototype.__constructor__ = v;
28
+ else cls.prototype[k] = v;
29
+ }
30
+ }
31
+ if (name) ctx[name] = cls;
32
+ return cls;
33
+ };
34
+ });
35
+ operator('static', a => (a = compile(a), ctx => [[STATIC, a(ctx)]]));
@@ -0,0 +1,41 @@
1
+ // Collection literals - eval half
2
+ // Block vs object detection (jessie):
3
+ // ['{}', [';', ...]] → ['{', body] block with statements
4
+ // ['{}', ['let', ...]] → ['{', body] block with declaration
5
+ // ['{}', null] → ['{}', null] empty object {}
6
+ // ['{}', 'a'] → ['{}', 'a'] object shorthand {a}
7
+ // ['{}', [':', k, v]] → ['{}', ...] object literal
8
+ import { operator, compile } from '../parse.js';
9
+ import { ACC } from './accessor.js';
10
+
11
+ // Object inner: null, string, or array starting with : , ... get set
12
+ const isObject = a => a == null || typeof a === 'string' || [':', ',', '...', 'get', 'set'].includes(a[0]);
13
+
14
+ operator('{}', (a, b) => {
15
+ if (b !== undefined) return;
16
+ // Block: not an object pattern
17
+ if (!isObject(a)) return compile(['{', a]);
18
+ // Object literal
19
+ a = !a ? [] : a[0] !== ',' ? [a] : a.slice(1);
20
+ const props = a.map(p => compile(typeof p === 'string' ? [':', p, p] : p));
21
+ return ctx => {
22
+ const obj = {}, acc = {};
23
+ for (const e of props.flatMap(f => f(ctx))) {
24
+ if (e[0] === ACC) {
25
+ const [, n, desc] = e;
26
+ acc[n] = { ...acc[n], ...desc, configurable: true, enumerable: true };
27
+ } else obj[e[0]] = e[1];
28
+ }
29
+ for (const n in acc) Object.defineProperty(obj, n, acc[n]);
30
+ return obj;
31
+ };
32
+ });
33
+
34
+ // Block statement - creates scope
35
+ operator('{', body => {
36
+ body = body ? compile(body) : () => undefined;
37
+ return ctx => body(Object.create(ctx));
38
+ });
39
+
40
+ operator(':', (a, b) => (b = compile(b), Array.isArray(a) ?
41
+ (a = compile(a), ctx => [[a(ctx), b(ctx)]]) : ctx => [[a, b(ctx)]]));
@@ -0,0 +1,37 @@
1
+ // Function declarations and expressions - eval half
2
+ import { operator, compile } from '../parse.js';
3
+ import { RETURN } from './op/arrow.js';
4
+
5
+ operator('function', (name, params, body) => {
6
+ body = body ? compile(body) : () => undefined;
7
+ // Normalize params: null → [], 'x' → ['x'], [',', 'a', 'b'] → ['a', 'b']
8
+ const ps = !params ? [] : params[0] === ',' ? params.slice(1) : [params];
9
+ // Check for rest param
10
+ let restName = null, restIdx = -1;
11
+ const last = ps[ps.length - 1];
12
+ if (Array.isArray(last) && last[0] === '...') {
13
+ restIdx = ps.length - 1;
14
+ restName = last[1];
15
+ ps.length--;
16
+ }
17
+ return ctx => {
18
+ const fn = (...args) => {
19
+ const l = {};
20
+ ps.forEach((p, i) => l[p] = args[i]);
21
+ if (restName) l[restName] = args.slice(restIdx);
22
+ const fnCtx = new Proxy(l, {
23
+ get: (l, k) => k in l ? l[k] : ctx[k],
24
+ set: (l, k, v) => ((k in l ? l : ctx)[k] = v, true),
25
+ has: (l, k) => k in l || k in ctx
26
+ });
27
+ try { return body(fnCtx); }
28
+ catch (e) { if (e === RETURN) return e[0]; throw e; }
29
+ };
30
+ if (name) ctx[name] = fn;
31
+ return fn;
32
+ };
33
+ });
34
+
35
+ operator('function*', (name, params, body) => {
36
+ throw Error('Generator functions are not supported in evaluation');
37
+ });
package/eval/if.js ADDED
@@ -0,0 +1,7 @@
1
+ // If/else - eval half
2
+ import { operator, compile } from '../parse.js';
3
+
4
+ operator('if', (cond, body, alt) => {
5
+ cond = compile(cond); body = compile(body); alt = alt !== undefined ? compile(alt) : null;
6
+ return ctx => cond(ctx) ? body(ctx) : alt?.(ctx);
7
+ });
package/eval/jessie.js ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * jessie - eval aggregator
3
+ * Runtime handlers for jessie dialect (must be paired with feature/jessie.js).
4
+ */
5
+ import './justin.js';
6
+
7
+ import './var.js';
8
+ import './function.js';
9
+ import './async.js';
10
+ import './class.js';
11
+ import './regex.js';
12
+
13
+ import './if.js';
14
+ import './loop.js';
15
+ import './try.js';
16
+ import './switch.js';
17
+
18
+ import './module.js';
19
+ import './accessor.js';
package/eval/justin.js ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * justin - eval aggregator
3
+ * Runtime handlers for justin dialect (must be paired with feature/justin.js).
4
+ */
5
+ import './subscript.js';
6
+
7
+ import './op/identity.js';
8
+ import './op/nullish.js';
9
+ import './op/pow.js';
10
+ import './op/membership.js';
11
+ import './op/bitwise-unsigned.js';
12
+ import './op/assign-logical.js';
13
+
14
+ import './op/ternary.js';
15
+ import './op/arrow.js';
16
+ import './op/spread.js';
17
+ import './op/optional.js';
18
+ import './op/unary.js';
19
+
20
+ import './collection.js';
21
+ import './template.js';
package/eval/loop.js ADDED
@@ -0,0 +1,108 @@
1
+ // Loops - eval half
2
+ import { operator, compile } from '../parse.js';
3
+ import { RETURN } from './op/arrow.js';
4
+ import { destructure } from './var.js';
5
+
6
+ export const BREAK = Symbol('break'), CONTINUE = Symbol('continue');
7
+
8
+ operator('while', (cond, body) => {
9
+ cond = compile(cond); body = compile(body);
10
+ return ctx => {
11
+ let res;
12
+ while (cond(ctx)) try { res = body(ctx); } catch (e) {
13
+ if (e === BREAK) break;
14
+ if (e === CONTINUE) continue;
15
+ if (e === RETURN) return e[0];
16
+ throw e;
17
+ }
18
+ return res;
19
+ };
20
+ });
21
+
22
+ operator('do', (body, cond) => {
23
+ body = compile(body); cond = compile(cond);
24
+ return ctx => {
25
+ let res;
26
+ do try { res = body(ctx); } catch (e) {
27
+ if (e === BREAK) break;
28
+ if (e === CONTINUE) continue;
29
+ if (e === RETURN) return e[0];
30
+ throw e;
31
+ } while (cond(ctx));
32
+ return res;
33
+ };
34
+ });
35
+
36
+ operator('for', (head, body) => {
37
+ // Normalize head: [';', init, cond, step] or single expr (for-in/of)
38
+ if (Array.isArray(head) && head[0] === ';') {
39
+ let [, init, cond, step] = head;
40
+ init = init ? compile(init) : null;
41
+ cond = cond ? compile(cond) : () => true;
42
+ step = step ? compile(step) : null;
43
+ body = compile(body);
44
+ return ctx => {
45
+ let res;
46
+ for (init?.(ctx); cond(ctx); step?.(ctx)) try { res = body(ctx); } catch (e) {
47
+ if (e === BREAK) break;
48
+ if (e === CONTINUE) continue;
49
+ if (e === RETURN) return e[0];
50
+ throw e;
51
+ }
52
+ return res;
53
+ };
54
+ }
55
+ // For-in/of: head is ['in', lhs, rhs] or ['of', lhs, rhs]
56
+ if (Array.isArray(head) && (head[0] === 'in' || head[0] === 'of')) {
57
+ let [op, lhs, rhs] = head;
58
+ // Extract name from declaration: ['let', 'x'] → 'x'
59
+ if (Array.isArray(lhs) && (lhs[0] === 'let' || lhs[0] === 'const' || lhs[0] === 'var')) lhs = lhs[1];
60
+ if (op === 'in') return forIn(lhs, rhs, body);
61
+ if (op === 'of') return forOf(lhs, rhs, body);
62
+ }
63
+ });
64
+
65
+ const forOf = (name, iterable, body) => {
66
+ iterable = compile(iterable); body = compile(body);
67
+ const isPattern = Array.isArray(name);
68
+ return ctx => {
69
+ let res;
70
+ const prev = isPattern ? null : ctx[name];
71
+ for (const val of iterable(ctx)) {
72
+ if (isPattern) destructure(name, val, ctx); else ctx[name] = val;
73
+ try { res = body(ctx); } catch (e) {
74
+ if (e === BREAK) break;
75
+ if (e === CONTINUE) continue;
76
+ if (e === RETURN) return e[0];
77
+ throw e;
78
+ }
79
+ }
80
+ if (!isPattern) ctx[name] = prev;
81
+ return res;
82
+ };
83
+ };
84
+
85
+ const forIn = (name, obj, body) => {
86
+ obj = compile(obj); body = compile(body);
87
+ const isPattern = Array.isArray(name);
88
+ return ctx => {
89
+ let res;
90
+ const prev = isPattern ? null : ctx[name];
91
+ for (const key in obj(ctx)) {
92
+ if (isPattern) destructure(name, key, ctx); else ctx[name] = key;
93
+ try { res = body(ctx); } catch (e) {
94
+ if (e === BREAK) break;
95
+ if (e === CONTINUE) continue;
96
+ if (e === RETURN) return e[0];
97
+ throw e;
98
+ }
99
+ }
100
+ if (!isPattern) ctx[name] = prev;
101
+ return res;
102
+ };
103
+ };
104
+
105
+ operator('break', () => () => { throw BREAK; });
106
+ operator('continue', () => () => { throw CONTINUE; });
107
+ operator('return', val => (val = val !== undefined ? compile(val) : null,
108
+ ctx => { throw (RETURN[0] = val?.(ctx), RETURN); }));
package/eval/module.js ADDED
@@ -0,0 +1,8 @@
1
+ // Import/Export - eval stubs (parse-only semantics)
2
+ import { operator, compile } from '../parse.js';
3
+
4
+ operator('import', () => () => undefined);
5
+ operator('export', () => () => undefined);
6
+ operator('from', (a, b) => () => undefined);
7
+ operator('as', (a, b) => () => undefined);
8
+ operator('default', (a) => compile(a));
@@ -0,0 +1,12 @@
1
+ // Arithmetic operators - eval half
2
+ import { operator, compile } from '../../parse.js';
3
+
4
+ operator('+', (a, b) => b !== undefined ?
5
+ (a = compile(a), b = compile(b), ctx => a(ctx) + b(ctx)) :
6
+ (a = compile(a), ctx => +a(ctx)));
7
+ operator('-', (a, b) => b !== undefined ?
8
+ (a = compile(a), b = compile(b), ctx => a(ctx) - b(ctx)) :
9
+ (a = compile(a), ctx => -a(ctx)));
10
+ operator('*', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) * b(ctx)));
11
+ operator('/', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) / b(ctx)));
12
+ operator('%', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) % b(ctx)));
@@ -0,0 +1,35 @@
1
+ // Arrow function operator - eval half
2
+ import { operator, compile } from '../../parse.js';
3
+
4
+ // RETURN: array to hold value - reused, no allocation per throw
5
+ export const RETURN = [];
6
+
7
+ operator('=>', (a, b) => {
8
+ // Normalize params: () → [], x → [x], (a, b) → [a, b]
9
+ a = a?.[0] === '()' ? a[1] : a;
10
+ const ps = !a ? [] : a[0] === ',' ? a.slice(1) : [a];
11
+ // Check for rest param
12
+ let restIdx = -1, restName = null;
13
+ const last = ps[ps.length - 1];
14
+ if (Array.isArray(last) && last[0] === '...') {
15
+ restIdx = ps.length - 1;
16
+ restName = last[1];
17
+ ps.length--;
18
+ }
19
+ // Arrow body: {} is always block (need parens for object: ({...}))
20
+ // Block body returns undefined unless explicit return
21
+ const isBlock = b?.[0] === '{}';
22
+ b = compile(isBlock ? ['{', b[1]] : b);
23
+ return ctx => (...args) => {
24
+ const l = {};
25
+ ps.forEach((p, i) => l[p] = args[i]);
26
+ if (restName) l[restName] = args.slice(restIdx);
27
+ const fnCtx = new Proxy(l, {
28
+ get: (l, k) => k in l ? l[k] : ctx?.[k],
29
+ set: (l, k, v) => ((k in l ? l : ctx)[k] = v, true),
30
+ has: (l, k) => k in l || (ctx ? k in ctx : false)
31
+ });
32
+ try { const r = b(fnCtx); return isBlock ? undefined : r; }
33
+ catch (e) { if (e === RETURN) return e[0]; throw e; }
34
+ };
35
+ });
@@ -0,0 +1,23 @@
1
+ // Logical/nullish assignment operators + destructuring assignment - eval half
2
+ import { operator, compile } from '../../parse.js';
3
+ import { destructure } from '../var.js';
4
+ import { isLval, prop } from '../access.js';
5
+
6
+ const err = msg => { throw Error(msg) };
7
+
8
+ // Override = to support destructuring
9
+ operator('=', (a, b) => {
10
+ // Handle let/const/var declarations: ['=', ['let', pattern], value]
11
+ if (Array.isArray(a) && (a[0] === 'let' || a[0] === 'const' || a[0] === 'var')) {
12
+ const pattern = a[1];
13
+ b = compile(b);
14
+ if (typeof pattern === 'string') return ctx => { ctx[pattern] = b(ctx); };
15
+ return ctx => destructure(pattern, b(ctx), ctx);
16
+ }
17
+ isLval(a) || err('Invalid assignment target');
18
+ return (b = compile(b), prop(a, (obj, path, ctx) => obj[path] = b(ctx)));
19
+ });
20
+
21
+ operator('||=', (a, b) => (isLval(a) || err('Invalid assignment target'), b = compile(b), prop(a, (o, k, ctx) => o[k] ||= b(ctx))));
22
+ operator('&&=', (a, b) => (isLval(a) || err('Invalid assignment target'), b = compile(b), prop(a, (o, k, ctx) => o[k] &&= b(ctx))));
23
+ operator('??=', (a, b) => (isLval(a) || err('Invalid assignment target'), b = compile(b), prop(a, (o, k, ctx) => o[k] ??= b(ctx))));
@@ -0,0 +1,15 @@
1
+ // Assignment operators (C-family) - eval half
2
+ import { operator, compile } from '../../parse.js';
3
+
4
+ // Simple assign helper for x, a.b, a[b], (x)
5
+ const assign = (a, fn, obj, key) =>
6
+ typeof a === 'string' ? ctx => fn(ctx, a, ctx) :
7
+ a[0] === '.' ? (obj = compile(a[1]), key = a[2], ctx => fn(obj(ctx), key, ctx)) :
8
+ a[0] === '[]' && a.length === 3 ? (obj = compile(a[1]), key = compile(a[2]), ctx => fn(obj(ctx), key(ctx), ctx)) :
9
+ a[0] === '()' && a.length === 2 ? assign(a[1], fn) :
10
+ (() => { throw Error('Invalid assignment target') })();
11
+
12
+ const ops = { '=': (o,k,v)=>o[k]=v, '+=': (o,k,v)=>o[k]+=v, '-=': (o,k,v)=>o[k]-=v, '*=': (o,k,v)=>o[k]*=v,
13
+ '/=': (o,k,v)=>o[k]/=v, '%=': (o,k,v)=>o[k]%=v, '|=': (o,k,v)=>o[k]|=v, '&=': (o,k,v)=>o[k]&=v,
14
+ '^=': (o,k,v)=>o[k]^=v, '>>=': (o,k,v)=>o[k]>>=v, '<<=': (o,k,v)=>o[k]<<=v };
15
+ for (const op in ops) operator(op, (a, b) => (b = compile(b), assign(a, (o, k, ctx) => ops[op](o, k, b(ctx)))));
@@ -0,0 +1,8 @@
1
+ // Unsigned right shift operators - eval half
2
+ import { operator, compile } from '../../parse.js';
3
+ import { isLval, prop } from '../access.js';
4
+
5
+ const err = msg => { throw Error(msg) };
6
+
7
+ operator('>>>', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) >>> b(ctx)));
8
+ operator('>>>=', (a, b) => (isLval(a) || err('Invalid assignment target'), b = compile(b), prop(a, (o, k, ctx) => o[k] >>>= b(ctx))));
@@ -0,0 +1,9 @@
1
+ // Bitwise operators - eval half
2
+ import { operator, compile } from '../../parse.js';
3
+
4
+ operator('~', a => (a = compile(a), ctx => ~a(ctx)));
5
+ operator('|', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) | b(ctx)));
6
+ operator('&', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) & b(ctx)));
7
+ operator('^', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) ^ b(ctx)));
8
+ operator('>>', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) >> b(ctx)));
9
+ operator('<<', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) << b(ctx)));
@@ -0,0 +1,7 @@
1
+ // Comparison operators - eval half
2
+ import { operator, compile } from '../../parse.js';
3
+
4
+ operator('>', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) > b(ctx)));
5
+ operator('<', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) < b(ctx)));
6
+ operator('>=', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) >= b(ctx)));
7
+ operator('<=', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) <= b(ctx)));
@@ -0,0 +1,4 @@
1
+ // Defer operator - eval half
2
+ import { operator, compile } from '../../parse.js';
3
+
4
+ operator('defer', a => (a = compile(a), ctx => { ctx.__deferred__ = ctx.__deferred__ || []; ctx.__deferred__.push(a); }));
@@ -0,0 +1,5 @@
1
+ // Equality operators (base) - eval half
2
+ import { operator, compile } from '../../parse.js';
3
+
4
+ operator('==', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) == b(ctx)));
5
+ operator('!=', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) != b(ctx)));
@@ -0,0 +1,5 @@
1
+ // Identity operators - eval half
2
+ import { operator, compile } from '../../parse.js';
3
+
4
+ operator('===', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) === b(ctx)));
5
+ operator('!==', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) !== b(ctx)));
@@ -0,0 +1,13 @@
1
+ // Increment/decrement operators - eval half
2
+ // b=null means postfix, b=undefined means prefix
3
+ import { operator, compile } from '../../parse.js';
4
+
5
+ const inc = (a, fn, obj, key) =>
6
+ typeof a === 'string' ? ctx => fn(ctx, a) :
7
+ a[0] === '.' ? (obj = compile(a[1]), key = a[2], ctx => fn(obj(ctx), key)) :
8
+ a[0] === '[]' && a.length === 3 ? (obj = compile(a[1]), key = compile(a[2]), ctx => fn(obj(ctx), key(ctx))) :
9
+ a[0] === '()' && a.length === 2 ? inc(a[1], fn) :
10
+ (() => { throw Error('Invalid increment target') })();
11
+
12
+ operator('++', (a, b) => inc(a, b === null ? (o, k) => o[k]++ : (o, k) => ++o[k]));
13
+ operator('--', (a, b) => inc(a, b === null ? (o, k) => o[k]-- : (o, k) => --o[k]));
@@ -0,0 +1,6 @@
1
+ // Logical operators (base) - eval half
2
+ import { operator, compile } from '../../parse.js';
3
+
4
+ operator('!', a => (a = compile(a), ctx => !a(ctx)));
5
+ operator('||', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) || b(ctx)));
6
+ operator('&&', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) && b(ctx)));
@@ -0,0 +1,4 @@
1
+ // Membership operator - eval half
2
+ import { operator, compile } from '../../parse.js';
3
+
4
+ operator('in', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) in b(ctx)));
@@ -0,0 +1,4 @@
1
+ // Nullish coalescing operator - eval half
2
+ import { operator, compile } from '../../parse.js';
3
+
4
+ operator('??', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) ?? b(ctx)));
@@ -0,0 +1,38 @@
1
+ // Optional chaining operators - eval half
2
+ import { operator, compile } from '../../parse.js';
3
+ import { unsafe } from '../access.js';
4
+
5
+ operator('?.', (a, b) => (a = compile(a), unsafe(b) ? () => undefined : ctx => a(ctx)?.[b]));
6
+ operator('?.[]', (a, b) => (a = compile(a), b = compile(b), ctx => { const k = b(ctx); return unsafe(k) ? undefined : a(ctx)?.[k]; }));
7
+ operator('?.()', (a, b) => {
8
+ const args = !b ? () => [] :
9
+ b[0] === ',' ? (b = b.slice(1).map(compile), ctx => b.map(arg => arg(ctx))) :
10
+ (b = compile(b), ctx => [b(ctx)]);
11
+
12
+ // Handle nested optional chain: a?.method?.() or a?.["method"]?.()
13
+ if (a[0] === '?.') {
14
+ const container = compile(a[1]);
15
+ const prop = a[2];
16
+ return unsafe(prop) ? () => undefined :
17
+ ctx => { const c = container(ctx); return c?.[prop]?.(...args(ctx)); };
18
+ }
19
+ if (a[0] === '?.[]') {
20
+ const container = compile(a[1]);
21
+ const prop = compile(a[2]);
22
+ return ctx => { const c = container(ctx); const p = prop(ctx); return unsafe(p) ? undefined : c?.[p]?.(...args(ctx)); };
23
+ }
24
+ // Handle a?.() where a is a.method or a[method] - need to bind this
25
+ if (a[0] === '.') {
26
+ const obj = compile(a[1]);
27
+ const prop = a[2];
28
+ return unsafe(prop) ? () => undefined :
29
+ ctx => { const o = obj(ctx); return o?.[prop]?.(...args(ctx)); };
30
+ }
31
+ if (a[0] === '[]' && a.length === 3) {
32
+ const obj = compile(a[1]);
33
+ const prop = compile(a[2]);
34
+ return ctx => { const o = obj(ctx); const p = prop(ctx); return unsafe(p) ? undefined : o?.[p]?.(...args(ctx)); };
35
+ }
36
+ const fn = compile(a);
37
+ return ctx => fn(ctx)?.(...args(ctx));
38
+ });
package/eval/op/pow.js ADDED
@@ -0,0 +1,8 @@
1
+ // Exponentiation operator - eval half
2
+ import { operator, compile } from '../../parse.js';
3
+ import { isLval, prop } from '../access.js';
4
+
5
+ const err = msg => { throw Error(msg) };
6
+
7
+ operator('**', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) ** b(ctx)));
8
+ operator('**=', (a, b) => (isLval(a) || err('Invalid assignment target'), b = compile(b), prop(a, (obj, path, ctx) => obj[path] **= b(ctx))));
@@ -0,0 +1,13 @@
1
+ // Range operators - eval half
2
+ import { operator, compile } from '../../parse.js';
3
+
4
+ operator('..', (a, b) => (a = compile(a), b = compile(b), ctx => {
5
+ const start = a(ctx), end = b(ctx), arr = [];
6
+ for (let i = start; i <= end; i++) arr.push(i);
7
+ return arr;
8
+ }));
9
+ operator('..<', (a, b) => (a = compile(a), b = compile(b), ctx => {
10
+ const start = a(ctx), end = b(ctx), arr = [];
11
+ for (let i = start; i < end; i++) arr.push(i);
12
+ return arr;
13
+ }));
@@ -0,0 +1,4 @@
1
+ // Spread/rest operator - eval half (for arrays/objects spread)
2
+ import { operator, compile } from '../../parse.js';
3
+
4
+ operator('...', a => (a = compile(a), ctx => Object.entries(a(ctx))));