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
@@ -0,0 +1,4 @@
1
+ // Ternary conditional operator - eval half
2
+ import { operator, compile } from '../../parse.js';
3
+
4
+ operator('?', (a, b, c) => (a = compile(a), b = compile(b), c = compile(c), ctx => a(ctx) ? b(ctx) : c(ctx)));
@@ -0,0 +1,5 @@
1
+ // Type operators - eval half (identity in JS)
2
+ import { operator, compile } from '../../parse.js';
3
+
4
+ operator('as', (a, b) => (a = compile(a), ctx => a(ctx)));
5
+ operator('is', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) instanceof b(ctx)));
@@ -0,0 +1,24 @@
1
+ // Unary keyword operators - eval half
2
+ import { operator, compile } from '../../parse.js';
3
+
4
+ operator('typeof', a => (a = compile(a), ctx => typeof a(ctx)));
5
+ operator('void', a => (a = compile(a), ctx => (a(ctx), undefined)));
6
+ operator('delete', a => {
7
+ if (a[0] === '.') {
8
+ const obj = compile(a[1]), key = a[2];
9
+ return ctx => delete obj(ctx)[key];
10
+ }
11
+ if (a[0] === '[]') {
12
+ const obj = compile(a[1]), key = compile(a[2]);
13
+ return ctx => delete obj(ctx)[key(ctx)];
14
+ }
15
+ return () => true;
16
+ });
17
+ operator('new', (call) => {
18
+ const target = compile(call?.[0] === '()' ? call[1] : call);
19
+ const args = call?.[0] === '()' ? call[2] : null;
20
+ const argList = !args ? () => [] :
21
+ args[0] === ',' ? (a => ctx => a.map(f => f(ctx)))(args.slice(1).map(compile)) :
22
+ (a => ctx => [a(ctx)])(compile(args));
23
+ return ctx => new (target(ctx))(...argList(ctx));
24
+ });
package/eval/prop.js ADDED
@@ -0,0 +1,16 @@
1
+ // Minimal property access - eval half
2
+ import { operator, compile } from '../parse.js';
3
+
4
+ const err = msg => { throw Error(msg) };
5
+ operator('[]', (a, b) => (b == null && err('Missing index'), a = compile(a), b = compile(b), ctx => a(ctx)[b(ctx)]));
6
+ operator('.', (a, b) => (a = compile(a), b = !b[0] ? b[1] : b, ctx => a(ctx)[b]));
7
+ operator('()', (a, b) => {
8
+ // Group: (expr) - no second argument means grouping, not call
9
+ if (b === undefined) return a == null ? err('Empty ()') : compile(a);
10
+ // Function call: a(b,c)
11
+ const args = !b ? () => [] :
12
+ b[0] === ',' ? (b = b.slice(1).map(compile), ctx => b.map(arg => arg(ctx))) :
13
+ (b = compile(b), ctx => [b(ctx)]);
14
+ a = compile(a);
15
+ return ctx => a(ctx)(...args(ctx));
16
+ });
package/eval/regex.js ADDED
@@ -0,0 +1,7 @@
1
+ // Regex literals - eval half: ['//', pattern, flags?] → RegExp
2
+ import { operator } from '../parse.js';
3
+
4
+ operator('//', (a, b) => {
5
+ const re = new RegExp(a, b || '');
6
+ return () => re;
7
+ });
package/eval/seq.js ADDED
@@ -0,0 +1,10 @@
1
+ // Sequence operators - eval half: returns last evaluated value
2
+ import { operator, compile } from '../parse.js';
3
+
4
+ const seq = (...args) => (args = args.map(compile), ctx => {
5
+ let r;
6
+ for (const arg of args) r = arg(ctx);
7
+ return r;
8
+ });
9
+ operator(',', seq);
10
+ operator(';', seq);
@@ -0,0 +1,15 @@
1
+ /**
2
+ * subscript - eval aggregator
3
+ * Runtime handlers for subscript dialect (must be paired with feature/subscript.js).
4
+ */
5
+
6
+ import './op/assignment.js';
7
+ import './op/logical.js';
8
+ import './op/bitwise.js';
9
+ import './op/comparison.js';
10
+ import './op/equality.js';
11
+ import './op/arithmetic.js';
12
+ import './op/increment.js';
13
+
14
+ import './seq.js';
15
+ import './access.js';
package/eval/switch.js ADDED
@@ -0,0 +1,27 @@
1
+ // Switch - eval half
2
+ import { operator, compile } from '../parse.js';
3
+ import { BREAK } from './loop.js';
4
+
5
+ operator('switch', (val, ...cases) => {
6
+ val = compile(val);
7
+ if (!cases.length) return ctx => val(ctx);
8
+
9
+ cases = cases.map(c => [
10
+ c[0] === 'case' ? compile(c[1]) : null,
11
+ (c[0] === 'case' ? c[2] : c[1])?.[0] === ';'
12
+ ? (c[0] === 'case' ? c[2] : c[1]).slice(1).map(compile)
13
+ : (c[0] === 'case' ? c[2] : c[1]) ? [compile(c[0] === 'case' ? c[2] : c[1])] : []
14
+ ]);
15
+
16
+ return ctx => {
17
+ const v = val(ctx);
18
+ let matched = false, r;
19
+ for (const [test, stmts] of cases)
20
+ if (matched || test === null || test(ctx) === v)
21
+ for (matched = true, i = 0; i < stmts.length; i++)
22
+ try { r = stmts[i](ctx); }
23
+ catch (e) { if (e === BREAK) return r; throw e; }
24
+ var i;
25
+ return r;
26
+ };
27
+ });
@@ -0,0 +1,14 @@
1
+ // Template literals - eval half
2
+ import { operator, compile } from '../parse.js';
3
+
4
+ operator('`', (...parts) => (parts = parts.map(compile), ctx => parts.map(p => p(ctx)).join('')));
5
+ operator('``', (tag, ...parts) => {
6
+ tag = compile(tag);
7
+ const strings = [], exprs = [];
8
+ for (const p of parts) {
9
+ if (Array.isArray(p) && p[0] === undefined) strings.push(p[1]);
10
+ else exprs.push(compile(p));
11
+ }
12
+ const strs = Object.assign([...strings], { raw: strings });
13
+ return ctx => tag(ctx)(strs, ...exprs.map(e => e(ctx)));
14
+ });
package/eval/try.js ADDED
@@ -0,0 +1,36 @@
1
+ // try/catch/finally/throw - eval half
2
+ import { operator, compile } from '../parse.js';
3
+ import { RETURN } from './op/arrow.js';
4
+ import { BREAK, CONTINUE } from './loop.js';
5
+
6
+ operator('try', (tryBody, ...clauses) => {
7
+ tryBody = tryBody ? compile(tryBody) : null;
8
+
9
+ let catchClause = clauses.find(c => c?.[0] === 'catch');
10
+ let finallyClause = clauses.find(c => c?.[0] === 'finally');
11
+
12
+ const catchParam = catchClause?.[1];
13
+ const catchBody = catchClause?.[2] ? compile(catchClause[2]) : null;
14
+ const finallyBody = finallyClause?.[1] ? compile(finallyClause[1]) : null;
15
+
16
+ return ctx => {
17
+ let result;
18
+ try {
19
+ result = tryBody?.(ctx);
20
+ } catch (e) {
21
+ if (e === BREAK || e === CONTINUE || e === RETURN) throw e;
22
+ if (catchParam !== null && catchParam !== undefined && catchBody) {
23
+ const had = catchParam in ctx, orig = ctx[catchParam];
24
+ ctx[catchParam] = e;
25
+ try { result = catchBody(ctx); }
26
+ finally { had ? ctx[catchParam] = orig : delete ctx[catchParam]; }
27
+ } else if (!catchBody) throw e;
28
+ }
29
+ finally {
30
+ finallyBody?.(ctx);
31
+ }
32
+ return result;
33
+ };
34
+ });
35
+
36
+ operator('throw', val => (val = compile(val), ctx => { throw val(ctx); }));
package/eval/unit.js ADDED
@@ -0,0 +1,10 @@
1
+ // Unit suffixes - eval half
2
+ import { operator, compile } from '../parse.js';
3
+ import { unit as parseUnit } from '../feature/unit.js';
4
+
5
+ export const unit = (...names) => {
6
+ parseUnit(...names);
7
+ names.forEach(name =>
8
+ operator(name, val => (val = compile(val), ctx => ({ value: val(ctx), unit: name })))
9
+ );
10
+ };
package/eval/var.js ADDED
@@ -0,0 +1,69 @@
1
+ // Variable declarations: let, const, var - eval half
2
+ import { operator, compile } from '../parse.js';
3
+
4
+ // Flatten comma into array: [',', a, b, c] → [a, b, c]
5
+ const flatten = items => items[0]?.[0] === ',' ? items[0].slice(1) : items;
6
+
7
+ // Destructure value into context
8
+ export const destructure = (pattern, value, ctx) => {
9
+ if (typeof pattern === 'string') { ctx[pattern] = value; return; }
10
+ const [op, ...raw] = pattern;
11
+ const items = flatten(raw);
12
+ if (op === '{}') {
13
+ const used = [];
14
+ for (const item of items) {
15
+ // Rest: {...rest}
16
+ if (Array.isArray(item) && item[0] === '...') {
17
+ const rest = {};
18
+ for (const k in value) if (!used.includes(k)) rest[k] = value[k];
19
+ ctx[item[1]] = rest;
20
+ break;
21
+ }
22
+ let key, binding, def;
23
+ // Shorthand: {x} → item is 'x'
24
+ // With default: {x = 1} → ['=', 'x', default]
25
+ // Rename: {x: y} → [':', 'x', 'y']
26
+ if (typeof item === 'string') { key = binding = item }
27
+ else if (item[0] === '=') { typeof item[1] === 'string' ? (key = binding = item[1]) : ([, key, binding] = item[1]); def = item[2] }
28
+ else { [, key, binding] = item }
29
+ used.push(key);
30
+ let val = value[key];
31
+ if (val === undefined && def) val = compile(def)(ctx);
32
+ destructure(binding, val, ctx);
33
+ }
34
+ }
35
+ else if (op === '[]') {
36
+ let i = 0;
37
+ for (const item of items) {
38
+ if (item === null) { i++; continue; }
39
+ if (Array.isArray(item) && item[0] === '...') { ctx[item[1]] = value.slice(i); break; }
40
+ let binding = item, def;
41
+ if (Array.isArray(item) && item[0] === '=') [, binding, def] = item;
42
+ let val = value[i++];
43
+ if (val === undefined && def) val = compile(def)(ctx);
44
+ destructure(binding, val, ctx);
45
+ }
46
+ }
47
+ };
48
+
49
+ const varOp = (...decls) => {
50
+ decls = decls.map(d => {
51
+ // Just identifier: let x
52
+ if (typeof d === 'string') return ctx => { ctx[d] = undefined; };
53
+ // Assignment: let x = 1
54
+ if (d[0] === '=') {
55
+ const [, pattern, val] = d;
56
+ const v = compile(val);
57
+ if (typeof pattern === 'string') return ctx => { ctx[pattern] = v(ctx); };
58
+ return ctx => destructure(pattern, v(ctx), ctx);
59
+ }
60
+ return compile(d);
61
+ });
62
+ return ctx => { for (const d of decls) d(ctx); };
63
+ };
64
+ operator('let', varOp);
65
+ operator('const', varOp);
66
+ // var just declares the variable (assignment handled by = operator)
67
+ operator('var', name => (typeof name === 'string'
68
+ ? ctx => { ctx[name] = undefined; }
69
+ : () => {}));
package/feature/access.js CHANGED
@@ -1,11 +1,8 @@
1
1
  /**
2
- * Property access: a.b, a[b], a(b), [1,2,3]
2
+ * Property access: a.b, a[b], a(b), [1,2,3] - parse half
3
3
  * For private fields (#x), see class.js
4
4
  */
5
- import { access, binary, operator, compile } from '../parse.js';
6
-
7
- // Block prototype chain attacks
8
- export const unsafe = k => k?.[0] === '_' && k[1] === '_' || k === 'constructor' || k === 'prototype';
5
+ import { access, binary } from '../parse.js';
9
6
 
10
7
  const ACCESS = 170;
11
8
 
@@ -17,56 +14,3 @@ binary('.', ACCESS);
17
14
 
18
15
  // a(b,c,d), a()
19
16
  access('()', ACCESS);
20
-
21
- // Compile
22
- const err = msg => { throw Error(msg) };
23
- operator('[]', (a, b) => {
24
- // Array literal: [1,2,3] - b is strictly undefined (AST length 2)
25
- if (b === undefined) {
26
- a = !a ? [] : a[0] === ',' ? a.slice(1) : [a];
27
- a = a.map(a => a == null ? (() => undefined) : a[0] === '...' ? (a = compile(a[1]), ctx => a(ctx)) : (a = compile(a), ctx => [a(ctx)]));
28
- return ctx => a.flatMap(a => a(ctx));
29
- }
30
- // Member access: a[b]
31
- if (b == null) err('Missing index');
32
- a = compile(a); b = compile(b);
33
- return ctx => { const k = b(ctx); return unsafe(k) ? undefined : a(ctx)[k]; };
34
- });
35
- operator('.', (a, b) => (a = compile(a), b = !b[0] ? b[1] : b, unsafe(b) ? () => undefined : ctx => a(ctx)[b]));
36
- operator('()', (a, b) => {
37
- // Group: (expr) - no second argument means grouping, not call
38
- if (b === undefined) return a == null ? err('Empty ()') : compile(a);
39
- // Validate: no sparse arguments in calls
40
- const hasSparse = n => n?.[0] === ',' && n.slice(1).some(a => a == null || hasSparse(a));
41
- if (hasSparse(b)) err('Empty argument');
42
- const args = !b ? () => [] :
43
- b[0] === ',' ? (b = b.slice(1).map(compile), ctx => b.map(arg => arg(ctx))) :
44
- (b = compile(b), ctx => [b(ctx)]);
45
- // Inline call handling for x(), a.b(), a[b](), (x)()
46
- return call(a, (obj, path, ctx) => obj[path](...args(ctx)));
47
- });
48
-
49
- // Left-value check (valid assignment target)
50
- export const isLval = n =>
51
- typeof n === 'string' ||
52
- (Array.isArray(n) && (
53
- n[0] === '.' || n[0] === '?.' ||
54
- (n[0] === '[]' && n.length === 3) || n[0] === '?.[]' ||
55
- (n[0] === '()' && n.length === 2 && isLval(n[1])) ||
56
- n[0] === '{}'
57
- ));
58
-
59
- // Simple call helper (no optional chaining) - handles x(), a.b(), a[b](), (x)()
60
- const call = (a, fn, obj, path) => (
61
- a == null ? err('Empty ()') :
62
- a[0] === '()' && a.length == 2 ? call(a[1], fn) :
63
- typeof a === 'string' ? ctx => fn(ctx, a, ctx) :
64
- a[0] === '.' ? (obj = compile(a[1]), path = a[2], ctx => fn(obj(ctx), path, ctx)) :
65
- a[0] === '?.' ? (obj = compile(a[1]), path = a[2], ctx => { const o = obj(ctx); return o == null ? undefined : fn(o, path, ctx); }) :
66
- a[0] === '[]' && a.length === 3 ? (obj = compile(a[1]), path = compile(a[2]), ctx => fn(obj(ctx), path(ctx), ctx)) :
67
- a[0] === '?.[]' ? (obj = compile(a[1]), path = compile(a[2]), ctx => { const o = obj(ctx); return o == null ? undefined : fn(o, path(ctx), ctx); }) :
68
- (a = compile(a), ctx => fn([a(ctx)], 0, ctx))
69
- );
70
-
71
- // Export as prop for backward compatibility with other features
72
- export const prop = call;
@@ -1,14 +1,10 @@
1
1
  /**
2
- * Object accessor properties (getters/setters)
2
+ * Object accessor properties (getters/setters) - parse half
3
3
  *
4
- * AST:
5
4
  * { get x() { body } } → ['{}', ['get', 'x', body]]
6
5
  * { set x(v) { body } } → ['{}', ['set', 'x', 'v', body]]
7
6
  */
8
- import { token, expr, skip, space, next, parse, cur, idx, operator, compile } from '../parse.js';
9
-
10
- // Accessor marker for object property definitions
11
- export const ACC = Symbol('accessor');
7
+ import { token, expr, skip, space, next, parse, cur, idx } from '../parse.js';
12
8
 
13
9
  const ASSIGN = 20;
14
10
  const OPAREN = 40, CPAREN = 41, OBRACE = 123, CBRACE = 125;
@@ -45,18 +41,3 @@ token('(', ASSIGN - 1, a => {
45
41
  skip();
46
42
  return [':', a, ['=>', ['()', params], expr(0, CBRACE) || null]];
47
43
  });
48
-
49
- // Compile
50
- operator('get', (name, body) => {
51
- body = body ? compile(body) : () => {};
52
- return ctx => [[ACC, name, {
53
- get: function() { const s = Object.create(ctx || {}); s.this = this; return body(s); }
54
- }]];
55
- });
56
-
57
- operator('set', (name, param, body) => {
58
- body = body ? compile(body) : () => {};
59
- return ctx => [[ACC, name, {
60
- set: function(v) { const s = Object.create(ctx || {}); s.this = this; s[param] = v; body(s); }
61
- }]];
62
- });
package/feature/asi.js CHANGED
@@ -1,32 +1,76 @@
1
- // ASI: newline at `;` precedence level triggers nary `;`
1
+ // ASI: layered onto parser via hooks.
2
+ // Parser core has no awareness of `;`, newlines, or block ends — everything
3
+ // dialect-specific (statement separator, line-break-sensitive call/access,
4
+ // `}` as implicit terminator) lives here.
2
5
  import { parse, prec, cur, idx, seek } from '../parse.js';
3
6
 
4
- // Set prec.asi before importing to customize (default: prec[';'])
7
+ const SPACE = 32, LF = 10, SEMI = 59, BLOCK_END = 125, BRACKET = 91, PAREN = 40;
8
+ // prec.asi customizable before importing (default: prec[';']).
5
9
  const lvl = prec.asi ?? prec[';'];
6
10
 
7
- const SPACE = 32, LF = 10, SEMI = 59, space = parse.space;
11
+ // Cache pristine parse.space / parse.step on first load so re-imports REPLACE
12
+ // (not chain) — otherwise `prec.asi=0; await import('./asi.js?v=disabled')`
13
+ // would leave previous ASI layers active.
14
+ const baseSpace = parse._baseSpace ??= parse.space;
15
+ const baseStep = parse._baseStep ??= parse.step;
8
16
 
9
- const lineBreak = (i = idx, c) => {
17
+ // LF immediately preceding the next non-space at i (used to detect `;\n`).
18
+ const hasLineBreak = (i, c) => {
10
19
  while ((c = cur.charCodeAt(i)) <= SPACE) {
11
20
  if (c === LF) return true;
12
21
  i++;
13
22
  }
23
+ return false;
24
+ };
25
+
26
+ // True iff an LF immediately precedes idx (only whitespace between).
27
+ // Computed on demand — robust against nested expr() calls eating the LF.
28
+ const lineBreak = (i = idx, c) => {
29
+ while (i-- > 0 && (c = cur.charCodeAt(i)) <= SPACE) if (c === LF) return true;
30
+ return false;
14
31
  };
15
32
 
16
- parse.space = () => {
17
- let cc;
18
- while ((cc = space()) === SEMI && lineBreak(idx + 1)) {
19
- seek(idx + 1);
20
- parse.newline = true;
33
+ // Override space: scan whitespace region for LFs (parse.js's space is
34
+ // LF-agnostic), and swallow `;\n` runs into ASI machinery (avoids deep nary `;`
35
+ // recursion on long files). parse.semi records that a hard terminator was
36
+ // consumed, so the surrounding expression knows to terminate.
37
+ parse.space = (cc, from) => {
38
+ for (;;) {
39
+ from = idx;
40
+ cc = baseSpace();
41
+ while (from < idx) if (cur.charCodeAt(from++) === LF) { parse.newline = true; break; }
42
+ if (cc === SEMI && hasLineBreak(idx + 1)) {
43
+ seek(idx + 1);
44
+ parse.newline = parse.semi = true;
45
+ continue;
46
+ }
47
+ return cc;
21
48
  }
22
- return cc;
49
+ };
50
+
51
+ // Sub-expression boundary: clear sticky flags on entry so outer state
52
+ // doesn't bleed into `(...)`, `[...]`, `{...}`. Top-level parse() also calls
53
+ // this with no args.
54
+ parse.enter = () => parse.newline = parse.semi = false;
55
+
56
+ // `}` closes a block — outer context sees an implicit newline.
57
+ parse.exit = (p, end) => { if (end === BLOCK_END) parse.newline = true; };
58
+
59
+ // Wrap iteration step: bail at high prec when `;\n` consumed; fire ASI before
60
+ // `[`/`(` on a new line; fire ASI when no operator continues across newline.
61
+ parse.step = (a, p, cc, expr) => {
62
+ if (parse.semi && p >= lvl) return false;
63
+ if (a && (parse.semi || ((cc === BRACKET || cc === PAREN) && lineBreak()))) return asi(a, p, expr) ?? null;
64
+ const nl = parse.newline;
65
+ return baseStep(a, p, cc, expr) ?? (a && nl ? asi(a, p, expr) ?? null : null);
23
66
  };
24
67
 
25
68
  let asiDepth = 0;
26
69
  const MAX_ASI_DEPTH = 100;
27
70
 
28
- parse.asi = (a, p, expr, b, items) => {
71
+ const asi = parse.asi = (a, p, expr, b, items) => {
29
72
  if (p >= lvl || asiDepth >= MAX_ASI_DEPTH) return;
73
+ parse.semi = false;
30
74
  // Bail if the inner expr didn't actually consume anything. Without this, a
31
75
  // lookup handler that returns a non-array sentinel (e.g. switch.js's
32
76
  // `reserve` flagging `case`/`default` inside a switch body) lets expr return
package/feature/async.js CHANGED
@@ -1,5 +1,5 @@
1
- // Async/await/yield: async function, async arrow, await, yield expressions
2
- import { unary, expr, skip, space, keyword, cur, idx, word, operator, compile } from '../parse.js';
1
+ // Async/await/yield: async function, async arrow, await, yield expressions - parse half
2
+ import { unary, expr, skip, space, keyword, cur, idx, word } from '../parse.js';
3
3
 
4
4
  const PREFIX = 140, ASSIGN = 20;
5
5
 
@@ -30,15 +30,3 @@ keyword('async', PREFIX, () => {
30
30
  const params = expr(ASSIGN - .5);
31
31
  return params && ['async', params];
32
32
  });
33
-
34
- // Compile
35
- operator('async', fn => {
36
- const inner = compile(fn);
37
- return ctx => {
38
- const f = inner(ctx);
39
- return async function(...args) { return f(...args); };
40
- };
41
- });
42
- operator('await', a => (a = compile(a), async ctx => await a(ctx)));
43
- operator('yield', a => (a = a ? compile(a) : null, ctx => { throw { __yield__: a ? a(ctx) : undefined }; }));
44
- operator('yield*', a => (a = compile(a), ctx => { throw { __yield_all__: a(ctx) }; }));
package/feature/class.js CHANGED
@@ -1,10 +1,9 @@
1
- // Class declarations and expressions
1
+ // Class declarations and expressions - parse half
2
2
  // class A extends B { ... }
3
- import { binary, unary, token, expr, space, next, parse, keyword, literal, word, operator, compile, skip, cur, idx } from '../parse.js';
3
+ import { binary, unary, token, expr, space, next, parse, keyword, word, skip } from '../parse.js';
4
4
  import { block } from './if.js';
5
5
 
6
6
  const TOKEN = 200, PREFIX = 140, COMP = 90;
7
- const STATIC = Symbol('static');
8
7
 
9
8
  // static member → ['static', member]
10
9
  unary('static', PREFIX);
@@ -35,35 +34,3 @@ keyword('class', TOKEN, () => {
35
34
  }
36
35
  return ['class', name, expr(TOKEN), block()];
37
36
  });
38
-
39
- // Compile
40
- const err = msg => { throw Error(msg) };
41
- operator('instanceof', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) instanceof b(ctx)));
42
- operator('class', (name, base, body) => {
43
- base = base ? compile(base) : null;
44
- body = body ? compile(body) : null;
45
- return ctx => {
46
- const Parent = base ? base(ctx) : Object;
47
- const cls = function(...args) {
48
- if (!(this instanceof cls)) return err('Class constructor must be called with new');
49
- const instance = base ? Reflect.construct(Parent, args, cls) : this;
50
- if (cls.prototype.__constructor__) cls.prototype.__constructor__.apply(instance, args);
51
- return instance;
52
- };
53
- Object.setPrototypeOf(cls.prototype, Parent.prototype);
54
- Object.setPrototypeOf(cls, Parent);
55
- if (body) {
56
- const methods = Object.create(ctx);
57
- methods['super'] = Parent;
58
- const entries = body(methods);
59
- const items = Array.isArray(entries) && typeof entries[0]?.[0] === 'string' ? entries : [];
60
- for (const [k, v] of items) {
61
- if (k === 'constructor') cls.prototype.__constructor__ = v;
62
- else cls.prototype[k] = v;
63
- }
64
- }
65
- if (name) ctx[name] = cls;
66
- return cls;
67
- };
68
- });
69
- operator('static', a => (a = compile(a), ctx => [[STATIC, a(ctx)]]));
@@ -1,28 +1,17 @@
1
1
  /**
2
- * Collection literals: arrays and objects (Justin feature)
2
+ * Collection literals: arrays and objects (Justin feature) - parse half
3
3
  *
4
4
  * [a, b, c]
5
5
  * {a: 1, b: 2}
6
6
  * {a, b} (shorthand)
7
- *
8
- * Block vs object detection (jessie):
9
- * ['{}', [';', ...]] → ['{', body] block with statements
10
- * ['{}', ['let', ...]] → ['{', body] block with declaration
11
- * ['{}', null] → ['{}', null] empty object {}
12
- * ['{}', 'a'] → ['{}', 'a'] object shorthand {a}
13
- * ['{}', [':', k, v]] → ['{}', ...] object literal
14
7
  */
15
- import { group, binary, operator, compile, parse, peek } from '../parse.js';
16
- import { ACC } from './accessor.js';
8
+ import { group, binary, parse, peek } from '../parse.js';
17
9
 
18
10
  const ASSIGN = 20, TOKEN = 200;
19
11
 
20
12
  // Allow {keyword: value} - prevent keyword match before colon
21
13
  parse.prop = pos => peek(pos) !== 58;
22
14
 
23
- // Object inner: null, string, or array starting with : , ... get set
24
- const isObject = a => a == null || typeof a === 'string' || [':', ',', '...', 'get', 'set'].includes(a[0]);
25
-
26
15
  // [a,b,c]
27
16
  group('[]', TOKEN);
28
17
 
@@ -31,33 +20,3 @@ group('{}', TOKEN);
31
20
 
32
21
  // a: b (colon operator for object properties)
33
22
  binary(':', ASSIGN - 1, true);
34
-
35
- // Compile - detect block vs object
36
- operator('{}', (a, b) => {
37
- if (b !== undefined) return;
38
- // Block: not an object pattern
39
- if (!isObject(a)) return compile(['{', a]);
40
- // Object literal
41
- a = !a ? [] : a[0] !== ',' ? [a] : a.slice(1);
42
- const props = a.map(p => compile(typeof p === 'string' ? [':', p, p] : p));
43
- return ctx => {
44
- const obj = {}, acc = {};
45
- for (const e of props.flatMap(f => f(ctx))) {
46
- if (e[0] === ACC) {
47
- const [, n, desc] = e;
48
- acc[n] = { ...acc[n], ...desc, configurable: true, enumerable: true };
49
- } else obj[e[0]] = e[1];
50
- }
51
- for (const n in acc) Object.defineProperty(obj, n, acc[n]);
52
- return obj;
53
- };
54
- });
55
-
56
- // Block statement - creates scope
57
- operator('{', body => {
58
- body = body ? compile(body) : () => undefined;
59
- return ctx => body(Object.create(ctx));
60
- });
61
-
62
- operator(':', (a, b) => (b = compile(b), Array.isArray(a) ?
63
- (a = compile(a), ctx => [[a(ctx), b(ctx)]]) : ctx => [[a, b(ctx)]]));
@@ -1,7 +1,6 @@
1
- // Function declarations and expressions
2
- import { space, next, parse, keyword, parens, expr, operator, compile, cur, idx, skip } from '../parse.js';
1
+ // Function declarations and expressions - parse half
2
+ import { space, next, parse, keyword, parens, cur, idx, skip } from '../parse.js';
3
3
  import { block } from './if.js';
4
- import { RETURN } from './op/arrow.js';
5
4
 
6
5
  const TOKEN = 200;
7
6
 
@@ -19,39 +18,3 @@ keyword('function', TOKEN, () => {
19
18
  const node = generator ? ['function*', name, parens() || null, block()] : ['function', name, parens() || null, block()];
20
19
  return node;
21
20
  });
22
-
23
- // Compile
24
- operator('function', (name, params, body) => {
25
- body = body ? compile(body) : () => undefined;
26
- // Normalize params: null → [], 'x' → ['x'], [',', 'a', 'b'] → ['a', 'b']
27
- const ps = !params ? [] : params[0] === ',' ? params.slice(1) : [params];
28
- // Check for rest param
29
- let restName = null, restIdx = -1;
30
- const last = ps[ps.length - 1];
31
- if (Array.isArray(last) && last[0] === '...') {
32
- restIdx = ps.length - 1;
33
- restName = last[1];
34
- ps.length--;
35
- }
36
- return ctx => {
37
- const fn = (...args) => {
38
- const l = {};
39
- ps.forEach((p, i) => l[p] = args[i]);
40
- if (restName) l[restName] = args.slice(restIdx);
41
- const fnCtx = new Proxy(l, {
42
- get: (l, k) => k in l ? l[k] : ctx[k],
43
- set: (l, k, v) => ((k in l ? l : ctx)[k] = v, true),
44
- has: (l, k) => k in l || k in ctx
45
- });
46
- try { return body(fnCtx); }
47
- catch (e) { if (e === RETURN) return e[0]; throw e; }
48
- };
49
- if (name) ctx[name] = fn;
50
- return fn;
51
- };
52
- });
53
-
54
- // Generator function (parse only, no implementation)
55
- operator('function*', (name, params, body) => {
56
- throw Error('Generator functions are not supported in evaluation');
57
- });
package/feature/if.js CHANGED
@@ -1,5 +1,5 @@
1
- // If/else statement - else consumed internally
2
- import { parse, space, skip, expr, err, keyword, parens, word, idx, seek, operator, compile } from '../parse.js';
1
+ // If/else statement - parse half. else consumed internally.
2
+ import { space, skip, expr, err, keyword, parens, word, idx, seek } from '../parse.js';
3
3
 
4
4
  const STATEMENT = 5, SEMI = 59;
5
5
 
@@ -27,9 +27,3 @@ keyword('if', STATEMENT + 1, () => {
27
27
  if (checkElse()) node.push(body());
28
28
  return node;
29
29
  });
30
-
31
- // Compile
32
- operator('if', (cond, body, alt) => {
33
- cond = compile(cond); body = compile(body); alt = alt !== undefined ? compile(alt) : null;
34
- return ctx => cond(ctx) ? body(ctx) : alt?.(ctx);
35
- });