subscript 9.2.0 → 10.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +101 -184
- package/feature/access.js +68 -7
- package/feature/accessor.js +49 -0
- package/feature/asi.js +15 -0
- package/feature/async.js +45 -0
- package/feature/block.js +41 -0
- package/feature/class.js +69 -0
- package/feature/collection.js +40 -0
- package/feature/comment.js +25 -5
- package/feature/control.js +2 -142
- package/feature/destruct.js +33 -0
- package/feature/function.js +44 -0
- package/feature/group.js +13 -9
- package/feature/if.js +23 -38
- package/feature/literal.js +13 -0
- package/feature/loop.js +107 -106
- package/feature/module.js +42 -0
- package/feature/number.js +41 -38
- package/feature/op/arithmetic.js +29 -0
- package/feature/op/arrow.js +33 -0
- package/feature/op/assign-logical.js +33 -0
- package/feature/op/assignment.js +26 -0
- package/feature/op/bitwise-unsigned.js +17 -0
- package/feature/op/bitwise.js +29 -0
- package/feature/op/comparison.js +19 -0
- package/feature/op/defer.js +15 -0
- package/feature/op/equality.js +16 -0
- package/feature/op/identity.js +15 -0
- package/feature/op/increment.js +23 -0
- package/feature/op/logical.js +21 -0
- package/feature/op/membership.js +17 -0
- package/feature/op/nullish.js +13 -0
- package/feature/op/optional.js +61 -0
- package/feature/op/pow.js +19 -0
- package/feature/op/range.js +26 -0
- package/feature/op/spread.js +15 -0
- package/feature/op/ternary.js +15 -0
- package/feature/op/type.js +18 -0
- package/feature/op/unary.js +41 -0
- package/feature/prop.js +34 -0
- package/feature/regex.js +31 -0
- package/feature/seq.js +21 -0
- package/feature/string.js +24 -17
- package/feature/switch.js +48 -0
- package/feature/template.js +39 -0
- package/feature/try.js +57 -0
- package/feature/unit.js +35 -0
- package/feature/var.js +51 -41
- package/jessie.js +31 -0
- package/jessie.min.js +8 -0
- package/justin.js +39 -48
- package/justin.min.js +8 -4
- package/package.json +15 -16
- package/parse.js +146 -0
- package/subscript.d.ts +45 -5
- package/subscript.js +62 -22
- package/subscript.min.js +5 -4
- package/util/bundle.js +507 -0
- package/util/stringify.js +172 -0
- package/feature/add.js +0 -22
- package/feature/array.js +0 -11
- package/feature/arrow.js +0 -23
- package/feature/assign.js +0 -11
- package/feature/bitwise.js +0 -11
- package/feature/bool.js +0 -5
- package/feature/call.js +0 -15
- package/feature/compare.js +0 -11
- package/feature/increment.js +0 -11
- package/feature/logic.js +0 -11
- package/feature/mult.js +0 -25
- package/feature/object.js +0 -17
- package/feature/optional.js +0 -23
- package/feature/pow.js +0 -5
- package/feature/shift.js +0 -12
- package/feature/spread.js +0 -6
- package/feature/ternary.js +0 -10
- package/src/compile.d.ts +0 -17
- package/src/compile.js +0 -28
- package/src/const.js +0 -45
- package/src/parse.d.ts +0 -22
- package/src/parse.js +0 -113
- package/src/stringify.js +0 -27
- /package/{LICENSE → license} +0 -0
package/README.md
CHANGED
|
@@ -1,245 +1,162 @@
|
|
|
1
|
-
|
|
1
|
+
<h1 align="center">sub<sub><sup> ✦ </sup></sub>script</h1>
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
<p align="center">Tiny expression parser & evaluator.</p>
|
|
4
|
+
<div align="center">
|
|
5
|
+
|
|
6
|
+
[](https://github.com/dy/subscript/actions/workflows/node.js.yml) [](http://npmjs.org/subscript) [](https://bundlephobia.com/package/subscript) [](http://microjs.com/#subscript) <!--[](https://dy.github.io/subscript/)-->
|
|
4
7
|
|
|
5
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
30
|
+
import subscript from 'subscript'
|
|
67
31
|
|
|
68
|
-
|
|
69
|
-
fn() // 1
|
|
32
|
+
subscript('a.b + c * 2')({ a: { b: 1 }, c: 3 }) // 7
|
|
70
33
|
```
|
|
71
34
|
|
|
72
|
-
|
|
35
|
+
**Justin**: JSON + expressions + templates + arrows:
|
|
73
36
|
|
|
74
|
-
|
|
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
|
-
+
|
|
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
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
56
|
+
fn({}) // 120
|
|
93
57
|
```
|
|
94
58
|
|
|
59
|
+
See [docs](./docs.md#presets) for full description.
|
|
95
60
|
|
|
96
|
-
##
|
|
97
|
-
|
|
98
|
-
Subscript exposes `parse` to build AST and `compile` to create evaluators.
|
|
61
|
+
## Extension
|
|
99
62
|
|
|
100
63
|
```js
|
|
101
|
-
import {
|
|
102
|
-
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
74
|
+
```js
|
|
75
|
+
import justin from 'subscript/justin.js'
|
|
76
|
+
justin('[1,2,3] ∩ [2,3,4]')({}) // [2, 3]
|
|
110
77
|
```
|
|
111
78
|
|
|
112
|
-
|
|
79
|
+
See [docs.md](./docs.md) for full API.
|
|
80
|
+
|
|
113
81
|
|
|
114
|
-
|
|
82
|
+
## Syntax Tree
|
|
115
83
|
|
|
116
|
-
|
|
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 {
|
|
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
|
-
|
|
89
|
+
parse('a + b * 2')
|
|
90
|
+
// ['+', 'a', ['*', 'b', [, 2]]]
|
|
91
|
+
```
|
|
147
92
|
|
|
148
|
-
|
|
93
|
+
Three forms:
|
|
149
94
|
|
|
150
95
|
```js
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
101
|
+
See [spec.md](./spec.md).
|
|
158
102
|
|
|
159
|
-
_Subscript_ provides premade language [features](./feature) and API to customize syntax:
|
|
160
103
|
|
|
161
|
-
|
|
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
|
-
|
|
106
|
+
Blocked by default:
|
|
107
|
+
- `__proto__`, `__defineGetter__`, `__defineSetter__`
|
|
108
|
+
- `constructor`, `prototype`
|
|
109
|
+
- Global access (only context is visible)
|
|
170
110
|
|
|
171
111
|
```js
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
184
|
-
binary('??', 3)
|
|
185
|
-
operator('??', (a, b) => b && (a = compile(a), b = compile(b), ctx => a(ctx) ?? b(ctx)))
|
|
116
|
+
## Performance
|
|
186
117
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
193
|
-
|
|
123
|
+
## Utils
|
|
194
124
|
|
|
125
|
+
### Codegen
|
|
195
126
|
|
|
196
|
-
|
|
127
|
+
Convert tree back to code:
|
|
197
128
|
|
|
198
|
-
|
|
129
|
+
```js
|
|
130
|
+
import { codegen } from 'subscript/util/stringify.js'
|
|
199
131
|
|
|
200
|
-
|
|
201
|
-
|
|
132
|
+
codegen(['+', ['*', 'min', [,60]], [,'sec']])
|
|
133
|
+
// 'min * 60 + "sec"'
|
|
202
134
|
```
|
|
203
135
|
|
|
204
|
-
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
[
|
|
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/"
|
|
162
|
+
<p align=center><a href="https://github.com/krsnzd/license/">ॐ</a></p>
|
package/feature/access.js
CHANGED
|
@@ -1,11 +1,72 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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('[]',
|
|
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('.',
|
|
11
|
-
|
|
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
|
+
};
|
package/feature/async.js
ADDED
|
@@ -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) }; }));
|
package/feature/block.js
ADDED
|
@@ -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)));
|
package/feature/class.js
ADDED
|
@@ -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)]]));
|