subscript 9.2.0 → 10.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +101 -184
  2. package/feature/access.js +68 -7
  3. package/feature/accessor.js +49 -0
  4. package/feature/asi.js +15 -0
  5. package/feature/async.js +45 -0
  6. package/feature/block.js +41 -0
  7. package/feature/class.js +69 -0
  8. package/feature/collection.js +40 -0
  9. package/feature/comment.js +25 -5
  10. package/feature/control.js +2 -142
  11. package/feature/destruct.js +33 -0
  12. package/feature/function.js +44 -0
  13. package/feature/group.js +13 -9
  14. package/feature/if.js +23 -38
  15. package/feature/literal.js +13 -0
  16. package/feature/loop.js +107 -106
  17. package/feature/module.js +42 -0
  18. package/feature/number.js +41 -38
  19. package/feature/op/arithmetic.js +29 -0
  20. package/feature/op/arrow.js +33 -0
  21. package/feature/op/assign-logical.js +33 -0
  22. package/feature/op/assignment.js +26 -0
  23. package/feature/op/bitwise-unsigned.js +17 -0
  24. package/feature/op/bitwise.js +29 -0
  25. package/feature/op/comparison.js +19 -0
  26. package/feature/op/defer.js +15 -0
  27. package/feature/op/equality.js +16 -0
  28. package/feature/op/identity.js +15 -0
  29. package/feature/op/increment.js +23 -0
  30. package/feature/op/logical.js +21 -0
  31. package/feature/op/membership.js +17 -0
  32. package/feature/op/nullish.js +13 -0
  33. package/feature/op/optional.js +61 -0
  34. package/feature/op/pow.js +19 -0
  35. package/feature/op/range.js +26 -0
  36. package/feature/op/spread.js +15 -0
  37. package/feature/op/ternary.js +15 -0
  38. package/feature/op/type.js +18 -0
  39. package/feature/op/unary.js +41 -0
  40. package/feature/prop.js +34 -0
  41. package/feature/regex.js +31 -0
  42. package/feature/seq.js +21 -0
  43. package/feature/string.js +24 -17
  44. package/feature/switch.js +48 -0
  45. package/feature/template.js +39 -0
  46. package/feature/try.js +57 -0
  47. package/feature/unit.js +35 -0
  48. package/feature/var.js +51 -41
  49. package/jessie.js +31 -0
  50. package/jessie.min.js +8 -0
  51. package/justin.js +39 -48
  52. package/justin.min.js +8 -4
  53. package/package.json +15 -16
  54. package/parse.js +146 -0
  55. package/subscript.d.ts +45 -5
  56. package/subscript.js +62 -22
  57. package/subscript.min.js +5 -4
  58. package/util/bundle.js +507 -0
  59. package/util/stringify.js +172 -0
  60. package/feature/add.js +0 -22
  61. package/feature/array.js +0 -11
  62. package/feature/arrow.js +0 -23
  63. package/feature/assign.js +0 -11
  64. package/feature/bitwise.js +0 -11
  65. package/feature/bool.js +0 -5
  66. package/feature/call.js +0 -15
  67. package/feature/compare.js +0 -11
  68. package/feature/increment.js +0 -11
  69. package/feature/logic.js +0 -11
  70. package/feature/mult.js +0 -25
  71. package/feature/object.js +0 -17
  72. package/feature/optional.js +0 -23
  73. package/feature/pow.js +0 -5
  74. package/feature/shift.js +0 -12
  75. package/feature/spread.js +0 -6
  76. package/feature/ternary.js +0 -10
  77. package/src/compile.d.ts +0 -17
  78. package/src/compile.js +0 -28
  79. package/src/const.js +0 -45
  80. package/src/parse.d.ts +0 -22
  81. package/src/parse.js +0 -113
  82. package/src/stringify.js +0 -27
  83. /package/{LICENSE → license} +0 -0
package/README.md CHANGED
@@ -1,245 +1,162 @@
1
- # sub<em>script</em> <a href="https://github.com/spectjs/subscript/actions/workflows/node.js.yml"><img src="https://github.com/spectjs/subscript/actions/workflows/node.js.yml/badge.svg"/></a> <a href="https://bundlejs.com/?q=subscript"><img alt="npm bundle size" src="https://img.shields.io/bundlejs/size/subscript"/></a> <a href="http://npmjs.org/subscript"><img src="https://img.shields.io/npm/v/subscript"/></a> <a href="http://microjs.com/#subscript"><img src="https://img.shields.io/badge/microjs-subscript-blue?color=darkslateblue"/></a>
1
+ <h1 align="center">sub<sub><sup> </sup></sub>script</h1>
2
2
 
3
- > _Subscript_ is fast, tiny & extensible parser / evaluator / microlanguage.
3
+ <p align="center">Tiny expression parser & evaluator.</p>
4
+ <div align="center">
5
+
6
+ [![build](https://github.com/dy/subscript/actions/workflows/node.js.yml/badge.svg)](https://github.com/dy/subscript/actions/workflows/node.js.yml) [![npm](https://img.shields.io/npm/v/subscript)](http://npmjs.org/subscript) [![size](https://img.shields.io/bundlephobia/minzip/subscript?label=size)](https://bundlephobia.com/package/subscript) [![microjs](https://img.shields.io/badge/µjs-subscript-darkslateblue)](http://microjs.com/#subscript) <!--[![demo](https://img.shields.io/badge/play-%F0%9F%9A%80-white)](https://dy.github.io/subscript/)-->
4
7
 
5
- #### Used for:
8
+ </div>
6
9
 
7
- * expressions evaluators, calculators
8
- * subsets of languages
9
- * sandboxes, playgrounds, safe eval
10
- * custom DSL
11
- * preprocessors
12
- * templates
13
10
 
14
- _Subscript_ has [3.5kb](https://npmfs.com/package/subscript/7.4.3/subscript.min.js) footprint <!-- (compare to [11.4kb](https://npmfs.com/package/jsep/1.2.0/dist/jsep.min.js) _jsep_ + [4.5kb](https://npmfs.com/package/expression-eval/5.0.0/dist/expression-eval.module.js) _expression-eval_) -->, good [performance](#performance) and extensive test coverage.
11
+ ```js
12
+ import subscript from 'subscript'
15
13
 
14
+ let fn = subscript('a + b * 2')
15
+ fn({ a: 1, b: 3 }) // 7
16
+ ```
16
17
 
17
- ## Usage
18
+ * **Safe** — sandboxed, blocks `__proto__`, `constructor`, no global access
19
+ * **Fast** — Pratt parser engine, see [benchmarks](#performance)
20
+ * **Portable** — universal expression format, see [spec](./spec.md)
21
+ * **Extensible** — pluggable syntax, see [DSL builder](https://dy.github.io/subscript/)
22
+ * **Metacircular** — can parse and compile itself
18
23
 
19
- ```js
20
- import subscript from './subscript.js'
21
24
 
22
- // parse expression
23
- const fn = subscript('a.b + Math.sqrt(c - 1)')
24
-
25
- // evaluate with context
26
- fn({ a: { b:1 }, c: 5, Math })
27
- // 3
28
- ```
25
+ ## Presets
29
26
 
30
- ## Operators
31
-
32
- _Subscript_ supports [common syntax](https://en.wikipedia.org/wiki/Comparison_of_programming_languages_(syntax)) (_JavaScript_, _C_, _C++_, _Java_, _C#_, _PHP_, _Swift_, _Objective-C_, _Kotlin_, _Perl_ etc.):
33
-
34
- * `a.b`, `a[b]`, `a(b)`
35
- * `a++`, `a--`, `++a`, `--a`
36
- * `a * b`, `a / b`, `a % b`
37
- * `+a`, `-a`, `a + b`, `a - b`
38
- * `a < b`, `a <= b`, `a > b`, `a >= b`, `a == b`, `a != b`
39
- * `~a`, `a & b`, `a ^ b`, `a | b`, `a << b`, `a >> b`
40
- * `!a`, `a && b`, `a || b`
41
- * `a = b`, `a += b`, `a -= b`, `a *= b`, `a /= b`, `a %= b`, `a <<= b`, `a >>= b`
42
- * `(a, (b))`, `a; b;`
43
- * `"abc"`, `'abc'`
44
- * `0.1`, `1.2e+3`
45
-
46
- ### Justin
47
-
48
- _Just-in_ is no-keywords JS subset, _JSON_ + _expressions_ (see [thread](https://github.com/endojs/Jessie/issues/66)).<br/>
49
- It extends _subscript_ with:
50
-
51
- + `a === b`, `a !== b`
52
- + `a ** b`, `a **= b`
53
- + `a ?? b`, `a ??= b`
54
- + `a ||= b`, `a &&= b`
55
- + `a >>> b`, `a >>>= b`
56
- + `a ? b : c`, `a?.b`
57
- + `...a`
58
- + `[a, b]`
59
- + `{a: b}`
60
- + `(a, b) => c`
61
- + `// foo`, `/* bar */`
62
- + `true`, `false`, `null`, `NaN`, `undefined`
63
- + `a in b`
27
+ **Subscript**: common expressions:
64
28
 
65
29
  ```js
66
- import justin from 'subscript/justin'
30
+ import subscript from 'subscript'
67
31
 
68
- let fn = justin('{ x: 1, "y": 2+2 }["x"]')
69
- fn() // 1
32
+ subscript('a.b + c * 2')({ a: { b: 1 }, c: 3 }) // 7
70
33
  ```
71
34
 
72
- ### Control Flow
35
+ **Justin**: JSON + expressions + templates + arrows:
73
36
 
74
- For statement syntax, import `subscript/feature/control.js`:
37
+ ```js
38
+ import justin from 'subscript/justin.js'
39
+
40
+ justin('{ x: a?.b ?? 0, y: [1, ...rest] }')({ a: null, rest: [2, 3] })
41
+ // { x: 0, y: [1, 2, 3] }
42
+ ```
75
43
 
76
- + `if (c) a`, `if (c) a else b`
77
- + `while (c) body`
78
- + `for (init; cond; step) body`
79
- + `{ a; b }` — block scope
80
- + `let x`, `const x = 1`
81
- + `break`, `continue`, `return x`
44
+ **Jessie**: JSON + expressions + statements, functions (JS subset):
82
45
 
83
46
  ```js
84
- import subscript from 'subscript/justin'
85
- import 'subscript/feature/control.js'
86
-
87
- let sum = subscript(`
88
- let sum = 0;
89
- for (i = 0; i < 10; i += 1) sum += i;
90
- sum
47
+ import jessie from 'subscript/jessie.js'
48
+
49
+ let fn = jessie(`
50
+ function factorial(n) {
51
+ if (n <= 1) return 1
52
+ return n * factorial(n - 1)
53
+ }
54
+ factorial(5)
91
55
  `)
92
- sum() // 45
56
+ fn({}) // 120
93
57
  ```
94
58
 
59
+ See [docs](./docs.md#presets) for full description.
95
60
 
96
- ## Parse / Compile
97
-
98
- Subscript exposes `parse` to build AST and `compile` to create evaluators.
61
+ ## Extension
99
62
 
100
63
  ```js
101
- import { parse, compile } from 'subscript'
102
-
103
- // parse expression
104
- let tree = parse('a.b + c - 1')
105
- tree // ['-', ['+', ['.', 'a', 'b'], 'c'], [,1]]
64
+ import { binary, operator, compile } from 'subscript/justin.js'
65
+
66
+ // add intersection operator
67
+ binary('∩', 80) // register parser
68
+ operator('', (a, b) => ( // register compiler
69
+ a = compile(a), b = compile(b),
70
+ ctx => a(ctx).filter(x => b(ctx).includes(x))
71
+ ))
72
+ ```
106
73
 
107
- // compile tree to evaluable function
108
- fn = compile(tree)
109
- fn({ a: {b: 1}, c: 2 }) // 2
74
+ ```js
75
+ import justin from 'subscript/justin.js'
76
+ justin('[1,2,3] [2,3,4]')({}) // [2, 3]
110
77
  ```
111
78
 
112
- ### Syntax Tree
79
+ See [docs.md](./docs.md) for full API.
80
+
113
81
 
114
- AST has simplified lispy tree structure (inspired by [frisk](https://ghub.io/frisk) / [nisp](https://github.com/ysmood/nisp)), opposed to [ESTree](https://github.com/estree/estree):
82
+ ## Syntax Tree
115
83
 
116
- * not limited to particular language (JS), can be compiled to different targets;
117
- * reflects execution sequence, rather than code layout;
118
- * has minimal overhead, directly maps to operators;
119
- * simplifies manual evaluation and debugging;
120
- * has conventional form and one-liner docs:
84
+ Expressions parse to a minimal JSON-compatible syntax tree:
121
85
 
122
86
  ```js
123
- import { compile } from 'subscript.js'
124
-
125
- const fn = compile(['+', ['*', 'min', [,60]], [,'sec']])
126
- fn({min: 5}) // min*60 + "sec" == "300sec"
127
-
128
- // node kinds
129
- ['+', a]; // unary operator `+a`
130
- ['+', a, b]; // binary operator `a + b`
131
- ['+', a, b, c]; // n-ary operator `a + b + c`
132
- ['()', a]; // group operator `(a)`
133
- ['()', a, b]; // access operator `a(b)`
134
- [, 'a']; // literal value `'a'`
135
- 'a'; // variable (from scope)
136
- null|empty; // placeholder
137
-
138
- // eg.
139
- ['()', 'a'] // (a)
140
- ['()', 'a', null] // a()
141
- ['()', 'a', 'b'] // a(b)
142
- ['++', 'a'] // ++a
143
- ['++','a', null] // a++
144
- ```
87
+ import { parse } from 'subscript'
145
88
 
146
- ### Stringify
89
+ parse('a + b * 2')
90
+ // ['+', 'a', ['*', 'b', [, 2]]]
91
+ ```
147
92
 
148
- To convert tree back to code, there's codegenerator function:
93
+ Three forms:
149
94
 
150
95
  ```js
151
- import { stringify } from 'subscript.js'
152
-
153
- stringify(['+', ['*', 'min', [,60]], [,'sec']])
154
- // 'min * 60 + "sec"'
96
+ 'x' // identifier resolve from context
97
+ [, value] // literal — return as-is (empty slot = data)
98
+ [op, ...args] // operation apply operator
155
99
  ```
156
100
 
157
- ## Extending
101
+ See [spec.md](./spec.md).
158
102
 
159
- _Subscript_ provides premade language [features](./feature) and API to customize syntax:
160
103
 
161
- * `unary(str, precedence, postfix=false)` − register unary operator, either prefix `⚬a` or postfix `a⚬`.
162
- * `binary(str, precedence, rassoc=false)` − register binary operator `a ⚬ b`, optionally right-associative.
163
- * `nary(str, precedence)` − register n-ary (sequence) operator like `a; b;` or `a, b`, allows missing args.
164
- * `group(str, precedence)` - register group, like `[a]`, `{a}`, `(a)` etc.
165
- * `access(str, precedence)` - register access operator, like `a[b]`, `a(b)` etc.
166
- * `token(str, precedence, lnode => node)` − register custom token or literal. Callback takes left-side node and returns complete expression node.
167
- * `operator(str, (a, b) => ctx => value)` − register evaluator for an operator. Callback takes node arguments and returns evaluator function.
104
+ ## Safety
168
105
 
169
- Longer operators should be registered after shorter ones, eg. first `|`, then `||`, then `||=`.
106
+ Blocked by default:
107
+ - `__proto__`, `__defineGetter__`, `__defineSetter__`
108
+ - `constructor`, `prototype`
109
+ - Global access (only context is visible)
170
110
 
171
111
  ```js
172
- import script, { compile, operator, unary, binary, token } from './subscript.js'
173
-
174
- // enable objects/arrays syntax
175
- import 'subscript/feature/array.js';
176
- import 'subscript/feature/object.js';
177
-
178
- // add identity operators (precedence of comparison)
179
- binary('===', 9), binary('!==', 9)
180
- operator('===', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx)===b(ctx)))
181
- operator('!==', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx)!==b(ctx)))
112
+ subscript('constructor.constructor("alert(1)")()')({})
113
+ // undefined (blocked)
114
+ ```
182
115
 
183
- // add nullish coalescing (precedence of logical or)
184
- binary('??', 3)
185
- operator('??', (a, b) => b && (a = compile(a), b = compile(b), ctx => a(ctx) ?? b(ctx)))
116
+ ## Performance
186
117
 
187
- // add JS literals
188
- token('undefined', 20, a => a ? err() : [, undefined])
189
- token('NaN', 20, a => a ? err() : [, NaN])
118
+ ```
119
+ Parse 30k: subscript 150ms · justin 183ms · jsep 270ms · expr-eval 480ms · jexl 1056ms
120
+ Eval 30k: new Function 7ms · subscript 15ms · jsep+eval 30ms · expr-eval 72ms
190
121
  ```
191
122
 
192
- See [`./feature/*`](./feature) or [`./justin.js`](./justin.js) for examples.
193
-
123
+ ## Utils
194
124
 
125
+ ### Codegen
195
126
 
196
- ## Performance
127
+ Convert tree back to code:
197
128
 
198
- Subscript shows good performance within other evaluators. Example expression:
129
+ ```js
130
+ import { codegen } from 'subscript/util/stringify.js'
199
131
 
200
- ```
201
- 1 + (a * b / c % d) - 2.0 + -3e-3 * +4.4e4 / f.g[0] - i.j(+k == 1)(0)
132
+ codegen(['+', ['*', 'min', [,60]], [,'sec']])
133
+ // 'min * 60 + "sec"'
202
134
  ```
203
135
 
204
- Parse 30k times:
136
+ ### Bundle
205
137
 
206
- ```
207
- subscript: ~150 ms 🥇
208
- justin: ~183 ms
209
- jsep: ~270 ms 🥈
210
- jexpr: ~297 ms 🥉
211
- mr-parser: ~420 ms
212
- expr-eval: ~480 ms
213
- math-parser: ~570 ms
214
- math-expression-evaluator: ~900ms
215
- jexl: ~1056 ms
216
- mathjs: ~1200 ms
217
- new Function: ~1154 ms
218
- ```
138
+ Bundle imports into a single file:
219
139
 
220
- Eval 30k times:
221
- ```
222
- new Function: ~7 ms 🥇
223
- subscript: ~15 ms 🥈
224
- justin: ~17 ms
225
- jexpr: ~23 ms 🥉
226
- jsep (expression-eval): ~30 ms
227
- math-expression-evaluator: ~50ms
228
- expr-eval: ~72 ms
229
- jexl: ~110 ms
230
- mathjs: ~119 ms
231
- mr-parser: -
232
- math-parser: -
140
+ ```js
141
+ import { bundle } from 'subscript/util/bundle.js'
142
+
143
+ const code = await bundle('subscript/jessie.js')
144
+ // self-contained ES module
233
145
  ```
234
146
 
235
- <!--
147
+
236
148
  ## Used by
237
149
 
238
- [prepr](https://github.com/dy/prepr), [justin](#justin), [jz](https://github.com/dy/jz), [glsl-transpiler](https://github.com/stackgl/glsl-transpiler), [piezo](https://github.com/dy/piezo)
239
- -->
150
+ * [jz](https://github.com/dy/jz) JS subset → WASM compiler
151
+ <!-- * [prepr](https://github.com/dy/prepr) -->
152
+ <!-- * [glsl-transpiler](https://github.com/stackgl/glsl-transpiler) -->
153
+ <!-- * [piezo](https://github.com/dy/piezo) -->
154
+
155
+ <!--
156
+ ## Refs
240
157
 
241
- ## Alternatives
158
+ [jsep](https://github.com/EricSmekens/jsep), [jexl](https://github.com/TomFrost/Jexl), [expr-eval](https://github.com/silentmatt/expr-eval), [math.js](https://mathjs.org/).
242
159
 
243
- [jexpr](https://github.com/justinfagnani/jexpr), [jsep](https://github.com/EricSmekens/jsep), [jexl](https://github.com/TomFrost/Jexl), [mozjexl](https://github.com/mozilla/mozjexl), [expr-eval](https://github.com/silentmatt/expr-eval), [expression-eval](https://github.com/donmccurdy/expression-eval), [string-math](https://github.com/devrafalko/string-math), [nerdamer](https://github.com/jiggzson/nerdamer), [math-codegen](https://github.com/mauriciopoppe/math-codegen), [math-parser](https://www.npmjs.com/package/math-parser), [math.js](https://mathjs.org/docs/expressions/parsing.html), [nx-compile](https://github.com/nx-js/compiler-util), [built-in-math-eval](https://github.com/mauriciopoppe/built-in-math-eval)
160
+ [mozjexl](https://github.com/mozilla/mozjexl), [jexpr](https://github.com/justinfagnani/jexpr), [expression-eval](https://github.com/donmccurdy/expression-eval), [string-math](https://github.com/devrafalko/string-math), [nerdamer](https://github.com/jiggzson/nerdamer), [math-codegen](https://github.com/mauriciopoppe/math-codegen), [math-parser](https://www.npmjs.com/package/math-parser), [nx-compile](https://github.com/nx-js/compiler-util), [built-in-math-eval](https://github.com/mauriciopoppe/built-in-math-eval) -->
244
161
 
245
- <p align=center><a href="https://github.com/krsnzd/license/">🕉</a></p>
162
+ <p align=center><a href="https://github.com/krsnzd/license/">ॐ</a></p>
package/feature/access.js CHANGED
@@ -1,11 +1,72 @@
1
- import { access, binary, group, err } from '../src/parse.js'
2
- import { operator, compile } from '../src/compile.js'
3
- import { PREC_ACCESS, unsafe } from '../src/const.js'
1
+ /**
2
+ * Property access: a.b, a[b], a(b), [1,2,3]
3
+ * For private fields (#x), see class.js
4
+ */
5
+ import { access, binary, operator, compile } from '../parse.js';
6
+
7
+ // Block prototype chain attacks
8
+ export const unsafe = k => k?.[0] === '_' && k[1] === '_' || k === 'constructor' || k === 'prototype';
9
+
10
+ const ACCESS = 170;
4
11
 
5
12
  // a[b]
6
- access('[]', PREC_ACCESS)
7
- operator('[]', (a, b) => !b ? err() : (a = compile(a), b = compile(b), ctx => { const k = b(ctx); return unsafe(k) ? undefined : a(ctx)[k] }))
13
+ access('[]', ACCESS);
8
14
 
9
15
  // a.b
10
- binary('.', PREC_ACCESS)
11
- operator('.', (a, b) => (a = compile(a), b = !b[0] ? b[1] : b, unsafe(b) ? () => undefined : ctx => a(ctx)[b]))
16
+ binary('.', ACCESS);
17
+
18
+ // a(b,c,d), a()
19
+ 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;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Object accessor properties (getters/setters)
3
+ *
4
+ * AST:
5
+ * { get x() { body } } → ['{}', ['get', 'x', body]]
6
+ * { set x(v) { body } } → ['{}', ['set', 'x', 'v', body]]
7
+ */
8
+ import { token, expr, skip, space, err, next, parse, cur, idx, operator, compile } from '../parse.js';
9
+
10
+ // Accessor marker for object property definitions
11
+ export const ACC = Symbol('accessor');
12
+
13
+ const ASSIGN = 20;
14
+ const OPAREN = 40, CPAREN = 41, OBRACE = 123, CBRACE = 125;
15
+
16
+ // Shared parser for get/set — returns false if not valid accessor pattern (falls through to identifier)
17
+ // Returns false (not undefined) to signal "fall through without setting reserved"
18
+ const accessor = (kind) => a => {
19
+ if (a) return; // not prefix
20
+ space();
21
+ const name = next(parse.id);
22
+ if (!name) return false; // no property name = not accessor (e.g. `{ get: 1 }`)
23
+ space();
24
+ if (cur.charCodeAt(idx) !== OPAREN) return false; // not followed by ( = not accessor
25
+ skip();
26
+ const params = expr(0, CPAREN);
27
+ space();
28
+ if (cur.charCodeAt(idx) !== OBRACE) return false;
29
+ skip();
30
+ return [kind, name, params, expr(0, CBRACE)];
31
+ };
32
+
33
+ token('get', ASSIGN - 1, accessor('get'));
34
+ token('set', ASSIGN - 1, accessor('set'));
35
+
36
+ // Compile
37
+ operator('get', (name, body) => {
38
+ body = body ? compile(body) : () => {};
39
+ return ctx => [[ACC, name, {
40
+ get: function() { const s = Object.create(ctx || {}); s.this = this; return body(s); }
41
+ }]];
42
+ });
43
+
44
+ operator('set', (name, param, body) => {
45
+ body = body ? compile(body) : () => {};
46
+ return ctx => [[ACC, name, {
47
+ set: function(v) { const s = Object.create(ctx || {}); s.this = this; s[param] = v; body(s); }
48
+ }]];
49
+ });
package/feature/asi.js ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Automatic Semicolon Insertion (ASI)
3
+ *
4
+ * JS-style ASI: insert virtual ; when newline precedes illegal token at statement level
5
+ */
6
+ import { parse } from '../parse.js';
7
+
8
+ const STATEMENT = 5;
9
+
10
+ parse.asi = (token, prec, expr) => {
11
+ if (prec >= STATEMENT) return; // only at statement level
12
+ const next = expr(STATEMENT - .5);
13
+ if (!next) return;
14
+ return token?.[0] !== ';' ? [';', token, next] : (token.push(next), token);
15
+ };
@@ -0,0 +1,45 @@
1
+ // Async/await/yield: async function, async arrow, await, yield expressions
2
+ import { unary, expr, skip, space, cur, idx, word, operator, compile } from '../parse.js';
3
+ import { keyword } from './block.js';
4
+
5
+ const PREFIX = 140, ASSIGN = 20;
6
+
7
+ // await expr → ['await', expr]
8
+ unary('await', PREFIX);
9
+
10
+ // yield expr → ['yield', expr]
11
+ // yield* expr → ['yield*', expr]
12
+ keyword('yield', PREFIX, () => {
13
+ space();
14
+ if (cur[idx] === '*') {
15
+ skip();
16
+ space();
17
+ return ['yield*', expr(ASSIGN)];
18
+ }
19
+ return ['yield', expr(ASSIGN)];
20
+ });
21
+
22
+ // async function name() {} → ['async', ['function', name, params, body]]
23
+ // async () => {} → ['async', ['=>', params, body]]
24
+ // async x => {} → ['async', ['=>', x, body]]
25
+ keyword('async', PREFIX, () => {
26
+ space();
27
+ // async function - check for 'function' word
28
+ if (word('function')) return ['async', expr(PREFIX)];
29
+ // async arrow: async () => or async x =>
30
+ // Parse at assign precedence to catch => operator
31
+ const params = expr(ASSIGN - .5);
32
+ return params && ['async', params];
33
+ });
34
+
35
+ // Compile
36
+ operator('async', fn => {
37
+ const inner = compile(fn);
38
+ return ctx => {
39
+ const f = inner(ctx);
40
+ return async function(...args) { return f(...args); };
41
+ };
42
+ });
43
+ operator('await', a => (a = compile(a), async ctx => await a(ctx)));
44
+ operator('yield', a => (a = a ? compile(a) : null, ctx => { throw { __yield__: a ? a(ctx) : undefined }; }));
45
+ operator('yield*', a => (a = compile(a), ctx => { throw { __yield_all__: a(ctx) }; }));
@@ -0,0 +1,41 @@
1
+ // Block parsing helpers
2
+ import { expr, skip, space, lookup, err, parse, seek, cur, idx, parens, loc, operator, compile } from '../parse.js';
3
+
4
+ const STATEMENT = 5, OBRACE = 123, CBRACE = 125;
5
+
6
+ // keyword(op, prec, fn) - prefix-only word token
7
+ // keyword('while', 6, () => ['while', parens(), body()])
8
+ // keyword('break', 6, () => ['break'])
9
+ // attaches .loc to array results for source mapping
10
+ export const keyword = (op, prec, map, c = op.charCodeAt(0), l = op.length, prev = lookup[c], r) =>
11
+ lookup[c] = (a, curPrec, curOp, from = idx) =>
12
+ !a &&
13
+ (curOp ? op == curOp : (l < 2 || cur.substr(idx, l) == op) && (curOp = op)) &&
14
+ curPrec < prec &&
15
+ !parse.id(cur.charCodeAt(idx + l)) &&
16
+ (seek(idx + l), (r = map()) ? loc(r, from) : (seek(from), !prev && err()), r) ||
17
+ prev?.(a, curPrec, curOp);
18
+
19
+ // infix(op, prec, fn) - infix word token (requires left operand)
20
+ // infix('catch', 6, a => ['catch', a, parens(), block()])
21
+ // infix('finally', 6, a => ['finally', a, block()])
22
+ // attaches .loc to array results for source mapping
23
+ export const infix = (op, prec, map, c = op.charCodeAt(0), l = op.length, prev = lookup[c], r) =>
24
+ lookup[c] = (a, curPrec, curOp, from = idx) =>
25
+ a &&
26
+ (curOp ? op == curOp : (l < 2 || cur.substr(idx, l) == op) && (curOp = op)) &&
27
+ curPrec < prec &&
28
+ !parse.id(cur.charCodeAt(idx + l)) &&
29
+ (seek(idx + l), loc(r = map(a), from), r) ||
30
+ prev?.(a, curPrec, curOp);
31
+
32
+ // block() - parse required { body }
33
+ export const block = () =>
34
+ (space() === OBRACE || err('Expected {'), skip(), expr(STATEMENT - .5, CBRACE) || null);
35
+
36
+ // body() - parse { body } or single statement
37
+ export const body = () =>
38
+ space() !== OBRACE ? expr(STATEMENT + .5) : (skip(), ['block', expr(STATEMENT - .5, CBRACE) || null]);
39
+
40
+ // Compile
41
+ operator('block', body => body === undefined ? () => {} : (body = compile(body), ctx => body(ctx)));
@@ -0,0 +1,69 @@
1
+ // Class declarations and expressions
2
+ // class A extends B { ... }
3
+ import { binary, unary, token, expr, space, next, parse, literal, word, operator, compile } from '../parse.js';
4
+ import { keyword, block } from './block.js';
5
+
6
+ const TOKEN = 200, PREFIX = 140, COMP = 90;
7
+ const STATIC = Symbol('static');
8
+
9
+ // super → literal
10
+ literal('super', Symbol.for('super'));
11
+
12
+ // static member → ['static', member]
13
+ unary('static', PREFIX);
14
+
15
+ // instanceof: object instanceof Constructor
16
+ binary('instanceof', COMP);
17
+
18
+ // #private fields: #x → '#x' (identifier starting with #)
19
+ token('#', TOKEN, a => {
20
+ if (a) return;
21
+ const id = next(parse.id);
22
+ return id ? '#' + id : void 0;
23
+ });
24
+
25
+ // class [Name] [extends Base] { body }
26
+ keyword('class', TOKEN, () => {
27
+ space();
28
+ let name = next(parse.id) || null;
29
+ // 'extends' parsed as name? → anonymous class
30
+ if (name === 'extends') name = null;
31
+ else {
32
+ space();
33
+ if (!word('extends')) return ['class', name, null, block()];
34
+ }
35
+ space();
36
+ return ['class', name, expr(TOKEN), block()];
37
+ });
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)]]));