subscript 6.0.4 → 6.3.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 +18 -8
- package/justin.js +183 -18
- package/justin.min.js +1 -1
- package/package.json +12 -8
- package/{index.js → src/index.js} +14 -9
- package/src/justin.js +74 -0
- package/src/subscript.js +91 -0
- package/subscript.js +95 -17
- package/subscript.min.js +1 -1
package/README.md
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
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
|
|
3
|
+
_Subscript_ is expression evaluator / microlanguage with standard syntax<br/>
|
|
4
4
|
|
|
5
|
-
*
|
|
6
|
-
* Any fragment can be copy-pasted to any target language
|
|
5
|
+
* Any fragment can be copy-pasted to any language: C++, JS, Java, Python, Go, Rust etc.
|
|
7
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>
|
|
8
7
|
* :rocket: Fast [performance](#performance)
|
|
9
8
|
* Configurable & extensible
|
|
10
9
|
* Trivial to use
|
|
11
10
|
|
|
12
11
|
```js
|
|
13
|
-
import script from 'subscript.js'
|
|
12
|
+
import script from './subscript.js'
|
|
14
13
|
let fn = script`a.b + c(d - 1)`
|
|
15
14
|
fn({ a: { b:1 }, c: x => x * 2, d: 3 }) // 5
|
|
15
|
+
fn.args // ['a', 'c', 'd']
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
## Motivation
|
|
19
19
|
|
|
20
20
|
_Subscript_ is designed to be useful for:
|
|
21
21
|
|
|
22
|
-
* templates (perfect match with [template parts](https://github.com/github/template-parts))
|
|
22
|
+
* templates (perfect match with [template parts](https://github.com/github/template-parts), [templize](https://github.com/spectjs/templize))
|
|
23
23
|
* expressions evaluators, calculators
|
|
24
24
|
* configurable subsets of languages (eg. [justin](#justin)) <!-- see sonr, mineural -->
|
|
25
25
|
* pluggable/mock language features (eg. pipe operator)
|
|
@@ -47,6 +47,7 @@ Default operators are (same as JS precedence order):
|
|
|
47
47
|
* `a | b`
|
|
48
48
|
* `a && b`
|
|
49
49
|
* `a || b`
|
|
50
|
+
* `a , b`
|
|
50
51
|
|
|
51
52
|
Default literals:
|
|
52
53
|
|
|
@@ -56,7 +57,7 @@ Default literals:
|
|
|
56
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.
|
|
57
58
|
|
|
58
59
|
```js
|
|
59
|
-
import script from 'subscript.js'
|
|
60
|
+
import script from './subscript.js'
|
|
60
61
|
|
|
61
62
|
// add ~ unary operator with precedence 15
|
|
62
63
|
script.set('~', 15, a => ~a)
|
|
@@ -120,6 +121,7 @@ It extends _subscript_ with:
|
|
|
120
121
|
+ `'` strings
|
|
121
122
|
+ `?:` ternary operator
|
|
122
123
|
+ `?.` optional chain operator
|
|
124
|
+
+ `??` nullish coalesce operator
|
|
123
125
|
+ `[...]` Array literal
|
|
124
126
|
+ `{...}` Object literal
|
|
125
127
|
+ `in` binary
|
|
@@ -287,6 +289,7 @@ Parse 30k times:
|
|
|
287
289
|
subscript: ~170 ms 🥇
|
|
288
290
|
justin: ~183 ms 🥈
|
|
289
291
|
jsep: ~270 ms 🥉
|
|
292
|
+
jexpr: ~297 ms
|
|
290
293
|
mr-parser: ~420 ms
|
|
291
294
|
expr-eval: ~480 ms
|
|
292
295
|
math-parser: ~570 ms
|
|
@@ -301,7 +304,8 @@ Eval 30k times:
|
|
|
301
304
|
new Function: ~7 ms 🥇
|
|
302
305
|
subscript: ~17 ms 🥈
|
|
303
306
|
justin: ~17 ms 🥈
|
|
304
|
-
|
|
307
|
+
jexpr: ~23 ms 🥉
|
|
308
|
+
jsep (expression-eval): ~30 ms
|
|
305
309
|
math-expression-evaluator: ~50ms
|
|
306
310
|
expr-eval: ~72 ms
|
|
307
311
|
jexl: ~110 ms
|
|
@@ -312,16 +316,22 @@ math-parser: -
|
|
|
312
316
|
|
|
313
317
|
## Alternatives
|
|
314
318
|
|
|
319
|
+
* [jexpr](https://github.com/justinfagnani/jexpr)
|
|
320
|
+
* [jsep](https://github.com/EricSmekens/jsep)
|
|
315
321
|
* [jexl](https://github.com/TomFrost/Jexl)
|
|
316
322
|
* [mozjexl](https://github.com/mozilla/mozjexl)
|
|
317
323
|
* [expr-eval](https://github.com/silentmatt/expr-eval)
|
|
318
324
|
* [expression-eval](https://github.com/donmccurdy/expression-eval)
|
|
319
|
-
* [jsep](https://github.com/EricSmekens/jsep)
|
|
320
325
|
* [string-math](https://github.com/devrafalko/string-math)
|
|
321
326
|
* [nerdamer](https://github.com/jiggzson/nerdamer)
|
|
322
327
|
* [math-codegen](https://github.com/mauriciopoppe/math-codegen)
|
|
323
328
|
* [math-parser](https://www.npmjs.com/package/math-parser)
|
|
324
329
|
* [math.js](https://mathjs.org/docs/expressions/parsing.html)
|
|
330
|
+
|
|
331
|
+
## Next door
|
|
332
|
+
|
|
333
|
+
* [engine262](https://github.com/engine262/engine262)
|
|
325
334
|
* [Jessie](https://github.com/endojs/Jessie)
|
|
335
|
+
* [xst](https://github.com/Moddable-OpenSource/moddable-xst)
|
|
326
336
|
|
|
327
337
|
<p align=center>🕉</p>
|
package/justin.js
CHANGED
|
@@ -1,22 +1,184 @@
|
|
|
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
|
+
|
|
1
166
|
// justin lang https://github.com/endojs/Jessie/issues/66
|
|
2
|
-
import {parse, set, lookup, skip, cur, idx, err, expr, isId, space} from './index.js'
|
|
3
|
-
import './subscript.js'
|
|
4
167
|
|
|
5
|
-
const
|
|
6
|
-
|
|
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
|
|
168
|
+
const CBRACK=93, DQUOTE=34, QUOTE=39, BSLASH=92,
|
|
169
|
+
PREC_OR=6, PREC_EQ=9, PREC_COMP=10, PREC_EXP=14, PREC_UNARY=15;
|
|
8
170
|
|
|
9
171
|
|
|
10
|
-
let
|
|
172
|
+
let list, op, prec, fn,
|
|
11
173
|
escape = {n:'\n', r:'\r', t:'\t', b:'\b', f:'\f', v:'\v'},
|
|
12
174
|
string = q => (qc, c, str='') => {
|
|
13
|
-
qc&&err('Unexpected string') // must not follow another token
|
|
175
|
+
qc&&err('Unexpected string'); // must not follow another token
|
|
14
176
|
while (c=cur.charCodeAt(idx), c-q) {
|
|
15
|
-
if (c === BSLASH) skip(), c=skip(), str += escape[c] || c
|
|
16
|
-
else str += skip()
|
|
177
|
+
if (c === BSLASH) skip(), c=skip(), str += escape[c] || c;
|
|
178
|
+
else str += skip();
|
|
17
179
|
}
|
|
18
180
|
return skip()||err('Bad string'), () => str
|
|
19
|
-
}
|
|
181
|
+
};
|
|
20
182
|
|
|
21
183
|
// operators
|
|
22
184
|
for (list=[
|
|
@@ -46,7 +208,9 @@ for (list=[
|
|
|
46
208
|
|
|
47
209
|
// ?:
|
|
48
210
|
':', 3.1, (a,b) => [a,b],
|
|
49
|
-
'?', 3, (a,b) => a ? b[
|
|
211
|
+
'?', 3, (a,b) => a ? b[0] : b[1],
|
|
212
|
+
|
|
213
|
+
'??', PREC_OR, (a,b) => a??b,
|
|
50
214
|
|
|
51
215
|
// a?.[, a?.( - postfix operator
|
|
52
216
|
'?.', a => a && (ctx => a(ctx)||(()=>{})),,//(a) => a||(()=>{}),
|
|
@@ -56,18 +220,19 @@ for (list=[
|
|
|
56
220
|
'in', PREC_COMP, (a,b) => a in b,
|
|
57
221
|
|
|
58
222
|
// [a,b,c]
|
|
59
|
-
'[', (a
|
|
223
|
+
'[', (a) => !a && (
|
|
60
224
|
a=expr(0,CBRACK),
|
|
61
225
|
!a ? ctx => [] : a.all ? ctx => a.all(ctx) : ctx => [a(ctx)]
|
|
62
226
|
),,
|
|
63
227
|
|
|
64
228
|
// {a:1, b:2, c:3}
|
|
65
|
-
'{', (a,
|
|
229
|
+
'{', (a, entries) => !a && (
|
|
66
230
|
a=expr(0,125),
|
|
67
|
-
!a ? ctx => ({}) : ctx => (
|
|
231
|
+
!a ? ctx => ({}) : ctx => (entries=(a.all||a)(ctx), Object.fromEntries(a.all?entries:[entries]))
|
|
68
232
|
),,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
]; [op,prec,fn,...list]=list, op;) set(op,prec,fn)
|
|
233
|
+
// for JSON case we should not collect arg (different evaluator than ternary)
|
|
234
|
+
':', (a, prec, b) => (b=expr(1.1)||err(), a.id&&args.pop(), ctx => [(a.id||a)(ctx), b(ctx)]), 1.1
|
|
72
235
|
|
|
73
|
-
|
|
236
|
+
]; [op,prec,fn,...list]=list, op;) set(op,prec,fn);
|
|
237
|
+
|
|
238
|
+
export { parse as default };
|
package/justin.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
let e,r,
|
|
1
|
+
let e,t,r,l,a,n,d,o=(l,a)=>(e=0,r=[],t=l.trim(),!(l=t?c():e=>a)||t[e]?f():a=e=>l(e||{}),a.args=r,a),i=e=>e>=48&&e<=57||e>=65&&e<=90||e>=97&&e<=122||36==e||95==e||e>=192&&215!=e&&247!=e,f=(r="Bad syntax",l=t[e])=>{throw SyntaxError(r+" `"+l+"` at "+e)},p=(r=1,l=e,a)=>{if("number"==typeof r)e+=r;else for(;r(t.charCodeAt(e));)e++;return t.slice(l,e)},c=(t=0,r,l,a,n,d)=>{for(;(l=s())&&(n=(d=h[l])&&d(a,t)||!a&&u());)a=n;return r&&(l==r?e++:f()),a},s=r=>{for(;(r=t.charCodeAt(e))<=32;)e++;return r},u=(e=p(i),t)=>e?(t=t=>t[e],r.push(e),t.id=()=>e,t):0,h=[],g=o.set=(r,l,a=32,n=r.charCodeAt(0),d=r.length,o=h[n],f=a.length||([a,l]=[l,a],0),p=r.toUpperCase()!==r,s=(f>1?(e,t)=>e&&(t=c(l))&&(e.length||t.length?r=>a(e(r),t(r),e.id?.(r),t.id?.(r)):(e=a(e(),t()),()=>e)):f?e=>!e&&(e=c(l-1))&&(t=>a(e(t))):a))=>h[n]=(a,n,f=e)=>n<l&&(d<2||t.substr(e,d)==r)&&(!p||!i(t.charCodeAt(e+d)))&&(e+=d,s(a,n))||(e=f,o&&o(a,n)),x=e=>e>=48&&e<=57,C=r=>(r&&f("Unexpected number"),r=p((e=>46==e||x(e))),(69==t.charCodeAt(e)||101==t.charCodeAt(e))&&(r+=p(2)+p(x)),(r=+r)!=r?f("Bad number"):()=>r),A=(e,t)=>r=>t(e.of?e.of(r):r,e.id(r));for(a=48;a<=57;)h[a++]=C;for(l=['"',e=>(e=e?f("Unexpected string"):p((e=>e-34)),p()||f("Bad string"),()=>e),,".",(e,t)=>(s(),t=p(i)||f(),d=r=>e(r)[t],d.id=()=>t,d.of=e,d),18,".",e=>!e&&C(p(-1)),,"[",(e,t,r)=>e&&(t=c(0,93)||f(),(r=r=>e(r)[t(r)]).id=t,r.of=e,r),18,"(",(e,t,r)=>(t=c(0,41),e?r=>e(r).apply(e.of?.(r),t?t.all?t.all(r):[t(r)]:[]):t||f()),18,",",(e,t,r=c(1),l=(t=>(e(t),r(t))))=>(r?l.all=e.all?t=>[...e.all(t),r(t)]:t=>[e(t),r(t)]:f("Skipped argument"),l),1,"|",6,(e,t)=>e|t,"||",4,(e,t)=>e||t,"&",8,(e,t)=>e&t,"&&",5,(e,t)=>e&&t,"^",7,(e,t)=>e^t,"==",9,(e,t)=>e==t,"!=",9,(e,t)=>e!=t,">",10,(e,t)=>e>t,">=",10,(e,t)=>e>=t,">>",11,(e,t)=>e>>t,">>>",11,(e,t)=>e>>>t,"<",10,(e,t)=>e<t,"<=",10,(e,t)=>e<=t,"<<",11,(e,t)=>e<<t,"+",12,(e,t)=>e+t,"+",15,e=>+e,"++",e=>A(e||c(14),e?(e,t)=>e[t]++:(e,t)=>++e[t]),15,"-",12,(e,t)=>e-t,"-",15,e=>-e,"--",e=>A(e||c(14),e?(e,t)=>e[t]--:(e,t)=>--e[t]),15,"!",15,e=>!e,"*",13,(e,t)=>e*t,"/",13,(e,t)=>e/t,"%",13,(e,t)=>e%t];[a,n,d,...l]=l,a;)g(a,n,d);let U,b,m,y,B={n:"\n",r:"\r",t:"\t",b:"\b",f:"\f",v:"\v"},v=r=>(l,a,n="")=>{for(l&&f("Unexpected string");(a=t.charCodeAt(e))-r;)92===a?(p(),a=p(),n+=B[a]||a):n+=p();return p()||f("Bad string"),()=>n};for((U=['"',v(34),,"'",v(39),,"/*",(r,l)=>(p((r=>42!==r&&47!==t.charCodeAt(e+1))),p(2),r||c(l)),,"//",(e,t)=>(p((e=>e>=32)),e||c(t)),,"null",e=>e?f("Unexpected literal"):()=>null,,"true",e=>e?f("Unexpected literal"):()=>!0,,"false",e=>e?f("Unexpected literal"):()=>!1,,"undefined",e=>e?f("Unexpected literal"):()=>{},,";",e=>c()||(()=>{}),,"===",9,(e,t)=>e===t,"!==",9,(e,t)=>e!==t,"~",15,e=>~e,"**",(e,t,r=c(13))=>t=>e(t)**r(t),14,":",3.1,(e,t)=>[e,t],"?",3,(e,t)=>e?t[0]:t[1],"??",6,(e,t)=>e??t,"?.",e=>e&&(t=>e(t)||(()=>{})),,"?.",(e,t)=>(s(),(t=p(i))&&(r=>e(r)?.[t])),,"in",10,(e,t)=>e in t,"[",e=>!e&&((e=c(0,93))?e.all?t=>e.all(t):t=>[e(t)]:e=>[]),,"{",(e,t)=>!e&&((e=c(0,125))?r=>(t=(e.all||e)(r),Object.fromEntries(e.all?t:[t])):e=>({})),,":",(e,t,l)=>(l=c(1.1)||f(),e.id&&r.pop(),t=>[(e.id||e)(t),l(t)]),1.1]);[b,m,y,...U]=U,b;)g(b,m,y);export{o as default};
|
package/package.json
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "subscript",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.1",
|
|
4
4
|
"description": "Fast and tiny expression evaluator with common syntax microlanguage.",
|
|
5
|
-
"main": "subscript.js",
|
|
5
|
+
"main": "src/subscript.js",
|
|
6
|
+
"module": "src/subscript.js",
|
|
7
|
+
"browser": "subscript.js",
|
|
6
8
|
"type": "module",
|
|
7
9
|
"files": [
|
|
8
|
-
"
|
|
10
|
+
"src",
|
|
9
11
|
"subscript.js",
|
|
10
12
|
"subscript.min.js",
|
|
11
13
|
"justin.js",
|
|
@@ -17,10 +19,11 @@
|
|
|
17
19
|
"test": "test"
|
|
18
20
|
},
|
|
19
21
|
"scripts": {
|
|
20
|
-
"build": "
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
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",
|
|
24
27
|
"test": "node test"
|
|
25
28
|
},
|
|
26
29
|
"repository": {
|
|
@@ -65,6 +68,7 @@
|
|
|
65
68
|
"homepage": "https://github.com/spectjs/subscript#readme",
|
|
66
69
|
"devDependencies": {
|
|
67
70
|
"rollup": "^2.60.2",
|
|
68
|
-
"terser": "^5.10.0"
|
|
71
|
+
"terser": "^5.10.0",
|
|
72
|
+
"tst": "^7.1.0"
|
|
69
73
|
}
|
|
70
74
|
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
const SPACE=32
|
|
1
|
+
const SPACE=32, CPAREN=41
|
|
2
2
|
|
|
3
|
-
// current string
|
|
4
|
-
export let idx, cur,
|
|
3
|
+
// current string, index and collected ids
|
|
4
|
+
export let idx, cur, args,
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
// no handling tagged literals since easily done on user side with cache, if needed
|
|
7
|
+
parse = (s, fn) => (
|
|
8
|
+
idx=0, args=[], cur=s.trim(),
|
|
9
|
+
!(s = cur ? expr() : ctx=>{}) || cur[idx] ? err() :
|
|
10
|
+
fn = ctx=>s(ctx||{}), fn.args = args, fn
|
|
11
|
+
),
|
|
7
12
|
|
|
8
13
|
isId = c =>
|
|
9
14
|
(c >= 48 && c <= 57) || // 0..9
|
|
@@ -12,7 +17,7 @@ isId = c =>
|
|
|
12
17
|
c == 36 || c == 95 || // $, _,
|
|
13
18
|
(c >= 192 && c != 215 && c != 247), // any non-ASCII
|
|
14
19
|
|
|
15
|
-
err = (msg='
|
|
20
|
+
err = (msg='Bad syntax',c=cur[idx]) => { throw SyntaxError(msg + ' `' + c + '` at ' + idx) },
|
|
16
21
|
|
|
17
22
|
skip = (is=1, from=idx, l) => {
|
|
18
23
|
if (typeof is == 'number') idx += is
|
|
@@ -34,7 +39,8 @@ expr = (prec=0, end, cc, token, newNode, fn) => {
|
|
|
34
39
|
) token = newNode;
|
|
35
40
|
|
|
36
41
|
// check end character
|
|
37
|
-
|
|
42
|
+
// FIXME: can't show "Unclose paren", because can be unknown operator within group as well
|
|
43
|
+
if (end) cc==end?idx++:err()
|
|
38
44
|
|
|
39
45
|
return token
|
|
40
46
|
},
|
|
@@ -43,7 +49,7 @@ expr = (prec=0, end, cc, token, newNode, fn) => {
|
|
|
43
49
|
space = cc => { while ((cc = cur.charCodeAt(idx)) <= SPACE) idx++; return cc },
|
|
44
50
|
|
|
45
51
|
// variable identifier
|
|
46
|
-
id = (name=skip(isId), fn) => name ? (fn=ctx => ctx[name], fn.id=()=>name, fn) : 0,
|
|
52
|
+
id = (name=skip(isId), fn) => name ? (fn=ctx => ctx[name], args.push(name), fn.id=()=>name, fn) : 0,
|
|
47
53
|
|
|
48
54
|
// operator/token lookup table
|
|
49
55
|
lookup = [],
|
|
@@ -61,11 +67,10 @@ set = parse.set = (
|
|
|
61
67
|
// binary
|
|
62
68
|
arity>1 ? (a,b) => a && (b=expr(opPrec)) && (
|
|
63
69
|
!a.length && !b.length ? (a=fn(a(),b()), ()=>a) : // static pre-eval like `"a"+"b"`
|
|
64
|
-
ctx => fn(a(ctx),b(ctx))
|
|
70
|
+
ctx => fn(a(ctx),b(ctx),a.id?.(ctx),b.id?.(ctx))
|
|
65
71
|
) :
|
|
66
72
|
// unary prefix (0 args)
|
|
67
73
|
arity ? a => !a && (a=expr(opPrec-1)) && (ctx => fn(a(ctx))) :
|
|
68
74
|
fn // custom parser
|
|
69
75
|
) =>
|
|
70
|
-
// FIXME: find out if that's possible to globalize precision and instead of passing it to map, just provide global
|
|
71
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))
|
package/src/justin.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
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
|
package/subscript.js
CHANGED
|
@@ -1,32 +1,110 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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=93, DQUOTE=34, _0=48, _9=57,
|
|
4
79
|
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,
|
|
80
|
+
PREC_EQ=9, PREC_COMP=10, PREC_SHIFT=11, PREC_SUM=12, PREC_MULT=13, PREC_UNARY=15, PREC_CALL=18;
|
|
6
81
|
|
|
7
|
-
let
|
|
82
|
+
let list, op, prec, fn,
|
|
8
83
|
isNum = c => c>=_0 && c<=_9,
|
|
9
84
|
// 1.2e+3, .5
|
|
10
85
|
num = n => (
|
|
11
86
|
n&&err('Unexpected number'),
|
|
12
|
-
n = skip(c=>c==PERIOD || isNum(c)),
|
|
87
|
+
n = skip(c=>c == PERIOD || isNum(c)),
|
|
13
88
|
(cur.charCodeAt(idx) == 69 || cur.charCodeAt(idx) == 101) && (n += skip(2) + skip(isNum)),
|
|
14
89
|
n=+n, n!=n ? err('Bad number') : () => n // 0 args means token is static
|
|
15
90
|
),
|
|
16
91
|
|
|
17
|
-
inc = (a,fn
|
|
92
|
+
inc = (a,fn) => ctx => fn(a.of?a.of(ctx):ctx, a.id(ctx));
|
|
18
93
|
|
|
19
94
|
// numbers
|
|
20
|
-
for (op=_0;op<=_9;) lookup[op++] = num
|
|
95
|
+
for (op=_0;op<=_9;) lookup[op++] = num;
|
|
21
96
|
|
|
22
97
|
// operators
|
|
23
98
|
for (list=[
|
|
24
99
|
// "a"
|
|
25
100
|
'"', a => (a=a?err('Unexpected string'):skip(c => c-DQUOTE), skip()||err('Bad string'), ()=>a),,
|
|
26
101
|
|
|
27
|
-
// a.b
|
|
28
|
-
'.', (a,id
|
|
29
|
-
|
|
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)),,
|
|
30
108
|
|
|
31
109
|
// a[b]
|
|
32
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,
|
|
@@ -41,9 +119,9 @@ for (list=[
|
|
|
41
119
|
), PREC_CALL,
|
|
42
120
|
|
|
43
121
|
// [a,b,c] or (a,b,c)
|
|
44
|
-
',', (a,prec,b=expr(PREC_SEQ)) => (
|
|
45
|
-
b.all = a.all ? ctx => [...a.all(ctx),
|
|
46
|
-
|
|
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
|
|
47
125
|
), PREC_SEQ,
|
|
48
126
|
|
|
49
127
|
'|', PREC_OR, (a,b)=>a|b,
|
|
@@ -83,6 +161,6 @@ for (list=[
|
|
|
83
161
|
'*', PREC_MULT, (a,b)=>a*b,
|
|
84
162
|
'/', PREC_MULT, (a,b)=>a/b,
|
|
85
163
|
'%', PREC_MULT, (a,b)=>a%b
|
|
86
|
-
]; [op,prec,fn,...list]=list, op;) set(op,prec,fn)
|
|
87
|
-
|
|
88
|
-
export default
|
|
164
|
+
]; [op,prec,fn,...list]=list, op;) set(op,prec,fn);
|
|
165
|
+
|
|
166
|
+
export { parse as default };
|
package/subscript.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
let e,r,t,o,
|
|
1
|
+
let e,r,t,a,o,d,l,n=(a,o)=>(e=0,t=[],r=a.trim(),!(a=r?s():e=>o)||r[e]?h():o=e=>a(e||{}),o.args=t,o),f=e=>e>=48&&e<=57||e>=65&&e<=90||e>=97&&e<=122||36==e||95==e||e>=192&&215!=e&&247!=e,h=(t="Bad syntax",a=r[e])=>{throw SyntaxError(t+" `"+a+"` at "+e)},i=(t=1,a=e,o)=>{if("number"==typeof t)e+=t;else for(;t(r.charCodeAt(e));)e++;return r.slice(a,e)},s=(r=0,t,a,o,d,l)=>{for(;(a=p())&&(d=(l=c[a])&&l(o,r)||!o&&u());)o=d;return t&&(a==t?e++:h()),o},p=t=>{for(;(t=r.charCodeAt(e))<=32;)e++;return t},u=(e=i(f),r)=>e?(r=r=>r[e],t.push(e),r.id=()=>e,r):0,c=[],g=n.set=(t,a,o=32,d=t.charCodeAt(0),l=t.length,n=c[d],h=o.length||([o,a]=[a,o],0),i=t.toUpperCase()!==t,p=(h>1?(e,r)=>e&&(r=s(a))&&(e.length||r.length?t=>o(e(t),r(t),e.id?.(t),r.id?.(t)):(e=o(e(),r()),()=>e)):h?e=>!e&&(e=s(a-1))&&(r=>o(e(r))):o))=>c[d]=(o,d,h=e)=>d<a&&(l<2||r.substr(e,l)==t)&&(!i||!f(r.charCodeAt(e+l)))&&(e+=l,p(o,d))||(e=h,n&&n(o,d)),C=e=>e>=48&&e<=57,A=t=>(t&&h("Unexpected number"),t=i((e=>46==e||C(e))),(69==r.charCodeAt(e)||101==r.charCodeAt(e))&&(t+=i(2)+i(C)),(t=+t)!=t?h("Bad number"):()=>t),m=(e,r)=>t=>r(e.of?e.of(t):t,e.id(t));for(o=48;o<=57;)c[o++]=A;for(a=['"',e=>(e=e?h("Unexpected string"):i((e=>e-34)),i()||h("Bad string"),()=>e),,".",(e,r)=>(p(),r=i(f)||h(),l=t=>e(t)[r],l.id=()=>r,l.of=e,l),18,".",e=>!e&&A(i(-1)),,"[",(e,r,t)=>e&&(r=s(0,93)||h(),(t=t=>e(t)[r(t)]).id=r,t.of=e,t),18,"(",(e,r,t)=>(r=s(0,41),e?t=>e(t).apply(e.of?.(t),r?r.all?r.all(t):[r(t)]:[]):r||h()),18,",",(e,r,t=s(1),a=(r=>(e(r),t(r))))=>(t?a.all=e.all?r=>[...e.all(r),t(r)]:r=>[e(r),t(r)]:h("Skipped argument"),a),1,"|",6,(e,r)=>e|r,"||",4,(e,r)=>e||r,"&",8,(e,r)=>e&r,"&&",5,(e,r)=>e&&r,"^",7,(e,r)=>e^r,"==",9,(e,r)=>e==r,"!=",9,(e,r)=>e!=r,">",10,(e,r)=>e>r,">=",10,(e,r)=>e>=r,">>",11,(e,r)=>e>>r,">>>",11,(e,r)=>e>>>r,"<",10,(e,r)=>e<r,"<=",10,(e,r)=>e<=r,"<<",11,(e,r)=>e<<r,"+",12,(e,r)=>e+r,"+",15,e=>+e,"++",e=>m(e||s(14),e?(e,r)=>e[r]++:(e,r)=>++e[r]),15,"-",12,(e,r)=>e-r,"-",15,e=>-e,"--",e=>m(e||s(14),e?(e,r)=>e[r]--:(e,r)=>--e[r]),15,"!",15,e=>!e,"*",13,(e,r)=>e*r,"/",13,(e,r)=>e/r,"%",13,(e,r)=>e%r];[o,d,l,...a]=a,o;)g(o,d,l);export{n as default};
|