subscript 9.1.0 → 10.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +123 -171
- package/feature/access.js +67 -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 -6
- package/feature/destruct.js +33 -0
- package/feature/function.js +44 -0
- package/feature/group.js +41 -12
- package/feature/if.js +28 -0
- package/feature/literal.js +13 -0
- package/feature/loop.js +123 -0
- package/feature/module.js +42 -0
- package/feature/number.js +45 -10
- 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 +47 -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 -16
- 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 +59 -0
- package/jessie.js +31 -0
- package/jessie.min.js +8 -0
- package/justin.js +39 -48
- package/justin.min.js +8 -1
- package/package.json +18 -23
- package/parse.js +153 -0
- package/subscript.d.ts +45 -5
- package/subscript.js +62 -22
- package/subscript.min.js +5 -1
- 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 -30
- 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 -42
- package/src/parse.d.ts +0 -22
- package/src/parse.js +0 -114
- package/src/stringify.js +0 -31
- /package/{LICENSE → license} +0 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comparison operators
|
|
3
|
+
*
|
|
4
|
+
* < > <= >=
|
|
5
|
+
*/
|
|
6
|
+
import { binary, operator, compile } from '../../parse.js';
|
|
7
|
+
|
|
8
|
+
const COMP = 90;
|
|
9
|
+
|
|
10
|
+
binary('<', COMP);
|
|
11
|
+
binary('>', COMP);
|
|
12
|
+
binary('<=', COMP);
|
|
13
|
+
binary('>=', COMP);
|
|
14
|
+
|
|
15
|
+
// Compile
|
|
16
|
+
operator('>', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) > b(ctx)));
|
|
17
|
+
operator('<', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) < b(ctx)));
|
|
18
|
+
operator('>=', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) >= b(ctx)));
|
|
19
|
+
operator('<=', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) <= b(ctx)));
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Defer operator
|
|
3
|
+
*
|
|
4
|
+
* defer expr: registers cleanup to run at scope exit
|
|
5
|
+
*
|
|
6
|
+
* Common in: Go, Swift, Zig
|
|
7
|
+
*/
|
|
8
|
+
import { unary, operator, compile } from '../../parse.js';
|
|
9
|
+
|
|
10
|
+
const PREFIX = 140;
|
|
11
|
+
|
|
12
|
+
unary('defer', PREFIX);
|
|
13
|
+
|
|
14
|
+
// Compile
|
|
15
|
+
operator('defer', a => (a = compile(a), ctx => { ctx.__deferred__ = ctx.__deferred__ || []; ctx.__deferred__.push(a); }));
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Equality operators (base)
|
|
3
|
+
*
|
|
4
|
+
* == !=
|
|
5
|
+
* For === !== see equality-strict.js
|
|
6
|
+
*/
|
|
7
|
+
import { binary, operator, compile } from '../../parse.js';
|
|
8
|
+
|
|
9
|
+
const EQ = 80;
|
|
10
|
+
|
|
11
|
+
binary('==', EQ);
|
|
12
|
+
binary('!=', EQ);
|
|
13
|
+
|
|
14
|
+
// Compile
|
|
15
|
+
operator('==', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) == b(ctx)));
|
|
16
|
+
operator('!=', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) != b(ctx)));
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity operators
|
|
3
|
+
*
|
|
4
|
+
* === !==
|
|
5
|
+
*/
|
|
6
|
+
import { binary, operator, compile } from '../../parse.js';
|
|
7
|
+
|
|
8
|
+
const EQ = 80;
|
|
9
|
+
|
|
10
|
+
binary('===', EQ);
|
|
11
|
+
binary('!==', EQ);
|
|
12
|
+
|
|
13
|
+
// Compile
|
|
14
|
+
operator('===', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) === b(ctx)));
|
|
15
|
+
operator('!==', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) !== b(ctx)));
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Increment/decrement operators
|
|
3
|
+
*
|
|
4
|
+
* ++ -- (prefix and postfix)
|
|
5
|
+
*/
|
|
6
|
+
import { token, expr, operator, compile } from '../../parse.js';
|
|
7
|
+
|
|
8
|
+
const POSTFIX = 150;
|
|
9
|
+
|
|
10
|
+
token('++', POSTFIX, a => a ? ['++', a, null] : ['++', expr(POSTFIX - 1)]);
|
|
11
|
+
token('--', POSTFIX, a => a ? ['--', a, null] : ['--', expr(POSTFIX - 1)]);
|
|
12
|
+
|
|
13
|
+
// Compile (b=null means postfix, b=undefined means prefix)
|
|
14
|
+
// Simple prop helper for increment - handles x, a.b, a[b], (x)
|
|
15
|
+
const inc = (a, fn, obj, key) =>
|
|
16
|
+
typeof a === 'string' ? ctx => fn(ctx, a) :
|
|
17
|
+
a[0] === '.' ? (obj = compile(a[1]), key = a[2], ctx => fn(obj(ctx), key)) :
|
|
18
|
+
a[0] === '[]' && a.length === 3 ? (obj = compile(a[1]), key = compile(a[2]), ctx => fn(obj(ctx), key(ctx))) :
|
|
19
|
+
a[0] === '()' && a.length === 2 ? inc(a[1], fn) : // unwrap parens: (x)++
|
|
20
|
+
(() => { throw Error('Invalid increment target') })();
|
|
21
|
+
|
|
22
|
+
operator('++', (a, b) => inc(a, b === null ? (o, k) => o[k]++ : (o, k) => ++o[k]));
|
|
23
|
+
operator('--', (a, b) => inc(a, b === null ? (o, k) => o[k]-- : (o, k) => --o[k]));
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logical operators (base)
|
|
3
|
+
*
|
|
4
|
+
* ! && ||
|
|
5
|
+
* For ?? see nullish.js
|
|
6
|
+
*/
|
|
7
|
+
import { binary, unary, operator, compile } from '../../parse.js';
|
|
8
|
+
|
|
9
|
+
const LOR = 30, LAND = 40, PREFIX = 140;
|
|
10
|
+
|
|
11
|
+
// ! must be registered before != and !==
|
|
12
|
+
binary('!', PREFIX);
|
|
13
|
+
unary('!', PREFIX);
|
|
14
|
+
|
|
15
|
+
binary('||', LOR);
|
|
16
|
+
binary('&&', LAND);
|
|
17
|
+
|
|
18
|
+
// Compile
|
|
19
|
+
operator('!', a => (a = compile(a), ctx => !a(ctx)));
|
|
20
|
+
operator('||', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) || b(ctx)));
|
|
21
|
+
operator('&&', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) && b(ctx)));
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Membership operator
|
|
3
|
+
*
|
|
4
|
+
* in: key in object
|
|
5
|
+
* of: for-of iteration (parsed as binary in for head)
|
|
6
|
+
*
|
|
7
|
+
* Note: instanceof is in class.js (jessie feature)
|
|
8
|
+
*/
|
|
9
|
+
import { binary, operator, compile } from '../../parse.js';
|
|
10
|
+
|
|
11
|
+
const COMP = 90;
|
|
12
|
+
|
|
13
|
+
binary('in', COMP);
|
|
14
|
+
binary('of', COMP);
|
|
15
|
+
|
|
16
|
+
// Compile
|
|
17
|
+
operator('in', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) in b(ctx)));
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nullish coalescing operator (JS-specific)
|
|
3
|
+
*
|
|
4
|
+
* ??
|
|
5
|
+
*/
|
|
6
|
+
import { binary, operator, compile } from '../../parse.js';
|
|
7
|
+
|
|
8
|
+
const LOR = 30;
|
|
9
|
+
|
|
10
|
+
binary('??', LOR);
|
|
11
|
+
|
|
12
|
+
// Compile
|
|
13
|
+
operator('??', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) ?? b(ctx)));
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional chaining operators
|
|
3
|
+
*
|
|
4
|
+
* a?.b → optional member access
|
|
5
|
+
* a?.[x] → optional computed access
|
|
6
|
+
* a?.() → optional call
|
|
7
|
+
*
|
|
8
|
+
* Common in: JS, TS, Swift, Kotlin, C#
|
|
9
|
+
*/
|
|
10
|
+
import { token, expr, skip, space, operator, compile } from '../../parse.js';
|
|
11
|
+
import { unsafe } from '../access.js';
|
|
12
|
+
|
|
13
|
+
const ACCESS = 170;
|
|
14
|
+
|
|
15
|
+
token('?.', ACCESS, (a, b) => {
|
|
16
|
+
if (!a) return;
|
|
17
|
+
const cc = space();
|
|
18
|
+
// Optional call: a?.()
|
|
19
|
+
if (cc === 40) { skip(); return ['?.()', a, expr(0, 41) || null]; }
|
|
20
|
+
// Optional computed: a?.[x]
|
|
21
|
+
if (cc === 91) { skip(); return ['?.[]', a, expr(0, 93)]; }
|
|
22
|
+
// Optional member: a?.b
|
|
23
|
+
b = expr(ACCESS);
|
|
24
|
+
return b ? ['?.', a, b] : void 0;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Compile
|
|
28
|
+
operator('?.', (a, b) => (a = compile(a), unsafe(b) ? () => undefined : ctx => a(ctx)?.[b]));
|
|
29
|
+
operator('?.[]', (a, b) => (a = compile(a), b = compile(b), ctx => { const k = b(ctx); return unsafe(k) ? undefined : a(ctx)?.[k]; }));
|
|
30
|
+
operator('?.()', (a, b) => {
|
|
31
|
+
const args = !b ? () => [] :
|
|
32
|
+
b[0] === ',' ? (b = b.slice(1).map(compile), ctx => b.map(arg => arg(ctx))) :
|
|
33
|
+
(b = compile(b), ctx => [b(ctx)]);
|
|
34
|
+
|
|
35
|
+
// Handle nested optional chain: a?.method?.() or a?.["method"]?.()
|
|
36
|
+
if (a[0] === '?.') {
|
|
37
|
+
const container = compile(a[1]);
|
|
38
|
+
const prop = a[2];
|
|
39
|
+
return unsafe(prop) ? () => undefined :
|
|
40
|
+
ctx => { const c = container(ctx); return c?.[prop]?.(...args(ctx)); };
|
|
41
|
+
}
|
|
42
|
+
if (a[0] === '?.[]') {
|
|
43
|
+
const container = compile(a[1]);
|
|
44
|
+
const prop = compile(a[2]);
|
|
45
|
+
return ctx => { const c = container(ctx); const p = prop(ctx); return unsafe(p) ? undefined : c?.[p]?.(...args(ctx)); };
|
|
46
|
+
}
|
|
47
|
+
// Handle a?.() where a is a.method or a[method] - need to bind this
|
|
48
|
+
if (a[0] === '.') {
|
|
49
|
+
const obj = compile(a[1]);
|
|
50
|
+
const prop = a[2];
|
|
51
|
+
return unsafe(prop) ? () => undefined :
|
|
52
|
+
ctx => { const o = obj(ctx); return o?.[prop]?.(...args(ctx)); };
|
|
53
|
+
}
|
|
54
|
+
if (a[0] === '[]' && a.length === 3) {
|
|
55
|
+
const obj = compile(a[1]);
|
|
56
|
+
const prop = compile(a[2]);
|
|
57
|
+
return ctx => { const o = obj(ctx); const p = prop(ctx); return unsafe(p) ? undefined : o?.[p]?.(...args(ctx)); };
|
|
58
|
+
}
|
|
59
|
+
const fn = compile(a);
|
|
60
|
+
return ctx => fn(ctx)?.(...args(ctx));
|
|
61
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exponentiation operator
|
|
3
|
+
*
|
|
4
|
+
* ** **=
|
|
5
|
+
*
|
|
6
|
+
* ES2016+, not in classic JS/C
|
|
7
|
+
*/
|
|
8
|
+
import { binary, operator, compile } from '../../parse.js';
|
|
9
|
+
import { isLval, prop } from '../access.js';
|
|
10
|
+
|
|
11
|
+
const EXP = 130, ASSIGN = 20;
|
|
12
|
+
|
|
13
|
+
binary('**', EXP, true);
|
|
14
|
+
binary('**=', ASSIGN, true);
|
|
15
|
+
|
|
16
|
+
// Compile
|
|
17
|
+
operator('**', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) ** b(ctx)));
|
|
18
|
+
const err = msg => { throw Error(msg) };
|
|
19
|
+
operator('**=', (a, b) => (isLval(a) || err('Invalid assignment target'), b = compile(b), prop(a, (obj, path, ctx) => obj[path] **= b(ctx))));
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Range operators
|
|
3
|
+
*
|
|
4
|
+
* .. (inclusive range): 1..5 → [1,2,3,4,5]
|
|
5
|
+
* ..< (exclusive range): 1..<5 → [1,2,3,4]
|
|
6
|
+
*
|
|
7
|
+
* Common in: Swift, Kotlin, Rust, Ruby
|
|
8
|
+
*/
|
|
9
|
+
import { binary, operator, compile } from '../../parse.js';
|
|
10
|
+
|
|
11
|
+
const COMP = 90;
|
|
12
|
+
|
|
13
|
+
binary('..', COMP);
|
|
14
|
+
binary('..<', COMP);
|
|
15
|
+
|
|
16
|
+
// Compile
|
|
17
|
+
operator('..', (a, b) => (a = compile(a), b = compile(b), ctx => {
|
|
18
|
+
const start = a(ctx), end = b(ctx), arr = [];
|
|
19
|
+
for (let i = start; i <= end; i++) arr.push(i);
|
|
20
|
+
return arr;
|
|
21
|
+
}));
|
|
22
|
+
operator('..<', (a, b) => (a = compile(a), b = compile(b), ctx => {
|
|
23
|
+
const start = a(ctx), end = b(ctx), arr = [];
|
|
24
|
+
for (let i = start; i < end; i++) arr.push(i);
|
|
25
|
+
return arr;
|
|
26
|
+
}));
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spread/rest operator
|
|
3
|
+
*
|
|
4
|
+
* ...x → spread in arrays/calls, rest in params
|
|
5
|
+
*
|
|
6
|
+
* Common in: JS, TS, Python (*args), Ruby (*splat)
|
|
7
|
+
*/
|
|
8
|
+
import { unary, operator, compile } from '../../parse.js';
|
|
9
|
+
|
|
10
|
+
const PREFIX = 140;
|
|
11
|
+
|
|
12
|
+
unary('...', PREFIX);
|
|
13
|
+
|
|
14
|
+
// Compile (for arrays/objects spread)
|
|
15
|
+
operator('...', a => (a = compile(a), ctx => Object.entries(a(ctx))));
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ternary conditional operator
|
|
3
|
+
*
|
|
4
|
+
* a ? b : c → conditional expression
|
|
5
|
+
*
|
|
6
|
+
* Common in: C, JS, Java, PHP, etc.
|
|
7
|
+
*/
|
|
8
|
+
import { token, expr, next, operator, compile } from '../../parse.js';
|
|
9
|
+
|
|
10
|
+
const ASSIGN = 20;
|
|
11
|
+
|
|
12
|
+
token('?', ASSIGN, (a, b, c) => a && (b = expr(ASSIGN - 1)) && next(c => c === 58) && (c = expr(ASSIGN - 1), ['?', a, b, c]));
|
|
13
|
+
|
|
14
|
+
// Compile
|
|
15
|
+
operator('?', (a, b, c) => (a = compile(a), b = compile(b), c = compile(c), ctx => a(ctx) ? b(ctx) : c(ctx)));
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type operators
|
|
3
|
+
*
|
|
4
|
+
* as: type cast/assertion (identity in JS)
|
|
5
|
+
* is: type check (instanceof in JS)
|
|
6
|
+
*
|
|
7
|
+
* Common in: TypeScript, Kotlin, Swift, C#
|
|
8
|
+
*/
|
|
9
|
+
import { binary, operator, compile } from '../../parse.js';
|
|
10
|
+
|
|
11
|
+
const COMP = 90;
|
|
12
|
+
|
|
13
|
+
binary('as', COMP);
|
|
14
|
+
binary('is', COMP);
|
|
15
|
+
|
|
16
|
+
// Compile (identity in JS)
|
|
17
|
+
operator('as', (a, b) => (a = compile(a), ctx => a(ctx)));
|
|
18
|
+
operator('is', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) instanceof b(ctx)));
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unary keyword operators
|
|
3
|
+
*
|
|
4
|
+
* typeof x → type string
|
|
5
|
+
* void x → undefined
|
|
6
|
+
* delete x → remove property
|
|
7
|
+
* new X() → construct instance
|
|
8
|
+
*
|
|
9
|
+
* JS-specific keywords
|
|
10
|
+
*/
|
|
11
|
+
import { unary, operator, compile } from '../../parse.js';
|
|
12
|
+
|
|
13
|
+
const PREFIX = 140;
|
|
14
|
+
|
|
15
|
+
unary('typeof', PREFIX);
|
|
16
|
+
unary('void', PREFIX);
|
|
17
|
+
unary('delete', PREFIX);
|
|
18
|
+
unary('new', PREFIX);
|
|
19
|
+
|
|
20
|
+
// Compile
|
|
21
|
+
operator('typeof', a => (a = compile(a), ctx => typeof a(ctx)));
|
|
22
|
+
operator('void', a => (a = compile(a), ctx => (a(ctx), undefined)));
|
|
23
|
+
operator('delete', a => {
|
|
24
|
+
if (a[0] === '.') {
|
|
25
|
+
const obj = compile(a[1]), key = a[2];
|
|
26
|
+
return ctx => delete obj(ctx)[key];
|
|
27
|
+
}
|
|
28
|
+
if (a[0] === '[]') {
|
|
29
|
+
const obj = compile(a[1]), key = compile(a[2]);
|
|
30
|
+
return ctx => delete obj(ctx)[key(ctx)];
|
|
31
|
+
}
|
|
32
|
+
return () => true;
|
|
33
|
+
});
|
|
34
|
+
operator('new', (call) => {
|
|
35
|
+
const target = compile(call?.[0] === '()' ? call[1] : call);
|
|
36
|
+
const args = call?.[0] === '()' ? call[2] : null;
|
|
37
|
+
const argList = !args ? () => [] :
|
|
38
|
+
args[0] === ',' ? (a => ctx => a.map(f => f(ctx)))(args.slice(1).map(compile)) :
|
|
39
|
+
(a => ctx => [a(ctx)])(compile(args));
|
|
40
|
+
return ctx => new (target(ctx))(...argList(ctx));
|
|
41
|
+
});
|
package/feature/prop.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal property access: a.b, a[b], f()
|
|
3
|
+
* For array literals, private fields, see member.js
|
|
4
|
+
*/
|
|
5
|
+
import { access, binary, group, operator, compile } from '../parse.js';
|
|
6
|
+
|
|
7
|
+
const ACCESS = 170;
|
|
8
|
+
|
|
9
|
+
// a[b] - computed member access only (no array literal support)
|
|
10
|
+
access('[]', ACCESS);
|
|
11
|
+
|
|
12
|
+
// a.b - dot member access
|
|
13
|
+
binary('.', ACCESS);
|
|
14
|
+
|
|
15
|
+
// (a) - grouping only (no sequences)
|
|
16
|
+
group('()', ACCESS);
|
|
17
|
+
|
|
18
|
+
// a(b,c,d), a() - function calls
|
|
19
|
+
access('()', ACCESS);
|
|
20
|
+
|
|
21
|
+
// Compile
|
|
22
|
+
const err = msg => { throw Error(msg) };
|
|
23
|
+
operator('[]', (a, b) => (b == null && err('Missing index'), a = compile(a), b = compile(b), ctx => a(ctx)[b(ctx)]));
|
|
24
|
+
operator('.', (a, b) => (a = compile(a), b = !b[0] ? b[1] : b, ctx => a(ctx)[b]));
|
|
25
|
+
operator('()', (a, b) => {
|
|
26
|
+
// Group: (expr) - no second argument means grouping, not call
|
|
27
|
+
if (b === undefined) return a == null ? err('Empty ()') : compile(a);
|
|
28
|
+
// Function call: a(b,c)
|
|
29
|
+
const args = !b ? () => [] :
|
|
30
|
+
b[0] === ',' ? (b = b.slice(1).map(compile), ctx => b.map(arg => arg(ctx))) :
|
|
31
|
+
(b = compile(b), ctx => [b(ctx)]);
|
|
32
|
+
a = compile(a);
|
|
33
|
+
return ctx => a(ctx)(...args(ctx));
|
|
34
|
+
});
|
package/feature/regex.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regex literals: /pattern/flags
|
|
3
|
+
*
|
|
4
|
+
* AST:
|
|
5
|
+
* /abc/gi → [, /abc/gi]
|
|
6
|
+
*
|
|
7
|
+
* Note: Disambiguates from division by context:
|
|
8
|
+
* - `/` after value = division (falls through to prev)
|
|
9
|
+
* - `/` at start or after operator = regex
|
|
10
|
+
*/
|
|
11
|
+
import { token, skip, err, next, idx, cur } from '../parse.js';
|
|
12
|
+
|
|
13
|
+
const PREFIX = 140, SLASH = 47, BSLASH = 92;
|
|
14
|
+
|
|
15
|
+
const regexChar = c => c === BSLASH ? 2 : c && c !== SLASH; // \x = 2 chars, else 1 until /
|
|
16
|
+
const regexFlag = c => c === 103 || c === 105 || c === 109 || c === 115 || c === 117 || c === 121; // g i m s u y
|
|
17
|
+
|
|
18
|
+
token('/', PREFIX, a => {
|
|
19
|
+
if (a) return; // has left operand = division, fall through
|
|
20
|
+
|
|
21
|
+
// Invalid regex start (quantifiers) or /= - fall through
|
|
22
|
+
const first = cur.charCodeAt(idx);
|
|
23
|
+
if (first === SLASH || first === 42 || first === 43 || first === 63 || first === 61) return;
|
|
24
|
+
|
|
25
|
+
const pattern = next(regexChar);
|
|
26
|
+
cur.charCodeAt(idx) === SLASH || err('Unterminated regex');
|
|
27
|
+
skip(); // consume closing /
|
|
28
|
+
|
|
29
|
+
try { return [, new RegExp(pattern, next(regexFlag))]; }
|
|
30
|
+
catch (e) { err('Invalid regex: ' + e.message); }
|
|
31
|
+
});
|
package/feature/seq.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sequence operators (C-family)
|
|
3
|
+
*
|
|
4
|
+
* , ; — returns last evaluated value
|
|
5
|
+
*/
|
|
6
|
+
import { nary, operator, compile } from '../parse.js';
|
|
7
|
+
|
|
8
|
+
const STATEMENT = 5, SEQ = 10;
|
|
9
|
+
|
|
10
|
+
// Sequences
|
|
11
|
+
nary(',', SEQ);
|
|
12
|
+
nary(';', STATEMENT, true); // right-assoc to allow same-prec statements
|
|
13
|
+
|
|
14
|
+
// Compile - returns last evaluated value
|
|
15
|
+
const seq = (...args) => (args = args.map(compile), ctx => {
|
|
16
|
+
let r;
|
|
17
|
+
for (const arg of args) r = arg(ctx);
|
|
18
|
+
return r;
|
|
19
|
+
});
|
|
20
|
+
operator(',', seq);
|
|
21
|
+
operator(';', seq);
|
package/feature/string.js
CHANGED
|
@@ -1,19 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Strings with escape sequences
|
|
3
|
+
*
|
|
4
|
+
* Configurable via parse.string: { '"': true } or { '"': true, "'": true }
|
|
5
|
+
*/
|
|
6
|
+
import { parse, lookup, next, err, skip, idx, cur } from '../parse.js';
|
|
3
7
|
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
qc && err('Unexpected string') // must not follow another token
|
|
7
|
-
skip() // first quote
|
|
8
|
-
while (c = cur.charCodeAt(idx), c - q) {
|
|
9
|
-
if (c === BSLASH) skip(), c = skip(), str += escape[c] || c
|
|
10
|
-
else str += skip()
|
|
11
|
-
}
|
|
12
|
-
skip() || err('Bad string')
|
|
13
|
-
return [, str]
|
|
14
|
-
}
|
|
8
|
+
const BSLASH = 92, DQUOTE = 34, SQUOTE = 39;
|
|
9
|
+
const esc = { n: '\n', r: '\r', t: '\t', b: '\b', f: '\f', v: '\v' };
|
|
15
10
|
|
|
11
|
+
// Parse string with given quote char code
|
|
12
|
+
const parseString = q => (a, _, s = '') => {
|
|
13
|
+
if (a || !parse.string?.[String.fromCharCode(q)]) return;
|
|
14
|
+
skip();
|
|
15
|
+
next(c => c - q && (c === BSLASH ? (s += esc[cur[idx + 1]] || cur[idx + 1], 2) : (s += cur[idx], 1)));
|
|
16
|
+
cur[idx] === String.fromCharCode(q) ? skip() : err('Bad string');
|
|
17
|
+
return [, s];
|
|
18
|
+
};
|
|
16
19
|
|
|
17
|
-
//
|
|
18
|
-
lookup[DQUOTE] =
|
|
19
|
-
lookup[
|
|
20
|
+
// Register both quote chars (enabled via parse.string config)
|
|
21
|
+
lookup[DQUOTE] = parseString(DQUOTE);
|
|
22
|
+
lookup[SQUOTE] = parseString(SQUOTE);
|
|
23
|
+
|
|
24
|
+
// Default: double quotes only
|
|
25
|
+
parse.string = { '"': true };
|
|
26
|
+
|
|
27
|
+
export { esc };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Switch/case/default
|
|
2
|
+
// AST: ['switch', val, [';', ['case', test], stmts..., ['default'], stmts...]]
|
|
3
|
+
import { expr, skip, space, parens, operator, compile } from '../parse.js';
|
|
4
|
+
import { keyword, block } from './block.js';
|
|
5
|
+
import { BREAK } from './loop.js';
|
|
6
|
+
|
|
7
|
+
const STATEMENT = 5, ASSIGN = 20, COLON = 58;
|
|
8
|
+
|
|
9
|
+
keyword('switch', STATEMENT + 1, () => (space(), ['switch', parens(), block()]));
|
|
10
|
+
keyword('case', STATEMENT + 1, () => (space(), (c => (space() === COLON && skip(), ['case', c]))(expr(ASSIGN))));
|
|
11
|
+
keyword('default', STATEMENT + 1, () => (space() === COLON && skip(), ['default']));
|
|
12
|
+
|
|
13
|
+
// Compile
|
|
14
|
+
operator('switch', (val, cases) => {
|
|
15
|
+
val = compile(val);
|
|
16
|
+
// Parse cases body: [';', ['case', test], stmts..., ['default'], stmts...]
|
|
17
|
+
if (!cases) return ctx => val(ctx);
|
|
18
|
+
const parsed = [];
|
|
19
|
+
const items = cases[0] === ';' ? cases.slice(1) : [cases];
|
|
20
|
+
let current = null;
|
|
21
|
+
for (const item of items) {
|
|
22
|
+
if (Array.isArray(item) && (item[0] === 'case' || item[0] === 'default')) {
|
|
23
|
+
if (current) parsed.push(current);
|
|
24
|
+
current = [item[0] === 'case' ? compile(item[1]) : null, []];
|
|
25
|
+
} else if (current) {
|
|
26
|
+
current[1].push(compile(item));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (current) parsed.push(current);
|
|
30
|
+
|
|
31
|
+
return ctx => {
|
|
32
|
+
const v = val(ctx);
|
|
33
|
+
let matched = false, result;
|
|
34
|
+
for (const [test, stmts] of parsed) {
|
|
35
|
+
if (matched || test === null || test(ctx) === v) {
|
|
36
|
+
matched = true;
|
|
37
|
+
for (const stmt of stmts) {
|
|
38
|
+
try { result = stmt(ctx); }
|
|
39
|
+
catch (e) {
|
|
40
|
+
if (e?.type === BREAK) return e.value !== undefined ? e.value : result;
|
|
41
|
+
throw e;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
};
|
|
48
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template literals: `a ${x} b` → ['`', [,'a '], 'x', [,' b']]
|
|
3
|
+
* Tagged templates: tag`...` → ['``', 'tag', ...]
|
|
4
|
+
*/
|
|
5
|
+
import { parse, skip, err, expr, lookup, cur, idx, operator, compile } from '../parse.js';
|
|
6
|
+
|
|
7
|
+
const ACCESS = 170, BACKTICK = 96, DOLLAR = 36, OBRACE = 123, BSLASH = 92;
|
|
8
|
+
const esc = { n: '\n', r: '\r', t: '\t', b: '\b', f: '\f', v: '\v' };
|
|
9
|
+
|
|
10
|
+
// Parse template body after opening `
|
|
11
|
+
const parseBody = () => {
|
|
12
|
+
const parts = [];
|
|
13
|
+
for (let s = '', c; (c = cur.charCodeAt(idx)) !== BACKTICK; )
|
|
14
|
+
!c ? err('Unterminated template') :
|
|
15
|
+
c === BSLASH ? (skip(), s += esc[cur[idx]] || cur[idx], skip()) :
|
|
16
|
+
c === DOLLAR && cur.charCodeAt(idx + 1) === OBRACE ? (s && parts.push([, s]), s = '', skip(2), parts.push(expr(0, 125))) :
|
|
17
|
+
(s += cur[idx], skip(), c = cur.charCodeAt(idx), c === BACKTICK && s && parts.push([, s]));
|
|
18
|
+
return skip(), parts;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const prev = lookup[BACKTICK];
|
|
22
|
+
// Tagged templates: decline when ASI with newline (return undefined to let ASI handle)
|
|
23
|
+
lookup[BACKTICK] = (a, prec) =>
|
|
24
|
+
a && prec < ACCESS ? (parse.asi && parse.newline ? void 0 : (skip(), ['``', a, ...parseBody()])) : // tagged
|
|
25
|
+
!a ? (skip(), (p => p.length < 2 && p[0]?.[0] === undefined ? p[0] || [,''] : ['`', ...p])(parseBody())) : // plain
|
|
26
|
+
prev?.(a, prec);
|
|
27
|
+
|
|
28
|
+
// Compile
|
|
29
|
+
operator('`', (...parts) => (parts = parts.map(compile), ctx => parts.map(p => p(ctx)).join('')));
|
|
30
|
+
operator('``', (tag, ...parts) => {
|
|
31
|
+
tag = compile(tag);
|
|
32
|
+
const strings = [], exprs = [];
|
|
33
|
+
for (const p of parts) {
|
|
34
|
+
if (Array.isArray(p) && p[0] === undefined) strings.push(p[1]);
|
|
35
|
+
else exprs.push(compile(p));
|
|
36
|
+
}
|
|
37
|
+
const strs = Object.assign([...strings], { raw: strings });
|
|
38
|
+
return ctx => tag(ctx)(strs, ...exprs.map(e => e(ctx)));
|
|
39
|
+
});
|
package/feature/try.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// try/catch/finally/throw statements
|
|
2
|
+
// AST: ['catch', ['try', body], param, catchBody] or ['finally', inner, body]
|
|
3
|
+
import { space, parse, parens, expr, operator, compile } from '../parse.js';
|
|
4
|
+
import { keyword, infix, block } from './block.js';
|
|
5
|
+
import { BREAK, CONTINUE, RETURN } from './loop.js';
|
|
6
|
+
|
|
7
|
+
const STATEMENT = 5;
|
|
8
|
+
|
|
9
|
+
keyword('try', STATEMENT + 1, () => ['try', block()]);
|
|
10
|
+
infix('catch', STATEMENT + 1, a => (space(), ['catch', a, parens(), block()]));
|
|
11
|
+
infix('finally', STATEMENT + 1, a => ['finally', a, block()]);
|
|
12
|
+
|
|
13
|
+
keyword('throw', STATEMENT + 1, () => {
|
|
14
|
+
parse.asi && (parse.newline = false);
|
|
15
|
+
space();
|
|
16
|
+
if (parse.newline) throw SyntaxError('Unexpected newline after throw');
|
|
17
|
+
return ['throw', expr(STATEMENT)];
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// Compile
|
|
21
|
+
operator('try', tryBody => {
|
|
22
|
+
tryBody = tryBody ? compile(tryBody) : null;
|
|
23
|
+
return ctx => tryBody?.(ctx);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
operator('catch', (tryNode, catchName, catchBody) => {
|
|
27
|
+
const tryBody = tryNode?.[1] ? compile(tryNode[1]) : null;
|
|
28
|
+
catchBody = catchBody ? compile(catchBody) : null;
|
|
29
|
+
return ctx => {
|
|
30
|
+
let result;
|
|
31
|
+
try {
|
|
32
|
+
result = tryBody?.(ctx);
|
|
33
|
+
} catch (e) {
|
|
34
|
+
if (e?.type === BREAK || e?.type === CONTINUE || e?.type === RETURN) throw e;
|
|
35
|
+
if (catchName !== null && catchBody) {
|
|
36
|
+
const had = catchName in ctx, orig = ctx[catchName];
|
|
37
|
+
ctx[catchName] = e;
|
|
38
|
+
try { result = catchBody(ctx); }
|
|
39
|
+
finally { had ? ctx[catchName] = orig : delete ctx[catchName]; }
|
|
40
|
+
} else if (catchName === null) throw e;
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
operator('finally', (inner, finallyBody) => {
|
|
47
|
+
inner = inner ? compile(inner) : null;
|
|
48
|
+
finallyBody = finallyBody ? compile(finallyBody) : null;
|
|
49
|
+
return ctx => {
|
|
50
|
+
let result;
|
|
51
|
+
try { result = inner?.(ctx); }
|
|
52
|
+
finally { finallyBody?.(ctx); }
|
|
53
|
+
return result;
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
operator('throw', val => (val = compile(val), ctx => { throw val(ctx); }));
|
package/feature/unit.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit suffixes: 5px, 10rem, 2s, 500ms
|
|
3
|
+
*
|
|
4
|
+
* AST:
|
|
5
|
+
* 5px → ['px', [,5]]
|
|
6
|
+
* 2.5s → ['s', [,2.5]]
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* import { unit } from 'subscript/feature/unit.js'
|
|
10
|
+
* unit('px', 'em', 'rem', 's', 'ms')
|
|
11
|
+
*/
|
|
12
|
+
import { lookup, next, parse, idx, seek, operator, compile } from '../parse.js';
|
|
13
|
+
|
|
14
|
+
const units = {};
|
|
15
|
+
|
|
16
|
+
export const unit = (...names) => names.forEach(name => {
|
|
17
|
+
units[name] = 1;
|
|
18
|
+
operator(name, val => (val = compile(val), ctx => ({ value: val(ctx), unit: name })));
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Wrap number handler to check for unit suffix
|
|
22
|
+
const wrapNum = cc => {
|
|
23
|
+
const orig = lookup[cc];
|
|
24
|
+
if (!orig) return;
|
|
25
|
+
lookup[cc] = (a, prec) => {
|
|
26
|
+
const r = orig(a, prec);
|
|
27
|
+
if (!r || r[0] !== undefined) return r;
|
|
28
|
+
const start = idx, u = next(c => parse.id(c) && !(c >= 48 && c <= 57));
|
|
29
|
+
return u && units[u] ? [u, r] : (u && seek(start), r);
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Wrap digit and period handlers
|
|
34
|
+
for (let i = 48; i <= 57; i++) wrapNum(i);
|
|
35
|
+
wrapNum(46);
|