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/README.md
CHANGED
|
@@ -58,7 +58,7 @@ let fn = jessie(`
|
|
|
58
58
|
fn({}) // 120
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
See [docs](./docs.md#presets) for full description.
|
|
61
|
+
Each preset has a parse-only entry — `subscript/feature/{subscript,justin,jessie}` — that drops the runtime (~50% smaller) for consumers that only need the AST. See [docs](./docs.md#presets) for full description.
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
## Syntax tree
|
|
@@ -83,7 +83,7 @@ See [spec.md](./spec.md).
|
|
|
83
83
|
|
|
84
84
|
## Extension
|
|
85
85
|
|
|
86
|
-
Add operators, literals or custom syntax:
|
|
86
|
+
Add operators, literals or custom syntax. `binary`/`unary`/`token`/`keyword` register parsers; `operator` registers compilers — pair them as needed:
|
|
87
87
|
|
|
88
88
|
```js
|
|
89
89
|
import justin, { binary, operator, compile } from 'subscript/justin.js'
|
|
@@ -98,6 +98,13 @@ operator('∩', (a, b) => ( // register compiler
|
|
|
98
98
|
justin('[1,2,3] ∩ [2,3,4]')({}) // [2, 3]
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
+
For parse-only consumers, register only the parse half:
|
|
102
|
+
|
|
103
|
+
```js
|
|
104
|
+
import { binary } from 'subscript/feature/justin.js'
|
|
105
|
+
binary('∩', 80)
|
|
106
|
+
```
|
|
107
|
+
|
|
101
108
|
See [docs.md](./docs.md) for full API.
|
|
102
109
|
|
|
103
110
|
|
package/eval/access.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Property access - eval half
|
|
2
|
+
import { operator, compile } from '../parse.js';
|
|
3
|
+
|
|
4
|
+
// Block prototype chain attacks
|
|
5
|
+
export const unsafe = k => k?.[0] === '_' && k[1] === '_' || k === 'constructor' || k === 'prototype';
|
|
6
|
+
|
|
7
|
+
const err = msg => { throw Error(msg) };
|
|
8
|
+
operator('[]', (a, b) => {
|
|
9
|
+
// Array literal: [1,2,3] - b is strictly undefined (AST length 2)
|
|
10
|
+
if (b === undefined) {
|
|
11
|
+
a = !a ? [] : a[0] === ',' ? a.slice(1) : [a];
|
|
12
|
+
a = a.map(a => a == null ? (() => undefined) : a[0] === '...' ? (a = compile(a[1]), ctx => a(ctx)) : (a = compile(a), ctx => [a(ctx)]));
|
|
13
|
+
return ctx => a.flatMap(a => a(ctx));
|
|
14
|
+
}
|
|
15
|
+
// Member access: a[b]
|
|
16
|
+
if (b == null) err('Missing index');
|
|
17
|
+
a = compile(a); b = compile(b);
|
|
18
|
+
return ctx => { const k = b(ctx); return unsafe(k) ? undefined : a(ctx)[k]; };
|
|
19
|
+
});
|
|
20
|
+
operator('.', (a, b) => (a = compile(a), b = !b[0] ? b[1] : b, unsafe(b) ? () => undefined : ctx => a(ctx)[b]));
|
|
21
|
+
operator('()', (a, b) => {
|
|
22
|
+
// Group: (expr) - no second argument means grouping, not call
|
|
23
|
+
if (b === undefined) return a == null ? err('Empty ()') : compile(a);
|
|
24
|
+
// Validate: no sparse arguments in calls
|
|
25
|
+
const hasSparse = n => n?.[0] === ',' && n.slice(1).some(a => a == null || hasSparse(a));
|
|
26
|
+
if (hasSparse(b)) err('Empty argument');
|
|
27
|
+
const args = !b ? () => [] :
|
|
28
|
+
b[0] === ',' ? (b = b.slice(1).map(compile), ctx => b.map(arg => arg(ctx))) :
|
|
29
|
+
(b = compile(b), ctx => [b(ctx)]);
|
|
30
|
+
// Inline call handling for x(), a.b(), a[b](), (x)()
|
|
31
|
+
return call(a, (obj, path, ctx) => obj[path](...args(ctx)));
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Left-value check (valid assignment target)
|
|
35
|
+
export const isLval = n =>
|
|
36
|
+
typeof n === 'string' ||
|
|
37
|
+
(Array.isArray(n) && (
|
|
38
|
+
n[0] === '.' || n[0] === '?.' ||
|
|
39
|
+
(n[0] === '[]' && n.length === 3) || n[0] === '?.[]' ||
|
|
40
|
+
(n[0] === '()' && n.length === 2 && isLval(n[1])) ||
|
|
41
|
+
n[0] === '{}'
|
|
42
|
+
));
|
|
43
|
+
|
|
44
|
+
// Simple call helper (no optional chaining) - handles x(), a.b(), a[b](), (x)()
|
|
45
|
+
const call = (a, fn, obj, path) => (
|
|
46
|
+
a == null ? err('Empty ()') :
|
|
47
|
+
a[0] === '()' && a.length == 2 ? call(a[1], fn) :
|
|
48
|
+
typeof a === 'string' ? ctx => fn(ctx, a, ctx) :
|
|
49
|
+
a[0] === '.' ? (obj = compile(a[1]), path = a[2], ctx => fn(obj(ctx), path, ctx)) :
|
|
50
|
+
a[0] === '?.' ? (obj = compile(a[1]), path = a[2], ctx => { const o = obj(ctx); return o == null ? undefined : fn(o, path, ctx); }) :
|
|
51
|
+
a[0] === '[]' && a.length === 3 ? (obj = compile(a[1]), path = compile(a[2]), ctx => fn(obj(ctx), path(ctx), ctx)) :
|
|
52
|
+
a[0] === '?.[]' ? (obj = compile(a[1]), path = compile(a[2]), ctx => { const o = obj(ctx); return o == null ? undefined : fn(o, path(ctx), ctx); }) :
|
|
53
|
+
(a = compile(a), ctx => fn([a(ctx)], 0, ctx))
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
// Export as prop for backward compatibility with other features
|
|
57
|
+
export const prop = call;
|
package/eval/accessor.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Object accessor properties (getters/setters) - eval half
|
|
2
|
+
import { operator, compile } from '../parse.js';
|
|
3
|
+
|
|
4
|
+
// Accessor marker for object property definitions
|
|
5
|
+
export const ACC = Symbol('accessor');
|
|
6
|
+
|
|
7
|
+
operator('get', (name, body) => {
|
|
8
|
+
body = body ? compile(body) : () => {};
|
|
9
|
+
return ctx => [[ACC, name, {
|
|
10
|
+
get: function() { const s = Object.create(ctx || {}); s.this = this; return body(s); }
|
|
11
|
+
}]];
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
operator('set', (name, param, body) => {
|
|
15
|
+
body = body ? compile(body) : () => {};
|
|
16
|
+
return ctx => [[ACC, name, {
|
|
17
|
+
set: function(v) { const s = Object.create(ctx || {}); s.this = this; s[param] = v; body(s); }
|
|
18
|
+
}]];
|
|
19
|
+
});
|
package/eval/async.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Async/await/yield - eval half
|
|
2
|
+
import { operator, compile } from '../parse.js';
|
|
3
|
+
|
|
4
|
+
operator('async', fn => {
|
|
5
|
+
const inner = compile(fn);
|
|
6
|
+
return ctx => {
|
|
7
|
+
const f = inner(ctx);
|
|
8
|
+
return async function(...args) { return f(...args); };
|
|
9
|
+
};
|
|
10
|
+
});
|
|
11
|
+
operator('await', a => (a = compile(a), async ctx => await a(ctx)));
|
|
12
|
+
operator('yield', a => (a = a ? compile(a) : null, ctx => { throw { __yield__: a ? a(ctx) : undefined }; }));
|
|
13
|
+
operator('yield*', a => (a = compile(a), ctx => { throw { __yield_all__: a(ctx) }; }));
|
package/eval/class.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Class declarations and expressions - eval half
|
|
2
|
+
import { operator, compile } from '../parse.js';
|
|
3
|
+
|
|
4
|
+
const STATIC = Symbol('static');
|
|
5
|
+
const err = msg => { throw Error(msg) };
|
|
6
|
+
|
|
7
|
+
operator('instanceof', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) instanceof b(ctx)));
|
|
8
|
+
operator('class', (name, base, body) => {
|
|
9
|
+
base = base ? compile(base) : null;
|
|
10
|
+
body = body ? compile(body) : null;
|
|
11
|
+
return ctx => {
|
|
12
|
+
const Parent = base ? base(ctx) : Object;
|
|
13
|
+
const cls = function(...args) {
|
|
14
|
+
if (!(this instanceof cls)) return err('Class constructor must be called with new');
|
|
15
|
+
const instance = base ? Reflect.construct(Parent, args, cls) : this;
|
|
16
|
+
if (cls.prototype.__constructor__) cls.prototype.__constructor__.apply(instance, args);
|
|
17
|
+
return instance;
|
|
18
|
+
};
|
|
19
|
+
Object.setPrototypeOf(cls.prototype, Parent.prototype);
|
|
20
|
+
Object.setPrototypeOf(cls, Parent);
|
|
21
|
+
if (body) {
|
|
22
|
+
const methods = Object.create(ctx);
|
|
23
|
+
methods['super'] = Parent;
|
|
24
|
+
const entries = body(methods);
|
|
25
|
+
const items = Array.isArray(entries) && typeof entries[0]?.[0] === 'string' ? entries : [];
|
|
26
|
+
for (const [k, v] of items) {
|
|
27
|
+
if (k === 'constructor') cls.prototype.__constructor__ = v;
|
|
28
|
+
else cls.prototype[k] = v;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (name) ctx[name] = cls;
|
|
32
|
+
return cls;
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
operator('static', a => (a = compile(a), ctx => [[STATIC, a(ctx)]]));
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Collection literals - eval half
|
|
2
|
+
// Block vs object detection (jessie):
|
|
3
|
+
// ['{}', [';', ...]] → ['{', body] block with statements
|
|
4
|
+
// ['{}', ['let', ...]] → ['{', body] block with declaration
|
|
5
|
+
// ['{}', null] → ['{}', null] empty object {}
|
|
6
|
+
// ['{}', 'a'] → ['{}', 'a'] object shorthand {a}
|
|
7
|
+
// ['{}', [':', k, v]] → ['{}', ...] object literal
|
|
8
|
+
import { operator, compile } from '../parse.js';
|
|
9
|
+
import { ACC } from './accessor.js';
|
|
10
|
+
|
|
11
|
+
// Object inner: null, string, or array starting with : , ... get set
|
|
12
|
+
const isObject = a => a == null || typeof a === 'string' || [':', ',', '...', 'get', 'set'].includes(a[0]);
|
|
13
|
+
|
|
14
|
+
operator('{}', (a, b) => {
|
|
15
|
+
if (b !== undefined) return;
|
|
16
|
+
// Block: not an object pattern
|
|
17
|
+
if (!isObject(a)) return compile(['{', a]);
|
|
18
|
+
// Object literal
|
|
19
|
+
a = !a ? [] : a[0] !== ',' ? [a] : a.slice(1);
|
|
20
|
+
const props = a.map(p => compile(typeof p === 'string' ? [':', p, p] : p));
|
|
21
|
+
return ctx => {
|
|
22
|
+
const obj = {}, acc = {};
|
|
23
|
+
for (const e of props.flatMap(f => f(ctx))) {
|
|
24
|
+
if (e[0] === ACC) {
|
|
25
|
+
const [, n, desc] = e;
|
|
26
|
+
acc[n] = { ...acc[n], ...desc, configurable: true, enumerable: true };
|
|
27
|
+
} else obj[e[0]] = e[1];
|
|
28
|
+
}
|
|
29
|
+
for (const n in acc) Object.defineProperty(obj, n, acc[n]);
|
|
30
|
+
return obj;
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Block statement - creates scope
|
|
35
|
+
operator('{', body => {
|
|
36
|
+
body = body ? compile(body) : () => undefined;
|
|
37
|
+
return ctx => body(Object.create(ctx));
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
operator(':', (a, b) => (b = compile(b), Array.isArray(a) ?
|
|
41
|
+
(a = compile(a), ctx => [[a(ctx), b(ctx)]]) : ctx => [[a, b(ctx)]]));
|
package/eval/function.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Function declarations and expressions - eval half
|
|
2
|
+
import { operator, compile } from '../parse.js';
|
|
3
|
+
import { RETURN } from './op/arrow.js';
|
|
4
|
+
|
|
5
|
+
operator('function', (name, params, body) => {
|
|
6
|
+
body = body ? compile(body) : () => undefined;
|
|
7
|
+
// Normalize params: null → [], 'x' → ['x'], [',', 'a', 'b'] → ['a', 'b']
|
|
8
|
+
const ps = !params ? [] : params[0] === ',' ? params.slice(1) : [params];
|
|
9
|
+
// Check for rest param
|
|
10
|
+
let restName = null, restIdx = -1;
|
|
11
|
+
const last = ps[ps.length - 1];
|
|
12
|
+
if (Array.isArray(last) && last[0] === '...') {
|
|
13
|
+
restIdx = ps.length - 1;
|
|
14
|
+
restName = last[1];
|
|
15
|
+
ps.length--;
|
|
16
|
+
}
|
|
17
|
+
return ctx => {
|
|
18
|
+
const fn = (...args) => {
|
|
19
|
+
const l = {};
|
|
20
|
+
ps.forEach((p, i) => l[p] = args[i]);
|
|
21
|
+
if (restName) l[restName] = args.slice(restIdx);
|
|
22
|
+
const fnCtx = new Proxy(l, {
|
|
23
|
+
get: (l, k) => k in l ? l[k] : ctx[k],
|
|
24
|
+
set: (l, k, v) => ((k in l ? l : ctx)[k] = v, true),
|
|
25
|
+
has: (l, k) => k in l || k in ctx
|
|
26
|
+
});
|
|
27
|
+
try { return body(fnCtx); }
|
|
28
|
+
catch (e) { if (e === RETURN) return e[0]; throw e; }
|
|
29
|
+
};
|
|
30
|
+
if (name) ctx[name] = fn;
|
|
31
|
+
return fn;
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
operator('function*', (name, params, body) => {
|
|
36
|
+
throw Error('Generator functions are not supported in evaluation');
|
|
37
|
+
});
|
package/eval/if.js
ADDED
package/eval/jessie.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* jessie - eval aggregator
|
|
3
|
+
* Runtime handlers for jessie dialect (must be paired with feature/jessie.js).
|
|
4
|
+
*/
|
|
5
|
+
import './justin.js';
|
|
6
|
+
|
|
7
|
+
import './var.js';
|
|
8
|
+
import './function.js';
|
|
9
|
+
import './async.js';
|
|
10
|
+
import './class.js';
|
|
11
|
+
import './regex.js';
|
|
12
|
+
|
|
13
|
+
import './if.js';
|
|
14
|
+
import './loop.js';
|
|
15
|
+
import './try.js';
|
|
16
|
+
import './switch.js';
|
|
17
|
+
|
|
18
|
+
import './module.js';
|
|
19
|
+
import './accessor.js';
|
package/eval/justin.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* justin - eval aggregator
|
|
3
|
+
* Runtime handlers for justin dialect (must be paired with feature/justin.js).
|
|
4
|
+
*/
|
|
5
|
+
import './subscript.js';
|
|
6
|
+
|
|
7
|
+
import './op/identity.js';
|
|
8
|
+
import './op/nullish.js';
|
|
9
|
+
import './op/pow.js';
|
|
10
|
+
import './op/membership.js';
|
|
11
|
+
import './op/bitwise-unsigned.js';
|
|
12
|
+
import './op/assign-logical.js';
|
|
13
|
+
|
|
14
|
+
import './op/ternary.js';
|
|
15
|
+
import './op/arrow.js';
|
|
16
|
+
import './op/spread.js';
|
|
17
|
+
import './op/optional.js';
|
|
18
|
+
import './op/unary.js';
|
|
19
|
+
|
|
20
|
+
import './collection.js';
|
|
21
|
+
import './template.js';
|
package/eval/loop.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Loops - eval half
|
|
2
|
+
import { operator, compile } from '../parse.js';
|
|
3
|
+
import { RETURN } from './op/arrow.js';
|
|
4
|
+
import { destructure } from './var.js';
|
|
5
|
+
|
|
6
|
+
export const BREAK = Symbol('break'), CONTINUE = Symbol('continue');
|
|
7
|
+
|
|
8
|
+
operator('while', (cond, body) => {
|
|
9
|
+
cond = compile(cond); body = compile(body);
|
|
10
|
+
return ctx => {
|
|
11
|
+
let res;
|
|
12
|
+
while (cond(ctx)) try { res = body(ctx); } catch (e) {
|
|
13
|
+
if (e === BREAK) break;
|
|
14
|
+
if (e === CONTINUE) continue;
|
|
15
|
+
if (e === RETURN) return e[0];
|
|
16
|
+
throw e;
|
|
17
|
+
}
|
|
18
|
+
return res;
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
operator('do', (body, cond) => {
|
|
23
|
+
body = compile(body); cond = compile(cond);
|
|
24
|
+
return ctx => {
|
|
25
|
+
let res;
|
|
26
|
+
do try { res = body(ctx); } catch (e) {
|
|
27
|
+
if (e === BREAK) break;
|
|
28
|
+
if (e === CONTINUE) continue;
|
|
29
|
+
if (e === RETURN) return e[0];
|
|
30
|
+
throw e;
|
|
31
|
+
} while (cond(ctx));
|
|
32
|
+
return res;
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
operator('for', (head, body) => {
|
|
37
|
+
// Normalize head: [';', init, cond, step] or single expr (for-in/of)
|
|
38
|
+
if (Array.isArray(head) && head[0] === ';') {
|
|
39
|
+
let [, init, cond, step] = head;
|
|
40
|
+
init = init ? compile(init) : null;
|
|
41
|
+
cond = cond ? compile(cond) : () => true;
|
|
42
|
+
step = step ? compile(step) : null;
|
|
43
|
+
body = compile(body);
|
|
44
|
+
return ctx => {
|
|
45
|
+
let res;
|
|
46
|
+
for (init?.(ctx); cond(ctx); step?.(ctx)) try { res = body(ctx); } catch (e) {
|
|
47
|
+
if (e === BREAK) break;
|
|
48
|
+
if (e === CONTINUE) continue;
|
|
49
|
+
if (e === RETURN) return e[0];
|
|
50
|
+
throw e;
|
|
51
|
+
}
|
|
52
|
+
return res;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
// For-in/of: head is ['in', lhs, rhs] or ['of', lhs, rhs]
|
|
56
|
+
if (Array.isArray(head) && (head[0] === 'in' || head[0] === 'of')) {
|
|
57
|
+
let [op, lhs, rhs] = head;
|
|
58
|
+
// Extract name from declaration: ['let', 'x'] → 'x'
|
|
59
|
+
if (Array.isArray(lhs) && (lhs[0] === 'let' || lhs[0] === 'const' || lhs[0] === 'var')) lhs = lhs[1];
|
|
60
|
+
if (op === 'in') return forIn(lhs, rhs, body);
|
|
61
|
+
if (op === 'of') return forOf(lhs, rhs, body);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const forOf = (name, iterable, body) => {
|
|
66
|
+
iterable = compile(iterable); body = compile(body);
|
|
67
|
+
const isPattern = Array.isArray(name);
|
|
68
|
+
return ctx => {
|
|
69
|
+
let res;
|
|
70
|
+
const prev = isPattern ? null : ctx[name];
|
|
71
|
+
for (const val of iterable(ctx)) {
|
|
72
|
+
if (isPattern) destructure(name, val, ctx); else ctx[name] = val;
|
|
73
|
+
try { res = body(ctx); } catch (e) {
|
|
74
|
+
if (e === BREAK) break;
|
|
75
|
+
if (e === CONTINUE) continue;
|
|
76
|
+
if (e === RETURN) return e[0];
|
|
77
|
+
throw e;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (!isPattern) ctx[name] = prev;
|
|
81
|
+
return res;
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const forIn = (name, obj, body) => {
|
|
86
|
+
obj = compile(obj); body = compile(body);
|
|
87
|
+
const isPattern = Array.isArray(name);
|
|
88
|
+
return ctx => {
|
|
89
|
+
let res;
|
|
90
|
+
const prev = isPattern ? null : ctx[name];
|
|
91
|
+
for (const key in obj(ctx)) {
|
|
92
|
+
if (isPattern) destructure(name, key, ctx); else ctx[name] = key;
|
|
93
|
+
try { res = body(ctx); } catch (e) {
|
|
94
|
+
if (e === BREAK) break;
|
|
95
|
+
if (e === CONTINUE) continue;
|
|
96
|
+
if (e === RETURN) return e[0];
|
|
97
|
+
throw e;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (!isPattern) ctx[name] = prev;
|
|
101
|
+
return res;
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
operator('break', () => () => { throw BREAK; });
|
|
106
|
+
operator('continue', () => () => { throw CONTINUE; });
|
|
107
|
+
operator('return', val => (val = val !== undefined ? compile(val) : null,
|
|
108
|
+
ctx => { throw (RETURN[0] = val?.(ctx), RETURN); }));
|
package/eval/module.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Import/Export - eval stubs (parse-only semantics)
|
|
2
|
+
import { operator, compile } from '../parse.js';
|
|
3
|
+
|
|
4
|
+
operator('import', () => () => undefined);
|
|
5
|
+
operator('export', () => () => undefined);
|
|
6
|
+
operator('from', (a, b) => () => undefined);
|
|
7
|
+
operator('as', (a, b) => () => undefined);
|
|
8
|
+
operator('default', (a) => compile(a));
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Arithmetic operators - eval half
|
|
2
|
+
import { operator, compile } from '../../parse.js';
|
|
3
|
+
|
|
4
|
+
operator('+', (a, b) => b !== undefined ?
|
|
5
|
+
(a = compile(a), b = compile(b), ctx => a(ctx) + b(ctx)) :
|
|
6
|
+
(a = compile(a), ctx => +a(ctx)));
|
|
7
|
+
operator('-', (a, b) => b !== undefined ?
|
|
8
|
+
(a = compile(a), b = compile(b), ctx => a(ctx) - b(ctx)) :
|
|
9
|
+
(a = compile(a), ctx => -a(ctx)));
|
|
10
|
+
operator('*', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) * b(ctx)));
|
|
11
|
+
operator('/', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) / b(ctx)));
|
|
12
|
+
operator('%', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) % b(ctx)));
|
package/eval/op/arrow.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Arrow function operator - eval half
|
|
2
|
+
import { operator, compile } from '../../parse.js';
|
|
3
|
+
|
|
4
|
+
// RETURN: array to hold value - reused, no allocation per throw
|
|
5
|
+
export const RETURN = [];
|
|
6
|
+
|
|
7
|
+
operator('=>', (a, b) => {
|
|
8
|
+
// Normalize params: () → [], x → [x], (a, b) → [a, b]
|
|
9
|
+
a = a?.[0] === '()' ? a[1] : a;
|
|
10
|
+
const ps = !a ? [] : a[0] === ',' ? a.slice(1) : [a];
|
|
11
|
+
// Check for rest param
|
|
12
|
+
let restIdx = -1, restName = null;
|
|
13
|
+
const last = ps[ps.length - 1];
|
|
14
|
+
if (Array.isArray(last) && last[0] === '...') {
|
|
15
|
+
restIdx = ps.length - 1;
|
|
16
|
+
restName = last[1];
|
|
17
|
+
ps.length--;
|
|
18
|
+
}
|
|
19
|
+
// Arrow body: {} is always block (need parens for object: ({...}))
|
|
20
|
+
// Block body returns undefined unless explicit return
|
|
21
|
+
const isBlock = b?.[0] === '{}';
|
|
22
|
+
b = compile(isBlock ? ['{', b[1]] : b);
|
|
23
|
+
return ctx => (...args) => {
|
|
24
|
+
const l = {};
|
|
25
|
+
ps.forEach((p, i) => l[p] = args[i]);
|
|
26
|
+
if (restName) l[restName] = args.slice(restIdx);
|
|
27
|
+
const fnCtx = new Proxy(l, {
|
|
28
|
+
get: (l, k) => k in l ? l[k] : ctx?.[k],
|
|
29
|
+
set: (l, k, v) => ((k in l ? l : ctx)[k] = v, true),
|
|
30
|
+
has: (l, k) => k in l || (ctx ? k in ctx : false)
|
|
31
|
+
});
|
|
32
|
+
try { const r = b(fnCtx); return isBlock ? undefined : r; }
|
|
33
|
+
catch (e) { if (e === RETURN) return e[0]; throw e; }
|
|
34
|
+
};
|
|
35
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Logical/nullish assignment operators + destructuring assignment - eval half
|
|
2
|
+
import { operator, compile } from '../../parse.js';
|
|
3
|
+
import { destructure } from '../var.js';
|
|
4
|
+
import { isLval, prop } from '../access.js';
|
|
5
|
+
|
|
6
|
+
const err = msg => { throw Error(msg) };
|
|
7
|
+
|
|
8
|
+
// Override = to support destructuring
|
|
9
|
+
operator('=', (a, b) => {
|
|
10
|
+
// Handle let/const/var declarations: ['=', ['let', pattern], value]
|
|
11
|
+
if (Array.isArray(a) && (a[0] === 'let' || a[0] === 'const' || a[0] === 'var')) {
|
|
12
|
+
const pattern = a[1];
|
|
13
|
+
b = compile(b);
|
|
14
|
+
if (typeof pattern === 'string') return ctx => { ctx[pattern] = b(ctx); };
|
|
15
|
+
return ctx => destructure(pattern, b(ctx), ctx);
|
|
16
|
+
}
|
|
17
|
+
isLval(a) || err('Invalid assignment target');
|
|
18
|
+
return (b = compile(b), prop(a, (obj, path, ctx) => obj[path] = b(ctx)));
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
operator('||=', (a, b) => (isLval(a) || err('Invalid assignment target'), b = compile(b), prop(a, (o, k, ctx) => o[k] ||= b(ctx))));
|
|
22
|
+
operator('&&=', (a, b) => (isLval(a) || err('Invalid assignment target'), b = compile(b), prop(a, (o, k, ctx) => o[k] &&= b(ctx))));
|
|
23
|
+
operator('??=', (a, b) => (isLval(a) || err('Invalid assignment target'), b = compile(b), prop(a, (o, k, ctx) => o[k] ??= b(ctx))));
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Assignment operators (C-family) - eval half
|
|
2
|
+
import { operator, compile } from '../../parse.js';
|
|
3
|
+
|
|
4
|
+
// Simple assign helper for x, a.b, a[b], (x)
|
|
5
|
+
const assign = (a, fn, obj, key) =>
|
|
6
|
+
typeof a === 'string' ? ctx => fn(ctx, a, ctx) :
|
|
7
|
+
a[0] === '.' ? (obj = compile(a[1]), key = a[2], ctx => fn(obj(ctx), key, ctx)) :
|
|
8
|
+
a[0] === '[]' && a.length === 3 ? (obj = compile(a[1]), key = compile(a[2]), ctx => fn(obj(ctx), key(ctx), ctx)) :
|
|
9
|
+
a[0] === '()' && a.length === 2 ? assign(a[1], fn) :
|
|
10
|
+
(() => { throw Error('Invalid assignment target') })();
|
|
11
|
+
|
|
12
|
+
const ops = { '=': (o,k,v)=>o[k]=v, '+=': (o,k,v)=>o[k]+=v, '-=': (o,k,v)=>o[k]-=v, '*=': (o,k,v)=>o[k]*=v,
|
|
13
|
+
'/=': (o,k,v)=>o[k]/=v, '%=': (o,k,v)=>o[k]%=v, '|=': (o,k,v)=>o[k]|=v, '&=': (o,k,v)=>o[k]&=v,
|
|
14
|
+
'^=': (o,k,v)=>o[k]^=v, '>>=': (o,k,v)=>o[k]>>=v, '<<=': (o,k,v)=>o[k]<<=v };
|
|
15
|
+
for (const op in ops) operator(op, (a, b) => (b = compile(b), assign(a, (o, k, ctx) => ops[op](o, k, b(ctx)))));
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Unsigned right shift operators - eval half
|
|
2
|
+
import { operator, compile } from '../../parse.js';
|
|
3
|
+
import { isLval, prop } from '../access.js';
|
|
4
|
+
|
|
5
|
+
const err = msg => { throw Error(msg) };
|
|
6
|
+
|
|
7
|
+
operator('>>>', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) >>> b(ctx)));
|
|
8
|
+
operator('>>>=', (a, b) => (isLval(a) || err('Invalid assignment target'), b = compile(b), prop(a, (o, k, ctx) => o[k] >>>= b(ctx))));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Bitwise operators - eval half
|
|
2
|
+
import { operator, compile } from '../../parse.js';
|
|
3
|
+
|
|
4
|
+
operator('~', a => (a = compile(a), ctx => ~a(ctx)));
|
|
5
|
+
operator('|', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) | b(ctx)));
|
|
6
|
+
operator('&', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) & b(ctx)));
|
|
7
|
+
operator('^', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) ^ b(ctx)));
|
|
8
|
+
operator('>>', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) >> b(ctx)));
|
|
9
|
+
operator('<<', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) << b(ctx)));
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Comparison operators - eval half
|
|
2
|
+
import { operator, compile } from '../../parse.js';
|
|
3
|
+
|
|
4
|
+
operator('>', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) > b(ctx)));
|
|
5
|
+
operator('<', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) < b(ctx)));
|
|
6
|
+
operator('>=', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) >= b(ctx)));
|
|
7
|
+
operator('<=', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) <= b(ctx)));
|
package/eval/op/defer.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Increment/decrement operators - eval half
|
|
2
|
+
// b=null means postfix, b=undefined means prefix
|
|
3
|
+
import { operator, compile } from '../../parse.js';
|
|
4
|
+
|
|
5
|
+
const inc = (a, fn, obj, key) =>
|
|
6
|
+
typeof a === 'string' ? ctx => fn(ctx, a) :
|
|
7
|
+
a[0] === '.' ? (obj = compile(a[1]), key = a[2], ctx => fn(obj(ctx), key)) :
|
|
8
|
+
a[0] === '[]' && a.length === 3 ? (obj = compile(a[1]), key = compile(a[2]), ctx => fn(obj(ctx), key(ctx))) :
|
|
9
|
+
a[0] === '()' && a.length === 2 ? inc(a[1], fn) :
|
|
10
|
+
(() => { throw Error('Invalid increment target') })();
|
|
11
|
+
|
|
12
|
+
operator('++', (a, b) => inc(a, b === null ? (o, k) => o[k]++ : (o, k) => ++o[k]));
|
|
13
|
+
operator('--', (a, b) => inc(a, b === null ? (o, k) => o[k]-- : (o, k) => --o[k]));
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Logical operators (base) - eval half
|
|
2
|
+
import { operator, compile } from '../../parse.js';
|
|
3
|
+
|
|
4
|
+
operator('!', a => (a = compile(a), ctx => !a(ctx)));
|
|
5
|
+
operator('||', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) || b(ctx)));
|
|
6
|
+
operator('&&', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) && b(ctx)));
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Optional chaining operators - eval half
|
|
2
|
+
import { operator, compile } from '../../parse.js';
|
|
3
|
+
import { unsafe } from '../access.js';
|
|
4
|
+
|
|
5
|
+
operator('?.', (a, b) => (a = compile(a), unsafe(b) ? () => undefined : ctx => a(ctx)?.[b]));
|
|
6
|
+
operator('?.[]', (a, b) => (a = compile(a), b = compile(b), ctx => { const k = b(ctx); return unsafe(k) ? undefined : a(ctx)?.[k]; }));
|
|
7
|
+
operator('?.()', (a, b) => {
|
|
8
|
+
const args = !b ? () => [] :
|
|
9
|
+
b[0] === ',' ? (b = b.slice(1).map(compile), ctx => b.map(arg => arg(ctx))) :
|
|
10
|
+
(b = compile(b), ctx => [b(ctx)]);
|
|
11
|
+
|
|
12
|
+
// Handle nested optional chain: a?.method?.() or a?.["method"]?.()
|
|
13
|
+
if (a[0] === '?.') {
|
|
14
|
+
const container = compile(a[1]);
|
|
15
|
+
const prop = a[2];
|
|
16
|
+
return unsafe(prop) ? () => undefined :
|
|
17
|
+
ctx => { const c = container(ctx); return c?.[prop]?.(...args(ctx)); };
|
|
18
|
+
}
|
|
19
|
+
if (a[0] === '?.[]') {
|
|
20
|
+
const container = compile(a[1]);
|
|
21
|
+
const prop = compile(a[2]);
|
|
22
|
+
return ctx => { const c = container(ctx); const p = prop(ctx); return unsafe(p) ? undefined : c?.[p]?.(...args(ctx)); };
|
|
23
|
+
}
|
|
24
|
+
// Handle a?.() where a is a.method or a[method] - need to bind this
|
|
25
|
+
if (a[0] === '.') {
|
|
26
|
+
const obj = compile(a[1]);
|
|
27
|
+
const prop = a[2];
|
|
28
|
+
return unsafe(prop) ? () => undefined :
|
|
29
|
+
ctx => { const o = obj(ctx); return o?.[prop]?.(...args(ctx)); };
|
|
30
|
+
}
|
|
31
|
+
if (a[0] === '[]' && a.length === 3) {
|
|
32
|
+
const obj = compile(a[1]);
|
|
33
|
+
const prop = compile(a[2]);
|
|
34
|
+
return ctx => { const o = obj(ctx); const p = prop(ctx); return unsafe(p) ? undefined : o?.[p]?.(...args(ctx)); };
|
|
35
|
+
}
|
|
36
|
+
const fn = compile(a);
|
|
37
|
+
return ctx => fn(ctx)?.(...args(ctx));
|
|
38
|
+
});
|
package/eval/op/pow.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Exponentiation operator - eval half
|
|
2
|
+
import { operator, compile } from '../../parse.js';
|
|
3
|
+
import { isLval, prop } from '../access.js';
|
|
4
|
+
|
|
5
|
+
const err = msg => { throw Error(msg) };
|
|
6
|
+
|
|
7
|
+
operator('**', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) ** b(ctx)));
|
|
8
|
+
operator('**=', (a, b) => (isLval(a) || err('Invalid assignment target'), b = compile(b), prop(a, (obj, path, ctx) => obj[path] **= b(ctx))));
|
package/eval/op/range.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Range operators - eval half
|
|
2
|
+
import { operator, compile } from '../../parse.js';
|
|
3
|
+
|
|
4
|
+
operator('..', (a, b) => (a = compile(a), b = compile(b), ctx => {
|
|
5
|
+
const start = a(ctx), end = b(ctx), arr = [];
|
|
6
|
+
for (let i = start; i <= end; i++) arr.push(i);
|
|
7
|
+
return arr;
|
|
8
|
+
}));
|
|
9
|
+
operator('..<', (a, b) => (a = compile(a), b = compile(b), ctx => {
|
|
10
|
+
const start = a(ctx), end = b(ctx), arr = [];
|
|
11
|
+
for (let i = start; i < end; i++) arr.push(i);
|
|
12
|
+
return arr;
|
|
13
|
+
}));
|