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 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 micro-language with common syntax subset of C++, JS, Java, Python, Go, Rust etc.<br/>
3
+ _Subscript_ is expression evaluator / microlanguage with standard syntax<br/>
4
4
 
5
- * Standard conventional syntax
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
- jsep (expression-eval): ~30 ms 🥉
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 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
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 u, list, op, prec, fn,
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[2] : b[1],
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, args) => !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, args) => !a && (
229
+ '{', (a, entries) => !a && (
66
230
  a=expr(0,125),
67
- !a ? ctx => ({}) : ctx => (args=(a.all||a)(ctx), Object.fromEntries(a.all?args:[args]))
231
+ !a ? ctx => ({}) : ctx => (entries=(a.all||a)(ctx), Object.fromEntries(a.all?entries:[entries]))
68
232
  ),,
69
- ':', (a, prec, b) => (b=expr(3.1)||err(), ctx => [(a.id||a)(ctx), b(ctx), a(ctx)]), 3.1
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
- export default parse
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,t,l,n,a,d=(t,...l)=>(r=t.raw?String.raw(t,...l):t,e=0,!(t=p())||r[e]?i("Unexpected end"):e=>t(e||{})),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=(t="Unexpected token",l=r[e])=>{throw SyntaxError(t+" `"+l+"` at "+e)},f=(t=1,l=e,n)=>{if("number"==typeof t)e+=t;else for(;t(r.charCodeAt(e));)e++;return r.slice(l,e)},p=(r=0,t,l,n,a,d)=>{for(;(l=c())&&(a=(d=u[l])&&d(n,r)||!n&&s());)n=a;return t&&(l==t?e++:i("Missing",String.fromCharCode(t))),n},c=t=>{for(;(t=r.charCodeAt(e))<=32;)e++;return t},s=(e=f(o),r)=>e?((r=r=>r[e]).id=()=>e,r):0,u=[],h=d.set=(t,l,n=32,a=t.charCodeAt(0),d=t.length,i=u[a],f=n.length||([n,l]=[l,n],0),c=t.toUpperCase()!==t,s=(f>1?(e,r)=>e&&(r=p(l))&&(e.length||r.length?t=>n(e(t),r(t)):(e=n(e(),r()),()=>e)):f?e=>!e&&(e=p(l-1))&&(r=>n(e(r))):n))=>u[a]=(n,a,f=e)=>a<l&&(d<2||r.substr(e,d)==t)&&(!c||!o(r.charCodeAt(e+d)))&&(e+=d,s(n,a))||(e=f,i&&i(n,a)),g=e=>e>=48&&e<=57,x=t=>(t&&i("Unexpected number"),t=f((e=>46==e||g(e))),(69==r.charCodeAt(e)||101==r.charCodeAt(e))&&(t+=f(2)+f(g)),(t=+t)!=t?i("Bad number"):()=>t),C=(e,r,t=e.of)=>l=>r(t?t(l):l,e.id());for(l=48;l<=57;)u[l++]=x;for(t=['"',e=>(e=e?i("Unexpected string"):f((e=>e-34)),f()||i("Bad string"),()=>e),,".",(e,r,t)=>e?(c(),r=f(o)||i(),(t=t=>e(t)[r]).id=()=>r,t.of=e,t):x(f(-1)),18,"[",(e,r,t)=>e&&(r=p(0,93)||i("Empty group"),(t=t=>e(t)[r(t)]).id=r,t.of=e,t),18,"(",(e,r,t)=>(r=p(0,41),e?t=>e(t).apply(e.of?.(t),r?r.all?r.all(t):[r(t)]:[]):r||i("Empty group")),18,",",(e,r,t=p(1))=>(t.all=e.all?(r,l,n=e.all(r))=>n.push(t(r))&&n:r=>[e(r),t(r)],t),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=>C(e||p(14),e?(e,r)=>e[r]++:(e,r)=>++e[r]),15,"-",12,(e,r)=>e-r,"-",15,e=>-e,"--",e=>C(e||p(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];[l,n,a,...t]=t,l;)h(l,n,a);let U,A,b,m,y={n:"\n",r:"\r",t:"\t",b:"\b",f:"\f",v:"\v"},E=t=>(l,n,a="")=>{for(l&&i("Unexpected string");(n=r.charCodeAt(e))-t;)92===n?(f(),n=f(),a+=y[n]||n):a+=f();return f()||i("Bad string"),()=>a};for((U=['"',E(34),,"'",E(39),,"/*",(t,l)=>(f((t=>42!==t&&47!==r.charCodeAt(e+1))),f(2),t||p(l)),,"//",(e,r)=>(f((e=>e>=32)),e||p(r)),,"null",e=>e?i("Unexpected literal"):()=>null,,"true",e=>e?i("Unexpected literal"):()=>!0,,"false",e=>e?i("Unexpected literal"):()=>!1,,"undefined",e=>e?i("Unexpected literal"):()=>{},,";",e=>p()||(()=>{}),,"===",9,(e,r)=>e===r,"!==",9,(e,r)=>e!==r,"~",15,e=>~e,"**",(e,r,t=p(13))=>r=>e(r)**t(r),14,":",3.1,(e,r)=>[e,r],"?",3,(e,r)=>e?r[2]:r[1],"?.",e=>e&&(r=>e(r)||(()=>{})),,"?.",(e,r)=>(c(),(r=f(o))&&(t=>e(t)?.[r])),,"in",10,(e,r)=>e in r,"[",(e,r)=>!e&&((e=p(0,93))?e.all?r=>e.all(r):r=>[e(r)]:e=>[]),,"{",(e,r)=>!e&&((e=p(0,125))?t=>(r=(e.all||e)(t),Object.fromEntries(e.all?r:[r])):e=>({})),,":",(e,r,t)=>(t=p(3.1)||i(),r=>[(e.id||e)(r),t(r),e(r)]),3.1]);[A,b,m,...U]=U,A;)h(A,b,m);export{d as default};
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.0.4",
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
- "index.js",
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": "rollup subscript.js --file subscript.min.js --format esm --name \"Subscript\"",
21
- "minify": "terser subscript.min.js -o subscript.min.js --module -c passes=3 -m",
22
- "build-justin": "rollup justin.js --file justin.min.js --format esm --name \"Justin\"",
23
- "minify-justin": "terser justin.min.js -o justin.min.js --module -c passes=3 -m",
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 & index
4
- export let idx, cur,
3
+ // current string, index and collected ids
4
+ export let idx, cur, args,
5
5
 
6
- parse = (s, ...fields) => !(cur=s.raw ? String.raw(s,...fields) : s, idx=0, s=expr()) || cur[idx] ? err('Unexpected end') : ctx=>s(ctx||{}),
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='Unexpected token',c=cur[idx]) => { throw SyntaxError(msg + ' `' + c + '` at ' + idx) },
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
- if (end) cc==end?idx++:err('Missing', String.fromCharCode(end))
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)
@@ -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
- import {parse, set, lookup, skip, cur, idx, err, expr, isId, 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,
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, PREC_POSTFIX=16, PREC_CALL=18, PREC_GROUP=19
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 u, list, op, prec, fn,
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,c=a.of) => ctx => fn(c?c(ctx):ctx, a.id())
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, .2, 1.2 parser in one
28
- '.', (a,id,fn) => !a ? num(skip(-1)) : // FIXME: .123 is not operator, so we skip back, but mb reorganizing num would be better
29
- (space(), id=skip(isId)||err(), fn=ctx=>a(ctx)[id], fn.id=()=>id, fn.of=a, fn), PREC_CALL,
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), b(ctx)] : ctx => [a(ctx),b(ctx)],
46
- b
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 parse
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,a,n,d=(t,...o)=>(r=t.raw?String.raw(t,...o):t,e=0,!(t=i())||r[e]?f("Unexpected end"):e=>t(e||{})),l=e=>e>=48&&e<=57||e>=65&&e<=90||e>=97&&e<=122||36==e||95==e||e>=192&&215!=e&&247!=e,f=(t="Unexpected token",o=r[e])=>{throw SyntaxError(t+" `"+o+"` at "+e)},h=(t=1,o=e,a)=>{if("number"==typeof t)e+=t;else for(;t(r.charCodeAt(e));)e++;return r.slice(o,e)},i=(r=0,t,o,a,n,d)=>{for(;(o=c())&&(n=(d=p[o])&&d(a,r)||!a&&s());)a=n;return t&&(o==t?e++:f("Missing",String.fromCharCode(t))),a},c=t=>{for(;(t=r.charCodeAt(e))<=32;)e++;return t},s=(e=h(l),r)=>e?((r=r=>r[e]).id=()=>e,r):0,p=[],g=d.set=(t,o,a=32,n=t.charCodeAt(0),d=t.length,f=p[n],h=a.length||([a,o]=[o,a],0),c=t.toUpperCase()!==t,s=(h>1?(e,r)=>e&&(r=i(o))&&(e.length||r.length?t=>a(e(t),r(t)):(e=a(e(),r()),()=>e)):h?e=>!e&&(e=i(o-1))&&(r=>a(e(r))):a))=>p[n]=(a,n,h=e)=>n<o&&(d<2||r.substr(e,d)==t)&&(!c||!l(r.charCodeAt(e+d)))&&(e+=d,s(a,n))||(e=h,f&&f(a,n)),C=e=>e>=48&&e<=57,u=t=>(t&&f("Unexpected number"),t=h((e=>46==e||C(e))),(69==r.charCodeAt(e)||101==r.charCodeAt(e))&&(t+=h(2)+h(C)),(t=+t)!=t?f("Bad number"):()=>t),x=(e,r,t=e.of)=>o=>r(t?t(o):o,e.id());for(o=48;o<=57;)p[o++]=u;for(t=['"',e=>(e=e?f("Unexpected string"):h((e=>e-34)),h()||f("Bad string"),()=>e),,".",(e,r,t)=>e?(c(),r=h(l)||f(),(t=t=>e(t)[r]).id=()=>r,t.of=e,t):u(h(-1)),18,"[",(e,r,t)=>e&&(r=i(0,93)||f(),(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||f()),18,",",(e,r,t=i(1))=>(t.all=e.all?r=>[...e.all(r),t(r)]:r=>[e(r),t(r)],t),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,a,n,...t]=t,o;)g(o,a,n);export{d as default};
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};