subscript 3.0.2 → 5.1.0

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,11 +1,11 @@
1
- # <!--<img alt="subscript" src="/subscript2.svg" height=42/>--> sub͘<em>script</em> <!--<sub>SUB͘<em>SCRIPT</em></sub>-->
1
+ # <img alt="subscript" src="/subscript2.svg" height=42/> <!--sub͘<em>script</em>--> <!--<sub>SUB͘<em>SCRIPT</em></sub>-->
2
2
 
3
- _Subscript_ is micro-language with common syntax subset of C++, JS, Java, Python, Go, Rust.<br/>
3
+ _Subscript_ is micro-language with common syntax subset of C++, JS, Java, Python, Go, Rust, Swift, Objective C, Kotlin etc.<br/>
4
4
 
5
- * It has well-known syntax
5
+ * Well-known syntax
6
6
  * Any _subscript_ fragment can be copy-pasted to any target language
7
7
  * It's tiny <sub>![npm bundle size](https://img.shields.io/bundlephobia/minzip/subscript/latest?color=brightgreen&label=gzip)</sub>
8
- * It's very fast ([see performance](#performance))
8
+ * It's :rocket: fast ([see performance](#performance))
9
9
  * Configurable & extensible
10
10
  * Trivial to use...
11
11
 
@@ -26,17 +26,28 @@ _Subscript_ is designed to be useful for:
26
26
  * sandboxes, playgrounds, safe eval
27
27
  * custom DSL
28
28
 
29
- [_Jsep_](https://github.com/EricSmekens/jsep) is generally fine for the listed tasks, unless you design a tiny module and prefer to keep dependencies as small as possible.
30
- _Subscript_ has [2.5kb](https://npmfs.com/package/subscript/3.0.0/subscript.min.js) footprint vs [11.4kb](https://npmfs.com/package/jsep/1.2.0/dist/jsep.min.js) _jsep_, with same or better performance. It also has more open API and generates lispy calltree (compatible with [frisk](https://npmjs.com/frisk)), compared to esprima AST: minimal possible overhead, clear precedence, overloading by context, manual evaluation, debugging, conventional form, one-liner docs:
29
+ [_Jsep_](https://github.com/EricSmekens/jsep) is generally fine for the listed tasks, unless you need dependencies as small as possible.
30
+ _Subscript_ has [2.5kb](https://npmfs.com/package/subscript/5.0.0/subscript.min.js) footprint vs [11.4kb](https://npmfs.com/package/jsep/1.2.0/dist/jsep.min.js) _jsep_, with better performance.
31
+
32
+
33
+ ## Evaluation
34
+
35
+ _Subscript_ parser generates lispy calltree (compatible with [frisk](https://npmjs.com/frisk)), which is compared to esprima AST has:
36
+
37
+ + minimal possible overhead
38
+ + clear precedence
39
+ + overloading by context
40
+ + manual evaluation and debugging
41
+ + conventional form
42
+ + one-liner docs:
31
43
 
32
44
  ```js
33
45
  import {evaluate} from 'subscript.js'
34
46
 
35
47
  evaluate(['+', ['*', 'min', 60], '"sec"'], { min: 5 }) // min*60 + "sec" == "300sec"
36
- ```
37
-
48
+ ```
38
49
 
39
- ## Operators
50
+ ## Extending
40
51
 
41
52
  Default operators include common operators for the listed languages in the following precedence:
42
53
 
@@ -53,17 +64,15 @@ Default operators include common operators for the listed languages in the follo
53
64
  * `&&`
54
65
  * `||`
55
66
 
56
- All other operators can be extended via `parse.binary`, `parse.unary` and `evaluate.operator`.
67
+ Operators can be extended via `parse.operator(str, prec, type)` and `evaluate.operator(str, fn)` functions for any unary/binary/postfix operators, calls, props, groups, arrays, objects etc.
57
68
 
58
69
  ```js
59
70
  import { parse, evaluate } from 'subscript.js'
60
71
 
61
- // add precedences
62
- parse.binary['=>'] = 10
72
+ parse.operator('=>', 10) // precedence=10, type=default (0 - binary, 1 - postfix, -1 - prefix)
63
73
 
64
- // define evaluators
65
- evaluate.operator['=>'] = ( args, body ) => evaluate(body, args)
66
- evaluate.operator['|'] = ( a, ...b ) => a.pipe(...b)
74
+ evaluate.operator('=>', ( args, body ) => evaluate(body, args))
75
+ evaluate.operator('|', ( a, ...b ) => a.pipe(...b))
67
76
 
68
77
  let tree = parse(`
69
78
  interval(350)
@@ -74,28 +83,35 @@ let tree = parse(`
74
83
  evaluate(tree, { Math, map, take, interval, gaussian })
75
84
  ```
76
85
 
77
- ## Extending
86
+ ---
78
87
 
79
- By default subscript detects the following tokens:
88
+ Tokens are extensible via `parse.token` list, can be added support of _literals_, _regexes_, _strings_, _numbers_ and others.
89
+ Default tokens include:
80
90
 
81
- * `"` strings
91
+ * `"abc"` strings
82
92
  * `1.2e+3` floats
83
- * `true`, `false`, `null` literals
84
- * `()` expression groups or fn calls
85
- * `.`, `[]` property access
93
+ * identifiers
94
+
95
+ ```js
96
+ import parse, {char} from 'subscript/parse.js'
97
+ import evaluate from 'subscript/evaluate.js'
86
98
 
87
- Literals can be extended via `parse.literal` dict.
99
+ conts ctx = {x:1}
100
+ parse.token.unshift(c => char(4) === 'this' ? ctx : null)
101
+ evaluate(parse(`this.x`)) // 1
102
+ ```
88
103
 
89
- Token parsers are extensible via `parse.token` list, can be added support of _regex_, _array_, _object_, _interpolated string_ and others.
104
+ ---
90
105
 
91
- Postfix parsers are applied to parsed tokens and can be used to provide _property chains_, _function calls_, _postfix operators_, _token mapping_, _ternary operators_ and so on. They're extensible via `parse.postfix`.
106
+ Comments can be added via extending `parse.space`. See [justin.js](./justin.js) for more examples.
92
107
 
93
108
 
94
109
  ## Justin
95
110
 
96
111
  _Justin_ extension (original [thread](https://github.com/endojs/Jessie/issues/66)) is minimal JS subset − JSON with JS expressions.<br/>
97
- It adds support for:
112
+ It adds support of:
98
113
 
114
+ + `===`, `!==` operators
99
115
  + `**` binary operator
100
116
  + `~` unary operator
101
117
  + `'` strings
@@ -104,8 +120,9 @@ It adds support for:
104
120
  + `{...}` Object literal
105
121
  + `in` binary operator
106
122
  + `;` expression separator
107
- <!-- + `//, /* */` comments -->
108
- <!-- + `undefined` literal -->
123
+ + unary word operators
124
+ + `//`, `/* */` comments
125
+ + `true`, `false`, `null`, `undefined` literals
109
126
  <!-- + `?` chaining operator -->
110
127
  <!-- + `...x` unary operator -->
111
128
  <!-- + strings interpolation -->
@@ -262,8 +279,8 @@ Subscript shows relatively good performance within other evaluators:
262
279
  // 1 + (a * b / c % d) - 2.0 + -3e-3 * +4.4e4 / f.g[0] - i.j(+k == 1)(0)
263
280
  // parse 30k times
264
281
 
265
- subscript: ~280 ms
266
- jsep: ~290 ms
282
+ subscript: ~220 ms
283
+ jsep: ~280 ms
267
284
  expr-eval: ~480 ms
268
285
  jexl: ~1200 ms
269
286
  new Function: ~1400 ms
@@ -278,6 +295,6 @@ new Function: ~1400 ms
278
295
  * [expression-eval](https://github.com/donmccurdy/expression-eval)
279
296
  * [jsep](https://github.com/EricSmekens/jsep)
280
297
  * [string-math](https://github.com/devrafalko/string-math)
281
-
298
+ * [nerdamer](https://github.com/jiggzson/nerdamer)
282
299
 
283
300
  <p align=center>🕉</p>
package/evaluate.js ADDED
@@ -0,0 +1,59 @@
1
+ export const isCmd = a => Array.isArray(a) && (typeof a[0] === 'string' || isCmd(a[0])),
2
+
3
+ // calltree → result
4
+ evaluate = (s, ctx={}, c, op) => {
5
+ if (isCmd(s)) {
6
+ c = s[0]
7
+ if (typeof c === 'string') op = lookup[c]
8
+ c = op || evaluate(c, ctx) // [[a,b], c]
9
+ if (typeof c !== 'function') return c
10
+ return c.call(...s.map(a => evaluate(a,ctx)))
11
+ }
12
+ if (s && typeof s === 'string')
13
+ return s[0] === '"' ? s.slice(1,-1)
14
+ : s[0]==='@' ? s.slice(1)
15
+ : s in ctx ? ctx[s] : s
16
+
17
+ return s
18
+ },
19
+ lookup = {},
20
+
21
+ // op evaluators
22
+ // multiple args allows shortcuts, lisp compatible, easy manual eval, functions anyways take multiple arguments
23
+ operator = evaluate.operator = (op, fn) => lookup[op] = fn.length == 2 ? (...a)=>a.reduce(fn) : fn
24
+
25
+ for (let fn,ops = [
26
+ '!', a=>!a,
27
+ '++', a=>++a,
28
+ '--', a=>--a,
29
+
30
+ '.', (a,b)=>a?a[b]:a,
31
+
32
+ '%', (a,b)=>a%b,
33
+ '/', (a,b)=>a/b,
34
+ '*', (a,b)=>a*b,
35
+
36
+ '+', (a,b)=>a+b,
37
+ '-', (...a)=>a.length < 2 ? -a : a.reduce((a,b)=>a-b),
38
+
39
+ '>>>', (a,b)=>a>>>b,
40
+ '>>', (a,b)=>a>>b,
41
+ '<<', (a,b)=>a<<b,
42
+
43
+ '>=', (a,b)=>a>=b,
44
+ '>', (a,b)=>a>b,
45
+ '<=', (a,b)=>a<=b,
46
+ '<', (a,b)=>a<b,
47
+
48
+ '!=', (a,b)=>a!=b,
49
+ '==', (a,b)=>a==b,
50
+
51
+ '&', (a,b)=>a&b,
52
+ '^', (a,b)=>a^b,
53
+ '|', (a,b)=>a|b,
54
+ '&&', (...a)=>a.every(Boolean),
55
+ '||', (...a)=>a.some(Boolean),
56
+ ',', (a,b)=>(a,b)
57
+ ]; fn=ops.pop();) operator(ops.pop(),fn)
58
+
59
+ export default evaluate
package/justin.js CHANGED
@@ -1,73 +1,100 @@
1
1
  // justin lang https://github.com/endojs/Jessie/issues/66
2
- import {evaluate, operator} from './src/evaluate.js'
3
- import {parse, binary, unary, postfix, token, literal,
4
- code, char, skip, space, expr} from './src/parse.js'
2
+ import {evaluate} from './evaluate.js'
3
+ import {parse, code, char, skip, expr, err} from './parse.js'
5
4
 
6
- // undefined
7
- literal['undefined'] = undefined
8
-
9
- // '
10
- token.push((q, qc) => q === 39 ? (qc = char(), index++, qc) + skip(c => c !== q) + (index++, qc) : null)
11
-
12
- // **
13
- binary['**'] = 16
14
- operator['**'] = (...args)=>args.reduceRight((a,b)=>Math.pow(b,a))
15
-
16
- // ~
17
- unary['~'] = 17
18
- operator['~'] = a=>~a
19
-
20
- // ...
21
- // unary[1]['...']=true
22
-
23
- // ;
24
- binary[';'] = 1
5
+ // literals
6
+ const v = v => ({valueOf:()=>v})
7
+ parse.token.splice(2,0, c =>
8
+ c === 116 && char(4) === 'true' && skip(4) ? v(true) :
9
+ c === 102 && char(5) === 'false' && skip(5) ? v(false) :
10
+ c === 110 && char(4) === 'null' && skip(4) ? v(null) :
11
+ c === 117 && char(9) === 'undefined' && skip(9) ? v(undefined) :
12
+ null
13
+ )
25
14
 
26
- // ?:
27
- operator['?:']=(a,b,c)=>a?b:c
28
- postfix.push(node => {
29
- let a, b
30
- if (code() !== 63) return node
31
- skip(), space(), a = expr(58)
32
- if (code() !== 58) return node
33
- skip(), space(), b = expr()
34
- return ['?:',node, a, b]
35
- })
15
+ // "' with /
16
+ parse.token[1] = (q, qc, c, str) => {
17
+ if (q !== 34 && q !== 39) return
18
+ qc = char(), skip(), str = ''
19
+ while (c=code(), c-q) {
20
+ if (c === 92) skip(), str += escape[char()] || char(); else str+=char()
21
+ skip()
22
+ }
23
+ return skip(), qc + str + qc
24
+ }
25
+ const escape = {n:'\n', r:'\r', t:'\t', b:'\b', f:'\f', v:'\v'}
36
26
 
37
27
  // /**/, //
38
- // comments['/*']='*/'
39
- // comments['//']='\n'
40
-
41
- // in
42
- evaluate.operator['in'] = (a,b)=>a in b
43
- parse.postfix.unshift(node => (char(2) === 'in' ? (skip(2), ['in', '"' + node + '"', expr()]) : node))
44
-
45
- // []
46
- operator['['] = (...args) => Array(...args)
47
- token.push((node, arg) =>
48
- code() === 91 ?
49
- (
50
- skip(), arg=expr(93),
51
- node = arg==null ? ['['] : arg[0] === ',' ? (arg[0]='[',arg) : ['[',arg],
52
- skip(), node
53
- ) : null
54
- )
28
+ parse.space = cc => {
29
+ while (cc = code(), cc <= 32) {
30
+ skip()
31
+ if (code() === 47)
32
+ // /**/
33
+ if (code(1) === 42) skip(2), skip(c => c !== 42 && code(1) !== 47), skip(2)
34
+ // //
35
+ else if (code(1) === 47) skip(2), skip(c => c >= 32)
36
+ }
37
+ return cc
38
+ }
55
39
 
56
40
  // {}
57
- binary[':'] = 2
58
- token.unshift((node) => code() === 123 ? (skip(), node = map(['{',expr(125)]), skip(), node) : null)
59
- operator['{'] = (...args)=>Object.fromEntries(args)
60
- operator[':'] = (a,b)=>[a,b]
61
-
41
+ parse.token.unshift((cc, node) => (
42
+ cc === 123 && (skip(), node = map(['{', expr(0,125)]), skip(), node)
43
+ ))
62
44
  const map = (n, args) => {
63
- if (n[1]==null) args = []
45
+ if (!n[1]) args = []
64
46
  else if (n[1][0]==':') args = [n[1]]
65
47
  else if (n[1][0]==',') args = n[1].slice(1)
66
48
  return ['{', ...args]
67
49
  }
68
50
 
51
+ // parse operators
52
+ for (let i = 0, ops = [
53
+ ';', 1,,
54
+ '===', 9,,
55
+ '!==', 9,,
56
+ '**', 14,,
57
+ '~', 13, -1,
58
+ '?', 3, (node) => {
59
+ if (!node) err('Expected expression')
60
+ let a, b
61
+ skip(), parse.space(), a = expr()
62
+ if (code() !== 58) err('Expected :')
63
+ skip(), parse.space(), b = expr()
64
+ return ['?:', node, a, b]
65
+ },
66
+ '}',,,
67
+ ':', 0,,
68
+ 'in', 10, (node) => code(2) <= 32 && [skip(2), '"'+node+'"', expr(10)],
69
+ '[', 20, (node,arg) => !node && (
70
+ skip(), arg=expr(0,93), skip(),
71
+ !arg ? ['['] : arg[0] === ',' ? (arg[0]='[',arg) : ['[',arg]
72
+ )
73
+ ]; i < ops.length;) parse.operator(ops[i++],ops[i++],ops[i++])
74
+
75
+ // evaluate operators
76
+ for (let i = 0, ops = [
77
+ // **
78
+ '**', (...args)=>args.reduceRight((a,b)=>Math.pow(b,a)),
79
+
80
+ // ~
81
+ '~', a=>~a,
82
+
83
+ // ?:
84
+ '?:', (a,b,c)=>a?b:c,
85
+ // parse.operator(':')
86
+ // in
87
+ 'in', (a,b)=>a in b,
88
+
89
+ // []
90
+ '[', (...args) => Array(...args),
91
+ // as operator it's faster to lookup (no need to call extra rule check), smaller and no conflict with word names
92
+ '{', (...args)=>Object.fromEntries(args),
93
+ ':', (a,b)=>[a,b]
94
+ ]; i < ops.length;) evaluate.operator(ops[i++],ops[i++])
69
95
 
96
+ // TODO ...
70
97
  // TODO: strings interpolation
71
98
 
72
- export { default } from './subscript.js';
99
+ export default parse
73
100
  export { parse, evaluate }
package/justin.min.js CHANGED
@@ -1 +1 @@
1
- var e,r,n,t=e=>Array.isArray(e)&&("string"==typeof e[0]||t(e[0])),l=(e,r={},n,i)=>t(e)?("string"==typeof(n=e[0])&&(i=u[n]),"function"!=typeof(n=i||l(n,r))?n:n.call(...e.map((e=>l(e,r))))):e&&"string"==typeof e?'"'===e[0]?e.slice(1,-1):"@"===e[0]?e.slice(1):e in r?r[e]:e:e,u=l.operator={"!":e=>!e,"++":e=>++e,"--":e=>--e,".":(...e)=>e.reduce(((e,r)=>e&&e[r])),"%":(...e)=>e.reduce(((e,r)=>e%r)),"/":(...e)=>e.reduce(((e,r)=>e/r)),"*":(...e)=>e.reduce(((e,r)=>e*r)),"+":(...e)=>e.reduce(((e,r)=>e+r)),"-":(...e)=>e.length<2?-e:e.reduce(((e,r)=>e-r)),">>>":(e,r)=>e>>>r,">>":(e,r)=>e>>r,"<<":(e,r)=>e<<r,">=":(e,r)=>e>=r,">":(e,r)=>e>r,"<=":(e,r)=>e<=r,"<":(e,r)=>e<r,"!=":(e,r)=>e!=r,"==":(e,r)=>e==r,"&":(e,r)=>e&r,"^":(e,r)=>e^r,"|":(e,r)=>e|r,"&&":(...e)=>e.every(Boolean),"||":(...e)=>e.some(Boolean),",":(...e)=>e.reduce(((e,r)=>r))},i=l,o=t=>(r=t,e=n=0,f()),a=()=>{for(;d()<=32;)e++},s=(r,t,l,u=3)=>{if(e&&n&&n[3]===e)return n;for(;u;)if(null!=(l=r[t=c(u--)]))return n=[t,l,t.length,e]},f=(r,n=-1)=>{a();let t,l,u,i=d(),o=0,p=e;if(i!==r){for(;p===e&&o<g.length;)l=g[o++](i);if(p===e)(t=s(v))&&(e+=t[2],l=[t[0],f(r,t[1])]);else{if(a(),i=d(),i===r)return l;for(o=0;o<x.length;)(u=x[o](l,i))!==l?(l=u,o=0,a(),i=d()):o++}for(a();(i=d())&&i!==r&&(t=s(b))&&t[1]>n;){l=[t[0],l];do{e+=t[2],l.push(f(r,t[1]))}while(c(t[2])===t[0]);a()}return l}},p=e=>(e=y((e=>e>=48&&e<=57||e>=65&&e<=90||e>=97&&e<=122||36==e||95==e||e>=192)))&&A.hasOwnProperty(e)?A[e]:e,d=()=>r.charCodeAt(e),c=(n=1)=>r.substr(e,n),h=r=>{throw Error(r+" at "+e)},y=(n=1,t=e)=>{if("number"==typeof n)e+=n;else for(;n(d());)++e>r.length&&h("Unexpected end "+n);return e>t?r.slice(t,e):null},g=o.token=[(r,n)=>40===r?(e++,n=f(41),e++,n):null,(r,n,t)=>{if(r=y(t=e=>e>=48&&e<=57)||"",46===d()&&(e++,r+="."+y(t)),r)return(69===(n=d())||101===n)&&(e++,r+="e",(43===(n=d())||45===n)&&(r+=c(),e++),r+=y(t)),parseFloat(r)},(r,n)=>34===r?(n=c(),e++,n+y((e=>e!==r))+(e++,n)):null,p],A=o.literal={true:!0,false:!1,null:null},x=o.postfix=[(r,n,t)=>(46===n?(e++,a(),r=[".",r,'"'+p()+'"']):91===n?(e++,r=[".",r,f(93)],e++):40===n&&(e++,t=f(41),r=Array.isArray(t)&&","===t[0]?(t[0]=r,t):null==t?[r]:[r,t],e++),r),(n,t)=>43!==t&&45!==t||r.charCodeAt(e+1)!==t?n:[y(2),n]],v=o.unary={"-":17,"!":17,"+":17,"++":17,"--":17},b=o.binary={",":1,"||":6,"&&":7,"|":8,"^":9,"&":10,"==":11,"!=":11,"<":12,">":12,"<=":12,">=":12,"<<":13,">>":13,">>>":13,"+":14,"-":14,"*":15,"/":15,"%":15},m=o,w=e=>(e="string"==typeof e?m(e):e,r=>i(e,r));A.undefined=void 0,g.push(((e,r)=>39===e?(r=c(),index++,r+y((r=>r!==e))+(index++,r)):null)),b["**"]=16,u["**"]=(...e)=>e.reduceRight(((e,r)=>Math.pow(r,e))),v["~"]=17,u["~"]=e=>~e,b[";"]=1,u["?:"]=(e,r,n)=>e?r:n,x.push((e=>{let r,n;return 63!==d()||(y(),a(),r=f(58),58!==d())?e:(y(),a(),n=f(),["?:",e,r,n])})),l.operator.in=(e,r)=>e in r,o.postfix.unshift((e=>"in"===c(2)?(y(2),["in",'"'+e+'"',f()]):e)),u["["]=(...e)=>Array(...e),g.push(((e,r)=>91===d()?(y(),e=null==(r=f(93))?["["]:","===r[0]?(r[0]="[",r):["[",r],y(),e):null)),b[":"]=2,g.unshift((e=>123===d()?(y(),e=B(["{",f(125)]),y(),e):null)),u["{"]=(...e)=>Object.fromEntries(e),u[":"]=(e,r)=>[e,r];var B=(e,r)=>(null==e[1]?r=[]:":"==e[1][0]?r=[e[1]]:","==e[1][0]&&(r=e[1].slice(1)),["{",...r]);export{w as default,l as evaluate,o as parse};
1
+ var e=r=>Array.isArray(r)&&("string"==typeof r[0]||e(r[0])),r=(a,n={},o,l)=>e(a)?("string"==typeof(o=a[0])&&(l=t[o]),"function"!=typeof(o=l||r(o,n))?o:o.call(...a.map((e=>r(e,n))))):a&&"string"==typeof a?'"'===a[0]?a.slice(1,-1):"@"===a[0]?a.slice(1):a in n?n[a]:a:a,t={},a=r.operator=(e,r)=>t[e]=2==r.length?(...e)=>e.reduce(r):r;for(let e,r=["!",e=>!e,"++",e=>++e,"--",e=>--e,".",(e,r)=>e&&e[r],"%",(e,r)=>e%r,"/",(e,r)=>e/r,"*",(e,r)=>e*r,"+",(e,r)=>e+r,"-",(...e)=>e.length<2?-e:e.reduce(((e,r)=>e-r)),">>>",(e,r)=>e>>>r,">>",(e,r)=>e>>r,"<<",(e,r)=>e<<r,">=",(e,r)=>e>=r,">",(e,r)=>e>r,"<=",(e,r)=>e<=r,"<",(e,r)=>e<r,"!=",(e,r)=>e!=r,"==",(e,r)=>e==r,"&",(e,r)=>e&r,"^",(e,r)=>e^r,"|",(e,r)=>e|r,"&&",(...e)=>e.every(Boolean),"||",(...e)=>e.some(Boolean),",",(e,r)=>r];e=r.pop();)a(r.pop(),e);var n,o,l=15,s=(e,r)=>(o=e,n=0,r=c(),n<o.length?f():r.valueOf()),f=(e="Bad syntax")=>{throw Error(e+" `"+o[n]+"` at "+n)},p=(e=1,r=n)=>{if("number"==typeof e)n+=e;else for(;e(u());)n++;return o.slice(r,n)},u=(e=0)=>o.charCodeAt(n+e),i=(e=1)=>o.substr(n,e),c=(e=0,r,t,a,n=0,o,l)=>{for(;(t=s.space())&&(l=d[t]?.(a,e)||!a&&h(t));)a=l;return r&&t!==r&&f("Unclosed paren"),a},v=(s.space=e=>{for(;(e=u())<=32;)n++;return e},s.token=[e=>(e=p((e=>e>47&&e<58||46==e)))&&((69==u()||101==u())&&(e+=p(2)+p((e=>e>=48&&e<=57))),isNaN(e=new Number(e))?f("Bad number"):e),(e,r)=>34==e&&p()+p((r=>r-e))+p(),e=>p((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,r=0,t)=>{for(;r<v.length;)if(t=v[r++](e))return t},d=[],g=s.operator=(e,r=0,t=0,a,o=e.charCodeAt(0),l=e.length,v=d[o],h=e.toUpperCase()!==e,g)=>(g=l<2?h?e=>u(1)<=32:e=>1:h?r=>i(l)==e&&u(l)<=32:r=>i(l)==e,a=t?t>0?e=>e&&[p(l),e]:t<0?e=>!e&&[p(l),(c(r-1)||f()).valueOf()]:t:t=>{t=[e,t||f()];do{n+=l,t.push((c(r)||f()).valueOf())}while(s.space()==o&&g());return t},d[o]=(e,t)=>t<r&&g()&&a(e)||v&&v(e,t));for(let e=0,r=[",",1,,"|",6,,"||",4,,"&",8,,"&&",5,,"^",7,,"==",9,,"!=",9,,">",10,,">=",10,,">>",11,,">>>",11,,"<",10,,"<=",10,,"<<",11,,"+",12,,"+",l,-1,"++",l,-1,"++",l,1,"-",12,,"-",l,-1,"--",l,-1,"--",l,1,"!",l,-1,"*",13,,"/",13,,"%",13,,".",18,(e,r)=>e&&[p(),e,"string"==typeof(r=c(18))?'"'+r+'"':r.valueOf()],"[",18,e=>(n++,e=[".",e,c(0,93).valueOf()],n++,e),"]",,,"(",18,(e,r)=>(n++,r=c(0,41),n++,Array.isArray(r)&&","===r[0]?(r[0]=e,r):r?[e,r.valueOf()]:[e]),"(",19,(e,r)=>!e&&(++n,r=c(0,41)||f(),++n,r),")",,,];e<r.length;)g(r[e++],r[e++],r[e++]);var y=e=>({valueOf:()=>e});s.token.splice(2,0,(e=>116===e&&"true"===i(4)&&p(4)?y(!0):102===e&&"false"===i(5)&&p(5)?y(!1):110===e&&"null"===i(4)&&p(4)?y(null):117===e&&"undefined"===i(9)&&p(9)?y(void 0):null)),s.token[1]=(e,r,t,a)=>{if(34===e||39===e){for(r=i(),p(),a="";(t=u())-e;)92===t?(p(),a+=O[i()]||i()):a+=i(),p();return p(),r+a+r}};var O={n:"\n",r:"\r",t:"\t",b:"\b",f:"\f",v:"\v"};s.space=e=>{for(;(e=u())<=32;)p(),47===u()&&(42===u(1)?(p(2),p((e=>42!==e&&47!==u(1))),p(2)):47===u(1)&&(p(2),p((e=>e>=32))));return e},s.token.unshift(((e,r)=>123===e&&(p(),r=b(["{",c(0,125)]),p(),r)));var b=(e,r)=>(e[1]?":"==e[1][0]?r=[e[1]]:","==e[1][0]&&(r=e[1].slice(1)):r=[],["{",...r]);for(let e=0,r=[";",1,,"===",9,,"!==",9,,"**",14,,"~",13,-1,"?",3,e=>{let r,t;return e||f("Expected expression"),p(),s.space(),r=c(),58!==u()&&f("Expected :"),p(),s.space(),t=c(),["?:",e,r,t]},"}",,,":",0,,"in",10,e=>u(2)<=32&&[p(2),'"'+e+'"',c(10)],"[",20,(e,r)=>!e&&(p(),r=c(0,93),p(),r?","===r[0]?(r[0]="[",r):["[",r]:["["])];e<r.length;)s.operator(r[e++],r[e++],r[e++]);for(let e=0,t=(["**",(...e)=>e.reduceRight(((e,r)=>Math.pow(r,e))),"~",e=>~e,"?:",(e,r,t)=>e?r:t,"in",(e,r)=>e in r,"[",(...e)=>Array(...e),"{",(...e)=>Object.fromEntries(e),":",(e,r)=>[e,r]]);e<t.length;)r.operator(t[e++],t[e++]);var A=s;export{A as default,r as evaluate,s as parse};
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "subscript",
3
- "version": "3.0.2",
3
+ "version": "5.1.0",
4
4
  "description": "Microlanguage with common syntax for JS/C++/Python/Rust",
5
5
  "main": "subscript.js",
6
6
  "type": "module",
7
7
  "files": [
8
- "src/*",
8
+ "parse.js",
9
+ "evaluate.js",
9
10
  "subscript.js",
10
11
  "subscript.min.js",
11
12
  "justin.js",
package/parse.js ADDED
@@ -0,0 +1,148 @@
1
+ const PERIOD=46, OPAREN=40, CPAREN=41, CBRACK=93, SPACE=32,
2
+
3
+ PREC_SEQ=1, PREC_SOME=4, PREC_EVERY=5, PREC_OR=6, PREC_XOR=7, PREC_AND=8,
4
+ 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
5
+
6
+
7
+ // current string & index
8
+ let idx, cur
9
+
10
+ export const parse = (str, tree) => (cur=str, idx=0, tree=expr(), idx<cur.length ? err() : tree.valueOf()),
11
+
12
+ err = (msg='Bad syntax') => { throw Error(msg + ' `' + cur[idx] + '` at ' + idx) },
13
+
14
+ skip = (is=1, from=idx) => {
15
+ if (typeof is === 'number') idx += is
16
+ else while (is(code())) idx++;
17
+ return cur.slice(from, idx)
18
+ },
19
+
20
+ code = (i=0) => cur.charCodeAt(idx+i),
21
+
22
+ char = (n=1) => cur.substr(idx, n),
23
+
24
+ // a + b - c
25
+ expr = (prec=0, end, cc, node, i=0, map, newNode) => {
26
+ // chunk/token parser
27
+ while (
28
+ (cc=parse.space()) && (newNode = lookup[cc]?.(node, prec) || (!node && token(cc)) )
29
+ ) node = newNode;
30
+
31
+ if (end && cc !== end) err('Unclosed paren')
32
+
33
+ return node
34
+ },
35
+
36
+ // can be extended with comments, so we export
37
+ space = parse.space = cc => { while (cc = code(), cc <= SPACE) idx++; return cc },
38
+
39
+ // tokens
40
+ tokens = parse.token = [
41
+ // 1.2e+3, .5 - fast & small version, but consumes corrupted nums as well
42
+ (number) => (
43
+ (number = skip(c => (c > 47 && c < 58) || c == PERIOD)) && (
44
+ (code() == 69 || code() == 101) && (number += skip(2) + skip(c => c >= 48 && c <= 57)),
45
+ isNaN(number = new Number(number)) ? err('Bad number') : number
46
+ )
47
+ ),
48
+ // "a"
49
+ (q, qc) => q == 34 && (skip() + skip(c => c-q) + skip()),
50
+ // id
51
+ c => skip(c =>
52
+ (c >= 48 && c <= 57) || // 0..9
53
+ (c >= 65 && c <= 90) || // A...Z
54
+ (c >= 97 && c <= 122) || // a...z
55
+ c == 36 || c == 95 || // $, _,
56
+ (c >= 192 && c != 215 && c != 247) // any non-ASCII
57
+ )
58
+ ],
59
+
60
+ token = (c,i=0,node) => { while(i<tokens.length) if (node = tokens[i++](c)) return node },
61
+
62
+ // operator lookup table
63
+ lookup = [],
64
+
65
+ // create operator checker/mapper (see examples)
66
+ // @param op is operator string
67
+ // @param prec is operator precedenc to check
68
+ // @param map is either number +1 - postfix unary, -1 prefix unary, 0 binary, else - custom mapper function
69
+ operator = parse.operator = (op, prec=0, type=0, map, c=op.charCodeAt(0), l=op.length, prev=lookup[c], word=op.toUpperCase()!==op, isop) => (
70
+ isop = l<2 ? // word operator must have space after
71
+ !word ? c=>1 : c=>code(1)<=SPACE :
72
+ !word ? c=>char(l)==op : c=>char(l)==op&&code(l)<=SPACE,
73
+
74
+ map = !type ? node => { // binary, consume same-op group
75
+ node = [op, node || err()]
76
+ // in order to support literal tokens, we call valueOf any time we create or modify calltree node
77
+ do { idx+=l, node.push((expr(prec) || err()).valueOf()) } while (parse.space()==c && isop())
78
+ return node
79
+ } :
80
+ type > 0 ? node => node && [skip(l), node] : // postfix unary
81
+ type < 0 ? node => !node && [skip(l), (expr(prec-1) || err()).valueOf()] : // prefix unary
82
+ type,
83
+
84
+ lookup[c] = (node, curPrec) => curPrec < prec && isop() && map(node) || (prev && prev(node, curPrec))
85
+ )
86
+
87
+ // ,
88
+ for (let i = 0, ops = [
89
+ // TODO: add ,, as node here
90
+ ',', PREC_SEQ,,
91
+
92
+ '|', PREC_OR,,
93
+ '||', PREC_SOME,,
94
+
95
+ '&', PREC_AND,,
96
+ '&&', PREC_EVERY,,
97
+
98
+ '^', PREC_XOR,,
99
+
100
+ // ==, !=
101
+ '==', PREC_EQ,,
102
+ '!=', PREC_EQ,,
103
+
104
+ // > >= >> >>>, < <= <<
105
+ '>', PREC_COMP,,
106
+ '>=', PREC_COMP,,
107
+ '>>', PREC_SHIFT,,
108
+ '>>>', PREC_SHIFT,,
109
+ '<', PREC_COMP,,
110
+ '<=', PREC_COMP,,
111
+ '<<', PREC_SHIFT,,
112
+
113
+ // + ++ - --
114
+ '+', PREC_SUM,,
115
+ '+', PREC_UNARY, -1,
116
+ '++', PREC_UNARY, -1,
117
+ '++', PREC_UNARY, +1,
118
+ '-', PREC_SUM,,
119
+ '-', PREC_UNARY, -1,
120
+ '--', PREC_UNARY, -1,
121
+ '--', PREC_UNARY, +1,
122
+
123
+ // ! ~
124
+ '!', PREC_UNARY, -1,
125
+
126
+ // * / %
127
+ '*', PREC_MULT,,
128
+ '/', PREC_MULT,,
129
+ '%', PREC_MULT,,
130
+
131
+ // a.b
132
+ '.', PREC_CALL, (node,b) => node && [skip(),node, typeof (b = expr(PREC_CALL)) === 'string' ? '"' + b + '"' : b.valueOf()],
133
+
134
+ // a[b]
135
+ '[', PREC_CALL, (node) => (idx++, node = ['.', node, expr(0,CBRACK).valueOf()], idx++, node),
136
+ ']',,,
137
+
138
+ // a(b)
139
+ '(', PREC_CALL, (node,b) => ( idx++, b=expr(0,CPAREN), idx++,
140
+ Array.isArray(b) && b[0]===',' ? (b[0]=node, b) : b ? [node, b.valueOf()] : [node]
141
+ ),
142
+ // (a+b)
143
+ '(', PREC_GROUP, (node,b) => !node && (++idx, b=expr(0,CPAREN) || err(), ++idx, b),
144
+ ')',,,
145
+ ]; i < ops.length;) operator(ops[i++],ops[i++],ops[i++])
146
+
147
+
148
+ export default parse
package/subscript.js CHANGED
@@ -1,5 +1,5 @@
1
- import parse from './src/parse.js'
2
- import evaluate from './src/evaluate.js'
1
+ import parse from './parse.js'
2
+ import evaluate from './evaluate.js'
3
3
 
4
4
  export { parse, evaluate }
5
5
 
package/subscript.min.js CHANGED
@@ -1 +1 @@
1
- var e,r,t,l=l=>(r=l,e=t=0,u()),n=()=>{for(;f()<=32;)e++},o=(r,l,n,o=3)=>{if(e&&t&&t[3]===e)return t;for(;o;)if(null!=(n=r[l=s(o--)]))return t=[l,n,l.length,e]},u=(r,t=-1)=>{n();let l,a,i,c=f(),y=0,A=e;if(c!==r){for(;A===e&&y<p.length;)a=p[y++](c);if(A===e)(l=o(h))&&(e+=l[2],a=[l[0],u(r,l[1])]);else{if(n(),c=f(),c===r)return a;for(y=0;y<d.length;)(i=d[y](a,c))!==a?(a=i,y=0,n(),c=f()):y++}for(n();(c=f())&&c!==r&&(l=o(g))&&l[1]>t;){a=[l[0],a];do{e+=l[2],a.push(u(r,l[1]))}while(s(l[2])===l[0]);n()}return a}},a=e=>(e=c((e=>e>=48&&e<=57||e>=65&&e<=90||e>=97&&e<=122||36==e||95==e||e>=192)))&&y.hasOwnProperty(e)?y[e]:e,f=()=>r.charCodeAt(e),s=(t=1)=>r.substr(e,t),i=r=>{throw Error(r+" at "+e)},c=(t=1,l=e)=>{if("number"==typeof t)e+=t;else for(;t(f());)++e>r.length&&i("Unexpected end "+t);return e>l?r.slice(l,e):null},p=l.token=[(r,t)=>40===r?(e++,t=u(41),e++,t):null,(e,r)=>{if(e=c((e=>e>=48&&e<=57||46===e)))return(69===f()||101===f())&&(e+=c(2)+c((e=>e>=48&&e<=57))),parseFloat(e)},(r,t)=>34===r?(t=s(),e++,t+c((e=>e!==r))+(e++,t)):null,a],y=l.literal={true:!0,false:!1,null:null},d=l.postfix=[(r,t,l)=>(46===t?(e++,n(),r=[".",r,'"'+a()+'"']):91===t?(e++,r=[".",r,u(93)],e++):40===t&&(e++,l=u(41),r=Array.isArray(l)&&","===l[0]?(l[0]=r,l):null==l?[r]:[r,l],e++),r),(t,l)=>43!==l&&45!==l||r.charCodeAt(e+1)!==l?t:[c(2),t]],h=l.unary={"-":17,"!":17,"+":17,"++":17,"--":17},g=l.binary={",":1,"||":6,"&&":7,"|":8,"^":9,"&":10,"==":11,"!=":11,"<":12,">":12,"<=":12,">=":12,"<<":13,">>":13,">>>":13,"+":14,"-":14,"*":15,"/":15,"%":15},A=l,b=e=>Array.isArray(e)&&("string"==typeof e[0]||b(e[0])),m=(e,r={},t,l)=>b(e)?("string"==typeof(t=e[0])&&(l=v[t]),"function"!=typeof(t=l||m(t,r))?t:t.call(...e.map((e=>m(e,r))))):e&&"string"==typeof e?'"'===e[0]?e.slice(1,-1):"@"===e[0]?e.slice(1):e in r?r[e]:e:e,v=m.operator={"!":e=>!e,"++":e=>++e,"--":e=>--e,".":(...e)=>e.reduce(((e,r)=>e&&e[r])),"%":(...e)=>e.reduce(((e,r)=>e%r)),"/":(...e)=>e.reduce(((e,r)=>e/r)),"*":(...e)=>e.reduce(((e,r)=>e*r)),"+":(...e)=>e.reduce(((e,r)=>e+r)),"-":(...e)=>e.length<2?-e:e.reduce(((e,r)=>e-r)),">>>":(e,r)=>e>>>r,">>":(e,r)=>e>>r,"<<":(e,r)=>e<<r,">=":(e,r)=>e>=r,">":(e,r)=>e>r,"<=":(e,r)=>e<=r,"<":(e,r)=>e<r,"!=":(e,r)=>e!=r,"==":(e,r)=>e==r,"&":(e,r)=>e&r,"^":(e,r)=>e^r,"|":(e,r)=>e|r,"&&":(...e)=>e.every(Boolean),"||":(...e)=>e.some(Boolean),",":(...e)=>e.reduce(((e,r)=>r))},w=m,x=e=>(e="string"==typeof e?A(e):e,r=>w(e,r));export{x as default,w as evaluate,A as parse};
1
+ var e,r,t=15,a=(t,a)=>(r=t,e=0,a=f(),e<r.length?o():a.valueOf()),o=(t="Bad syntax")=>{throw Error(t+" `"+r[e]+"` at "+e)},n=(t=1,a=e)=>{if("number"==typeof t)e+=t;else for(;t(l());)e++;return r.slice(a,e)},l=(t=0)=>r.charCodeAt(e+t),s=(t=1)=>r.substr(e,t),f=(e=0,r,t,n,l=0,s,f)=>{for(;(t=a.space())&&(f=i[t]?.(n,e)||!n&&u(t));)n=f;return r&&t!==r&&o("Unclosed paren"),n},p=(a.space=r=>{for(;(r=l())<=32;)e++;return r},a.token=[e=>(e=n((e=>e>47&&e<58||46==e)))&&((69==l()||101==l())&&(e+=n(2)+n((e=>e>=48&&e<=57))),isNaN(e=new Number(e))?o("Bad number"):e),(e,r)=>34==e&&n()+n((r=>r-e))+n(),e=>n((e=>e>=48&&e<=57||e>=65&&e<=90||e>=97&&e<=122||36==e||95==e||e>=192&&215!=e&&247!=e))]),u=(e,r=0,t)=>{for(;r<p.length;)if(t=p[r++](e))return t},i=[],c=a.operator=(r,t=0,p=0,u,c=r.charCodeAt(0),y=r.length,g=i[c],h=r.toUpperCase()!==r,v)=>(v=y<2?h?e=>l(1)<=32:e=>1:h?e=>s(y)==r&&l(y)<=32:e=>s(y)==r,u=p?p>0?e=>e&&[n(y),e]:p<0?e=>!e&&[n(y),(f(t-1)||o()).valueOf()]:p:n=>{n=[r,n||o()];do{e+=y,n.push((f(t)||o()).valueOf())}while(a.space()==c&&v());return n},i[c]=(e,r)=>r<t&&v()&&u(e)||g&&g(e,r));for(let r=0,a=[",",1,,"|",6,,"||",4,,"&",8,,"&&",5,,"^",7,,"==",9,,"!=",9,,">",10,,">=",10,,">>",11,,">>>",11,,"<",10,,"<=",10,,"<<",11,,"+",12,,"+",t,-1,"++",t,-1,"++",t,1,"-",12,,"-",t,-1,"--",t,-1,"--",t,1,"!",t,-1,"*",13,,"/",13,,"%",13,,".",18,(e,r)=>e&&[n(),e,"string"==typeof(r=f(18))?'"'+r+'"':r.valueOf()],"[",18,r=>(e++,r=[".",r,f(0,93).valueOf()],e++,r),"]",,,"(",18,(r,t)=>(e++,t=f(0,41),e++,Array.isArray(t)&&","===t[0]?(t[0]=r,t):t?[r,t.valueOf()]:[r]),"(",19,(r,t)=>!r&&(++e,t=f(0,41)||o(),++e,t),")",,,];r<a.length;)c(a[r++],a[r++],a[r++]);var y=a,g=e=>Array.isArray(e)&&("string"==typeof e[0]||g(e[0])),h=(e,r={},t,a)=>g(e)?("string"==typeof(t=e[0])&&(a=v[t]),"function"!=typeof(t=a||h(t,r))?t:t.call(...e.map((e=>h(e,r))))):e&&"string"==typeof e?'"'===e[0]?e.slice(1,-1):"@"===e[0]?e.slice(1):e in r?r[e]:e:e,v={},d=h.operator=(e,r)=>v[e]=2==r.length?(...e)=>e.reduce(r):r;for(let e,r=["!",e=>!e,"++",e=>++e,"--",e=>--e,".",(e,r)=>e&&e[r],"%",(e,r)=>e%r,"/",(e,r)=>e/r,"*",(e,r)=>e*r,"+",(e,r)=>e+r,"-",(...e)=>e.length<2?-e:e.reduce(((e,r)=>e-r)),">>>",(e,r)=>e>>>r,">>",(e,r)=>e>>r,"<<",(e,r)=>e<<r,">=",(e,r)=>e>=r,">",(e,r)=>e>r,"<=",(e,r)=>e<=r,"<",(e,r)=>e<r,"!=",(e,r)=>e!=r,"==",(e,r)=>e==r,"&",(e,r)=>e&r,"^",(e,r)=>e^r,"|",(e,r)=>e|r,"&&",(...e)=>e.every(Boolean),"||",(...e)=>e.some(Boolean),",",(e,r)=>r];e=r.pop();)d(r.pop(),e);var A=h,O=e=>(e="string"==typeof e?y(e):e,r=>A(e,r));export{O as default,A as evaluate,y as parse};
package/src/evaluate.js DELETED
@@ -1,57 +0,0 @@
1
- export const isCmd = a => Array.isArray(a) && (typeof a[0] === 'string' || isCmd(a[0])),
2
-
3
- // calltree → result
4
- evaluate = (s, ctx={}, c, op) => {
5
- if (isCmd(s)) {
6
- c = s[0]
7
- if (typeof c === 'string') op = operator[c]
8
- c = op || evaluate(c, ctx) // [[a,b], c]
9
- if (typeof c !== 'function') return c
10
-
11
- return c.call(...s.map(a => evaluate(a,ctx)))
12
- }
13
- if (s && typeof s === 'string')
14
- return s[0] === '"' ? s.slice(1,-1)
15
- : s[0]==='@' ? s.slice(1)
16
- : s in ctx ? ctx[s] : s
17
-
18
- return s
19
- },
20
-
21
- // op evaluators
22
- // multiple args allows shortcuts, lisp compatible, easy manual eval, functions anyways take multiple arguments
23
- operator = evaluate.operator = {
24
- '!':a=>!a,
25
- '++':a=>++a,
26
- '--':a=>--a,
27
-
28
- '.':(...a)=>a.reduce((a,b)=>a?a[b]:a),
29
-
30
- '%':(...a)=>a.reduce((a,b)=>a%b),
31
- '/':(...a)=>a.reduce((a,b)=>a/b),
32
- '*':(...a)=>a.reduce((a,b)=>a*b),
33
-
34
- '+':(...a)=>a.reduce((a,b)=>a+b),
35
- '-':(...a)=>a.length < 2 ? -a : a.reduce((a,b)=>a-b),
36
-
37
- '>>>':(a,b)=>a>>>b,
38
- '>>':(a,b)=>a>>b,
39
- '<<':(a,b)=>a<<b,
40
-
41
- '>=':(a,b)=>a>=b,
42
- '>':(a,b)=>a>b,
43
- '<=':(a,b)=>a<=b,
44
- '<':(a,b)=>a<b,
45
-
46
- '!=':(a,b)=>a!=b,
47
- '==':(a,b)=>a==b,
48
-
49
- '&':(a,b)=>a&b,
50
- '^':(a,b)=>a^b,
51
- '|':(a,b)=>a|b,
52
- '&&':(...a)=>a.every(Boolean),
53
- '||':(...a)=>a.some(Boolean),
54
- ',':(...a)=>a.reduce((a,b)=>(a,b))
55
- }
56
-
57
- export default evaluate
package/src/parse.js DELETED
@@ -1,127 +0,0 @@
1
- const PERIOD = 46, OPAREN = 40, CPAREN = 41, OBRACK = 91, CBRACK = 93, PLUS = 43, MINUS = 45
2
-
3
- export let index, current, lastOp
4
-
5
- export const parse = str => (current=str, index=lastOp=0, expr()),
6
-
7
- space = () => { while (code() <= 32) index++ },
8
-
9
- // consume operator
10
- operator = (ops, op, prec, l=3) => {
11
- // memoize by index - saves 20% to perf
12
- if (index && lastOp && lastOp[3] === index) return lastOp
13
-
14
- // ascending lookup is faster for 1-char operators, longer for 2+ char ops, so we use descending
15
- while (l) if ((prec=ops[op=char(l--)])!=null) return lastOp = [op, prec, op.length, index] //opinfo
16
- },
17
-
18
- expr = (end, prec=-1) => {
19
- space()
20
-
21
- let cc = code(), op, node, i=0, mapped, from=index
22
-
23
- if (cc === end) return
24
-
25
- // parse node by token parsers (direct loop is faster than token.find)
26
- while (from===index && i < token.length) node = token[i++](cc)
27
-
28
- // unary prefix
29
- if (from===index) (op = operator(unary)) && (index += op[2], node = [op[0], expr(end, op[1])])
30
-
31
- // postfix handlers allow a.b[c](d).e, postfix operators, literals etc.
32
- else {
33
- if (space(), cc=code(), cc === end) return node
34
- for (i=0; i < postfix.length;) if ((mapped=postfix[i](node, cc)) !== node) node=mapped, i=0, space(), cc=code(); else i++
35
- }
36
- // ALT: seems to be slower
37
- // else do {space(), cc=code()} while (postfix.find((parse, mapped) => (mapped = parse(node, cc)) !== node && (node = mapped)))
38
-
39
- space()
40
-
41
- // consume expression for current precedence or higher
42
- while ((cc = code()) && cc !== end && (op = operator(binary)) && op[1] > prec) {
43
- node = [op[0], node]
44
- // consume same-op group, do..while both saves op lookups and space
45
- do { index += op[2], node.push(expr(end, op[1])) } while (char(op[2]) === op[0])
46
- space()
47
- }
48
-
49
- return node;
50
- },
51
-
52
- // ------------------- tokens
53
- // 1.2e+3, .5 - fast & small version, but consumes corrupted nums as well
54
- float = (number, c) => {
55
- if (number = skip(c => (c >= 48 && c <= 57) || c === PERIOD)) {
56
- if (code() === 69 || code() === 101) number += skip(2) + skip(c => c >= 48 && c <= 57)
57
- return parseFloat(number)
58
- }
59
- },
60
-
61
- // "a"
62
- string = (q, qc) => q === 34 ? (qc = char(), index++, qc) + skip(c => c !== q) + (index++, qc) : null,
63
-
64
- // (...exp)
65
- group = (c, node) => c === OPAREN ? (index++, node = expr(CPAREN), index++, node) : null,
66
-
67
- // var or literal
68
- id = name => (name = skip(c =>
69
- (c >= 48 && c <= 57) || // 0..9
70
- (c >= 65 && c <= 90) || // A...Z
71
- (c >= 97 && c <= 122) || // a...z
72
- c == 36 || c == 95 || // $, _,
73
- c >= 192 // any non-ASCII
74
- )) && literal.hasOwnProperty(name) ? literal[name] : name,
75
-
76
-
77
- // ------------ util
78
- code = () => current.charCodeAt(index), // current char code
79
- char = (n=1) => current.substr(index, n), // skip n chars
80
- err = (msg) => { throw Error(msg + ' at ' + index) },
81
- skip = (is=1, from=index) => { // consume N or until condition matches
82
- if (typeof is === 'number') index += is
83
- else while (is(code())) ++index > current.length && err('Unexpected end ' + is) // 1 + true === 2;
84
- return index > from ? current.slice(from, index) : null
85
- },
86
-
87
-
88
- // ----------- config
89
- token = parse.token = [ group, float, string, id ],
90
-
91
- literal = parse.literal = {true:true, false:false, null:null},
92
-
93
- postfix = parse.postfix = [
94
- // a.b[c](d), 3 in 1 for performance
95
- (node, cc, arg) => {
96
- if (cc === PERIOD) index++, space(), node = ['.', node, '"'+id()+'"']
97
- else if (cc === OBRACK) index++, node = ['.', node, expr(CBRACK)], index++
98
- else if (cc === OPAREN)
99
- index++, arg=expr(CPAREN),
100
- node = Array.isArray(arg) && arg[0]===',' ? (arg[0]=node, arg) : arg == null ? [node] : [node, arg],
101
- index++
102
- return node
103
- },
104
-
105
- // a++, a--
106
- (node, cc) => (cc===0x2b || cc===0x2d) && current.charCodeAt(index+1)===cc ? [skip(2), node] : node,
107
- ],
108
-
109
- unary = parse.unary = {
110
- '-': 17,
111
- '!': 17,
112
- '+': 17,
113
- '++': 17,
114
- '--': 17
115
- },
116
-
117
- binary = parse.binary = {
118
- ',': 1,
119
- '||': 6, '&&': 7, '|': 8, '^': 9, '&': 10,
120
- '==': 11, '!=': 11,
121
- '<': 12, '>': 12, '<=': 12, '>=': 12,
122
- '<<': 13, '>>': 13, '>>>': 13,
123
- '+': 14, '-': 14,
124
- '*': 15, '/': 15, '%': 15
125
- }
126
-
127
- export default parse