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.
- package/README.md +101 -184
- package/feature/access.js +68 -7
- package/feature/accessor.js +49 -0
- package/feature/asi.js +15 -0
- package/feature/async.js +45 -0
- package/feature/block.js +41 -0
- package/feature/class.js +69 -0
- package/feature/collection.js +40 -0
- package/feature/comment.js +25 -5
- package/feature/control.js +2 -142
- package/feature/destruct.js +33 -0
- package/feature/function.js +44 -0
- package/feature/group.js +13 -9
- package/feature/if.js +23 -38
- package/feature/literal.js +13 -0
- package/feature/loop.js +107 -106
- package/feature/module.js +42 -0
- package/feature/number.js +41 -38
- package/feature/op/arithmetic.js +29 -0
- package/feature/op/arrow.js +33 -0
- package/feature/op/assign-logical.js +33 -0
- package/feature/op/assignment.js +26 -0
- package/feature/op/bitwise-unsigned.js +17 -0
- package/feature/op/bitwise.js +29 -0
- package/feature/op/comparison.js +19 -0
- package/feature/op/defer.js +15 -0
- package/feature/op/equality.js +16 -0
- package/feature/op/identity.js +15 -0
- package/feature/op/increment.js +23 -0
- package/feature/op/logical.js +21 -0
- package/feature/op/membership.js +17 -0
- package/feature/op/nullish.js +13 -0
- package/feature/op/optional.js +61 -0
- package/feature/op/pow.js +19 -0
- package/feature/op/range.js +26 -0
- package/feature/op/spread.js +15 -0
- package/feature/op/ternary.js +15 -0
- package/feature/op/type.js +18 -0
- package/feature/op/unary.js +41 -0
- package/feature/prop.js +34 -0
- package/feature/regex.js +31 -0
- package/feature/seq.js +21 -0
- package/feature/string.js +24 -17
- package/feature/switch.js +48 -0
- package/feature/template.js +39 -0
- package/feature/try.js +57 -0
- package/feature/unit.js +35 -0
- package/feature/var.js +51 -41
- package/jessie.js +31 -0
- package/jessie.min.js +8 -0
- package/justin.js +39 -48
- package/justin.min.js +8 -4
- package/package.json +15 -16
- package/parse.js +146 -0
- package/subscript.d.ts +45 -5
- package/subscript.js +62 -22
- package/subscript.min.js +5 -4
- package/util/bundle.js +507 -0
- package/util/stringify.js +172 -0
- package/feature/add.js +0 -22
- package/feature/array.js +0 -11
- package/feature/arrow.js +0 -23
- package/feature/assign.js +0 -11
- package/feature/bitwise.js +0 -11
- package/feature/bool.js +0 -5
- package/feature/call.js +0 -15
- package/feature/compare.js +0 -11
- package/feature/increment.js +0 -11
- package/feature/logic.js +0 -11
- package/feature/mult.js +0 -25
- package/feature/object.js +0 -17
- package/feature/optional.js +0 -23
- package/feature/pow.js +0 -5
- package/feature/shift.js +0 -12
- package/feature/spread.js +0 -6
- package/feature/ternary.js +0 -10
- package/src/compile.d.ts +0 -17
- package/src/compile.js +0 -28
- package/src/const.js +0 -45
- package/src/parse.d.ts +0 -22
- package/src/parse.js +0 -113
- package/src/stringify.js +0 -27
- /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)]]));
|
package/feature/comment.js
CHANGED
|
@@ -1,6 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
1
|
+
/** Configurable comments via parse.comment = { start: end } */
|
|
2
|
+
import { parse, cur, idx, seek } from '../parse.js';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
+
};
|
package/feature/control.js
CHANGED
|
@@ -1,142 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
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
|
|
5
|
+
const STATEMENT = 5, SEMI = 59;
|
|
12
6
|
|
|
13
|
-
//
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
15
|
-
const SEMI = 59
|
|
7
|
+
export { BREAK, CONTINUE, RETURN };
|
|
16
8
|
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
|
|
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 {
|
|
28
|
-
if (e === CONTINUE) return {
|
|
29
|
-
if (e
|
|
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
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
73
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
109
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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('
|
|
120
|
-
|
|
121
|
-
|
|
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) }; }));
|