subscript 6.3.0 → 7.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 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
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,234 +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= !(cur=s, idx=0, args=[], s=expr()) || cur[idx] ? err() : ctx=>s(ctx||{})) => (fn.args = args, fn),
8
-
9
- isId = c =>
10
- (c >= 48 && c <= 57) || // 0..9
11
- (c >= 65 && c <= 90) || // A...Z
12
- (c >= 97 && c <= 122) || // a...z
13
- c == 36 || c == 95 || // $, _,
14
- (c >= 192 && c != 215 && c != 247), // any non-ASCII
15
-
16
- err = (msg='Bad syntax',c=cur[idx]) => { throw SyntaxError(msg + ' `' + c + '` at ' + idx) },
17
-
18
- skip = (is=1, from=idx, l) => {
19
- if (typeof is == 'number') idx += is;
20
- else while (is(cur.charCodeAt(idx))) idx++;
21
- return cur.slice(from, idx)
22
- },
23
-
24
- // a + b - c
25
- expr = (prec=0, end, cc, token, newNode, fn) => {
26
- // chunk/token parser
27
- while (
28
- ( cc=space() ) && // till not end
29
- // FIXME: extra work is happening here, when lookup bails out due to lower precedence -
30
- // it makes extra `space` call for parent exprs on the same character to check precedence again
31
- ( newNode =
32
- (fn=lookup[cc]) && fn(token, prec) || // if operator with higher precedence isn't found
33
- (!token && id()) // parse literal or quit. token seqs are forbidden: `a b`, `a "b"`, `1.32 a`
34
- )
35
- ) token = newNode;
36
-
37
- // check end character
38
- // FIXME: can't show "Unclose paren", because can be unknown operator within group as well
39
- if (end) cc==end?idx++:err();
40
-
41
- return token
42
- },
43
-
44
- // skip space chars, return first non-space character
45
- space = cc => { while ((cc = cur.charCodeAt(idx)) <= SPACE) idx++; return cc },
46
-
47
- // variable identifier
48
- id = (name=skip(isId), fn) => name ? (fn=ctx => ctx[name], args.push(name), fn.id=()=>name, fn) : 0,
49
-
50
- // operator/token lookup table
51
- lookup = [],
52
-
53
- // create operator checker/mapper (see examples)
54
- set = parse.set = (
55
- op,
56
- opPrec, fn=SPACE, // if opPrec & fn come in reverse order - consider them raw parse fn case, still precedence possible
57
- c=op.charCodeAt(0),
58
- l=op.length,
59
- prev=lookup[c],
60
- arity=fn.length || ([fn,opPrec]=[opPrec,fn], 0),
61
- word=op.toUpperCase()!==op, // make sure word boundary comes after word operator
62
- map=
63
- // binary
64
- arity>1 ? (a,b) => a && (b=expr(opPrec)) && (
65
- !a.length && !b.length ? (a=fn(a(),b()), ()=>a) : // static pre-eval like `"a"+"b"`
66
- ctx => fn(a(ctx),b(ctx),a.id?.(ctx),b.id?.(ctx))
67
- ) :
68
- // unary prefix (0 args)
69
- arity ? a => !a && (a=expr(opPrec-1)) && (ctx => fn(a(ctx))) :
70
- fn // custom parser
71
- ) =>
72
- 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));
73
-
74
- const PERIOD=46, CPAREN=41, CBRACK$1=93, DQUOTE$1=34, _0=48, _9=57,
75
- PREC_SEQ=1, PREC_SOME=4, PREC_EVERY=5, PREC_OR$1=6, PREC_XOR=7, PREC_AND=8,
76
- PREC_EQ$1=9, PREC_COMP$1=10, PREC_SHIFT=11, PREC_SUM=12, PREC_MULT=13, PREC_UNARY$1=15, PREC_CALL=18;
77
-
78
- let list$1, op$1, prec$1, fn$1,
79
- isNum = c => c>=_0 && c<=_9,
80
- // 1.2e+3, .5
81
- num = n => (
82
- n&&err('Unexpected number'),
83
- n = skip(c=>c == PERIOD || isNum(c)),
84
- (cur.charCodeAt(idx) == 69 || cur.charCodeAt(idx) == 101) && (n += skip(2) + skip(isNum)),
85
- n=+n, n!=n ? err('Bad number') : () => n // 0 args means token is static
86
- ),
87
-
88
- inc = (a,fn) => ctx => fn(a.of?a.of(ctx):ctx, a.id(ctx));
89
-
90
- // numbers
91
- for (op$1=_0;op$1<=_9;) lookup[op$1++] = num;
92
-
93
- // operators
94
- for (list$1=[
95
- // "a"
96
- '"', a => (a=a?err('Unexpected string'):skip(c => c-DQUOTE$1), skip()||err('Bad string'), ()=>a),,
97
-
98
- // a.b
99
- '.', (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,
100
-
101
- // .2
102
- // FIXME: .123 is not operator, so we skip back, but mb reorganizing num would be better
103
- '.', a => !a && num(skip(-1)),,
104
-
105
- // a[b]
106
- '[', (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,
107
-
108
- // a(), a(b), (a,b), (a+b)
109
- '(', (a,b,fn) => (
110
- b=expr(0,CPAREN),
111
- // a(), a(b), a(b,c,d)
112
- a ? ctx => a(ctx).apply(a.of?.(ctx), b ? b.all ? b.all(ctx) : [b(ctx)] : []) :
113
- // (a+b)
114
- b || err()
115
- ), PREC_CALL,
116
-
117
- // [a,b,c] or (a,b,c)
118
- ',', (a,prec,b=expr(PREC_SEQ),fn=ctx => (a(ctx), b(ctx))) => (
119
- b ? (fn.all = a.all ? ctx => [...a.all(ctx),b(ctx)] : ctx => [a(ctx),b(ctx)]) : err('Skipped argument',),
120
- fn
121
- ), PREC_SEQ,
122
-
123
- '|', PREC_OR$1, (a,b)=>a|b,
124
- '||', PREC_SOME, (a,b)=>a||b,
125
-
126
- '&', PREC_AND, (a,b)=>a&b,
127
- '&&', PREC_EVERY, (a,b)=>a&&b,
128
-
129
- '^', PREC_XOR, (a,b)=>a^b,
130
-
131
- // ==, !=
132
- '==', PREC_EQ$1, (a,b)=>a==b,
133
- '!=', PREC_EQ$1, (a,b)=>a!=b,
134
-
135
- // > >= >> >>>, < <= <<
136
- '>', PREC_COMP$1, (a,b)=>a>b,
137
- '>=', PREC_COMP$1, (a,b)=>a>=b,
138
- '>>', PREC_SHIFT, (a,b)=>a>>b,
139
- '>>>', PREC_SHIFT, (a,b)=>a>>>b,
140
- '<', PREC_COMP$1, (a,b)=>a<b,
141
- '<=', PREC_COMP$1, (a,b)=>a<=b,
142
- '<<', PREC_SHIFT, (a,b)=>a<<b,
143
-
144
- // + ++ - --
145
- '+', PREC_SUM, (a,b)=>a+b,
146
- '+', PREC_UNARY$1, (a)=>+a,
147
- '++', a => inc(a||expr(PREC_UNARY$1-1), a ? (a,b)=>a[b]++ : (a,b)=>++a[b]), PREC_UNARY$1,
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
- // ! ~
154
- '!', PREC_UNARY$1, (a)=>!a,
155
-
156
- // * / %
157
- '*', PREC_MULT, (a,b)=>a*b,
158
- '/', PREC_MULT, (a,b)=>a/b,
159
- '%', PREC_MULT, (a,b)=>a%b
160
- ]; [op$1,prec$1,fn$1,...list$1]=list$1, op$1;) set(op$1,prec$1,fn$1);
161
-
162
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
+ ],
163
33
 
164
- const CBRACK=93, DQUOTE=34, QUOTE=39, BSLASH=92,
165
- PREC_OR=6, PREC_EQ=9, PREC_COMP=10, PREC_EXP=14, PREC_UNARY=15;
34
+ '??', PREC_OR, (a,b) => a ?? b,
166
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
+ ],
167
43
 
168
- let list, op, prec, fn,
169
- escape = {n:'\n', r:'\r', t:'\t', b:'\b', f:'\f', v:'\v'},
170
- string = q => (qc, c, str='') => {
171
- qc&&err('Unexpected string'); // must not follow another token
172
- while (c=cur.charCodeAt(idx), c-q) {
173
- if (c === BSLASH) skip(), c=skip(), str += escape[c] || c;
174
- else str += skip();
175
- }
176
- return skip()||err('Bad string'), () => str
177
- };
44
+ 'in', PREC_COMP , (a,b) => a in b,
178
45
 
179
- // operators
180
- for (list=[
181
46
  // "' with /
182
- '"', string(DQUOTE),,
183
- "'", string(QUOTE),,
47
+ '"', , [string(DQUOTE)],
48
+ "'", , [string(QUOTE)],
184
49
 
185
50
  // /**/, //
186
- '/*', (a, prec) => (skip(c => c !== 42 && cur.charCodeAt(idx+1) !== 47), skip(2), a||expr(prec)),,
187
- '//', (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))],
188
53
 
189
54
  // literals
190
- 'null', a => a ? err('Unexpected literal') : ()=>null,,
191
- 'true', a => a ? err('Unexpected literal') : ()=>true,,
192
- 'false', a => a ? err('Unexpected literal') : ()=>false,,
193
- '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]],
194
59
 
195
- ';', a => expr()||(()=>{}),,
196
-
197
- // operators
198
- '===', PREC_EQ, (a,b) => a===b,
199
- '!==', PREC_EQ, (a,b) => a!==b,
200
- '~', PREC_UNARY, (a) => ~a,
60
+ // FIXME: make sure that is right
61
+ ';', 20, [a => expr()||['']],
201
62
 
202
63
  // right order
203
- '**', (a,prec,b=expr(PREC_EXP-1)) => ctx=>a(ctx)**b(ctx), PREC_EXP,
204
-
205
- // ?:
206
- ':', 3.1, (a,b) => [a,b],
207
- '?', 3, (a,b) => a ? b[0] : b[1],
208
-
209
- '??', PREC_OR, (a,b) => a??b,
210
-
211
- // a?.[, a?.( - postfix operator
212
- '?.', a => a && (ctx => a(ctx)||(()=>{})),,//(a) => a||(()=>{}),
213
- // a?.b - optional chain operator
214
- '?.', (a,id) => (space(), id=skip(isId)) && (ctx => a(ctx)?.[id]),,
215
-
216
- '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,
217
66
 
218
67
  // [a,b,c]
219
- '[', (a) => !a && (
220
- a=expr(0,CBRACK),
221
- !a ? ctx => [] : a.all ? ctx => a.all(ctx) : ctx => [a(ctx)]
222
- ),,
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
+ )],
223
75
 
224
76
  // {a:1, b:2, c:3}
225
- '{', (a, entries) => !a && (
226
- a=expr(0,125),
227
- !a ? ctx => ({}) : ctx => (entries=(a.all||a)(ctx), Object.fromEntries(a.all?entries:[entries]))
228
- ),,
229
- // for JSON case we should not collect arg (different evaluator than ternary)
230
- ':', (a, prec, b) => (b=expr(1.1)||err(), a.id&&args.pop(), ctx => [(a.id||a)(ctx), b(ctx)]), 1.1
231
-
232
- ]; [op,prec,fn,...list]=list, op;) set(op,prec,fn);
233
-
234
- 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=(t=l,e=0,r=[],!(l=c())||t[e]?f():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,c)=>{for(;(n=s())&&(o=(c=i[n])&&c(a,r)||!a&&i[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,i=[e=>n(o)];t.set=(t,l=32,n,a=t.charCodeAt(0),s=t.length,c=i[a],f=t.toUpperCase()!==t)=>i[a]=(a,i,p=e)=>i<l&&(s<2||r.substr(e,s)==t)&&(!f||!o(r.charCodeAt(e+s)))&&(e+=s,n(a,i))||(e=p,c&&c(a,i));const c=e=>Array.isArray(e)?f[e[0]](...e.slice(1)):r=>r[e];c.set=(e,r,t=f[e])=>f[e]=(...e)=>r(...e)||t&&t(...e);const f={},p=e=>(e=e.trim())?(e=t(e.trim()),r=>(e.call?e:e=c(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=c(e),r=c(r),e.length||r.length?t=>l(e(t),r(t)):(e=l(e(),r()),()=>e)):(e,r)=>!r&&((e=c(e)).length?r=>l(e(r)):(e=l(e()),()=>e)):(...e)=>(e=e.map(c),r=>l(...e.map((e=>e(r))))),(r=n?-r:r)?t.set(e,r,s):i[e.charCodeAt(0)||1]=s,c.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=c(e[1]),l=>t(e(l),r)):"["===e[0]?([,e,r]=e,e=c(e),r=c(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=c(e),r=c(r),t=>e(t)[r(t)])],".",18,[(e,r)=>e&&(r=a(18))&&[".",e,r],(e,r)=>(e=c(e),r=r[0]?r:r[1],t=>e(t)[r])],"(",18,[e=>!e&&["(",a(0,41)||l()],c],"(",18,[e=>e&&["(",e,a(0,41)||""],(e,r,t,l)=>null!=r&&(l=""==r?()=>[]:","===r[0]?(r=r.slice(1).map(c),e=>r.map((r=>r(e)))):(r=c(r),e=>[r(e)]),"."===e[0]?(t=e[2],e=c(e[1]),r=>e(r)[t](...l(r))):"["===e[0]?(t=c(e[2]),e=c(e[1]),r=>e(r)[t(r)](...l(r))):(e=c(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=c(e),r=c(r),t=c(t),l=>e(l)?r(l):t(l))],"??",6,(e,r)=>e??r,"?.",18,[e=>e&&["?.",e],e=>(e=c(e),r=>e(r)||(()=>{}))],"?.",18,[(e,r)=>e&&!(r=a(18))?.map&&["?.",e,r],(e,r)=>r&&(e=c(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(c),r=>e.map((e=>e(r)))):(e=c(e),r=>[e(r)]):()=>[])],"{",20,[e=>!e&&["{",a(0,125)||""],(e,r)=>e?","===e[0]?(e=e.slice(1).map(c),r=>Object.fromEntries(e.map((e=>e(r))))):":"===e[0]?(e=c(e),r=>Object.fromEntries([e(r)])):(r=c(e),t=>({[e]:r(t)})):e=>({})],":",1.1,[(e,r)=>[":",e,a(1.1)||l()],(e,r)=>(r=c(r),e=Array.isArray(e)?c(e):(e=>e).bind(0,e),t=>[e(t),r(t)])]];for(;y[2];)p.set(...y.splice(0,3));export{c 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.0",
3
+ "version": "7.0.1",
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",
@@ -68,6 +76,7 @@
68
76
  "homepage": "https://github.com/spectjs/subscript#readme",
69
77
  "devDependencies": {
70
78
  "rollup": "^2.60.2",
71
- "terser": "^5.10.0"
79
+ "terser": "^5.10.0",
80
+ "tst": "^7.1.0"
72
81
  }
73
82
  }
@@ -1,36 +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= !(cur=s, idx=0, args=[], s=expr()) || cur[idx] ? err() : ctx=>s(ctx||{})) => (fn.args = args, fn),
8
-
9
- isId = c =>
10
- (c >= 48 && c <= 57) || // 0..9
11
- (c >= 65 && c <= 90) || // A...Z
12
- (c >= 97 && c <= 122) || // a...z
13
- c == 36 || c == 95 || // $, _,
14
- (c >= 192 && c != 215 && c != 247), // any non-ASCII
7
+ parse = s => (idx=0, cur=s, s = expr(), !s || cur[idx] ? err() : s),
15
8
 
16
9
  err = (msg='Bad syntax',c=cur[idx]) => { throw SyntaxError(msg + ' `' + c + '` at ' + idx) },
17
10
 
18
11
  skip = (is=1, from=idx, l) => {
19
12
  if (typeof is == 'number') idx += is
20
- else while (is(cur.charCodeAt(idx))) idx++
13
+ else while (l=is(cur.charCodeAt(idx))) idx+=l
21
14
  return cur.slice(from, idx)
22
15
  },
23
16
 
24
17
  // a + b - c
25
18
  expr = (prec=0, end, cc, token, newNode, fn) => {
19
+
26
20
  // chunk/token parser
27
21
  while (
28
22
  ( cc=space() ) && // till not end
29
23
  // FIXME: extra work is happening here, when lookup bails out due to lower precedence -
30
24
  // it makes extra `space` call for parent exprs on the same character to check precedence again
31
- ( newNode =
25
+ (newNode =
32
26
  (fn=lookup[cc]) && fn(token, prec) || // if operator with higher precedence isn't found
33
- (!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`
34
28
  )
35
29
  ) token = newNode;
36
30
 
@@ -44,29 +38,28 @@ expr = (prec=0, end, cc, token, newNode, fn) => {
44
38
  // skip space chars, return first non-space character
45
39
  space = cc => { while ((cc = cur.charCodeAt(idx)) <= SPACE) idx++; return cc },
46
40
 
47
- // variable identifier
48
- 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
49
47
 
50
48
  // operator/token lookup table
51
- lookup = [],
49
+ // lookup[0] is id parser to let configs redefine it
50
+ lookup = [n=>skip(isId)],
52
51
 
53
52
  // create operator checker/mapper (see examples)
54
53
  set = parse.set = (
55
54
  op,
56
- opPrec, fn=SPACE, // if opPrec & fn come in reverse order - consider them raw parse fn case, still precedence possible
55
+ prec=SPACE,
56
+ map,
57
57
  c=op.charCodeAt(0),
58
58
  l=op.length,
59
59
  prev=lookup[c],
60
- arity=fn.length || ([fn,opPrec]=[opPrec,fn], 0),
61
- word=op.toUpperCase()!==op, // make sure word boundary comes after word operator
62
- map=
63
- // binary
64
- arity>1 ? (a,b) => a && (b=expr(opPrec)) && (
65
- !a.length && !b.length ? (a=fn(a(),b()), ()=>a) : // static pre-eval like `"a"+"b"`
66
- ctx => fn(a(ctx),b(ctx),a.id?.(ctx),b.id?.(ctx))
67
- ) :
68
- // unary prefix (0 args)
69
- arity ? a => !a && (a=expr(opPrec-1)) && (ctx => fn(a(ctx))) :
70
- fn // custom parser
71
- ) =>
72
- 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,162 +1,122 @@
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= !(cur=s, idx=0, args=[], s=expr()) || cur[idx] ? err() : ctx=>s(ctx||{})) => (fn.args = args, fn),
8
-
9
- isId = c =>
10
- (c >= 48 && c <= 57) || // 0..9
11
- (c >= 65 && c <= 90) || // A...Z
12
- (c >= 97 && c <= 122) || // a...z
13
- c == 36 || c == 95 || // $, _,
14
- (c >= 192 && c != 215 && c != 247), // any non-ASCII
15
-
16
- err = (msg='Bad syntax',c=cur[idx]) => { throw SyntaxError(msg + ' `' + c + '` at ' + idx) },
17
-
18
- skip = (is=1, from=idx, l) => {
19
- if (typeof is == 'number') idx += is;
20
- else while (is(cur.charCodeAt(idx))) idx++;
21
- return cur.slice(from, idx)
22
- },
23
-
24
- // a + b - c
25
- expr = (prec=0, end, cc, token, newNode, fn) => {
26
- // chunk/token parser
27
- while (
28
- ( cc=space() ) && // till not end
29
- // FIXME: extra work is happening here, when lookup bails out due to lower precedence -
30
- // it makes extra `space` call for parent exprs on the same character to check precedence again
31
- ( newNode =
32
- (fn=lookup[cc]) && fn(token, prec) || // if operator with higher precedence isn't found
33
- (!token && id()) // parse literal or quit. token seqs are forbidden: `a b`, `a "b"`, `1.32 a`
34
- )
35
- ) token = newNode;
36
-
37
- // check end character
38
- // FIXME: can't show "Unclose paren", because can be unknown operator within group as well
39
- if (end) cc==end?idx++:err();
40
-
41
- return token
42
- },
43
-
44
- // skip space chars, return first non-space character
45
- space = cc => { while ((cc = cur.charCodeAt(idx)) <= SPACE) idx++; return cc },
46
-
47
- // variable identifier
48
- id = (name=skip(isId), fn) => name ? (fn=ctx => ctx[name], args.push(name), fn.id=()=>name, fn) : 0,
49
-
50
- // operator/token lookup table
51
- lookup = [],
52
-
53
- // create operator checker/mapper (see examples)
54
- set = parse.set = (
55
- op,
56
- opPrec, fn=SPACE, // if opPrec & fn come in reverse order - consider them raw parse fn case, still precedence possible
57
- c=op.charCodeAt(0),
58
- l=op.length,
59
- prev=lookup[c],
60
- arity=fn.length || ([fn,opPrec]=[opPrec,fn], 0),
61
- word=op.toUpperCase()!==op, // make sure word boundary comes after word operator
62
- map=
63
- // binary
64
- arity>1 ? (a,b) => a && (b=expr(opPrec)) && (
65
- !a.length && !b.length ? (a=fn(a(),b()), ()=>a) : // static pre-eval like `"a"+"b"`
66
- ctx => fn(a(ctx),b(ctx),a.id?.(ctx),b.id?.(ctx))
67
- ) :
68
- // unary prefix (0 args)
69
- arity ? a => !a && (a=expr(opPrec-1)) && (ctx => fn(a(ctx))) :
70
- fn // custom parser
71
- ) =>
72
- 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));
73
-
74
- const PERIOD=46, CPAREN=41, CBRACK=93, DQUOTE=34, _0=48, _9=57,
75
- PREC_SEQ=1, PREC_SOME=4, PREC_EVERY=5, PREC_OR=6, PREC_XOR=7, PREC_AND=8,
76
- PREC_EQ=9, PREC_COMP=10, PREC_SHIFT=11, PREC_SUM=12, PREC_MULT=13, PREC_UNARY=15, PREC_CALL=18;
77
-
78
- let list, op, prec, fn,
79
- isNum = c => c>=_0 && c<=_9,
80
- // 1.2e+3, .5
81
- num = n => (
82
- n&&err('Unexpected number'),
83
- n = skip(c=>c == PERIOD || isNum(c)),
84
- (cur.charCodeAt(idx) == 69 || cur.charCodeAt(idx) == 101) && (n += skip(2) + skip(isNum)),
85
- n=+n, n!=n ? err('Bad number') : () => n // 0 args means token is static
86
- ),
87
-
88
- inc = (a,fn) => ctx => fn(a.of?a.of(ctx):ctx, a.id(ctx));
89
-
90
- // numbers
91
- for (op=_0;op<=_9;) lookup[op++] = num;
92
-
93
- // operators
94
- for (list=[
95
- // "a"
96
- '"', a => (a=a?err('Unexpected string'):skip(c => c-DQUOTE), skip()||err('Bad string'), ()=>a),,
97
-
98
- // a.b
99
- '.', (a,id) => (space(), id=skip(isId)||err(), fn=ctx=>a(ctx)[id], fn.id=()=>id, fn.of=a, fn), PREC_CALL,
100
-
101
- // .2
102
- // FIXME: .123 is not operator, so we skip back, but mb reorganizing num would be better
103
- '.', a => !a && num(skip(-1)),,
104
-
105
- // a[b]
106
- '[', (a,b,fn) => a && (b=expr(0,CBRACK)||err(), fn=ctx=>a(ctx)[b(ctx)], fn.id=b, fn.of=a, fn), PREC_CALL,
107
-
108
- // a(), a(b), (a,b), (a+b)
109
- '(', (a,b,fn) => (
110
- b=expr(0,CPAREN),
111
- // a(), a(b), a(b,c,d)
112
- a ? ctx => a(ctx).apply(a.of?.(ctx), b ? b.all ? b.all(ctx) : [b(ctx)] : []) :
113
- // (a+b)
114
- b || err()
115
- ), PREC_CALL,
116
-
117
- // [a,b,c] or (a,b,c)
118
- ',', (a,prec,b=expr(PREC_SEQ),fn=ctx => (a(ctx), b(ctx))) => (
119
- b ? (fn.all = a.all ? ctx => [...a.all(ctx),b(ctx)] : ctx => [a(ctx),b(ctx)]) : err('Skipped argument',),
120
- fn
121
- ), PREC_SEQ,
1
+ import parse, { lookup, skip, cur, idx, err, expr } from './parse.js'
2
+ import compile from './compile.js'
122
3
 
4
+ const OPAREN=40, CPAREN=41, OBRACK=91, CBRACK=93, SPACE=32, DQUOTE=34, PERIOD=46, _0=48, _9=57,
5
+ PREC_SEQ=1, PREC_SOME=4, PREC_EVERY=5, PREC_OR=6, PREC_XOR=7, PREC_AND=8,
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=s.trim(), s ? (s=parse(s.trim()), 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
+ ),
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,
123
64
  '|', PREC_OR, (a,b)=>a|b,
124
- '||', PREC_SOME, (a,b)=>a||b,
125
-
126
65
  '&', PREC_AND, (a,b)=>a&b,
127
- '&&', PREC_EVERY, (a,b)=>a&&b,
128
-
129
66
  '^', PREC_XOR, (a,b)=>a^b,
130
-
131
- // ==, !=
132
67
  '==', PREC_EQ, (a,b)=>a==b,
133
68
  '!=', PREC_EQ, (a,b)=>a!=b,
134
-
135
- // > >= >> >>>, < <= <<
136
69
  '>', PREC_COMP, (a,b)=>a>b,
137
70
  '>=', PREC_COMP, (a,b)=>a>=b,
138
- '>>', PREC_SHIFT, (a,b)=>a>>b,
139
- '>>>', PREC_SHIFT, (a,b)=>a>>>b,
140
71
  '<', PREC_COMP, (a,b)=>a<b,
141
72
  '<=', PREC_COMP, (a,b)=>a<=b,
73
+ '>>', PREC_SHIFT, (a,b)=>a>>b,
74
+ '>>>', PREC_SHIFT, (a,b)=>a>>>b,
142
75
  '<<', PREC_SHIFT, (a,b)=>a<<b,
143
76
 
144
- // + ++ - --
145
- '+', PREC_SUM, (a,b)=>a+b,
146
- '+', PREC_UNARY, (a)=>+a,
147
- '++', 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,
148
81
 
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,
82
+ // increments
83
+ ...inc('++', PREC_UNARY, (a,b) => ++a[b]),
84
+ ...inc('--', PREC_UNARY, (a,b) => --a[b]),
152
85
 
153
- // ! ~
154
- '!', 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
+ ],
155
91
 
156
- // * / %
157
- '*', PREC_MULT, (a,b)=>a*b,
158
- '/', PREC_MULT, (a,b)=>a/b,
159
- '%', PREC_MULT, (a,b)=>a%b
160
- ]; [op,prec,fn,...list]=list, op;) set(op,prec,fn);
161
-
162
- 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=(r=a,e=0,t=[],!(a=i())||r[e]?h():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)},s=(t=1,a=e,o)=>{if("number"==typeof t)e+=t;else for(;t(r.charCodeAt(e));)e++;return r.slice(a,e)},i=(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=s(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),s=t.toUpperCase()!==t,p=(h>1?(e,r)=>e&&(r=i(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=i(a-1))&&(r=>o(e(r))):o))=>c[d]=(o,d,h=e)=>d<a&&(l<2||r.substr(e,l)==t)&&(!s||!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=s((e=>46==e||C(e))),(69==r.charCodeAt(e)||101==r.charCodeAt(e))&&(t+=s(2)+s(C)),(t=+t)!=t?h("Bad number"):()=>t),x=(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"):s((e=>e-34)),s()||h("Bad string"),()=>e),,".",(e,r)=>(p(),r=s(f)||h(),l=t=>e(t)[r],l.id=()=>r,l.of=e,l),18,".",e=>!e&&A(s(-1)),,"[",(e,r,t)=>e&&(r=i(0,93)||h(),(t=t=>e(t)[r(t)]).id=r,t.of=e,t),18,"(",(e,r,t)=>(r=i(0,41),e?t=>e(t).apply(e.of?.(t),r?r.all?r.all(t):[r(t)]:[]):r||h()),18,",",(e,r,t=i(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=>x(e||i(14),e?(e,r)=>e[r]++:(e,r)=>++e[r]),15,"-",12,(e,r)=>e-r,"-",15,e=>-e,"--",e=>x(e||i(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 t,e,r=r=>(t=0,e=r,!(r=s())||e[t]?l():r),l=(r="Bad syntax",l=e[t])=>{throw SyntaxError(r+" `"+l+"` at "+t)},a=(r=1,l=t,a)=>{if("number"==typeof r)t+=r;else for(;a=r(e.charCodeAt(t));)t+=a;return e.slice(l,t)},s=(e=0,r,a,s,o,c)=>{for(;(a=n())&&(o=(c=h[a])&&c(s,e)||!s&&h[0]());)s=o;return r&&(a==r?t++:l()),s},n=r=>{for(;(r=e.charCodeAt(t))<=32;)t++;return r},o=t=>t>=48&&t<=57||t>=65&&t<=90||t>=97&&t<=122||36==t||95==t||t>=192&&215!=t&&247!=t,h=[t=>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,i=t)=>h<l&&(n<2||e.substr(t,n)==r)&&(!f||!o(e.charCodeAt(t+n)))&&(t+=n,a(s,h))||(t=i,c&&c(s,h));const c=t=>Array.isArray(t)?f[t[0]](...t.slice(1)):e=>e[t];c.set=(t,e,r=f[t])=>f[t]=(...t)=>e(...t)||r&&r(...t);const f={},i=t=>(t=t.trim())?(t=r(t.trim()),e=>(t.call?t:t=c(t))(e)):()=>{},p=i.set=(t,e,l,a=e<0,n=l[0],o=l[1],f=!n&&l.length)=>(n||=f?f>1?(r,l)=>r&&(l=s(e-a))&&[t,r,l]:r=>!r&&(r=s(e-1))&&[t,r]:(r,l)=>r&&(l=s(e))&&(r[0]===t&&r[2]?(r.push(l),r):[t,r,l]),o||=f?f>1?(t,e)=>e&&(t=c(t),e=c(e),t.length||e.length?r=>l(t(r),e(r)):(t=l(t(),e()),()=>t)):(t,e)=>!e&&((t=c(t)).length?e=>l(t(e)):(t=l(t()),()=>t)):(...t)=>(t=t.map(c),e=>l(...t.map((t=>t(e))))),(e=a?-e:e)?r.set(t,e,n):h[t.charCodeAt(0)||1]=n,c.set(t,o)),u=t=>t?l():["",(t=+a((t=>46===t||t>=48&&t<=57||(69===t||101===t?2:0))))!=t?l():t],g=(t,e,r,l)=>[t,e,[r=>r?["++"===t?"-":"+",[t,r],["",1]]:[t,s(e-1)],l=(t,e)=>"("===t[0]?l(t[1]):"."===t[0]?(e=t[2],t=c(t[1]),l=>r(t(l),e)):"["===t[0]?([,t,e]=t,t=c(t),e=c(e),l=>r(t(l),e(l))):e=>r(e,t)]],d=["",,[,t=>()=>t],'"',,[t=>t?l():["",(a()+a((t=>t-34?1:0))+(a()||l("Bad string"))).slice(1,-1)]],".",,[t=>!t&&u()],...Array(10).fill(0).flatMap(((t,e)=>[""+e,0,[u]])),",",1,(...t)=>t[t.length-1],"||",4,(...t)=>{let e,r=0;for(;!e&&r<t.length;)e=t[r++];return e},"&&",5,(...t)=>{let e=0,r=!0;for(;r&&e<t.length;)r=t[e++];return r},"+",12,(t,e)=>t+e,"-",12,(t,e)=>t-e,"*",13,(t,e)=>t*e,"/",13,(t,e)=>t/e,"%",13,(t,e)=>t%e,"|",6,(t,e)=>t|e,"&",8,(t,e)=>t&e,"^",7,(t,e)=>t^e,"==",9,(t,e)=>t==e,"!=",9,(t,e)=>t!=e,">",10,(t,e)=>t>e,">=",10,(t,e)=>t>=e,"<",10,(t,e)=>t<e,"<=",10,(t,e)=>t<=e,">>",11,(t,e)=>t>>e,">>>",11,(t,e)=>t>>>e,"<<",11,(t,e)=>t<<e,"+",15,t=>+t,"-",15,t=>-t,"!",15,t=>!t,...g("++",15,((t,e)=>++t[e])),...g("--",15,((t,e)=>--t[e])),"[",18,[t=>t&&["[",t,s(0,93)||l()],(t,e)=>e&&(t=c(t),e=c(e),r=>t(r)[e(r)])],".",18,[(t,e)=>t&&(e=s(18))&&[".",t,e],(t,e)=>(t=c(t),e=e[0]?e:e[1],r=>t(r)[e])],"(",18,[t=>!t&&["(",s(0,41)||l()],c],"(",18,[t=>t&&["(",t,s(0,41)||""],(t,e,r,l)=>null!=e&&(l=""==e?()=>[]:","===e[0]?(e=e.slice(1).map(c),t=>e.map((e=>e(t)))):(e=c(e),t=>[e(t)]),"."===t[0]?(r=t[2],t=c(t[1]),e=>t(e)[r](...l(e))):"["===t[0]?(r=c(t[2]),t=c(t[1]),e=>t(e)[r(e)](...l(e))):(t=c(t),e=>t(e)(...l(e))))]];for(;d[2];)p(...d.splice(0,3));export{c as compile,i 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