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 +51 -20
- package/compile.js +9 -0
- package/justin.js +77 -221
- package/justin.min.js +1 -1
- package/package.json +18 -10
- package/{src/index.js → parse.js} +23 -34
- package/subscript.js +105 -149
- package/subscript.min.js +1 -1
- package/src/justin.js +0 -74
- package/src/subscript.js +0 -91
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
|
|
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
|
-
|
|
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
|
-
|
|
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))
|
|
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 [
|
|
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 `
|
|
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 =>
|
|
70
|
-
script.set('false', a =>
|
|
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
|
-
|
|
290
|
-
|
|
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: ~
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
195
|
-
'true', a => a ? err(
|
|
196
|
-
'false', a => a ? err(
|
|
197
|
-
'undefined', a => a ? err(
|
|
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
|
-
|
|
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
|
-
'[',
|
|
224
|
-
a
|
|
225
|
-
|
|
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
|
-
'{',
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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,
|
|
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": "
|
|
3
|
+
"version": "7.0.2",
|
|
4
4
|
"description": "Fast and tiny expression evaluator with common syntax microlanguage.",
|
|
5
|
-
"main": "
|
|
6
|
-
"module": "
|
|
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
|
-
"
|
|
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
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
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
|
|
1
|
+
const SPACE=32
|
|
2
2
|
|
|
3
3
|
// current string, index and collected ids
|
|
4
|
-
export let idx, cur,
|
|
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,
|
|
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
|
-
(
|
|
25
|
+
(newNode =
|
|
36
26
|
(fn=lookup[cc]) && fn(token, prec) || // if operator with higher precedence isn't found
|
|
37
|
-
(!token &&
|
|
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
|
-
|
|
52
|
-
|
|
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
|
-
|
|
55
|
+
prec=SPACE,
|
|
56
|
+
map,
|
|
61
57
|
c=op.charCodeAt(0),
|
|
62
58
|
l=op.length,
|
|
63
59
|
prev=lookup[c],
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
map
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
1
|
+
import parse, { lookup, skip, cur, idx, err, expr } from './parse.js'
|
|
2
|
+
import compile from './compile.js'
|
|
2
3
|
|
|
3
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
),
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
'+',
|
|
150
|
-
'
|
|
151
|
-
'
|
|
77
|
+
// unaries
|
|
78
|
+
'+', PREC_UNARY, a => +a,
|
|
79
|
+
'-', PREC_UNARY, a => -a,
|
|
80
|
+
'!', PREC_UNARY, a => !a,
|
|
152
81
|
|
|
153
|
-
|
|
154
|
-
'
|
|
155
|
-
'--',
|
|
82
|
+
// increments
|
|
83
|
+
...inc('++', PREC_UNARY, (a,b) => ++a[b]),
|
|
84
|
+
...inc('--', PREC_UNARY, (a,b) => --a[b]),
|
|
156
85
|
|
|
157
|
-
//
|
|
158
|
-
'
|
|
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
|
-
'
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
]
|
|
165
|
-
|
|
166
|
-
|
|
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,
|
|
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
|