subscript 9.2.0 → 10.0.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 (83) hide show
  1. package/README.md +101 -184
  2. package/feature/access.js +68 -7
  3. package/feature/accessor.js +49 -0
  4. package/feature/asi.js +15 -0
  5. package/feature/async.js +45 -0
  6. package/feature/block.js +41 -0
  7. package/feature/class.js +69 -0
  8. package/feature/collection.js +40 -0
  9. package/feature/comment.js +25 -5
  10. package/feature/control.js +2 -142
  11. package/feature/destruct.js +33 -0
  12. package/feature/function.js +44 -0
  13. package/feature/group.js +13 -9
  14. package/feature/if.js +23 -38
  15. package/feature/literal.js +13 -0
  16. package/feature/loop.js +107 -106
  17. package/feature/module.js +42 -0
  18. package/feature/number.js +41 -38
  19. package/feature/op/arithmetic.js +29 -0
  20. package/feature/op/arrow.js +33 -0
  21. package/feature/op/assign-logical.js +33 -0
  22. package/feature/op/assignment.js +26 -0
  23. package/feature/op/bitwise-unsigned.js +17 -0
  24. package/feature/op/bitwise.js +29 -0
  25. package/feature/op/comparison.js +19 -0
  26. package/feature/op/defer.js +15 -0
  27. package/feature/op/equality.js +16 -0
  28. package/feature/op/identity.js +15 -0
  29. package/feature/op/increment.js +23 -0
  30. package/feature/op/logical.js +21 -0
  31. package/feature/op/membership.js +17 -0
  32. package/feature/op/nullish.js +13 -0
  33. package/feature/op/optional.js +61 -0
  34. package/feature/op/pow.js +19 -0
  35. package/feature/op/range.js +26 -0
  36. package/feature/op/spread.js +15 -0
  37. package/feature/op/ternary.js +15 -0
  38. package/feature/op/type.js +18 -0
  39. package/feature/op/unary.js +41 -0
  40. package/feature/prop.js +34 -0
  41. package/feature/regex.js +31 -0
  42. package/feature/seq.js +21 -0
  43. package/feature/string.js +24 -17
  44. package/feature/switch.js +48 -0
  45. package/feature/template.js +39 -0
  46. package/feature/try.js +57 -0
  47. package/feature/unit.js +35 -0
  48. package/feature/var.js +51 -41
  49. package/jessie.js +31 -0
  50. package/jessie.min.js +8 -0
  51. package/justin.js +39 -48
  52. package/justin.min.js +8 -4
  53. package/package.json +15 -16
  54. package/parse.js +146 -0
  55. package/subscript.d.ts +45 -5
  56. package/subscript.js +62 -22
  57. package/subscript.min.js +5 -4
  58. package/util/bundle.js +507 -0
  59. package/util/stringify.js +172 -0
  60. package/feature/add.js +0 -22
  61. package/feature/array.js +0 -11
  62. package/feature/arrow.js +0 -23
  63. package/feature/assign.js +0 -11
  64. package/feature/bitwise.js +0 -11
  65. package/feature/bool.js +0 -5
  66. package/feature/call.js +0 -15
  67. package/feature/compare.js +0 -11
  68. package/feature/increment.js +0 -11
  69. package/feature/logic.js +0 -11
  70. package/feature/mult.js +0 -25
  71. package/feature/object.js +0 -17
  72. package/feature/optional.js +0 -23
  73. package/feature/pow.js +0 -5
  74. package/feature/shift.js +0 -12
  75. package/feature/spread.js +0 -6
  76. package/feature/ternary.js +0 -10
  77. package/src/compile.d.ts +0 -17
  78. package/src/compile.js +0 -28
  79. package/src/const.js +0 -45
  80. package/src/parse.d.ts +0 -22
  81. package/src/parse.js +0 -113
  82. package/src/stringify.js +0 -27
  83. /package/{LICENSE → license} +0 -0
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Collection literals: arrays and objects (Justin feature)
3
+ *
4
+ * [a, b, c]
5
+ * {a: 1, b: 2}
6
+ * {a, b} (shorthand)
7
+ */
8
+ import { group, binary, operator, compile } from '../parse.js';
9
+ import { ACC } from './accessor.js';
10
+
11
+ const ASSIGN = 20, TOKEN = 200;
12
+
13
+ // [a,b,c]
14
+ group('[]', TOKEN);
15
+
16
+ // {a:1, b:2, c:3}
17
+ group('{}', TOKEN);
18
+
19
+ // a: b (colon operator for object properties)
20
+ binary(':', ASSIGN - 1, true);
21
+
22
+ // Compile
23
+ operator('{}', (a, b) => {
24
+ if (b !== undefined) return;
25
+ a = !a ? [] : a[0] !== ',' ? [a] : a.slice(1);
26
+ const props = a.map(p => compile(typeof p === 'string' ? [':', p, p] : p));
27
+ return ctx => {
28
+ const obj = {}, acc = {};
29
+ for (const e of props.flatMap(f => f(ctx))) {
30
+ if (e[0] === ACC) {
31
+ const [, n, desc] = e;
32
+ acc[n] = { ...acc[n], ...desc, configurable: true, enumerable: true };
33
+ } else obj[e[0]] = e[1];
34
+ }
35
+ for (const n in acc) Object.defineProperty(obj, n, acc[n]);
36
+ return obj;
37
+ };
38
+ });
39
+ operator(':', (a, b) => (b = compile(b), Array.isArray(a) ?
40
+ (a = compile(a), ctx => [[a(ctx), b(ctx)]]) : ctx => [[a, b(ctx)]]));
@@ -1,6 +1,26 @@
1
- import { SPACE, STAR, PREC_TOKEN } from "../src/const.js"
2
- import { token, skip, next, cur, idx, expr } from "../src/parse.js"
1
+ /** Configurable comments via parse.comment = { start: end } */
2
+ import { parse, cur, idx, seek } from '../parse.js';
3
3
 
4
- // /**/, //
5
- token('/*', PREC_TOKEN, (a, prec) => (next(c => c !== STAR && cur.charCodeAt(idx + 1) !== 47), skip(),skip(), a || expr(prec) || []))
6
- token('//', PREC_TOKEN, (a, prec) => (next(c => c >= SPACE), a || expr(prec) || []))
4
+ const SPACE = 32, space = parse.space;
5
+
6
+ // Default C-style comments
7
+ parse.comment ??= { '//': '\n', '/*': '*/' };
8
+
9
+ // Cached array: [[start, end, firstCharCode], ...]
10
+ let comments;
11
+
12
+ parse.space = () => {
13
+ if (!comments) comments = Object.entries(parse.comment).map(([s, e]) => [s, e, s.charCodeAt(0)]);
14
+ for (var cc; (cc = space()); ) {
15
+ for (var j = 0, c; c = comments[j++]; ) {
16
+ if (cc === c[2] && cur.substr(idx, c[0].length) === c[0]) {
17
+ var i = idx + c[0].length;
18
+ if (c[1] === '\n') while (cur.charCodeAt(i) >= SPACE) i++;
19
+ else { while (cur[i] && cur.substr(i, c[1].length) !== c[1]) i++; if (cur[i]) i += c[1].length; }
20
+ seek(i); cc = 0; break;
21
+ }
22
+ }
23
+ if (cc) return cc;
24
+ }
25
+ return cc;
26
+ };
@@ -1,142 +1,2 @@
1
- /**
2
- * Control flow: if/else, while, for, break, continue, return, blocks
3
- *
4
- * AST:
5
- * if (c) a else b → ['if', c, a, b?]
6
- * while (c) a → ['while', c, a]
7
- * for (i;c;s) a → ['for', i, c, s, a]
8
- * { a; b } → ['block', [';', a, b]]
9
- * break/continue → ['break'] / ['continue']
10
- * return x → ['return', x?]
11
- */
12
- import * as P from '../src/parse.js'
13
- import { operator, compile } from '../src/compile.js'
14
- import { PREC_STATEMENT, OPAREN, CPAREN, OBRACE, CBRACE, PREC_SEQ, PREC_TOKEN } from '../src/const.js'
15
-
16
- const { token, expr, skip, space, err, parse, next } = P
17
- const SEMI = 59
18
-
19
- // Control signals
20
- class Break {}
21
- class Continue {}
22
- class Return { constructor(v) { this.value = v } }
23
- export const BREAK = new Break(), CONTINUE = new Continue()
24
-
25
- // Shared loop body executor
26
- const loop = (body, ctx) => {
27
- try { return { val: body(ctx) } }
28
- catch (e) {
29
- if (e === BREAK) return { brk: 1 }
30
- if (e === CONTINUE) return { cnt: 1 }
31
- if (e instanceof Return) return { ret: 1, val: e.value }
32
- throw e
33
- }
34
- }
35
-
36
- // if (cond) body [else alt]
37
- token('if', PREC_STATEMENT, a => {
38
- if (a) return
39
- space() === OPAREN || err('Expected (')
40
- skip()
41
- const cond = expr(0, CPAREN), body = parseBody()
42
- space()
43
- // check 'else' — skip 4 chars via skip() since P.idx is read-only from module
44
- const alt = P.cur.substr(P.idx, 4) === 'else' && !parse.id(P.cur.charCodeAt(P.idx + 4))
45
- ? (skip(), skip(), skip(), skip(), parseBody()) : undefined
46
- return alt !== undefined ? ['if', cond, body, alt] : ['if', cond, body]
47
- })
48
-
49
- operator('if', (cond, body, alt) => {
50
- cond = compile(cond); body = compile(body); alt = alt !== undefined ? compile(alt) : null
51
- return ctx => cond(ctx) ? body(ctx) : alt?.(ctx)
52
- })
53
-
54
- // while (cond) body
55
- token('while', PREC_STATEMENT, a => {
56
- if (a) return
57
- space() === OPAREN || err('Expected (')
58
- skip()
59
- return ['while', expr(0, CPAREN), parseBody()]
60
- })
61
-
62
- operator('while', (cond, body) => {
63
- cond = compile(cond); body = compile(body)
64
- return ctx => {
65
- let r, res
66
- while (cond(ctx)) {
67
- r = loop(body, ctx)
68
- if (r.brk) break
69
- if (r.cnt) continue
70
- if (r.ret) return r.val
71
- res = r.val
72
- }
73
- return res
74
- }
75
- })
76
-
77
- // for (init; cond; step) body
78
- token('for', PREC_STATEMENT, a => {
79
- if (a) return
80
- space() === OPAREN || err('Expected (')
81
- skip()
82
- const init = space() === SEMI ? null : expr(PREC_SEQ)
83
- space() === SEMI ? skip() : err('Expected ;')
84
- const cond = space() === SEMI ? null : expr(PREC_SEQ)
85
- space() === SEMI ? skip() : err('Expected ;')
86
- const step = space() === CPAREN ? null : expr(PREC_SEQ)
87
- space() === CPAREN ? skip() : err('Expected )')
88
- return ['for', init, cond, step, parseBody()]
89
- })
90
-
91
- operator('for', (init, cond, step, body) => {
92
- init = init ? compile(init) : null
93
- cond = cond ? compile(cond) : () => true
94
- step = step ? compile(step) : null
95
- body = compile(body)
96
- return ctx => {
97
- let r, res
98
- for (init?.(ctx); cond(ctx); step?.(ctx)) {
99
- r = loop(body, ctx)
100
- if (r.brk) break
101
- if (r.cnt) continue
102
- if (r.ret) return r.val
103
- res = r.val
104
- }
105
- return res
106
- }
107
- })
108
-
109
- // Block parsing helper - only used by control structures
110
- const parseBody = () => {
111
- if (space() === OBRACE) {
112
- skip() // consume {
113
- return ['block', expr(0, CBRACE)]
114
- }
115
- return expr(0) // prec=0 to allow nested control structures
116
- }
117
-
118
- operator('block', body => {
119
- if (body === undefined) return () => {}
120
- body = compile(body)
121
- return ctx => body(Object.create(ctx)) // new scope
122
- })
123
-
124
- // break / continue / return
125
- token('break', PREC_TOKEN, a => a ? null : ['break'])
126
- operator('break', () => () => { throw BREAK })
127
-
128
- token('continue', PREC_TOKEN, a => a ? null : ['continue'])
129
- operator('continue', () => () => { throw CONTINUE })
130
-
131
- token('return', PREC_STATEMENT, a => {
132
- if (a) return
133
- space()
134
- const c = P.cur.charCodeAt(P.idx)
135
- if (!c || c === CBRACE || c === SEMI) return ['return']
136
- return ['return', expr(PREC_STATEMENT)]
137
- })
138
-
139
- operator('return', val => {
140
- val = val !== undefined ? compile(val) : null
141
- return ctx => { throw new Return(val?.(ctx)) }
142
- })
1
+ // Control flow symbols (shared by loop, group, switch, try, function)
2
+ export const BREAK = Symbol('break'), CONTINUE = Symbol('continue'), RETURN = Symbol('return');
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Destructuring patterns and binding
3
+ *
4
+ * Handles: [a, b] = arr, {x, y} = obj, [a, ...rest] = arr, {x = default} = obj
5
+ */
6
+ import { compile } from '../parse.js';
7
+
8
+ // Destructure value into context
9
+ export const destructure = (pattern, value, ctx) => {
10
+ if (typeof pattern === 'string') { ctx[pattern] = value; return; }
11
+ const [op, ...items] = pattern;
12
+ if (op === '{}') {
13
+ for (const item of items) {
14
+ let key, binding, def;
15
+ if (item[0] === '=') [, [, key, binding], def] = item;
16
+ else [, key, binding] = item;
17
+ let val = value[key];
18
+ if (val === undefined && def) val = compile(def)(ctx);
19
+ destructure(binding, val, ctx);
20
+ }
21
+ } else if (op === '[]') {
22
+ let i = 0;
23
+ for (const item of items) {
24
+ if (item === null) { i++; continue; }
25
+ if (Array.isArray(item) && item[0] === '...') { ctx[item[1]] = value.slice(i); break; }
26
+ let binding = item, def;
27
+ if (Array.isArray(item) && item[0] === '=') [, binding, def] = item;
28
+ let val = value[i++];
29
+ if (val === undefined && def) val = compile(def)(ctx);
30
+ destructure(binding, val, ctx);
31
+ }
32
+ }
33
+ };
@@ -0,0 +1,44 @@
1
+ // Function declarations and expressions
2
+ import { space, next, parse, parens, expr, operator, compile } from '../parse.js';
3
+ import { RETURN } from './control.js';
4
+ import { keyword, block } from './block.js';
5
+
6
+ const TOKEN = 200;
7
+
8
+ keyword('function', TOKEN, () => {
9
+ space();
10
+ const name = next(parse.id);
11
+ name && space();
12
+ return ['function', name, parens() || null, block()];
13
+ });
14
+
15
+ // Compile
16
+ operator('function', (name, params, body) => {
17
+ body = body ? compile(body) : () => undefined;
18
+ // Normalize params: null → [], 'x' → ['x'], [',', 'a', 'b'] → ['a', 'b']
19
+ const ps = !params ? [] : params[0] === ',' ? params.slice(1) : [params];
20
+ // Check for rest param
21
+ let restName = null, restIdx = -1;
22
+ const last = ps[ps.length - 1];
23
+ if (Array.isArray(last) && last[0] === '...') {
24
+ restIdx = ps.length - 1;
25
+ restName = last[1];
26
+ ps.length--;
27
+ }
28
+ return ctx => {
29
+ const fn = (...args) => {
30
+ const l = {};
31
+ ps.forEach((p, i) => l[p] = args[i]);
32
+ if (restName) l[restName] = args.slice(restIdx);
33
+ const fnCtx = new Proxy(l, {
34
+ get: (l, k) => k in l ? l[k] : ctx[k],
35
+ set: (l, k, v) => ((k in l ? l : ctx)[k] = v, true),
36
+ has: (l, k) => k in l || k in ctx
37
+ });
38
+ try { return body(fnCtx); }
39
+ catch (e) { if (e?.type === RETURN) return e.value; throw e; }
40
+ };
41
+ if (name) ctx[name] = fn;
42
+ return fn;
43
+ };
44
+ });
package/feature/group.js CHANGED
@@ -1,11 +1,15 @@
1
- import { err, nary, group } from '../src/parse.js'
2
- import { compile, operator } from '../src/compile.js'
3
- import { PREC_ACCESS, PREC_GROUP, PREC_SEQ, PREC_STATEMENT } from '../src/const.js'
1
+ import { nary, group, operator, compile } from '../parse.js';
4
2
 
5
- // (a,b,c), (a) uses PREC_ACCESS to avoid conflict with ?.
6
- group('()', PREC_ACCESS)
7
- operator('()', (a, b) => b === undefined && (!a && err('Empty ()'), compile(a)))
3
+ const STATEMENT = 5, SEQ = 10, ACCESS = 170;
8
4
 
9
- const last = (...args) => (args = args.map(compile), ctx => args.map(arg => arg(ctx)).pop())
10
- nary(',', PREC_SEQ), operator(',', last)
11
- nary(';', PREC_STATEMENT, true), operator(';', last)
5
+ // (a,b,c), (a) uses ACCESS to avoid conflict with ?.
6
+ group('()', ACCESS);
7
+
8
+ // Sequences
9
+ nary(',', SEQ);
10
+ nary(';', STATEMENT, true); // right-assoc to allow same-prec statements
11
+
12
+ // Compile sequences - returns last evaluated value
13
+ const seq = (...args) => (args = args.map(compile), ctx => { let r; for (const a of args) r = a(ctx); return r; });
14
+ operator(',', seq);
15
+ operator(';', seq);
package/feature/if.js CHANGED
@@ -1,43 +1,28 @@
1
- /**
2
- * Conditionals: if/else
3
- *
4
- * AST:
5
- * if (c) a else b → ['if', c, a, b?]
6
- */
7
- import * as P from '../src/parse.js'
8
- import { operator, compile } from '../src/compile.js'
9
- import { PREC_STATEMENT, OPAREN, CPAREN, OBRACE, CBRACE } from '../src/const.js'
1
+ // If/else statement - else consumed internally
2
+ import { space, skip, parens, word, idx, seek, operator, compile } from '../parse.js';
3
+ import { body, keyword } from './block.js';
10
4
 
11
- const { token, expr, skip, space, err, parse } = P
5
+ const STATEMENT = 5, SEMI = 59;
12
6
 
13
- // Block parsing helper
14
- const parseBody = () => {
15
- if (space() === OBRACE) {
16
- skip()
17
- return ['block', expr(0, CBRACE)]
18
- }
19
- return expr(0)
20
- }
7
+ // Check for `else` after optional semicolon
8
+ const checkElse = () => {
9
+ const from = idx;
10
+ if (space() === SEMI) skip();
11
+ space();
12
+ if (word('else')) return skip(4), true;
13
+ return seek(from), false;
14
+ };
21
15
 
22
- operator('block', body => {
23
- if (body === undefined) return () => {}
24
- body = compile(body)
25
- return ctx => body(Object.create(ctx))
26
- })
27
-
28
- // if (cond) body [else alt]
29
- token('if', PREC_STATEMENT, a => {
30
- if (a) return
31
- space() === OPAREN || err('Expected (')
32
- skip()
33
- const cond = expr(0, CPAREN), body = parseBody()
34
- space()
35
- const alt = P.cur.substr(P.idx, 4) === 'else' && !parse.id(P.cur.charCodeAt(P.idx + 4))
36
- ? (skip(), skip(), skip(), skip(), parseBody()) : undefined
37
- return alt !== undefined ? ['if', cond, body, alt] : ['if', cond, body]
38
- })
16
+ // if (cond) body [else body] - self-contained
17
+ keyword('if', STATEMENT + 1, () => {
18
+ space();
19
+ const node = ['if', parens(), body()];
20
+ if (checkElse()) node.push(body());
21
+ return node;
22
+ });
39
23
 
24
+ // Compile
40
25
  operator('if', (cond, body, alt) => {
41
- cond = compile(cond); body = compile(body); alt = alt !== undefined ? compile(alt) : null
42
- return ctx => cond(ctx) ? body(ctx) : alt?.(ctx)
43
- })
26
+ cond = compile(cond); body = compile(body); alt = alt !== undefined ? compile(alt) : null;
27
+ return ctx => cond(ctx) ? body(ctx) : alt?.(ctx);
28
+ });
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Literal values
3
+ *
4
+ * true, false, null, undefined, NaN, Infinity
5
+ */
6
+ import { literal } from '../parse.js';
7
+
8
+ literal('true', true);
9
+ literal('false', false);
10
+ literal('null', null);
11
+ literal('undefined', undefined);
12
+ literal('NaN', NaN);
13
+ literal('Infinity', Infinity);
package/feature/loop.js CHANGED
@@ -1,122 +1,123 @@
1
- /**
2
- * Loops: while, for, break, continue, return
3
- *
4
- * AST:
5
- * while (c) a → ['while', c, a]
6
- * for (i;c;s) a → ['for', i, c, s, a]
7
- * break/continue → ['break'] / ['continue']
8
- * return x → ['return', x?]
9
- */
10
- import * as P from '../src/parse.js'
11
- import { operator, compile } from '../src/compile.js'
12
- import { PREC_STATEMENT, OPAREN, CPAREN, OBRACE, CBRACE, PREC_SEQ, PREC_TOKEN } from '../src/const.js'
1
+ // Loops: while, do-while, for, for await, break, continue, return
2
+ import { expr, skip, space, parse, word, parens, cur, idx, operator, compile } from '../parse.js';
3
+ import { body, keyword } from './block.js';
4
+ import { destructure } from './destruct.js';
5
+ import { BREAK, CONTINUE, RETURN } from './control.js';
13
6
 
14
- const { token, expr, skip, space, err } = P
15
- const SEMI = 59
7
+ export { BREAK, CONTINUE, RETURN };
16
8
 
17
- // Control signals
18
- class Break {}
19
- class Continue {}
20
- class Return { constructor(v) { this.value = v } }
21
- export const BREAK = new Break(), CONTINUE = new Continue()
22
-
23
- // Shared loop body executor
24
- const loop = (body, ctx) => {
25
- try { return { val: body(ctx) } }
9
+ // Loop body executor - catches control flow and returns status
10
+ export const loop = (body, ctx) => {
11
+ try { return { v: body(ctx) }; }
26
12
  catch (e) {
27
- if (e === BREAK) return { brk: 1 }
28
- if (e === CONTINUE) return { cnt: 1 }
29
- if (e instanceof Return) return { ret: 1, val: e.value }
30
- throw e
13
+ if (e?.type === BREAK) return { b: 1 };
14
+ if (e?.type === CONTINUE) return { c: 1 };
15
+ if (e?.type === RETURN) return { r: 1, v: e.value };
16
+ throw e;
31
17
  }
32
- }
18
+ };
33
19
 
34
- // Block parsing helper
35
- const parseBody = () => {
36
- if (space() === OBRACE) {
37
- skip()
38
- return ['block', expr(0, CBRACE)]
39
- }
40
- return expr(0)
41
- }
20
+ const STATEMENT = 5, CBRACE = 125, SEMI = 59;
42
21
 
43
- operator('block', body => {
44
- if (body === undefined) return () => {}
45
- body = compile(body)
46
- return ctx => body(Object.create(ctx))
47
- })
22
+ keyword('while', STATEMENT + 1, () => (space(), ['while', parens(), body()]));
23
+ keyword('do', STATEMENT + 1, () => (b => (space(), skip(5), space(), ['do', b, parens()]))(body()));
24
+
25
+ // for / for await
26
+ keyword('for', STATEMENT + 1, () => {
27
+ space();
28
+ // for await (x of y)
29
+ if (word('await')) {
30
+ skip(5);
31
+ space();
32
+ return ['for await', parens(), body()];
33
+ }
34
+ return ['for', parens(), body()];
35
+ });
48
36
 
49
- // while (cond) body
50
- token('while', PREC_STATEMENT, a => {
51
- if (a) return
52
- space() === OPAREN || err('Expected (')
53
- skip()
54
- return ['while', expr(0, CPAREN), parseBody()]
55
- })
37
+ keyword('break', STATEMENT + 1, () => ['break']);
38
+ keyword('continue', STATEMENT + 1, () => ['continue']);
39
+ keyword('return', STATEMENT + 1, () => {
40
+ parse.asi && (parse.newline = false);
41
+ space();
42
+ const c = cur.charCodeAt(idx);
43
+ return !c || c === CBRACE || c === SEMI || parse.newline ? ['return'] : ['return', expr(STATEMENT)];
44
+ });
56
45
 
46
+ // Compile
57
47
  operator('while', (cond, body) => {
58
- cond = compile(cond); body = compile(body)
48
+ cond = compile(cond); body = compile(body);
59
49
  return ctx => {
60
- let r, res
61
- while (cond(ctx)) {
62
- r = loop(body, ctx)
63
- if (r.brk) break
64
- if (r.cnt) continue
65
- if (r.ret) return r.val
66
- res = r.val
67
- }
68
- return res
69
- }
70
- })
50
+ let r, res;
51
+ while (cond(ctx)) if ((r = loop(body, ctx)).b) break; else if (r.r) return r.v; else if (!r.c) res = r.v;
52
+ return res;
53
+ };
54
+ });
71
55
 
72
- // for (init; cond; step) body
73
- token('for', PREC_STATEMENT, a => {
74
- if (a) return
75
- space() === OPAREN || err('Expected (')
76
- skip()
77
- const init = space() === SEMI ? null : expr(PREC_SEQ)
78
- space() === SEMI ? skip() : err('Expected ;')
79
- const cond = space() === SEMI ? null : expr(PREC_SEQ)
80
- space() === SEMI ? skip() : err('Expected ;')
81
- const step = space() === CPAREN ? null : expr(PREC_SEQ)
82
- space() === CPAREN ? skip() : err('Expected )')
83
- return ['for', init, cond, step, parseBody()]
84
- })
85
-
86
- operator('for', (init, cond, step, body) => {
87
- init = init ? compile(init) : null
88
- cond = cond ? compile(cond) : () => true
89
- step = step ? compile(step) : null
90
- body = compile(body)
56
+ operator('do', (body, cond) => {
57
+ body = compile(body); cond = compile(cond);
91
58
  return ctx => {
92
- let r, res
93
- for (init?.(ctx); cond(ctx); step?.(ctx)) {
94
- r = loop(body, ctx)
95
- if (r.brk) break
96
- if (r.cnt) continue
97
- if (r.ret) return r.val
98
- res = r.val
99
- }
100
- return res
101
- }
102
- })
59
+ let r, res;
60
+ do { if ((r = loop(body, ctx)).b) break; else if (r.r) return r.v; else if (!r.c) res = r.v; } while (cond(ctx));
61
+ return res;
62
+ };
63
+ });
103
64
 
104
- // break / continue / return
105
- token('break', PREC_TOKEN, a => a ? null : ['break'])
106
- operator('break', () => () => { throw BREAK })
65
+ operator('for', (head, body) => {
66
+ // Normalize head: [';', init, cond, step] or single expr (for-in/of)
67
+ if (Array.isArray(head) && head[0] === ';') {
68
+ let [, init, cond, step] = head;
69
+ init = init ? compile(init) : null;
70
+ cond = cond ? compile(cond) : () => true;
71
+ step = step ? compile(step) : null;
72
+ body = compile(body);
73
+ return ctx => {
74
+ let r, res;
75
+ for (init?.(ctx); cond(ctx); step?.(ctx))
76
+ if ((r = loop(body, ctx)).b) break; else if (r.r) return r.v; else if (!r.c) res = r.v;
77
+ return res;
78
+ };
79
+ }
80
+ // For-in/of: head is ['in', lhs, rhs] or ['of', lhs, rhs]
81
+ if (Array.isArray(head) && (head[0] === 'in' || head[0] === 'of')) {
82
+ let [op, lhs, rhs] = head;
83
+ // Extract name from declaration: ['let', 'x'] → 'x'
84
+ if (Array.isArray(lhs) && (lhs[0] === 'let' || lhs[0] === 'const' || lhs[0] === 'var')) lhs = lhs[1];
85
+ if (op === 'in') return forIn(lhs, rhs, body);
86
+ if (op === 'of') return forOf(lhs, rhs, body);
87
+ }
88
+ });
107
89
 
108
- token('continue', PREC_TOKEN, a => a ? null : ['continue'])
109
- operator('continue', () => () => { throw CONTINUE })
90
+ const forOf = (name, iterable, body) => {
91
+ iterable = compile(iterable); body = compile(body);
92
+ const isPattern = Array.isArray(name);
93
+ return ctx => {
94
+ let r, res;
95
+ const prev = isPattern ? null : ctx[name];
96
+ for (const val of iterable(ctx)) {
97
+ if (isPattern) destructure(name, val, ctx); else ctx[name] = val;
98
+ if ((r = loop(body, ctx)).b) break; else if (r.r) return r.v; else if (!r.c) res = r.v;
99
+ }
100
+ if (!isPattern) ctx[name] = prev;
101
+ return res;
102
+ };
103
+ };
110
104
 
111
- token('return', PREC_STATEMENT, a => {
112
- if (a) return
113
- space()
114
- const c = P.cur.charCodeAt(P.idx)
115
- if (!c || c === CBRACE || c === SEMI) return ['return']
116
- return ['return', expr(PREC_STATEMENT)]
117
- })
105
+ const forIn = (name, obj, body) => {
106
+ obj = compile(obj); body = compile(body);
107
+ const isPattern = Array.isArray(name);
108
+ return ctx => {
109
+ let r, res;
110
+ const prev = isPattern ? null : ctx[name];
111
+ for (const key in obj(ctx)) {
112
+ if (isPattern) destructure(name, key, ctx); else ctx[name] = key;
113
+ if ((r = loop(body, ctx)).b) break; else if (r.r) return r.v; else if (!r.c) res = r.v;
114
+ }
115
+ if (!isPattern) ctx[name] = prev;
116
+ return res;
117
+ };
118
+ };
118
119
 
119
- operator('return', val => {
120
- val = val !== undefined ? compile(val) : null
121
- return ctx => { throw new Return(val?.(ctx)) }
122
- })
120
+ operator('break', () => () => { throw { type: BREAK }; });
121
+ operator('continue', () => () => { throw { type: CONTINUE }; });
122
+ operator('return', val => (val = val !== undefined ? compile(val) : null,
123
+ ctx => { throw { type: RETURN, value: val?.(ctx) }; }));