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.
- package/README.md +9 -2
- package/eval/access.js +57 -0
- package/eval/accessor.js +19 -0
- package/eval/async.js +13 -0
- package/eval/class.js +35 -0
- package/eval/collection.js +41 -0
- package/eval/function.js +37 -0
- package/eval/if.js +7 -0
- package/eval/jessie.js +19 -0
- package/eval/justin.js +21 -0
- package/eval/loop.js +108 -0
- package/eval/module.js +8 -0
- package/eval/op/arithmetic.js +12 -0
- package/eval/op/arrow.js +35 -0
- package/eval/op/assign-logical.js +23 -0
- package/eval/op/assignment.js +15 -0
- package/eval/op/bitwise-unsigned.js +8 -0
- package/eval/op/bitwise.js +9 -0
- package/eval/op/comparison.js +7 -0
- package/eval/op/defer.js +4 -0
- package/eval/op/equality.js +5 -0
- package/eval/op/identity.js +5 -0
- package/eval/op/increment.js +13 -0
- package/eval/op/logical.js +6 -0
- package/eval/op/membership.js +4 -0
- package/eval/op/nullish.js +4 -0
- package/eval/op/optional.js +38 -0
- package/eval/op/pow.js +8 -0
- package/eval/op/range.js +13 -0
- package/eval/op/spread.js +4 -0
- package/eval/op/ternary.js +4 -0
- package/eval/op/type.js +5 -0
- package/eval/op/unary.js +24 -0
- package/eval/prop.js +16 -0
- package/eval/regex.js +7 -0
- package/eval/seq.js +10 -0
- package/eval/subscript.js +15 -0
- package/eval/switch.js +27 -0
- package/eval/template.js +14 -0
- package/eval/try.js +36 -0
- package/eval/unit.js +10 -0
- package/eval/var.js +69 -0
- package/feature/access.js +2 -58
- package/feature/accessor.js +2 -21
- package/feature/asi.js +55 -11
- package/feature/async.js +2 -14
- package/feature/class.js +2 -35
- package/feature/collection.js +2 -43
- package/feature/function.js +2 -39
- package/feature/if.js +2 -8
- package/feature/jessie.js +32 -0
- package/feature/justin.js +36 -0
- package/feature/loop.js +2 -109
- package/feature/module.js +1 -10
- package/feature/op/arithmetic.js +2 -13
- package/feature/op/arrow.js +2 -38
- package/feature/op/assign-logical.js +3 -24
- package/feature/op/assignment.js +2 -18
- package/feature/op/bitwise-unsigned.js +2 -8
- package/feature/op/bitwise.js +2 -14
- package/feature/op/comparison.js +2 -8
- package/feature/op/defer.js +2 -7
- package/feature/op/equality.js +2 -7
- package/feature/op/identity.js +2 -6
- package/feature/op/increment.js +2 -14
- package/feature/op/logical.js +2 -8
- package/feature/op/membership.js +2 -7
- package/feature/op/nullish.js +2 -5
- package/feature/op/optional.js +2 -41
- package/feature/op/pow.js +2 -10
- package/feature/op/range.js +2 -16
- package/feature/op/spread.js +2 -7
- package/feature/op/ternary.js +2 -7
- package/feature/op/type.js +2 -8
- package/feature/op/unary.js +2 -27
- package/feature/prop.js +2 -17
- package/feature/regex.js +3 -12
- package/feature/seq.js +2 -11
- package/feature/shebang.js +7 -0
- package/feature/subscript.js +23 -0
- package/feature/switch.js +2 -28
- package/feature/template.js +4 -16
- package/feature/try.js +4 -38
- package/feature/unit.js +4 -7
- package/feature/var.js +2 -70
- package/jessie.js +4 -25
- package/jessie.min.js +9 -8
- package/justin.js +4 -30
- package/justin.min.js +8 -8
- package/package.json +9 -1
- package/parse.js +27 -29
- package/subscript.js +7 -16
- package/subscript.min.js +5 -5
package/eval/op/type.js
ADDED
package/eval/op/unary.js
ADDED
|
@@ -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
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
|
+
});
|
package/eval/template.js
ADDED
|
@@ -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
|
|
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;
|
package/feature/accessor.js
CHANGED
|
@@ -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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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)]]));
|
package/feature/collection.js
CHANGED
|
@@ -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,
|
|
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)]]));
|
package/feature/function.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
// Function declarations and expressions
|
|
2
|
-
import { space, next, parse, keyword, parens,
|
|
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 {
|
|
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
|
-
});
|