subscript 6.3.1 → 7.0.2

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 CHANGED
@@ -1,32 +1,41 @@
1
1
  # <img alt="subscript" src="/subscript2.svg" height=28/> <!--sub͘<em>script</em>--> <!--<sub>SUB͘<em>SCRIPT</em></sub>--> <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="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>
2
2
 
3
- _Subscript_ is expression evaluator / microlanguage with standard syntax<br/>
3
+ _Subscript_ is expression evaluator / microlanguage with [common syntax](https://en.wikipedia.org/wiki/Comparison_of_programming_languages_(syntax)).<br/>
4
4
 
5
- * Any fragment can be copy-pasted to any language: C++, JS, Java, Python, Go, Rust etc.
5
+ * Any fragment can be copy-pasted to any language: C++, JS, Java, Python, Go, Rust etc.
6
6
  * Tiny size <sub><a href="https://bundlephobia.com/package/subscript@6.0.0"><img alt="npm bundle size" src="https://img.shields.io/bundlephobia/minzip/subscript/latest?color=brightgreen&label=gzip"/></a></sub>
7
7
  * :rocket: Fast [performance](#performance)
8
8
  * Configurable & extensible
9
9
  * Trivial to use
10
10
 
11
11
  ```js
12
- import script from './subscript.js'
13
- let fn = script`a.b + c(d - 1)`
12
+ import script, { parse, compile } from './subscript.js'
13
+
14
+ // create expression evaluator
15
+ let fn = script('a.b + c(d - 1)')
14
16
  fn({ a: { b:1 }, c: x => x * 2, d: 3 }) // 5
15
- fn.args // ['a', 'c', 'd']
17
+
18
+ // or
19
+ // parse expression tree
20
+ let tree = parse('a.b + c')
21
+ tree // ['+', ['.', 'a', 'b'], 'c']
22
+
23
+ // compile tree to evaluable function
24
+ let evaluate = compile(tree)
16
25
  ```
17
26
 
18
27
  ## Motivation
19
28
 
20
29
  _Subscript_ is designed to be useful for:
21
30
 
22
- * templates (perfect match with [template parts](https://github.com/github/template-parts), [templize](https://github.com/spectjs/templize))
31
+ * templates (perfect match with [template parts](https://github.com/github/template-parts), see [templize](https://github.com/spectjs/templize))
23
32
  * expressions evaluators, calculators
24
- * configurable subsets of languages (eg. [justin](#justin)) <!-- see sonr, mineural -->
33
+ * configurable subsets of languages (eg. [justin](#justin))
25
34
  * pluggable/mock language features (eg. pipe operator)
26
35
  * sandboxes, playgrounds, safe eval
27
- * custom DSL
36
+ * custom DSL <!-- see sonr, mineural -->
28
37
 
29
- _Subscript_ has [2kb](https://npmfs.com/package/subscript/6.0.0/subscript.min.js) footprint, compared 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_, with better test coverage and better performance.
38
+ _Subscript_ has [2.8kb](https://npmfs.com/package/subscript/7.0.0/subscript.min.js) footprint, compared 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_, with better test coverage and better performance.
30
39
 
31
40
 
32
41
  ## Design
@@ -54,26 +63,47 @@ Default literals:
54
63
  * `"abc"` strings
55
64
  * `1.2e+3` numbers
56
65
 
57
- Everything else can be extended via `parse.set(token, precedence, operator)` for unary or binary operators (detected by number of arguments in `operator`), or via `parse.set(token, parse, precedence)` for custom tokens.
66
+ Everything else can be extended via `subscript.set(str, prec, fn)` for unary, binary or n-ary operators (detected by number of arguments in `fn`), or via `subscript.set(str, prec, [parse, compile])` for custom tokens.
58
67
 
59
68
  ```js
60
- import script from './subscript.js'
69
+ import script, { compile } from './subscript.js'
61
70
 
62
71
  // add ~ unary operator with precedence 15
63
72
  script.set('~', 15, a => ~a)
64
73
 
65
- // add === binary operator
74
+ // add === binary operator with precedence 9
66
75
  script.set('===', 9, (a, b) => a===b)
67
76
 
68
77
  // add literals
69
- script.set('true', a => ()=>true)
70
- script.set('false', a => ()=>false)
78
+ script.set('true', 20, [a => ['',true], a => ctx => a[1]])
79
+ script.set('false', 20, [a => ['',false], a => ctx => a[1]])
71
80
 
72
- script`true === false`() // false
81
+ script(`true === false`)() // false
73
82
  ```
74
83
 
75
84
  See [subscript.js](subscript.js) or [justin.js](./justin.js) for examples.
76
85
 
86
+
87
+ ## Parser & Compiler
88
+
89
+ Subscript exposes separate `./parse.js` and `./compile.js` entries. Parser builds AST, compiler converts it to evaluable function.
90
+
91
+ AST has simplified lispy calltree structure (inspired by [frisk](https://ghub.io/frisk)), opposed to [ESTree](https://github.com/estree/estree):
92
+
93
+ * is not limited to particular language, can be cross-compiled;
94
+ * reflects execution sequence, rather than code layout;
95
+ * has minimal possible overhead, better fits for directly mapping to operators;
96
+ * simplifies manual evaluation and debugging;
97
+ * has conventional form and one-liner docs:
98
+
99
+ ```js
100
+ import { compile } from 'subscript.js'
101
+
102
+ const fn = compile(['+', ['*', 'min', ['',60]], ['','sec']])
103
+
104
+ fn({min: 5}) // min*60 + "sec" == "300sec"
105
+ ```
106
+
77
107
  <!--
78
108
  Operators can be extended via .
79
109
 
@@ -286,8 +316,9 @@ Subscript shows relatively good performance within other evaluators:
286
316
  Parse 30k times:
287
317
 
288
318
  ```
289
- subscript: ~170 ms 🥇
290
- justin: ~183 ms 🥈
319
+ es-module-lexer: 50ms 🥇
320
+ subscript: ~150 ms 🥈
321
+ justin: ~183 ms
291
322
  jsep: ~270 ms 🥉
292
323
  jexpr: ~297 ms
293
324
  mr-parser: ~420 ms
@@ -302,8 +333,8 @@ new Function: ~1154 ms
302
333
  Eval 30k times:
303
334
  ```
304
335
  new Function: ~7 ms 🥇
305
- subscript: ~17 ms 🥈
306
- justin: ~17 ms 🥈
336
+ subscript: ~15 ms 🥈
337
+ justin: ~17 ms
307
338
  jexpr: ~23 ms 🥉
308
339
  jsep (expression-eval): ~30 ms
309
340
  math-expression-evaluator: ~50ms
@@ -328,7 +359,7 @@ math-parser: -
328
359
  * [math-parser](https://www.npmjs.com/package/math-parser)
329
360
  * [math.js](https://mathjs.org/docs/expressions/parsing.html)
330
361
 
331
- ## Next door
362
+ ## JS engines
332
363
 
333
364
  * [engine262](https://github.com/engine262/engine262)
334
365
  * [Jessie](https://github.com/endojs/Jessie)
package/compile.js ADDED
@@ -0,0 +1,9 @@
1
+ // build optimized evaluator for the tree
2
+ export const compile = (node) => !Array.isArray(node) ? ctx => ctx?.[node] : operator[node[0]](...node.slice(1)),
3
+
4
+ set = compile.set = (op, fn, prev=operator[op]) => operator[op] = (...args) => fn(...args) || prev && prev(...args),
5
+
6
+ operator = {}
7
+
8
+
9
+ export default compile
package/justin.js CHANGED
@@ -1,238 +1,94 @@
1
- const SPACE=32;
2
-
3
- // current string, index and collected ids
4
- let idx, cur, args,
5
-
6
- // no handling tagged literals since easily done on user side with cache, if needed
7
- parse = (s, fn) => (
8
- idx=0, args=[], cur=s.trim(),
9
- !(s = cur ? expr() : ctx=>fn) || cur[idx] ? err() :
10
- fn = ctx=>s(ctx||{}), fn.args = args, fn
11
- ),
12
-
13
- isId = c =>
14
- (c >= 48 && c <= 57) || // 0..9
15
- (c >= 65 && c <= 90) || // A...Z
16
- (c >= 97 && c <= 122) || // a...z
17
- c == 36 || c == 95 || // $, _,
18
- (c >= 192 && c != 215 && c != 247), // any non-ASCII
19
-
20
- err = (msg='Bad syntax',c=cur[idx]) => { throw SyntaxError(msg + ' `' + c + '` at ' + idx) },
21
-
22
- skip = (is=1, from=idx, l) => {
23
- if (typeof is == 'number') idx += is;
24
- else while (is(cur.charCodeAt(idx))) idx++;
25
- return cur.slice(from, idx)
26
- },
27
-
28
- // a + b - c
29
- expr = (prec=0, end, cc, token, newNode, fn) => {
30
- // chunk/token parser
31
- while (
32
- ( cc=space() ) && // till not end
33
- // FIXME: extra work is happening here, when lookup bails out due to lower precedence -
34
- // it makes extra `space` call for parent exprs on the same character to check precedence again
35
- ( newNode =
36
- (fn=lookup[cc]) && fn(token, prec) || // if operator with higher precedence isn't found
37
- (!token && id()) // parse literal or quit. token seqs are forbidden: `a b`, `a "b"`, `1.32 a`
38
- )
39
- ) token = newNode;
40
-
41
- // check end character
42
- // FIXME: can't show "Unclose paren", because can be unknown operator within group as well
43
- if (end) cc==end?idx++:err();
44
-
45
- return token
46
- },
47
-
48
- // skip space chars, return first non-space character
49
- space = cc => { while ((cc = cur.charCodeAt(idx)) <= SPACE) idx++; return cc },
50
-
51
- // variable identifier
52
- id = (name=skip(isId), fn) => name ? (fn=ctx => ctx[name], args.push(name), fn.id=()=>name, fn) : 0,
53
-
54
- // operator/token lookup table
55
- lookup = [],
56
-
57
- // create operator checker/mapper (see examples)
58
- set = parse.set = (
59
- op,
60
- opPrec, fn=SPACE, // if opPrec & fn come in reverse order - consider them raw parse fn case, still precedence possible
61
- c=op.charCodeAt(0),
62
- l=op.length,
63
- prev=lookup[c],
64
- arity=fn.length || ([fn,opPrec]=[opPrec,fn], 0),
65
- word=op.toUpperCase()!==op, // make sure word boundary comes after word operator
66
- map=
67
- // binary
68
- arity>1 ? (a,b) => a && (b=expr(opPrec)) && (
69
- !a.length && !b.length ? (a=fn(a(),b()), ()=>a) : // static pre-eval like `"a"+"b"`
70
- ctx => fn(a(ctx),b(ctx),a.id?.(ctx),b.id?.(ctx))
71
- ) :
72
- // unary prefix (0 args)
73
- arity ? a => !a && (a=expr(opPrec-1)) && (ctx => fn(a(ctx))) :
74
- fn // custom parser
75
- ) =>
76
- lookup[c] = (a, curPrec, from=idx) => curPrec<opPrec && (l<2||cur.substr(idx,l)==op) && (!word||!isId(cur.charCodeAt(idx+l))) && (idx+=l, map(a, curPrec)) || (idx=from, prev&&prev(a, curPrec));
77
-
78
- const PERIOD=46, CPAREN=41, CBRACK$1=93, DQUOTE$1=34, _0=48, _9=57,
79
- PREC_SEQ=1, PREC_SOME=4, PREC_EVERY=5, PREC_OR$1=6, PREC_XOR=7, PREC_AND=8,
80
- PREC_EQ$1=9, PREC_COMP$1=10, PREC_SHIFT=11, PREC_SUM=12, PREC_MULT=13, PREC_UNARY$1=15, PREC_CALL=18;
81
-
82
- let list$1, op$1, prec$1, fn$1,
83
- isNum = c => c>=_0 && c<=_9,
84
- // 1.2e+3, .5
85
- num = n => (
86
- n&&err('Unexpected number'),
87
- n = skip(c=>c == PERIOD || isNum(c)),
88
- (cur.charCodeAt(idx) == 69 || cur.charCodeAt(idx) == 101) && (n += skip(2) + skip(isNum)),
89
- n=+n, n!=n ? err('Bad number') : () => n // 0 args means token is static
90
- ),
91
-
92
- inc = (a,fn) => ctx => fn(a.of?a.of(ctx):ctx, a.id(ctx));
93
-
94
- // numbers
95
- for (op$1=_0;op$1<=_9;) lookup[op$1++] = num;
96
-
97
- // operators
98
- for (list$1=[
99
- // "a"
100
- '"', a => (a=a?err('Unexpected string'):skip(c => c-DQUOTE$1), skip()||err('Bad string'), ()=>a),,
101
-
102
- // a.b
103
- '.', (a,id) => (space(), id=skip(isId)||err(), fn$1=ctx=>a(ctx)[id], fn$1.id=()=>id, fn$1.of=a, fn$1), PREC_CALL,
104
-
105
- // .2
106
- // FIXME: .123 is not operator, so we skip back, but mb reorganizing num would be better
107
- '.', a => !a && num(skip(-1)),,
108
-
109
- // a[b]
110
- '[', (a,b,fn) => a && (b=expr(0,CBRACK$1)||err(), fn=ctx=>a(ctx)[b(ctx)], fn.id=b, fn.of=a, fn), PREC_CALL,
111
-
112
- // a(), a(b), (a,b), (a+b)
113
- '(', (a,b,fn) => (
114
- b=expr(0,CPAREN),
115
- // a(), a(b), a(b,c,d)
116
- a ? ctx => a(ctx).apply(a.of?.(ctx), b ? b.all ? b.all(ctx) : [b(ctx)] : []) :
117
- // (a+b)
118
- b || err()
119
- ), PREC_CALL,
120
-
121
- // [a,b,c] or (a,b,c)
122
- ',', (a,prec,b=expr(PREC_SEQ),fn=ctx => (a(ctx), b(ctx))) => (
123
- b ? (fn.all = a.all ? ctx => [...a.all(ctx),b(ctx)] : ctx => [a(ctx),b(ctx)]) : err('Skipped argument',),
124
- fn
125
- ), PREC_SEQ,
126
-
127
- '|', PREC_OR$1, (a,b)=>a|b,
128
- '||', PREC_SOME, (a,b)=>a||b,
129
-
130
- '&', PREC_AND, (a,b)=>a&b,
131
- '&&', PREC_EVERY, (a,b)=>a&&b,
132
-
133
- '^', PREC_XOR, (a,b)=>a^b,
134
-
135
- // ==, !=
136
- '==', PREC_EQ$1, (a,b)=>a==b,
137
- '!=', PREC_EQ$1, (a,b)=>a!=b,
138
-
139
- // > >= >> >>>, < <= <<
140
- '>', PREC_COMP$1, (a,b)=>a>b,
141
- '>=', PREC_COMP$1, (a,b)=>a>=b,
142
- '>>', PREC_SHIFT, (a,b)=>a>>b,
143
- '>>>', PREC_SHIFT, (a,b)=>a>>>b,
144
- '<', PREC_COMP$1, (a,b)=>a<b,
145
- '<=', PREC_COMP$1, (a,b)=>a<=b,
146
- '<<', PREC_SHIFT, (a,b)=>a<<b,
147
-
148
- // + ++ - --
149
- '+', PREC_SUM, (a,b)=>a+b,
150
- '+', PREC_UNARY$1, (a)=>+a,
151
- '++', a => inc(a||expr(PREC_UNARY$1-1), a ? (a,b)=>a[b]++ : (a,b)=>++a[b]), PREC_UNARY$1,
152
-
153
- '-', PREC_SUM, (a,b)=>a-b,
154
- '-', PREC_UNARY$1, (a)=>-a,
155
- '--', a => inc(a||expr(PREC_UNARY$1-1), a ? (a,b)=>a[b]-- : (a,b)=>--a[b]), PREC_UNARY$1,
156
-
157
- // ! ~
158
- '!', PREC_UNARY$1, (a)=>!a,
159
-
160
- // * / %
161
- '*', PREC_MULT, (a,b)=>a*b,
162
- '/', PREC_MULT, (a,b)=>a/b,
163
- '%', PREC_MULT, (a,b)=>a%b
164
- ]; [op$1,prec$1,fn$1,...list$1]=list$1, op$1;) set(op$1,prec$1,fn$1);
165
-
166
1
  // justin lang https://github.com/endojs/Jessie/issues/66
2
+ import { skip, cur, idx, err, expr } from './parse.js'
3
+ import compile from './compile.js'
4
+ import subscript from './subscript.js'
5
+
6
+ const PERIOD=46, OPAREN=40, CPAREN=41, OBRACK=91, CBRACK=93, SPACE=32, DQUOTE=34, QUOTE=39, _0=48, _9=57, BSLASH=92,
7
+ PREC_SEQ=1, PREC_COND=3, PREC_SOME=4, PREC_EVERY=5, PREC_OR=6, PREC_XOR=7, PREC_AND=8,
8
+ PREC_EQ=9, PREC_COMP=10, PREC_SHIFT=11, PREC_SUM=12, PREC_MULT=13, PREC_EXP=14, PREC_UNARY=15, PREC_POSTFIX=16, PREC_CALL=18, PREC_GROUP=19
9
+
10
+ let escape = {n:'\n', r:'\r', t:'\t', b:'\b', f:'\f', v:'\v'},
11
+ string = q => (qc, c, str='') => {
12
+ qc&&err('Unexpected string') // must not follow another token
13
+ skip()
14
+ while (c=cur.charCodeAt(idx), c-q) {
15
+ if (c === BSLASH) skip(), c=skip(), str += escape[c] || c
16
+ else str += skip()
17
+ }
18
+ skip()
19
+ return ['', str]
20
+ },
21
+
22
+ list = [
23
+ // operators
24
+ '===', PREC_EQ, (a,b) => a===b,
25
+ '!==', PREC_EQ, (a,b) => a!==b,
26
+ '~', PREC_UNARY, (a) => ~a,
27
+
28
+ // ?:
29
+ '?', PREC_COND, [
30
+ (a, b, c) => a && (b=expr(2,58)) && (c=expr(3), ['?', a, b, c]),
31
+ (a, b, c) => (a=compile(a),b=compile(b),c=compile(c), ctx => a(ctx) ? b(ctx) : c(ctx))
32
+ ],
167
33
 
168
- const CBRACK=93, DQUOTE=34, QUOTE=39, BSLASH=92,
169
- PREC_OR=6, PREC_EQ=9, PREC_COMP=10, PREC_EXP=14, PREC_UNARY=15;
34
+ '??', PREC_OR, (a,b) => a ?? b,
170
35
 
36
+ // a?.[, a?.( - postfix operator
37
+ '?.', PREC_CALL, [a => a && ['?.', a], a => (a=compile(a), ctx => a(ctx)||(()=>{})) ],
38
+ // a?.b - optional chain operator
39
+ '?.', PREC_CALL, [
40
+ (a,b) => a && (b=expr(PREC_CALL),!b?.map) && ['?.',a,b],
41
+ (a,b) => b && (a=compile(a), ctx => a(ctx)?.[b])
42
+ ],
171
43
 
172
- let list, op, prec, fn,
173
- escape = {n:'\n', r:'\r', t:'\t', b:'\b', f:'\f', v:'\v'},
174
- string = q => (qc, c, str='') => {
175
- qc&&err('Unexpected string'); // must not follow another token
176
- while (c=cur.charCodeAt(idx), c-q) {
177
- if (c === BSLASH) skip(), c=skip(), str += escape[c] || c;
178
- else str += skip();
179
- }
180
- return skip()||err('Bad string'), () => str
181
- };
44
+ 'in', PREC_COMP , (a,b) => a in b,
182
45
 
183
- // operators
184
- for (list=[
185
46
  // "' with /
186
- '"', string(DQUOTE),,
187
- "'", string(QUOTE),,
47
+ '"', , [string(DQUOTE)],
48
+ "'", , [string(QUOTE)],
188
49
 
189
50
  // /**/, //
190
- '/*', (a, prec) => (skip(c => c !== 42 && cur.charCodeAt(idx+1) !== 47), skip(2), a||expr(prec)),,
191
- '//', (a, prec) => (skip(c => c >= 32), a||expr(prec)),,
51
+ '/*', 20, [(a, prec) => (skip(c => c !== 42 && cur.charCodeAt(idx+1) !== 47), skip(2), a||expr(prec))],
52
+ '//', 20, [(a, prec) => (skip(c => c >= 32), a||expr(prec))],
192
53
 
193
54
  // literals
194
- 'null', a => a ? err('Unexpected literal') : ()=>null,,
195
- 'true', a => a ? err('Unexpected literal') : ()=>true,,
196
- 'false', a => a ? err('Unexpected literal') : ()=>false,,
197
- 'undefined', a => a ? err('Unexpected literal') : ()=>undefined,,
55
+ 'null', 20, [a => a ? err() : ['',null]],
56
+ 'true', 20, [a => a ? err() : ['',true]],
57
+ 'false', 20, [a => a ? err() : ['',false]],
58
+ 'undefined', 20, [a => a ? err() : ['',undefined]],
198
59
 
199
- ';', a => expr()||(()=>{}),,
200
-
201
- // operators
202
- '===', PREC_EQ, (a,b) => a===b,
203
- '!==', PREC_EQ, (a,b) => a!==b,
204
- '~', PREC_UNARY, (a) => ~a,
60
+ // FIXME: make sure that is right
61
+ ';', 20, [a => expr()||['']],
205
62
 
206
63
  // right order
207
- '**', (a,prec,b=expr(PREC_EXP-1)) => ctx=>a(ctx)**b(ctx), PREC_EXP,
208
-
209
- // ?:
210
- ':', 3.1, (a,b) => [a,b],
211
- '?', 3, (a,b) => a ? b[0] : b[1],
212
-
213
- '??', PREC_OR, (a,b) => a??b,
214
-
215
- // a?.[, a?.( - postfix operator
216
- '?.', a => a && (ctx => a(ctx)||(()=>{})),,//(a) => a||(()=>{}),
217
- // a?.b - optional chain operator
218
- '?.', (a,id) => (space(), id=skip(isId)) && (ctx => a(ctx)?.[id]),,
219
-
220
- 'in', PREC_COMP, (a,b) => a in b,
64
+ // '**', (a,prec,b=expr(PREC_EXP-1)) => ctx=>a(ctx)**b(ctx), PREC_EXP,
65
+ '**', -PREC_EXP, (a,b)=>a**b,
221
66
 
222
67
  // [a,b,c]
223
- '[', (a) => !a && (
224
- a=expr(0,CBRACK),
225
- !a ? ctx => [] : a.all ? ctx => a.all(ctx) : ctx => [a(ctx)]
226
- ),,
68
+ '[', 20, [
69
+ (a) => !a && ['[', expr(0,93)||''],
70
+ (a,b) => !b && (
71
+ !a ? () => [] : // []
72
+ a[0] === ',' ? (a=a.slice(1).map(compile), ctx => a.map(a=>a(ctx))) : // [a,b,c]
73
+ (a=compile(a), ctx => [a(ctx)]) // [a]
74
+ )],
227
75
 
228
76
  // {a:1, b:2, c:3}
229
- '{', (a, entries) => !a && (
230
- a=expr(0,125),
231
- !a ? ctx => ({}) : ctx => (entries=(a.all||a)(ctx), Object.fromEntries(a.all?entries:[entries]))
232
- ),,
233
- // for JSON case we should not collect arg (different evaluator than ternary)
234
- ':', (a, prec, b) => (b=expr(1.1)||err(), a.id&&args.pop(), ctx => [(a.id||a)(ctx), b(ctx)]), 1.1
235
-
236
- ]; [op,prec,fn,...list]=list, op;) set(op,prec,fn);
237
-
238
- export { parse as default };
77
+ '{', 20, [
78
+ a => !a && (['{', expr(0,125)||'']),
79
+ (a,b) => (
80
+ !a ? ctx => ({}) : // {}
81
+ a[0] === ',' ? (a=a.slice(1).map(compile), ctx=>Object.fromEntries(a.map(a=>a(ctx)))) : // {a:1,b:2}
82
+ a[0] === ':' ? (a=compile(a), ctx => Object.fromEntries([a(ctx)])) : // {a:1}
83
+ (b=compile(a), ctx=>({[a]:b(ctx)}))
84
+ )
85
+ ],
86
+ ':', 1.1, [
87
+ (a, b) => (b=expr(1.1)||err(), [':',a,b]),
88
+ (a,b) => (b=compile(b),a=Array.isArray(a)?compile(a):(a=>a).bind(0,a), ctx=>[a(ctx),b(ctx)])
89
+ ]
90
+ ]
91
+ for (;list[2];) subscript.set(...list.splice(0,3))
92
+
93
+ export default subscript
94
+ export * from './subscript.js'
package/justin.min.js CHANGED
@@ -1 +1 @@
1
- let e,t,r,l,a,n,d,o=(l,a)=>(e=0,r=[],t=l.trim(),!(l=t?c():e=>a)||t[e]?f():a=e=>l(e||{}),a.args=r,a),i=e=>e>=48&&e<=57||e>=65&&e<=90||e>=97&&e<=122||36==e||95==e||e>=192&&215!=e&&247!=e,f=(r="Bad syntax",l=t[e])=>{throw SyntaxError(r+" `"+l+"` at "+e)},p=(r=1,l=e,a)=>{if("number"==typeof r)e+=r;else for(;r(t.charCodeAt(e));)e++;return t.slice(l,e)},c=(t=0,r,l,a,n,d)=>{for(;(l=s())&&(n=(d=h[l])&&d(a,t)||!a&&u());)a=n;return r&&(l==r?e++:f()),a},s=r=>{for(;(r=t.charCodeAt(e))<=32;)e++;return r},u=(e=p(i),t)=>e?(t=t=>t[e],r.push(e),t.id=()=>e,t):0,h=[],g=o.set=(r,l,a=32,n=r.charCodeAt(0),d=r.length,o=h[n],f=a.length||([a,l]=[l,a],0),p=r.toUpperCase()!==r,s=(f>1?(e,t)=>e&&(t=c(l))&&(e.length||t.length?r=>a(e(r),t(r),e.id?.(r),t.id?.(r)):(e=a(e(),t()),()=>e)):f?e=>!e&&(e=c(l-1))&&(t=>a(e(t))):a))=>h[n]=(a,n,f=e)=>n<l&&(d<2||t.substr(e,d)==r)&&(!p||!i(t.charCodeAt(e+d)))&&(e+=d,s(a,n))||(e=f,o&&o(a,n)),x=e=>e>=48&&e<=57,C=r=>(r&&f("Unexpected number"),r=p((e=>46==e||x(e))),(69==t.charCodeAt(e)||101==t.charCodeAt(e))&&(r+=p(2)+p(x)),(r=+r)!=r?f("Bad number"):()=>r),A=(e,t)=>r=>t(e.of?e.of(r):r,e.id(r));for(a=48;a<=57;)h[a++]=C;for(l=['"',e=>(e=e?f("Unexpected string"):p((e=>e-34)),p()||f("Bad string"),()=>e),,".",(e,t)=>(s(),t=p(i)||f(),d=r=>e(r)[t],d.id=()=>t,d.of=e,d),18,".",e=>!e&&C(p(-1)),,"[",(e,t,r)=>e&&(t=c(0,93)||f(),(r=r=>e(r)[t(r)]).id=t,r.of=e,r),18,"(",(e,t,r)=>(t=c(0,41),e?r=>e(r).apply(e.of?.(r),t?t.all?t.all(r):[t(r)]:[]):t||f()),18,",",(e,t,r=c(1),l=(t=>(e(t),r(t))))=>(r?l.all=e.all?t=>[...e.all(t),r(t)]:t=>[e(t),r(t)]:f("Skipped argument"),l),1,"|",6,(e,t)=>e|t,"||",4,(e,t)=>e||t,"&",8,(e,t)=>e&t,"&&",5,(e,t)=>e&&t,"^",7,(e,t)=>e^t,"==",9,(e,t)=>e==t,"!=",9,(e,t)=>e!=t,">",10,(e,t)=>e>t,">=",10,(e,t)=>e>=t,">>",11,(e,t)=>e>>t,">>>",11,(e,t)=>e>>>t,"<",10,(e,t)=>e<t,"<=",10,(e,t)=>e<=t,"<<",11,(e,t)=>e<<t,"+",12,(e,t)=>e+t,"+",15,e=>+e,"++",e=>A(e||c(14),e?(e,t)=>e[t]++:(e,t)=>++e[t]),15,"-",12,(e,t)=>e-t,"-",15,e=>-e,"--",e=>A(e||c(14),e?(e,t)=>e[t]--:(e,t)=>--e[t]),15,"!",15,e=>!e,"*",13,(e,t)=>e*t,"/",13,(e,t)=>e/t,"%",13,(e,t)=>e%t];[a,n,d,...l]=l,a;)g(a,n,d);let U,b,m,y,B={n:"\n",r:"\r",t:"\t",b:"\b",f:"\f",v:"\v"},v=r=>(l,a,n="")=>{for(l&&f("Unexpected string");(a=t.charCodeAt(e))-r;)92===a?(p(),a=p(),n+=B[a]||a):n+=p();return p()||f("Bad string"),()=>n};for((U=['"',v(34),,"'",v(39),,"/*",(r,l)=>(p((r=>42!==r&&47!==t.charCodeAt(e+1))),p(2),r||c(l)),,"//",(e,t)=>(p((e=>e>=32)),e||c(t)),,"null",e=>e?f("Unexpected literal"):()=>null,,"true",e=>e?f("Unexpected literal"):()=>!0,,"false",e=>e?f("Unexpected literal"):()=>!1,,"undefined",e=>e?f("Unexpected literal"):()=>{},,";",e=>c()||(()=>{}),,"===",9,(e,t)=>e===t,"!==",9,(e,t)=>e!==t,"~",15,e=>~e,"**",(e,t,r=c(13))=>t=>e(t)**r(t),14,":",3.1,(e,t)=>[e,t],"?",3,(e,t)=>e?t[0]:t[1],"??",6,(e,t)=>e??t,"?.",e=>e&&(t=>e(t)||(()=>{})),,"?.",(e,t)=>(s(),(t=p(i))&&(r=>e(r)?.[t])),,"in",10,(e,t)=>e in t,"[",e=>!e&&((e=c(0,93))?e.all?t=>e.all(t):t=>[e(t)]:e=>[]),,"{",(e,t)=>!e&&((e=c(0,125))?r=>(t=(e.all||e)(r),Object.fromEntries(e.all?t:[t])):e=>({})),,":",(e,t,l)=>(l=c(1.1)||f(),e.id&&r.pop(),t=>[(e.id||e)(t),l(t)]),1.1]);[b,m,y,...U]=U,b;)g(b,m,y);export{o as default};
1
+ let e,r,t=t=>(e=0,r=t,t=a(),r[e]?l():t||""),l=(t="Bad syntax",l=r[e])=>{throw SyntaxError(t+" `"+l+"` at "+e)},n=(t=1,l=e,n)=>{if("number"==typeof t)e+=t;else for(;n=t(r.charCodeAt(e));)e+=n;return r.slice(l,e)},a=(r=0,t,n,a,o,i)=>{for(;(n=s())&&(o=(i=c[n])&&i(a,r)||!a&&c[0]());)a=o;return t&&(n==t?e++:l()),a},s=t=>{for(;(t=r.charCodeAt(e))<=32;)e++;return t},o=e=>e>=48&&e<=57||e>=65&&e<=90||e>=97&&e<=122||36==e||95==e||e>=192&&215!=e&&247!=e,c=[e=>n(o)];t.set=(t,l=32,n,a=t.charCodeAt(0),s=t.length,i=c[a],f=t.toUpperCase()!==t)=>c[a]=(a,c,p=e)=>c<l&&(s<2||r.substr(e,s)==t)&&(!f||!o(r.charCodeAt(e+s)))&&(e+=s,n(a,c))||(e=p,i&&i(a,c));const i=e=>Array.isArray(e)?f[e[0]](...e.slice(1)):r=>r?.[e];i.set=(e,r,t=f[e])=>f[e]=(...e)=>r(...e)||t&&t(...e);const f={},p=e=>(e=t(e),r=>(e.call?e:e=i(e))(r)),h=p.set=(e,r,l,n=r<0,s=l[0],o=l[1],f=!s&&l.length)=>(s||=f?f>1?(t,l)=>t&&(l=a(r-n))&&[e,t,l]:t=>!t&&(t=a(r-1))&&[e,t]:(t,l)=>t&&(l=a(r))&&(t[0]===e&&t[2]?(t.push(l),t):[e,t,l]),o||=f?f>1?(e,r)=>r&&(e=i(e),r=i(r),e.length||r.length?t=>l(e(t),r(t)):(e=l(e(),r()),()=>e)):(e,r)=>!r&&((e=i(e)).length?r=>l(e(r)):(e=l(e()),()=>e)):(...e)=>(e=e.map(i),r=>l(...e.map((e=>e(r))))),(r=n?-r:r)?t.set(e,r,s):c[e.charCodeAt(0)||1]=s,i.set(e,o)),d=e=>e?l():["",(e=+n((e=>46===e||e>=48&&e<=57||(69===e||101===e?2:0))))!=e?l():e],u=(e,r,t,l)=>[e,r,[t=>t?["++"===e?"-":"+",[e,t],["",1]]:[e,a(r-1)],l=(e,r)=>"("===e[0]?l(e[1]):"."===e[0]?(r=e[2],e=i(e[1]),l=>t(e(l),r)):"["===e[0]?([,e,r]=e,e=i(e),r=i(r),l=>t(e(l),r(l))):r=>t(r,e)]],m=["",,[,e=>()=>e],'"',,[e=>e?l():["",(n()+n((e=>e-34?1:0))+(n()||l("Bad string"))).slice(1,-1)]],".",,[e=>!e&&d()],...Array(10).fill(0).flatMap(((e,r)=>[""+r,0,[d]])),",",1,(...e)=>e[e.length-1],"||",4,(...e)=>{let r,t=0;for(;!r&&t<e.length;)r=e[t++];return r},"&&",5,(...e)=>{let r=0,t=!0;for(;t&&r<e.length;)t=e[r++];return t},"+",12,(e,r)=>e+r,"-",12,(e,r)=>e-r,"*",13,(e,r)=>e*r,"/",13,(e,r)=>e/r,"%",13,(e,r)=>e%r,"|",6,(e,r)=>e|r,"&",8,(e,r)=>e&r,"^",7,(e,r)=>e^r,"==",9,(e,r)=>e==r,"!=",9,(e,r)=>e!=r,">",10,(e,r)=>e>r,">=",10,(e,r)=>e>=r,"<",10,(e,r)=>e<r,"<=",10,(e,r)=>e<=r,">>",11,(e,r)=>e>>r,">>>",11,(e,r)=>e>>>r,"<<",11,(e,r)=>e<<r,"+",15,e=>+e,"-",15,e=>-e,"!",15,e=>!e,...u("++",15,((e,r)=>++e[r])),...u("--",15,((e,r)=>--e[r])),"[",18,[e=>e&&["[",e,a(0,93)||l()],(e,r)=>r&&(e=i(e),r=i(r),t=>e(t)[r(t)])],".",18,[(e,r)=>e&&(r=a(18))&&[".",e,r],(e,r)=>(e=i(e),r=r[0]?r:r[1],t=>e(t)[r])],"(",18,[e=>!e&&["(",a(0,41)||l()],i],"(",18,[e=>e&&["(",e,a(0,41)||""],(e,r,t,l)=>null!=r&&(l=""==r?()=>[]:","===r[0]?(r=r.slice(1).map(i),e=>r.map((r=>r(e)))):(r=i(r),e=>[r(e)]),"."===e[0]?(t=e[2],e=i(e[1]),r=>e(r)[t](...l(r))):"["===e[0]?(t=i(e[2]),e=i(e[1]),r=>e(r)[t(r)](...l(r))):(e=i(e),r=>e(r)(...l(r))))]];for(;m[2];)h(...m.splice(0,3));let A={n:"\n",r:"\r",t:"\t",b:"\b",f:"\f",v:"\v"},g=t=>(a,s,o="")=>{for(a&&l("Unexpected string"),n();(s=r.charCodeAt(e))-t;)92===s?(n(),s=n(),o+=A[s]||s):o+=n();return n(),["",o]},y=["===",9,(e,r)=>e===r,"!==",9,(e,r)=>e!==r,"~",15,e=>~e,"?",3,[(e,r,t)=>e&&(r=a(2,58))&&["?",e,r,a(3)],(e,r,t)=>(e=i(e),r=i(r),t=i(t),l=>e(l)?r(l):t(l))],"??",6,(e,r)=>e??r,"?.",18,[e=>e&&["?.",e],e=>(e=i(e),r=>e(r)||(()=>{}))],"?.",18,[(e,r)=>e&&!(r=a(18))?.map&&["?.",e,r],(e,r)=>r&&(e=i(e),t=>e(t)?.[r])],"in",10,(e,r)=>e in r,'"',,[g(34)],"'",,[g(39)],"/*",20,[(t,l)=>(n((t=>42!==t&&47!==r.charCodeAt(e+1))),n(2),t||a(l))],"//",20,[(e,r)=>(n((e=>e>=32)),e||a(r))],"null",20,[e=>e?l():["",null]],"true",20,[e=>e?l():["",!0]],"false",20,[e=>e?l():["",!1]],"undefined",20,[e=>e?l():["",void 0]],";",20,[e=>a()||[""]],"**",-14,(e,r)=>e**r,"[",20,[e=>!e&&["[",a(0,93)||""],(e,r)=>!r&&(e?","===e[0]?(e=e.slice(1).map(i),r=>e.map((e=>e(r)))):(e=i(e),r=>[e(r)]):()=>[])],"{",20,[e=>!e&&["{",a(0,125)||""],(e,r)=>e?","===e[0]?(e=e.slice(1).map(i),r=>Object.fromEntries(e.map((e=>e(r))))):":"===e[0]?(e=i(e),r=>Object.fromEntries([e(r)])):(r=i(e),t=>({[e]:r(t)})):e=>({})],":",1.1,[(e,r)=>[":",e,a(1.1)||l()],(e,r)=>(r=i(r),e=Array.isArray(e)?i(e):(e=>e).bind(0,e),t=>[e(t),r(t)])]];for(;y[2];)p.set(...y.splice(0,3));export{i as compile,p as default,t as parse};
package/package.json CHANGED
@@ -1,13 +1,20 @@
1
1
  {
2
2
  "name": "subscript",
3
- "version": "6.3.1",
3
+ "version": "7.0.2",
4
4
  "description": "Fast and tiny expression evaluator with common syntax microlanguage.",
5
- "main": "src/subscript.js",
6
- "module": "src/subscript.js",
5
+ "main": "subscript.js",
6
+ "module": "subscript.js",
7
7
  "browser": "subscript.js",
8
+ "exports": {
9
+ ".": "./subscript.js",
10
+ "./parse": "./parse.js",
11
+ "./subscript": "./subscript.js",
12
+ "./justin": "./justin.js"
13
+ },
8
14
  "type": "module",
9
15
  "files": [
10
- "src",
16
+ "parse.js",
17
+ "compile.js",
11
18
  "subscript.js",
12
19
  "subscript.min.js",
13
20
  "justin.js",
@@ -19,12 +26,13 @@
19
26
  "test": "test"
20
27
  },
21
28
  "scripts": {
22
- "build": "npm run build-subscript && npm run minify-subscript && npm run build-justin && npm run minify-justin",
23
- "build-subscript": "rollup src/subscript.js --file subscript.js --format esm --name \"Subscript\"",
24
- "minify-subscript": "terser subscript.js -o subscript.min.js --module -c passes=3 -m",
25
- "build-justin": "rollup src/justin.js --file justin.js --format esm --name \"Justin\"",
26
- "minify-justin": "terser justin.js -o justin.min.js --module -c passes=3 -m",
27
- "test": "node test"
29
+ "build": "npm run build-subscript && npm run build-justin",
30
+ "minify": "npm run minify-subscript && npm run minify-justin",
31
+ "build-subscript": "rollup subscript.js --file subscript.min.js --format esm --name \"Subscript\"",
32
+ "minify-subscript": "terser subscript.min.js -o subscript.min.js --module -c passes=3 -m",
33
+ "build-justin": "rollup justin.js --file justin.min.js --format esm --name \"Justin\"",
34
+ "minify-justin": "terser justin.min.js -o justin.min.js --module -c passes=3 -m",
35
+ "test": "node test && node test/jsep && node test/perf"
28
36
  },
29
37
  "repository": {
30
38
  "type": "git",
@@ -1,40 +1,30 @@
1
- const SPACE=32, CPAREN=41
1
+ const SPACE=32
2
2
 
3
3
  // current string, index and collected ids
4
- export let idx, cur, args,
4
+ export let idx, cur,
5
5
 
6
6
  // no handling tagged literals since easily done on user side with cache, if needed
7
- parse = (s, fn) => (
8
- idx=0, args=[], cur=s.trim(),
9
- !(s = cur ? expr() : ctx=>{}) || cur[idx] ? err() :
10
- fn = ctx=>s(ctx||{}), fn.args = args, fn
11
- ),
12
-
13
- isId = c =>
14
- (c >= 48 && c <= 57) || // 0..9
15
- (c >= 65 && c <= 90) || // A...Z
16
- (c >= 97 && c <= 122) || // a...z
17
- c == 36 || c == 95 || // $, _,
18
- (c >= 192 && c != 215 && c != 247), // any non-ASCII
7
+ parse = s => (idx=0, cur=s, s = expr(), cur[idx] ? err() : s || ''),
19
8
 
20
9
  err = (msg='Bad syntax',c=cur[idx]) => { throw SyntaxError(msg + ' `' + c + '` at ' + idx) },
21
10
 
22
11
  skip = (is=1, from=idx, l) => {
23
12
  if (typeof is == 'number') idx += is
24
- else while (is(cur.charCodeAt(idx))) idx++
13
+ else while (l=is(cur.charCodeAt(idx))) idx+=l
25
14
  return cur.slice(from, idx)
26
15
  },
27
16
 
28
17
  // a + b - c
29
18
  expr = (prec=0, end, cc, token, newNode, fn) => {
19
+
30
20
  // chunk/token parser
31
21
  while (
32
22
  ( cc=space() ) && // till not end
33
23
  // FIXME: extra work is happening here, when lookup bails out due to lower precedence -
34
24
  // it makes extra `space` call for parent exprs on the same character to check precedence again
35
- ( newNode =
25
+ (newNode =
36
26
  (fn=lookup[cc]) && fn(token, prec) || // if operator with higher precedence isn't found
37
- (!token && id()) // parse literal or quit. token seqs are forbidden: `a b`, `a "b"`, `1.32 a`
27
+ (!token && lookup[0]()) // parse literal or quit. token seqs are forbidden: `a b`, `a "b"`, `1.32 a`
38
28
  )
39
29
  ) token = newNode;
40
30
 
@@ -48,29 +38,28 @@ expr = (prec=0, end, cc, token, newNode, fn) => {
48
38
  // skip space chars, return first non-space character
49
39
  space = cc => { while ((cc = cur.charCodeAt(idx)) <= SPACE) idx++; return cc },
50
40
 
51
- // variable identifier
52
- id = (name=skip(isId), fn) => name ? (fn=ctx => ctx[name], args.push(name), fn.id=()=>name, fn) : 0,
41
+ isId = c =>
42
+ (c >= 48 && c <= 57) || // 0..9
43
+ (c >= 65 && c <= 90) || // A...Z
44
+ (c >= 97 && c <= 122) || // a...z
45
+ c == 36 || c == 95 || // $, _,
46
+ (c >= 192 && c != 215 && c != 247), // any non-ASCII
53
47
 
54
48
  // operator/token lookup table
55
- lookup = [],
49
+ // lookup[0] is id parser to let configs redefine it
50
+ lookup = [n=>skip(isId)],
56
51
 
57
52
  // create operator checker/mapper (see examples)
58
53
  set = parse.set = (
59
54
  op,
60
- opPrec, fn=SPACE, // if opPrec & fn come in reverse order - consider them raw parse fn case, still precedence possible
55
+ prec=SPACE,
56
+ map,
61
57
  c=op.charCodeAt(0),
62
58
  l=op.length,
63
59
  prev=lookup[c],
64
- arity=fn.length || ([fn,opPrec]=[opPrec,fn], 0),
65
- word=op.toUpperCase()!==op, // make sure word boundary comes after word operator
66
- map=
67
- // binary
68
- arity>1 ? (a,b) => a && (b=expr(opPrec)) && (
69
- !a.length && !b.length ? (a=fn(a(),b()), ()=>a) : // static pre-eval like `"a"+"b"`
70
- ctx => fn(a(ctx),b(ctx),a.id?.(ctx),b.id?.(ctx))
71
- ) :
72
- // unary prefix (0 args)
73
- arity ? a => !a && (a=expr(opPrec-1)) && (ctx => fn(a(ctx))) :
74
- fn // custom parser
75
- ) =>
76
- lookup[c] = (a, curPrec, from=idx) => curPrec<opPrec && (l<2||cur.substr(idx,l)==op) && (!word||!isId(cur.charCodeAt(idx+l))) && (idx+=l, map(a, curPrec)) || (idx=from, prev&&prev(a, curPrec))
60
+ word=op.toUpperCase()!==op // make sure word boundary comes after word operator
61
+ ) => lookup[c] = (a, curPrec, from=idx) =>
62
+ curPrec<prec && (l<2||cur.substr(idx,l)==op) && (!word||!isId(cur.charCodeAt(idx+l))) && (idx+=l, map(a, curPrec)) ||
63
+ (idx=from, prev&&prev(a, curPrec))
64
+
65
+ export default parse
package/subscript.js CHANGED
@@ -1,166 +1,122 @@
1
- const SPACE=32;
1
+ import parse, { lookup, skip, cur, idx, err, expr } from './parse.js'
2
+ import compile from './compile.js'
2
3
 
3
- // current string, index and collected ids
4
- let idx, cur, args,
5
-
6
- // no handling tagged literals since easily done on user side with cache, if needed
7
- parse = (s, fn) => (
8
- idx=0, args=[], cur=s.trim(),
9
- !(s = cur ? expr() : ctx=>fn) || cur[idx] ? err() :
10
- fn = ctx=>s(ctx||{}), fn.args = args, fn
11
- ),
12
-
13
- isId = c =>
14
- (c >= 48 && c <= 57) || // 0..9
15
- (c >= 65 && c <= 90) || // A...Z
16
- (c >= 97 && c <= 122) || // a...z
17
- c == 36 || c == 95 || // $, _,
18
- (c >= 192 && c != 215 && c != 247), // any non-ASCII
19
-
20
- err = (msg='Bad syntax',c=cur[idx]) => { throw SyntaxError(msg + ' `' + c + '` at ' + idx) },
21
-
22
- skip = (is=1, from=idx, l) => {
23
- if (typeof is == 'number') idx += is;
24
- else while (is(cur.charCodeAt(idx))) idx++;
25
- return cur.slice(from, idx)
26
- },
27
-
28
- // a + b - c
29
- expr = (prec=0, end, cc, token, newNode, fn) => {
30
- // chunk/token parser
31
- while (
32
- ( cc=space() ) && // till not end
33
- // FIXME: extra work is happening here, when lookup bails out due to lower precedence -
34
- // it makes extra `space` call for parent exprs on the same character to check precedence again
35
- ( newNode =
36
- (fn=lookup[cc]) && fn(token, prec) || // if operator with higher precedence isn't found
37
- (!token && id()) // parse literal or quit. token seqs are forbidden: `a b`, `a "b"`, `1.32 a`
38
- )
39
- ) token = newNode;
40
-
41
- // check end character
42
- // FIXME: can't show "Unclose paren", because can be unknown operator within group as well
43
- if (end) cc==end?idx++:err();
44
-
45
- return token
46
- },
47
-
48
- // skip space chars, return first non-space character
49
- space = cc => { while ((cc = cur.charCodeAt(idx)) <= SPACE) idx++; return cc },
50
-
51
- // variable identifier
52
- id = (name=skip(isId), fn) => name ? (fn=ctx => ctx[name], args.push(name), fn.id=()=>name, fn) : 0,
53
-
54
- // operator/token lookup table
55
- lookup = [],
56
-
57
- // create operator checker/mapper (see examples)
58
- set = parse.set = (
59
- op,
60
- opPrec, fn=SPACE, // if opPrec & fn come in reverse order - consider them raw parse fn case, still precedence possible
61
- c=op.charCodeAt(0),
62
- l=op.length,
63
- prev=lookup[c],
64
- arity=fn.length || ([fn,opPrec]=[opPrec,fn], 0),
65
- word=op.toUpperCase()!==op, // make sure word boundary comes after word operator
66
- map=
67
- // binary
68
- arity>1 ? (a,b) => a && (b=expr(opPrec)) && (
69
- !a.length && !b.length ? (a=fn(a(),b()), ()=>a) : // static pre-eval like `"a"+"b"`
70
- ctx => fn(a(ctx),b(ctx),a.id?.(ctx),b.id?.(ctx))
71
- ) :
72
- // unary prefix (0 args)
73
- arity ? a => !a && (a=expr(opPrec-1)) && (ctx => fn(a(ctx))) :
74
- fn // custom parser
75
- ) =>
76
- lookup[c] = (a, curPrec, from=idx) => curPrec<opPrec && (l<2||cur.substr(idx,l)==op) && (!word||!isId(cur.charCodeAt(idx+l))) && (idx+=l, map(a, curPrec)) || (idx=from, prev&&prev(a, curPrec));
77
-
78
- const PERIOD=46, CPAREN=41, CBRACK=93, DQUOTE=34, _0=48, _9=57,
4
+ const OPAREN=40, CPAREN=41, OBRACK=91, CBRACK=93, SPACE=32, DQUOTE=34, PERIOD=46, _0=48, _9=57,
79
5
  PREC_SEQ=1, PREC_SOME=4, PREC_EVERY=5, PREC_OR=6, PREC_XOR=7, PREC_AND=8,
80
- PREC_EQ=9, PREC_COMP=10, PREC_SHIFT=11, PREC_SUM=12, PREC_MULT=13, PREC_UNARY=15, PREC_CALL=18;
81
-
82
- let list, op, prec, fn,
83
- isNum = c => c>=_0 && c<=_9,
84
- // 1.2e+3, .5
85
- num = n => (
86
- n&&err('Unexpected number'),
87
- n = skip(c=>c == PERIOD || isNum(c)),
88
- (cur.charCodeAt(idx) == 69 || cur.charCodeAt(idx) == 101) && (n += skip(2) + skip(isNum)),
89
- n=+n, n!=n ? err('Bad number') : () => n // 0 args means token is static
90
- ),
91
-
92
- inc = (a,fn) => ctx => fn(a.of?a.of(ctx):ctx, a.id(ctx));
93
-
94
- // numbers
95
- for (op=_0;op<=_9;) lookup[op++] = num;
96
-
97
- // operators
98
- for (list=[
99
- // "a"
100
- '"', a => (a=a?err('Unexpected string'):skip(c => c-DQUOTE), skip()||err('Bad string'), ()=>a),,
101
-
102
- // a.b
103
- '.', (a,id) => (space(), id=skip(isId)||err(), fn=ctx=>a(ctx)[id], fn.id=()=>id, fn.of=a, fn), PREC_CALL,
104
-
105
- // .2
106
- // FIXME: .123 is not operator, so we skip back, but mb reorganizing num would be better
107
- '.', a => !a && num(skip(-1)),,
108
-
109
- // a[b]
110
- '[', (a,b,fn) => a && (b=expr(0,CBRACK)||err(), fn=ctx=>a(ctx)[b(ctx)], fn.id=b, fn.of=a, fn), PREC_CALL,
111
-
112
- // a(), a(b), (a,b), (a+b)
113
- '(', (a,b,fn) => (
114
- b=expr(0,CPAREN),
115
- // a(), a(b), a(b,c,d)
116
- a ? ctx => a(ctx).apply(a.of?.(ctx), b ? b.all ? b.all(ctx) : [b(ctx)] : []) :
117
- // (a+b)
118
- b || err()
119
- ), PREC_CALL,
120
-
121
- // [a,b,c] or (a,b,c)
122
- ',', (a,prec,b=expr(PREC_SEQ),fn=ctx => (a(ctx), b(ctx))) => (
123
- b ? (fn.all = a.all ? ctx => [...a.all(ctx),b(ctx)] : ctx => [a(ctx),b(ctx)]) : err('Skipped argument',),
124
- fn
125
- ), PREC_SEQ,
6
+ PREC_EQ=9, PREC_COMP=10, PREC_SHIFT=11, PREC_SUM=12, PREC_MULT=13, PREC_UNARY=15, PREC_POSTFIX=16, PREC_CALL=18
7
+
8
+ const subscript = s => (s=parse(s), ctx => (s.call?s:(s=compile(s)))(ctx)),
9
+
10
+ // set any operator
11
+ // right assoc is indicated by negative precedence (meaning go from right to left)
12
+ set = subscript.set = (op, prec, fn, right=prec<0, parseFn=fn[0], evalFn=fn[1], arity=!parseFn&&fn.length) => (
13
+ parseFn ||=
14
+ !arity ? (a, b) => a && (b=expr(prec)) && (a[0] === op && a[2] ? (a.push(b), a) : [op,a,b]) :
15
+ arity > 1 ? (a, b) => a && (b=expr(prec-right)) && [op,a,b] :
16
+ a => !a && (a=expr(prec-1)) && [op, a]
17
+ ,
18
+ evalFn ||=
19
+ !arity ? (...args) => (args=args.map(compile), ctx => fn(...args.map(arg=>arg(ctx)))) :
20
+ arity > 1 ? (a,b) => b && (a=compile(a),b=compile(b), !a.length&&!b.length ? (a=fn(a(),b()),()=>a) : ctx => fn(a(ctx),b(ctx))) :
21
+ (a,b) => !b && (a=compile(a), !a.length ? (a=fn(a()),()=>a) : ctx => fn(a(ctx)))
22
+ ,
23
+ (prec=right?-prec:prec) ? parse.set(op,prec,parseFn) : (lookup[op.charCodeAt(0)||1]=parseFn),
24
+ compile.set(op, evalFn)
25
+ ),
126
26
 
27
+ // create increment-assign pair from fn
28
+ num = a => a ? err() : ['', (a=+skip(c => c === PERIOD || (c>=_0 && c<=_9) || (c===69||c===101?2:0)))!=a?err():a],
29
+ inc = (op, prec, fn, ev) => [op, prec, [
30
+ a => a ? [op==='++'?'-':'+',[op,a],['',1]] : [op,expr(prec-1)], // ++a → [++, a], a++ → [-,[++,a],1]
31
+ ev = (a,b) => (
32
+ a[0] === '(' ? ev(a[1]) : // ++(((a)))
33
+ a[0] === '.' ? (b=a[2], a=compile(a[1]), ctx => fn(a(ctx), b)) : // ++a.b
34
+ a[0] === '[' ? ([,a,b]=a, a=compile(a),b=compile(b), ctx => fn(a(ctx),b(ctx))) : // ++a[b]
35
+ (ctx => fn(ctx,a)) // ++a
36
+ )
37
+ ]],
38
+ list = [
39
+ // literals
40
+ // null operator returns first value (needed for direct literals)
41
+ '',, [,v => () => v],
42
+
43
+ '"',, [
44
+ (a) => a ? err() : ['', (skip() + skip(c => c - DQUOTE ? 1 : 0) + (skip()||err('Bad string'))).slice(1,-1)],
45
+ ],
46
+
47
+ // .1
48
+ '.',, [a=>!a && num()],
49
+
50
+ // 0-9
51
+ ...Array(10).fill(0).flatMap((_,i)=>[''+i,0,[num]]),
52
+
53
+ // sequences
54
+ ',', PREC_SEQ, (...args) => args[args.length-1],
55
+ '||', PREC_SOME, (...args) => { let i=0, v; for (; !v && i < args.length; ) v = args[i++]; return v },
56
+ '&&', PREC_EVERY, (...args) => { let i=0, v=true; for (; v && i < args.length; ) v = args[i++]; return v },
57
+
58
+ // binaries
59
+ '+', PREC_SUM, (a,b)=>a+b,
60
+ '-', PREC_SUM, (a,b)=>a-b,
61
+ '*', PREC_MULT, (a,b)=>a*b,
62
+ '/', PREC_MULT, (a,b)=>a/b,
63
+ '%', PREC_MULT, (a,b)=>a%b,
127
64
  '|', PREC_OR, (a,b)=>a|b,
128
- '||', PREC_SOME, (a,b)=>a||b,
129
-
130
65
  '&', PREC_AND, (a,b)=>a&b,
131
- '&&', PREC_EVERY, (a,b)=>a&&b,
132
-
133
66
  '^', PREC_XOR, (a,b)=>a^b,
134
-
135
- // ==, !=
136
67
  '==', PREC_EQ, (a,b)=>a==b,
137
68
  '!=', PREC_EQ, (a,b)=>a!=b,
138
-
139
- // > >= >> >>>, < <= <<
140
69
  '>', PREC_COMP, (a,b)=>a>b,
141
70
  '>=', PREC_COMP, (a,b)=>a>=b,
142
- '>>', PREC_SHIFT, (a,b)=>a>>b,
143
- '>>>', PREC_SHIFT, (a,b)=>a>>>b,
144
71
  '<', PREC_COMP, (a,b)=>a<b,
145
72
  '<=', PREC_COMP, (a,b)=>a<=b,
73
+ '>>', PREC_SHIFT, (a,b)=>a>>b,
74
+ '>>>', PREC_SHIFT, (a,b)=>a>>>b,
146
75
  '<<', PREC_SHIFT, (a,b)=>a<<b,
147
76
 
148
- // + ++ - --
149
- '+', PREC_SUM, (a,b)=>a+b,
150
- '+', PREC_UNARY, (a)=>+a,
151
- '++', a => inc(a||expr(PREC_UNARY-1), a ? (a,b)=>a[b]++ : (a,b)=>++a[b]), PREC_UNARY,
77
+ // unaries
78
+ '+', PREC_UNARY, a => +a,
79
+ '-', PREC_UNARY, a => -a,
80
+ '!', PREC_UNARY, a => !a,
152
81
 
153
- '-', PREC_SUM, (a,b)=>a-b,
154
- '-', PREC_UNARY, (a)=>-a,
155
- '--', a => inc(a||expr(PREC_UNARY-1), a ? (a,b)=>a[b]-- : (a,b)=>--a[b]), PREC_UNARY,
82
+ // increments
83
+ ...inc('++', PREC_UNARY, (a,b) => ++a[b]),
84
+ ...inc('--', PREC_UNARY, (a,b) => --a[b]),
156
85
 
157
- // ! ~
158
- '!', PREC_UNARY, (a)=>!a,
86
+ // a[b]
87
+ '[', PREC_CALL, [
88
+ a => a && ['[', a, expr(0,CBRACK)||err()],
89
+ (a,b) => b && (a=compile(a), b=compile(b), ctx => a(ctx)[b(ctx)])
90
+ ],
159
91
 
160
- // * / %
161
- '*', PREC_MULT, (a,b)=>a*b,
162
- '/', PREC_MULT, (a,b)=>a/b,
163
- '%', PREC_MULT, (a,b)=>a%b
164
- ]; [op,prec,fn,...list]=list, op;) set(op,prec,fn);
165
-
166
- export { parse as default };
92
+ // a.b
93
+ '.', PREC_CALL, [
94
+ (a,b) => a && (b=expr(PREC_CALL)) && ['.',a,b],
95
+ (a,b) => (a=compile(a),b=!b[0]?b[1]:b, ctx => a(ctx)[b]) // a.true, a.1 → needs to work fine
96
+ ],
97
+
98
+ // (a,b,c), (a)
99
+ '(', PREC_CALL, [
100
+ a => !a && ['(', expr(0,CPAREN)||err()],
101
+ compile
102
+ ],
103
+
104
+ // a(b,c,d), a()
105
+ '(', PREC_CALL, [
106
+ a => a && ['(', a, expr(0,CPAREN)||''],
107
+ (a,b,path,args) => b!=null && (
108
+ args = b=='' ? () => [] : // a()
109
+ b[0] === ',' ? (b=b.slice(1).map(compile), ctx => b.map(a=>a(ctx))) : // a(b,c)
110
+ (b=compile(b), ctx => [b(ctx)]), // a(b)
111
+
112
+ a[0] === '.' ? (path=a[2], a=compile(a[1]), ctx => a(ctx)[path](...args(ctx))) : // a.b(...args)
113
+ a[0] === '[' ? (path=compile(a[2]), a=compile(a[1]), ctx => a(ctx)[path(ctx)](...args(ctx))) : // a[b](...args)
114
+ (a=compile(a), ctx => a(ctx)(...args(ctx))) // a(...args)
115
+ )
116
+ ]
117
+ ]
118
+
119
+ for (;list[2];) set(...list.splice(0,3))
120
+
121
+ export default subscript
122
+ export {compile, parse}
package/subscript.min.js CHANGED
@@ -1 +1 @@
1
- let e,r,t,a,o,d,l,n=(a,o)=>(e=0,t=[],r=a.trim(),!(a=r?s():e=>o)||r[e]?h():o=e=>a(e||{}),o.args=t,o),f=e=>e>=48&&e<=57||e>=65&&e<=90||e>=97&&e<=122||36==e||95==e||e>=192&&215!=e&&247!=e,h=(t="Bad syntax",a=r[e])=>{throw SyntaxError(t+" `"+a+"` at "+e)},i=(t=1,a=e,o)=>{if("number"==typeof t)e+=t;else for(;t(r.charCodeAt(e));)e++;return r.slice(a,e)},s=(r=0,t,a,o,d,l)=>{for(;(a=p())&&(d=(l=c[a])&&l(o,r)||!o&&u());)o=d;return t&&(a==t?e++:h()),o},p=t=>{for(;(t=r.charCodeAt(e))<=32;)e++;return t},u=(e=i(f),r)=>e?(r=r=>r[e],t.push(e),r.id=()=>e,r):0,c=[],g=n.set=(t,a,o=32,d=t.charCodeAt(0),l=t.length,n=c[d],h=o.length||([o,a]=[a,o],0),i=t.toUpperCase()!==t,p=(h>1?(e,r)=>e&&(r=s(a))&&(e.length||r.length?t=>o(e(t),r(t),e.id?.(t),r.id?.(t)):(e=o(e(),r()),()=>e)):h?e=>!e&&(e=s(a-1))&&(r=>o(e(r))):o))=>c[d]=(o,d,h=e)=>d<a&&(l<2||r.substr(e,l)==t)&&(!i||!f(r.charCodeAt(e+l)))&&(e+=l,p(o,d))||(e=h,n&&n(o,d)),C=e=>e>=48&&e<=57,A=t=>(t&&h("Unexpected number"),t=i((e=>46==e||C(e))),(69==r.charCodeAt(e)||101==r.charCodeAt(e))&&(t+=i(2)+i(C)),(t=+t)!=t?h("Bad number"):()=>t),m=(e,r)=>t=>r(e.of?e.of(t):t,e.id(t));for(o=48;o<=57;)c[o++]=A;for(a=['"',e=>(e=e?h("Unexpected string"):i((e=>e-34)),i()||h("Bad string"),()=>e),,".",(e,r)=>(p(),r=i(f)||h(),l=t=>e(t)[r],l.id=()=>r,l.of=e,l),18,".",e=>!e&&A(i(-1)),,"[",(e,r,t)=>e&&(r=s(0,93)||h(),(t=t=>e(t)[r(t)]).id=r,t.of=e,t),18,"(",(e,r,t)=>(r=s(0,41),e?t=>e(t).apply(e.of?.(t),r?r.all?r.all(t):[r(t)]:[]):r||h()),18,",",(e,r,t=s(1),a=(r=>(e(r),t(r))))=>(t?a.all=e.all?r=>[...e.all(r),t(r)]:r=>[e(r),t(r)]:h("Skipped argument"),a),1,"|",6,(e,r)=>e|r,"||",4,(e,r)=>e||r,"&",8,(e,r)=>e&r,"&&",5,(e,r)=>e&&r,"^",7,(e,r)=>e^r,"==",9,(e,r)=>e==r,"!=",9,(e,r)=>e!=r,">",10,(e,r)=>e>r,">=",10,(e,r)=>e>=r,">>",11,(e,r)=>e>>r,">>>",11,(e,r)=>e>>>r,"<",10,(e,r)=>e<r,"<=",10,(e,r)=>e<=r,"<<",11,(e,r)=>e<<r,"+",12,(e,r)=>e+r,"+",15,e=>+e,"++",e=>m(e||s(14),e?(e,r)=>e[r]++:(e,r)=>++e[r]),15,"-",12,(e,r)=>e-r,"-",15,e=>-e,"--",e=>m(e||s(14),e?(e,r)=>e[r]--:(e,r)=>--e[r]),15,"!",15,e=>!e,"*",13,(e,r)=>e*r,"/",13,(e,r)=>e/r,"%",13,(e,r)=>e%r];[o,d,l,...a]=a,o;)g(o,d,l);export{n as default};
1
+ let e,t,r=r=>(e=0,t=r,r=s(),t[e]?l():r||""),l=(r="Bad syntax",l=t[e])=>{throw SyntaxError(r+" `"+l+"` at "+e)},a=(r=1,l=e,a)=>{if("number"==typeof r)e+=r;else for(;a=r(t.charCodeAt(e));)e+=a;return t.slice(l,e)},s=(t=0,r,a,s,o,c)=>{for(;(a=n())&&(o=(c=h[a])&&c(s,t)||!s&&h[0]());)s=o;return r&&(a==r?e++:l()),s},n=r=>{for(;(r=t.charCodeAt(e))<=32;)e++;return r},o=e=>e>=48&&e<=57||e>=65&&e<=90||e>=97&&e<=122||36==e||95==e||e>=192&&215!=e&&247!=e,h=[e=>a(o)];r.set=(r,l=32,a,s=r.charCodeAt(0),n=r.length,c=h[s],f=r.toUpperCase()!==r)=>h[s]=(s,h,p=e)=>h<l&&(n<2||t.substr(e,n)==r)&&(!f||!o(t.charCodeAt(e+n)))&&(e+=n,a(s,h))||(e=p,c&&c(s,h));const c=e=>Array.isArray(e)?f[e[0]](...e.slice(1)):t=>t?.[e];c.set=(e,t,r=f[e])=>f[e]=(...e)=>t(...e)||r&&r(...e);const f={},p=e=>(e=r(e),t=>(e.call?e:e=c(e))(t)),u=p.set=(e,t,l,a=t<0,n=l[0],o=l[1],f=!n&&l.length)=>(n||=f?f>1?(r,l)=>r&&(l=s(t-a))&&[e,r,l]:r=>!r&&(r=s(t-1))&&[e,r]:(r,l)=>r&&(l=s(t))&&(r[0]===e&&r[2]?(r.push(l),r):[e,r,l]),o||=f?f>1?(e,t)=>t&&(e=c(e),t=c(t),e.length||t.length?r=>l(e(r),t(r)):(e=l(e(),t()),()=>e)):(e,t)=>!t&&((e=c(e)).length?t=>l(e(t)):(e=l(e()),()=>e)):(...e)=>(e=e.map(c),t=>l(...e.map((e=>e(t))))),(t=a?-t:t)?r.set(e,t,n):h[e.charCodeAt(0)||1]=n,c.set(e,o)),g=e=>e?l():["",(e=+a((e=>46===e||e>=48&&e<=57||(69===e||101===e?2:0))))!=e?l():e],i=(e,t,r,l)=>[e,t,[r=>r?["++"===e?"-":"+",[e,r],["",1]]:[e,s(t-1)],l=(e,t)=>"("===e[0]?l(e[1]):"."===e[0]?(t=e[2],e=c(e[1]),l=>r(e(l),t)):"["===e[0]?([,e,t]=e,e=c(e),t=c(t),l=>r(e(l),t(l))):t=>r(t,e)]],d=["",,[,e=>()=>e],'"',,[e=>e?l():["",(a()+a((e=>e-34?1:0))+(a()||l("Bad string"))).slice(1,-1)]],".",,[e=>!e&&g()],...Array(10).fill(0).flatMap(((e,t)=>[""+t,0,[g]])),",",1,(...e)=>e[e.length-1],"||",4,(...e)=>{let t,r=0;for(;!t&&r<e.length;)t=e[r++];return t},"&&",5,(...e)=>{let t=0,r=!0;for(;r&&t<e.length;)r=e[t++];return r},"+",12,(e,t)=>e+t,"-",12,(e,t)=>e-t,"*",13,(e,t)=>e*t,"/",13,(e,t)=>e/t,"%",13,(e,t)=>e%t,"|",6,(e,t)=>e|t,"&",8,(e,t)=>e&t,"^",7,(e,t)=>e^t,"==",9,(e,t)=>e==t,"!=",9,(e,t)=>e!=t,">",10,(e,t)=>e>t,">=",10,(e,t)=>e>=t,"<",10,(e,t)=>e<t,"<=",10,(e,t)=>e<=t,">>",11,(e,t)=>e>>t,">>>",11,(e,t)=>e>>>t,"<<",11,(e,t)=>e<<t,"+",15,e=>+e,"-",15,e=>-e,"!",15,e=>!e,...i("++",15,((e,t)=>++e[t])),...i("--",15,((e,t)=>--e[t])),"[",18,[e=>e&&["[",e,s(0,93)||l()],(e,t)=>t&&(e=c(e),t=c(t),r=>e(r)[t(r)])],".",18,[(e,t)=>e&&(t=s(18))&&[".",e,t],(e,t)=>(e=c(e),t=t[0]?t:t[1],r=>e(r)[t])],"(",18,[e=>!e&&["(",s(0,41)||l()],c],"(",18,[e=>e&&["(",e,s(0,41)||""],(e,t,r,l)=>null!=t&&(l=""==t?()=>[]:","===t[0]?(t=t.slice(1).map(c),e=>t.map((t=>t(e)))):(t=c(t),e=>[t(e)]),"."===e[0]?(r=e[2],e=c(e[1]),t=>e(t)[r](...l(t))):"["===e[0]?(r=c(e[2]),e=c(e[1]),t=>e(t)[r(t)](...l(t))):(e=c(e),t=>e(t)(...l(t))))]];for(;d[2];)u(...d.splice(0,3));export{c as compile,p as default,r as parse};
package/src/justin.js DELETED
@@ -1,74 +0,0 @@
1
- // justin lang https://github.com/endojs/Jessie/issues/66
2
- import {set, lookup, skip, cur, idx, err, expr, isId, space, args} from './index.js'
3
- export { default } from './subscript.js'
4
-
5
- const PERIOD=46, OPAREN=40, CPAREN=41, OBRACK=91, CBRACK=93, SPACE=32, DQUOTE=34, QUOTE=39, _0=48, _9=57, BSLASH=92,
6
- PREC_SEQ=1, PREC_COND=3, PREC_SOME=4, PREC_EVERY=5, PREC_OR=6, PREC_XOR=7, PREC_AND=8,
7
- PREC_EQ=9, PREC_COMP=10, PREC_SHIFT=11, PREC_SUM=12, PREC_MULT=13, PREC_EXP=14, PREC_UNARY=15, PREC_POSTFIX=16, PREC_CALL=18, PREC_GROUP=19
8
-
9
-
10
- let u, list, op, prec, fn,
11
- escape = {n:'\n', r:'\r', t:'\t', b:'\b', f:'\f', v:'\v'},
12
- string = q => (qc, c, str='') => {
13
- qc&&err('Unexpected string') // must not follow another token
14
- while (c=cur.charCodeAt(idx), c-q) {
15
- if (c === BSLASH) skip(), c=skip(), str += escape[c] || c
16
- else str += skip()
17
- }
18
- return skip()||err('Bad string'), () => str
19
- }
20
-
21
- // operators
22
- for (list=[
23
- // "' with /
24
- '"', string(DQUOTE),,
25
- "'", string(QUOTE),,
26
-
27
- // /**/, //
28
- '/*', (a, prec) => (skip(c => c !== 42 && cur.charCodeAt(idx+1) !== 47), skip(2), a||expr(prec)),,
29
- '//', (a, prec) => (skip(c => c >= 32), a||expr(prec)),,
30
-
31
- // literals
32
- 'null', a => a ? err('Unexpected literal') : ()=>null,,
33
- 'true', a => a ? err('Unexpected literal') : ()=>true,,
34
- 'false', a => a ? err('Unexpected literal') : ()=>false,,
35
- 'undefined', a => a ? err('Unexpected literal') : ()=>undefined,,
36
-
37
- ';', a => expr()||(()=>{}),,
38
-
39
- // operators
40
- '===', PREC_EQ, (a,b) => a===b,
41
- '!==', PREC_EQ, (a,b) => a!==b,
42
- '~', PREC_UNARY, (a) => ~a,
43
-
44
- // right order
45
- '**', (a,prec,b=expr(PREC_EXP-1)) => ctx=>a(ctx)**b(ctx), PREC_EXP,
46
-
47
- // ?:
48
- ':', 3.1, (a,b) => [a,b],
49
- '?', 3, (a,b) => a ? b[0] : b[1],
50
-
51
- '??', PREC_OR, (a,b) => a??b,
52
-
53
- // a?.[, a?.( - postfix operator
54
- '?.', a => a && (ctx => a(ctx)||(()=>{})),,//(a) => a||(()=>{}),
55
- // a?.b - optional chain operator
56
- '?.', (a,id) => (space(), id=skip(isId)) && (ctx => a(ctx)?.[id]),,
57
-
58
- 'in', PREC_COMP, (a,b) => a in b,
59
-
60
- // [a,b,c]
61
- '[', (a) => !a && (
62
- a=expr(0,CBRACK),
63
- !a ? ctx => [] : a.all ? ctx => a.all(ctx) : ctx => [a(ctx)]
64
- ),,
65
-
66
- // {a:1, b:2, c:3}
67
- '{', (a, entries) => !a && (
68
- a=expr(0,125),
69
- !a ? ctx => ({}) : ctx => (entries=(a.all||a)(ctx), Object.fromEntries(a.all?entries:[entries]))
70
- ),,
71
- // for JSON case we should not collect arg (different evaluator than ternary)
72
- ':', (a, prec, b) => (b=expr(1.1)||err(), a.id&&args.pop(), ctx => [(a.id||a)(ctx), b(ctx)]), 1.1
73
-
74
- ]; [op,prec,fn,...list]=list, op;) set(op,prec,fn)
package/src/subscript.js DELETED
@@ -1,91 +0,0 @@
1
- import {parse, set, lookup, skip, cur, idx, err, expr, isId, args, space} from './index.js'
2
-
3
- const PERIOD=46, OPAREN=40, CPAREN=41, OBRACK=91, CBRACK=93, SPACE=32, DQUOTE=34, _0=48, _9=57,
4
- PREC_SEQ=1, PREC_SOME=4, PREC_EVERY=5, PREC_OR=6, PREC_XOR=7, PREC_AND=8,
5
- PREC_EQ=9, PREC_COMP=10, PREC_SHIFT=11, PREC_SUM=12, PREC_MULT=13, PREC_UNARY=15, PREC_POSTFIX=16, PREC_CALL=18, PREC_GROUP=19
6
-
7
- let u, list, op, prec, fn,
8
- isNum = c => c>=_0 && c<=_9,
9
- // 1.2e+3, .5
10
- num = n => (
11
- n&&err('Unexpected number'),
12
- n = skip(c=>c == PERIOD || isNum(c)),
13
- (cur.charCodeAt(idx) == 69 || cur.charCodeAt(idx) == 101) && (n += skip(2) + skip(isNum)),
14
- n=+n, n!=n ? err('Bad number') : () => n // 0 args means token is static
15
- ),
16
-
17
- inc = (a,fn) => ctx => fn(a.of?a.of(ctx):ctx, a.id(ctx))
18
-
19
- // numbers
20
- for (op=_0;op<=_9;) lookup[op++] = num
21
-
22
- // operators
23
- for (list=[
24
- // "a"
25
- '"', a => (a=a?err('Unexpected string'):skip(c => c-DQUOTE), skip()||err('Bad string'), ()=>a),,
26
-
27
- // a.b
28
- '.', (a,id) => (space(), id=skip(isId)||err(), fn=ctx=>a(ctx)[id], fn.id=()=>id, fn.of=a, fn), PREC_CALL,
29
-
30
- // .2
31
- // FIXME: .123 is not operator, so we skip back, but mb reorganizing num would be better
32
- '.', a => !a && num(skip(-1)),,
33
-
34
- // a[b]
35
- '[', (a,b,fn) => a && (b=expr(0,CBRACK)||err(), fn=ctx=>a(ctx)[b(ctx)], fn.id=b, fn.of=a, fn), PREC_CALL,
36
-
37
- // a(), a(b), (a,b), (a+b)
38
- '(', (a,b,fn) => (
39
- b=expr(0,CPAREN),
40
- // a(), a(b), a(b,c,d)
41
- a ? ctx => a(ctx).apply(a.of?.(ctx), b ? b.all ? b.all(ctx) : [b(ctx)] : []) :
42
- // (a+b)
43
- b || err()
44
- ), PREC_CALL,
45
-
46
- // [a,b,c] or (a,b,c)
47
- ',', (a,prec,b=expr(PREC_SEQ),fn=ctx => (a(ctx), b(ctx))) => (
48
- b ? (fn.all = a.all ? ctx => [...a.all(ctx),b(ctx)] : ctx => [a(ctx),b(ctx)]) : err('Skipped argument',),
49
- fn
50
- ), PREC_SEQ,
51
-
52
- '|', PREC_OR, (a,b)=>a|b,
53
- '||', PREC_SOME, (a,b)=>a||b,
54
-
55
- '&', PREC_AND, (a,b)=>a&b,
56
- '&&', PREC_EVERY, (a,b)=>a&&b,
57
-
58
- '^', PREC_XOR, (a,b)=>a^b,
59
-
60
- // ==, !=
61
- '==', PREC_EQ, (a,b)=>a==b,
62
- '!=', PREC_EQ, (a,b)=>a!=b,
63
-
64
- // > >= >> >>>, < <= <<
65
- '>', PREC_COMP, (a,b)=>a>b,
66
- '>=', PREC_COMP, (a,b)=>a>=b,
67
- '>>', PREC_SHIFT, (a,b)=>a>>b,
68
- '>>>', PREC_SHIFT, (a,b)=>a>>>b,
69
- '<', PREC_COMP, (a,b)=>a<b,
70
- '<=', PREC_COMP, (a,b)=>a<=b,
71
- '<<', PREC_SHIFT, (a,b)=>a<<b,
72
-
73
- // + ++ - --
74
- '+', PREC_SUM, (a,b)=>a+b,
75
- '+', PREC_UNARY, (a)=>+a,
76
- '++', a => inc(a||expr(PREC_UNARY-1), a ? (a,b)=>a[b]++ : (a,b)=>++a[b]), PREC_UNARY,
77
-
78
- '-', PREC_SUM, (a,b)=>a-b,
79
- '-', PREC_UNARY, (a)=>-a,
80
- '--', a => inc(a||expr(PREC_UNARY-1), a ? (a,b)=>a[b]-- : (a,b)=>--a[b]), PREC_UNARY,
81
-
82
- // ! ~
83
- '!', PREC_UNARY, (a)=>!a,
84
-
85
- // * / %
86
- '*', PREC_MULT, (a,b)=>a*b,
87
- '/', PREC_MULT, (a,b)=>a/b,
88
- '%', PREC_MULT, (a,b)=>a%b
89
- ]; [op,prec,fn,...list]=list, op;) set(op,prec,fn)
90
-
91
- export default parse