subscript 3.0.3 → 4.0.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 +50 -34
- package/evaluate.js +2 -2
- package/justin.js +53 -51
- package/justin.min.js +1 -1
- package/package.json +1 -1
- package/parse.js +106 -106
- package/subscript.min.js +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ _Subscript_ is micro-language with common syntax subset of C++, JS, Java, Python
|
|
|
5
5
|
* Well-known syntax
|
|
6
6
|
* Any _subscript_ fragment can be copy-pasted to any target language
|
|
7
7
|
* It's tiny <sub></sub>
|
|
8
|
-
* It's
|
|
8
|
+
* It's :rocket: fast ([see performance](#performance))
|
|
9
9
|
* Configurable & extensible
|
|
10
10
|
* Trivial to use...
|
|
11
11
|
|
|
@@ -26,14 +26,8 @@ _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
|
|
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
|
|
31
|
-
|
|
32
|
-
```js
|
|
33
|
-
import {evaluate} from 'subscript.js'
|
|
34
|
-
|
|
35
|
-
evaluate(['+', ['*', 'min', 60], '"sec"'], { min: 5 }) // min*60 + "sec" == "300sec"
|
|
36
|
-
```
|
|
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/3.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.
|
|
37
31
|
|
|
38
32
|
|
|
39
33
|
## Operators
|
|
@@ -53,17 +47,53 @@ Default operators include common operators for the listed languages in the follo
|
|
|
53
47
|
* `&&`
|
|
54
48
|
* `||`
|
|
55
49
|
|
|
56
|
-
All other operators can be extended
|
|
50
|
+
All other operators can be extended, see [extension](#extension).
|
|
51
|
+
|
|
52
|
+
## Evaluation
|
|
53
|
+
|
|
54
|
+
_Subscript_ parser generates lispy calltree (compatible with [frisk](https://npmjs.com/frisk)), which is compared to esprima AST has:
|
|
55
|
+
|
|
56
|
+
+ minimal possible overhead
|
|
57
|
+
+ clear precedence
|
|
58
|
+
+ overloading by context
|
|
59
|
+
+ manual evaluation and debugging
|
|
60
|
+
+ conventional form
|
|
61
|
+
+ one-liner docs:
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
import {evaluate} from 'subscript.js'
|
|
65
|
+
|
|
66
|
+
evaluate(['+', ['*', 'min', 60], '"sec"'], { min: 5 }) // min*60 + "sec" == "300sec"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Extension
|
|
70
|
+
|
|
71
|
+
Tokens are extensible via `parse.token` list, can be added support of _regex_, _array_, _object_, _interpolated string_ and others.
|
|
72
|
+
Default tokens include:
|
|
73
|
+
|
|
74
|
+
* `"abc"` strings
|
|
75
|
+
* `1.2e+3` floats
|
|
76
|
+
* `()` expression groups or fn calls
|
|
77
|
+
* `.`, `[]` property access
|
|
78
|
+
|
|
79
|
+
Operators can be extended via `parse.operator` to add support for any unary/binary/postfix operators, calls, props or chains.
|
|
80
|
+
|
|
81
|
+
Comments can be added via extending `parse.space`.
|
|
57
82
|
|
|
83
|
+
For now see justin extension how things can be done.
|
|
84
|
+
|
|
85
|
+
<!--
|
|
58
86
|
```js
|
|
59
87
|
import { parse, evaluate } from 'subscript.js'
|
|
60
88
|
|
|
61
89
|
// add precedences
|
|
62
|
-
|
|
90
|
+
// TODO
|
|
91
|
+
// parse.operator['=>'] = 10
|
|
63
92
|
|
|
64
93
|
// define evaluators
|
|
65
|
-
|
|
66
|
-
evaluate.operator['
|
|
94
|
+
// TODO
|
|
95
|
+
// evaluate.operator['=>'] = ( args, body ) => evaluate(body, args)
|
|
96
|
+
// evaluate.operator['|'] = ( a, ...b ) => a.pipe(...b)
|
|
67
97
|
|
|
68
98
|
let tree = parse(`
|
|
69
99
|
interval(350)
|
|
@@ -73,28 +103,13 @@ let tree = parse(`
|
|
|
73
103
|
`)
|
|
74
104
|
evaluate(tree, { Math, map, take, interval, gaussian })
|
|
75
105
|
```
|
|
76
|
-
|
|
77
|
-
## Extending
|
|
78
|
-
|
|
79
|
-
By default subscript detects the following tokens:
|
|
80
|
-
|
|
81
|
-
* `"` strings
|
|
82
|
-
* `1.2e+3` floats
|
|
83
|
-
* `true`, `false`, `null` literals
|
|
84
|
-
* `()` expression groups or fn calls
|
|
85
|
-
* `.`, `[]` property access
|
|
86
|
-
|
|
87
|
-
Literals can be extended via `parse.literal` dict.
|
|
88
|
-
|
|
89
|
-
Token parsers are extensible via `parse.token` list, can be added support of _regex_, _array_, _object_, _interpolated string_ and others.
|
|
90
|
-
|
|
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
|
+
-->
|
|
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
|
|
112
|
+
It adds support of:
|
|
98
113
|
|
|
99
114
|
+ `**` binary operator
|
|
100
115
|
+ `~` unary operator
|
|
@@ -105,8 +120,9 @@ It adds support for:
|
|
|
105
120
|
+ `in` binary operator
|
|
106
121
|
+ `;` expression separator
|
|
107
122
|
+ unary word operators
|
|
108
|
-
|
|
109
|
-
|
|
123
|
+
+ `//`, `/* */` comments
|
|
124
|
+
+ `true`, `false`, `null`, `undefined` literals
|
|
125
|
+
<!-- + `===`, `!==` operators -->
|
|
110
126
|
<!-- + `?` chaining operator -->
|
|
111
127
|
<!-- + `...x` unary operator -->
|
|
112
128
|
<!-- + strings interpolation -->
|
|
@@ -263,8 +279,8 @@ Subscript shows relatively good performance within other evaluators:
|
|
|
263
279
|
// 1 + (a * b / c % d) - 2.0 + -3e-3 * +4.4e4 / f.g[0] - i.j(+k == 1)(0)
|
|
264
280
|
// parse 30k times
|
|
265
281
|
|
|
266
|
-
subscript: ~
|
|
267
|
-
jsep: ~
|
|
282
|
+
subscript: ~220 ms
|
|
283
|
+
jsep: ~280 ms
|
|
268
284
|
expr-eval: ~480 ms
|
|
269
285
|
jexl: ~1200 ms
|
|
270
286
|
new Function: ~1400 ms
|
package/evaluate.js
CHANGED
package/justin.js
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
// justin lang https://github.com/endojs/Jessie/issues/66
|
|
2
|
-
import {evaluate
|
|
3
|
-
import {parse,
|
|
4
|
-
|
|
2
|
+
import {evaluate} from './evaluate.js'
|
|
3
|
+
import {parse, code, char, skip, expr, nil, binary, unary} from './parse.js'
|
|
4
|
+
|
|
5
|
+
// ;
|
|
6
|
+
parse.operator[0] = binary(c => c==44||c==59)
|
|
5
7
|
|
|
6
8
|
// undefined
|
|
7
|
-
|
|
9
|
+
parse.token.splice(3,0, c =>
|
|
10
|
+
c === 116 && char(4) === 'true' && skip(4) ? true :
|
|
11
|
+
c === 102 && char(5) === 'false' && skip(5) ? false :
|
|
12
|
+
c === 110 && char(4) === 'null' && skip(4) ? null :
|
|
13
|
+
c === 117 && char(9) === 'undefined' && skip(9) ? undefined :
|
|
14
|
+
undefined
|
|
15
|
+
)
|
|
8
16
|
|
|
9
17
|
// "' with /
|
|
10
|
-
token[
|
|
18
|
+
parse.token[1] = (q, qc, c, str) => {
|
|
11
19
|
if (q !== 34 && q !== 39) return
|
|
12
20
|
qc = char(), skip(), str = ''
|
|
13
21
|
while (c=code(), c-q) {
|
|
@@ -18,80 +26,74 @@ token[2] = (q, qc, c, str) => {
|
|
|
18
26
|
}
|
|
19
27
|
const escape = {n:'\n', r:'\r', t:'\t', b:'\b', f:'\f', v:'\v'}
|
|
20
28
|
|
|
21
|
-
// unary word
|
|
22
|
-
postfix.push((node, prec) => typeof node === 'string' && (prec=unary[node]) ? [node, expr(prec)] : node)
|
|
23
|
-
|
|
24
|
-
// detect custom operators
|
|
25
|
-
token[3] = name => (name = skip(c =>
|
|
26
|
-
(
|
|
27
|
-
(c >= 48 && c <= 57) || // 0..9
|
|
28
|
-
(c >= 65 && c <= 90) || // A...Z
|
|
29
|
-
(c >= 97 && c <= 122) || // a...z
|
|
30
|
-
c == 36 || c == 95 || // $, _,
|
|
31
|
-
c >= 192 // any non-ASCII
|
|
32
|
-
) && !binary[String.fromCharCode(c)]
|
|
33
|
-
)),
|
|
34
|
-
|
|
35
|
-
|
|
36
29
|
// **
|
|
37
|
-
|
|
38
|
-
operator
|
|
30
|
+
evaluate.operator['**'] = (...args)=>args.reduceRight((a,b)=>Math.pow(b,a))
|
|
31
|
+
parse.operator.splice(parse.operator.length - 3, 0, binary(cc=>cc===42 && code(1) === cc && 2))
|
|
39
32
|
|
|
40
33
|
// ~
|
|
41
|
-
unary
|
|
42
|
-
operator['~'] = a=>~a
|
|
34
|
+
parse.operator.splice(parse.operator.length-2, 0, unary(cc => cc === 126))
|
|
35
|
+
evaluate.operator['~'] = a=>~a
|
|
43
36
|
|
|
44
|
-
// ...
|
|
45
|
-
// unary[1]['...']=true
|
|
37
|
+
// TODO ...
|
|
38
|
+
// // unary[1]['...']=true
|
|
46
39
|
|
|
47
|
-
// ;
|
|
48
|
-
binary[';'] = 1
|
|
49
40
|
|
|
50
41
|
// ?:
|
|
51
|
-
operator['?:']=(a,b,c)=>a?b:c
|
|
52
|
-
|
|
42
|
+
evaluate.operator['?:']=(a,b,c)=>a?b:c
|
|
43
|
+
parse.operator.splice(1,0, (node,cc,prec,end) => {
|
|
53
44
|
let a, b
|
|
54
|
-
if (
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
45
|
+
if (cc !== 63) return
|
|
46
|
+
if (node===nil) err('Bad expression')
|
|
47
|
+
skip(), parse.space(), a = expr(-1,58)
|
|
48
|
+
if (code() !== 58) return
|
|
49
|
+
skip(), parse.space(), b = expr()
|
|
50
|
+
return ['?:', node, a, b]
|
|
59
51
|
})
|
|
60
52
|
|
|
61
53
|
// /**/, //
|
|
62
|
-
|
|
63
|
-
|
|
54
|
+
parse.space = cc => {
|
|
55
|
+
while (cc = code(), cc <= 32) {
|
|
56
|
+
skip()
|
|
57
|
+
if (code() === 47)
|
|
58
|
+
// /**/
|
|
59
|
+
if (code(1) === 42) skip(2), skip(c => c !== 42 && code(1) !== 47), skip(2)
|
|
60
|
+
// //
|
|
61
|
+
else if (code(1) === 47) skip(2), skip(c => c >= 32)
|
|
62
|
+
}
|
|
63
|
+
return cc
|
|
64
|
+
}
|
|
64
65
|
|
|
65
66
|
// in
|
|
66
67
|
evaluate.operator['in'] = (a,b)=>a in b
|
|
67
|
-
parse.
|
|
68
|
+
parse.operator.splice(6,0,(a,cc,prec,end) => (char(2) === 'in' && [skip(2), '"' + a + '"', expr(prec,end)]))
|
|
68
69
|
|
|
69
70
|
// []
|
|
70
|
-
operator['['] = (...args) => Array(...args)
|
|
71
|
-
token.
|
|
72
|
-
|
|
71
|
+
evaluate.operator['['] = (...args) => Array(...args)
|
|
72
|
+
parse.token.unshift((cc, node, arg) =>
|
|
73
|
+
cc === 91 &&
|
|
73
74
|
(
|
|
74
|
-
skip(), arg=expr(
|
|
75
|
-
node = arg
|
|
75
|
+
skip(), arg=expr(0,93),
|
|
76
|
+
node = arg===nil ? ['['] : arg[0] === ',' ? (arg[0]='[',arg) : ['[',arg],
|
|
76
77
|
skip(), node
|
|
77
|
-
)
|
|
78
|
+
)
|
|
78
79
|
)
|
|
79
80
|
|
|
80
81
|
// {}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
operator
|
|
82
|
+
parse.token.unshift((cc, node) => (
|
|
83
|
+
cc === 123 ? (skip(), node = map(['{',expr(0,125)]), skip(), node) : null
|
|
84
|
+
))
|
|
85
|
+
parse.operator.splice(4,0, binary(cc=>cc===58))
|
|
86
|
+
evaluate.operator['{'] = (...args)=>Object.fromEntries(args)
|
|
87
|
+
evaluate.operator[':'] = (a,b)=>[a,b]
|
|
85
88
|
|
|
86
89
|
const map = (n, args) => {
|
|
87
|
-
if (n[1]
|
|
90
|
+
if (n[1]===nil) args = []
|
|
88
91
|
else if (n[1][0]==':') args = [n[1]]
|
|
89
92
|
else if (n[1][0]==',') args = n[1].slice(1)
|
|
90
93
|
return ['{', ...args]
|
|
91
94
|
}
|
|
92
95
|
|
|
93
|
-
|
|
94
96
|
// TODO: strings interpolation
|
|
95
97
|
|
|
96
|
-
export
|
|
98
|
+
export default parse
|
|
97
99
|
export { parse, evaluate }
|
package/justin.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var e,r,o=e=>Array.isArray(e)&&("string"==typeof e[0]||o(e[0])),t=(e,r={},n,p)=>o(e)?("string"==typeof(n=e[0])&&(p=a[n]),"function"!=typeof(n=p||t(n,r))?n:n.call(...e.map((e=>t(e,r))))):e&&"string"==typeof e?'"'===e[0]?e.slice(1,-1):"@"===e[0]?e.slice(1):e in r?r[e]:e:e,a=t.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))},n=(o,t)=>(r=o,e=0,t=u(),e<r.length?p():t),p=(o="Bad syntax")=>{throw Error(o+" `"+r[e]+"` at "+e)},s=(o=1,t=e)=>{if("number"==typeof o)e+=o;else for(;o(i());)e++;return r.slice(t,e)||c},i=(o=0)=>r.charCodeAt(e+o),l=(o=1)=>r.substr(e,o),c="",u=(r=0,o,t=n.space(),a,s=e,l=0,u)=>{for(;s===e&&l<n.token.length;)a=n.token[l++](t);for(l=Math.max(0|h[t=n.space()],r);l<n.operator.length&&!(t===o||l<r);)(u=n.operator[l++](a,t,l,o))&&(u.indexOf(c)>=0&&p("Bad expression"),a=u,l=Math.max(0|h[t=n.space()],r));return!r&&o&&i()!=o&&p("Unclosed paren"),a},f=(n.space=r=>{for(;(r=i())<=32;)e++;return r},n.token=[e=>(e=s((e=>e>47&&e<58||46===e)))?((69===i()||101===i())&&(e+=s(2)+s((e=>e>=48&&e<=57))),isNaN(e=parseFloat(e))?p("Bad number"):e):c,(e,r)=>34===e?s()+s((r=>r-e))+s():c,r=>40===r?(++e,r=u(0,41),++e,r===c?p():r):c,e=>s((e=>e>=48&&e<=57||e>=65&&e<=90||e>=97&&e<=122||36==e||95==e||e>=192&&215!=e&&247!=e))],e=>(r,o,t,a,p,i,f)=>{if(r!==c&&(i=0|e(o))){p=[f=l(i),r];do{s(i),p.push(u(t,a))}while(n.space()==o&&l(i)==f);return p}}),d=(e,r=!1)=>r?(r,o,t,a,n)=>r!==c&&(n=0|e(o))&&[s(n),r]:(r,o,t,a,n)=>r===c&&(n=0|e(o))&&[s(n),u(t-1,a)],h=(n.operator=[f((e=>44==e)),f((e=>124==e&&i(1)==e&&2)),f((e=>38==e&&i(1)==e&&2)),f((e=>124==e)),f((e=>94==e)),f((e=>38==e)),f((e=>(61==e||33==e)&&61==i(1)&&(i(1)==i(2)?3:2))),f((e=>(62==e||60==e)&&e!=i(1))),f((e=>(60==e||62==e)&&e==i(1)&&(e==i(2)?3:2))),f((e=>(43==e||45==e)&&i(1)!=e)),f((e=>42==e&&42!=i(1)||47==e||37==e)),d((e=>(43==e||45==e)&&i(1)==e&&2),!0),d((e=>(43==e||45==e||33==e)&&(i(1)==e?2:1))),(r,o,t,a,n)=>46==o?[s(),r,"string"==typeof(n=u(t,a))?'"'+n+'"':n]:91==o?(e++,r=[".",r,u(0,93)],e++,r):40==o?(e++,n=u(0,41),e++,Array.isArray(n)&&","===n[0]?(n[0]=r,n):n===c?[r]:[r,n]):c],[]);h[44]=0,h[124]=1,h[38]=2,h[94]=4,h[61]=h[33]=6,h[60]=h[62]=7,h[43]=h[45]=9,h[42]=h[47]=h[37]=10,h[46]=h[91]=h[40]=13,n.operator[0]=f((e=>44==e||59==e)),n.token.splice(3,0,(e=>!(116!==e||"true"!==l(4)||!s(4))||(102!==e||"false"!==l(5)||!s(5))&&(110===e&&"null"===l(4)&&s(4)?null:void(117===e&&"undefined"===l(9)&&s(9))))),n.token[1]=(e,r,o,t)=>{if(34===e||39===e){for(r=l(),s(),t="";(o=i())-e;)92===o?(s(),t+=y[l()]||l()):t+=l(),s();return s(),r+t+r}};var y={n:"\n",r:"\r",t:"\t",b:"\b",f:"\f",v:"\v"};t.operator["**"]=(...e)=>e.reduceRight(((e,r)=>Math.pow(r,e))),n.operator.splice(n.operator.length-3,0,f((e=>42===e&&i(1)===e&&2))),n.operator.splice(n.operator.length-2,0,d((e=>126===e))),t.operator["~"]=e=>~e,t.operator["?:"]=(e,r,o)=>e?r:o,n.operator.splice(1,0,((e,r,o,t)=>{let a,p;if(63===r&&(e===c&&err("Bad expression"),s(),n.space(),a=u(-1,58),58===i()))return s(),n.space(),p=u(),["?:",e,a,p]})),n.space=e=>{for(;(e=i())<=32;)s(),47===i()&&(42===i(1)?(s(2),s((e=>42!==e&&47!==i(1))),s(2)):47===i(1)&&(s(2),s((e=>e>=32))));return e},t.operator.in=(e,r)=>e in r,n.operator.splice(6,0,((e,r,o,t)=>"in"===l(2)&&[s(2),'"'+e+'"',u(o,t)])),t.operator["["]=(...e)=>Array(...e),n.token.unshift(((e,r,o)=>91===e&&(s(),r=(o=u(0,93))===c?["["]:","===o[0]?(o[0]="[",o):["[",o],s(),r))),n.token.unshift(((e,r)=>123===e?(s(),r=g(["{",u(0,125)]),s(),r):null)),n.operator.splice(4,0,f((e=>58===e))),t.operator["{"]=(...e)=>Object.fromEntries(e),t.operator[":"]=(e,r)=>[e,r];var g=(e,r)=>(e[1]===c?r=[]:":"==e[1][0]?r=[e[1]]:","==e[1][0]&&(r=e[1].slice(1)),["{",...r]),v=n;export{v as default,t as evaluate,n as parse};
|
package/package.json
CHANGED
package/parse.js
CHANGED
|
@@ -1,134 +1,134 @@
|
|
|
1
|
-
|
|
1
|
+
// precedence-based parsing
|
|
2
|
+
const GT=62, LT=60, EQ=61, PLUS=43, MINUS=45, AND=38, OR=124, HAT=94, MUL=42, DIV=47, MOD=37, PERIOD=46, OBRACK=91, CBRACK=93, OPAREN=40, CPAREN=41, COMMA=44, SPACE=32, EXCL=33
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
// current string & index
|
|
5
|
+
let idx, cur
|
|
4
6
|
|
|
5
|
-
export const parse = (str, tree) => (
|
|
7
|
+
export const parse = (str, tree) => (cur=str, idx=0, tree=expr(), idx<cur.length ? err() : tree),
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
code = () => current.charCodeAt(index), // current char code
|
|
9
|
-
char = (n=1) => current.substr(index, n), // skip n chars
|
|
10
|
-
err = (msg='Bad syntax '+char()) => { throw Error(msg + ' at ' + index) },
|
|
11
|
-
skip = (is=1, from=index) => { // consume N or until condition matches
|
|
12
|
-
if (typeof is === 'number') index += is
|
|
13
|
-
else while (is(code())) ++index > current.length && err('End by ' + is) // 1 + true === 2;
|
|
14
|
-
return index > from ? current.slice(from, index) : null
|
|
15
|
-
},
|
|
16
|
-
space = () => { while (code() <= 32) index++ },
|
|
17
|
-
|
|
18
|
-
// ------------- expr
|
|
19
|
-
// consume operator
|
|
20
|
-
operator = (ops, op, prec, l=3) => {
|
|
21
|
-
// memoize by index - saves 20% to perf
|
|
22
|
-
if (index && lastOp && lastOp[3] === index) return lastOp
|
|
9
|
+
err = (msg='Bad syntax') => { throw Error(msg + ' `' + cur[idx] + '` at ' + idx) },
|
|
23
10
|
|
|
24
|
-
|
|
25
|
-
|
|
11
|
+
skip = (is=1, from=idx) => {
|
|
12
|
+
if (typeof is === 'number') idx += is
|
|
13
|
+
else while (is(code())) idx++;
|
|
14
|
+
return cur.slice(from, idx) || nil
|
|
26
15
|
},
|
|
27
16
|
|
|
28
|
-
|
|
29
|
-
space()
|
|
30
|
-
|
|
31
|
-
let cc = code(), op, node, i=0, mapped, from=index, prevEnd
|
|
17
|
+
code = (i=0) => cur.charCodeAt(idx+i),
|
|
32
18
|
|
|
33
|
-
|
|
19
|
+
char = (n=1) => cur.substr(idx, n),
|
|
34
20
|
|
|
35
|
-
|
|
36
|
-
while (from===index && i < token.length) node = token[i++](cc)
|
|
21
|
+
nil = '',
|
|
37
22
|
|
|
38
|
-
|
|
39
|
-
|
|
23
|
+
// a + b - c
|
|
24
|
+
expr = (prec=0, end, cc=parse.space(), node, from=idx, i=0, mapped) => {
|
|
25
|
+
// prefix or token
|
|
26
|
+
while (from===idx && i < parse.token.length) node = parse.token[i++](cc)
|
|
40
27
|
|
|
41
|
-
// postfix
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
// else do {space(), cc=code()} while (postfix.find((parse, mapped) => (mapped = parse(node, cc)) !== node && (node = mapped)))
|
|
48
|
-
|
|
49
|
-
// consume binary expression for current precedence or higher
|
|
50
|
-
while (cc = code() && (cc !== end) && (op = operator(binary)) && op[1] > prec) {
|
|
51
|
-
node = [op[0], node]
|
|
52
|
-
// consume same-op group, do..while both saves op lookups and space
|
|
53
|
-
do { index += op[2], node.push(expr(op[1])) } while (char(op[2]) === op[0])
|
|
54
|
-
space()
|
|
28
|
+
// postfix or binary
|
|
29
|
+
for (i = Math.max(lookup[cc=parse.space()]|0, prec); i < parse.operator.length;) {
|
|
30
|
+
if (cc===end || i<prec) break // if lookup got prec lower than current - end group
|
|
31
|
+
else if (mapped = parse.operator[i++](node, cc, i, end))
|
|
32
|
+
mapped.indexOf(nil) >=0 && err('Bad expression'),
|
|
33
|
+
node = mapped, i = Math.max(lookup[cc=parse.space()]|0, prec); // we pass i+1 as precision
|
|
55
34
|
}
|
|
56
35
|
|
|
57
|
-
if (
|
|
58
|
-
// if (node == null) err('Missing argument')
|
|
36
|
+
if (!prec && end && code()!=end) err('Unclosed paren')
|
|
59
37
|
|
|
60
|
-
return node
|
|
38
|
+
return node
|
|
61
39
|
},
|
|
62
40
|
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
|
|
41
|
+
// can be extended with comments, so we export
|
|
42
|
+
space = parse.space = cc => { while (cc = code(), cc <= SPACE) idx++; return cc },
|
|
43
|
+
|
|
44
|
+
// tokens
|
|
45
|
+
token = parse.token = [
|
|
46
|
+
// 1.2e+3, .5 - fast & small version, but consumes corrupted nums as well
|
|
47
|
+
(number) => {
|
|
48
|
+
if (number = skip(c => (c > 47 && c < 58) || c === PERIOD)) {
|
|
49
|
+
if (code() === 69 || code() === 101) number += skip(2) + skip(c => c >= 48 && c <= 57)
|
|
50
|
+
return isNaN(number = parseFloat(number)) ? err('Bad number') : number
|
|
51
|
+
}
|
|
52
|
+
return nil
|
|
53
|
+
},
|
|
54
|
+
// "a"
|
|
55
|
+
(q, qc) => q === 34 ? (skip() + skip(c => c-q) + skip()) : nil,
|
|
56
|
+
// (...exp)
|
|
57
|
+
c => c === OPAREN ? (++idx, c=expr(0,CPAREN), ++idx, c===nil?err():c) : nil,
|
|
58
|
+
// var
|
|
59
|
+
c => skip(c =>
|
|
81
60
|
(c >= 48 && c <= 57) || // 0..9
|
|
82
61
|
(c >= 65 && c <= 90) || // A...Z
|
|
83
62
|
(c >= 97 && c <= 122) || // a...z
|
|
84
63
|
c == 36 || c == 95 || // $, _,
|
|
85
|
-
c >= 192 // any non-ASCII
|
|
64
|
+
(c >= 192 && c != 215 && c != 247) // any non-ASCII
|
|
86
65
|
)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
// ----------- config
|
|
90
|
-
token = parse.token = [ float, group, string, id ],
|
|
91
|
-
|
|
92
|
-
literal = parse.literal = {true:true, false:false, null:null},
|
|
93
|
-
|
|
94
|
-
postfix = parse.postfix = [
|
|
95
|
-
// postfix parsers merged into 1 for performance & compactness
|
|
96
|
-
(node, cc, arg) => {
|
|
97
|
-
// a.b[c](d)
|
|
98
|
-
if (cc === PERIOD) index++, space(), node = ['.', node, '"'+id()+'"']
|
|
99
|
-
else if (cc === OBRACK) index++, node = ['.', node, expr(-1,CBRACK)], index++
|
|
100
|
-
else if (cc === OPAREN)
|
|
101
|
-
index++, arg=expr(-1,CPAREN),
|
|
102
|
-
node = Array.isArray(arg) && arg[0]===',' ? (arg[0]=node, arg) : arg == null ? [node] : [node, arg],
|
|
103
|
-
index++
|
|
104
|
-
|
|
105
|
-
// a++, a--
|
|
106
|
-
else if ((cc===0x2b || cc===0x2d) && current.charCodeAt(index+1)===cc) node = [skip(2), node]
|
|
107
|
-
|
|
108
|
-
// literal
|
|
109
|
-
else if (typeof node === 'string' && literal.hasOwnProperty(node)) node = literal[node]
|
|
66
|
+
],
|
|
110
67
|
|
|
111
|
-
|
|
68
|
+
// create binary operator parser
|
|
69
|
+
binary = (is) => (a,cc,prec,end, list,len,op) => {
|
|
70
|
+
if (a!==nil && (len = is(cc)|0)) {
|
|
71
|
+
// consume same-op group, do..while saves op lookups
|
|
72
|
+
list = [op=char(len),a]
|
|
73
|
+
do { skip(len), list.push(expr(prec,end)) } while (parse.space()==cc && char(len)==op)
|
|
74
|
+
return list
|
|
112
75
|
}
|
|
76
|
+
},
|
|
77
|
+
unary = (is, post=false) => post ?
|
|
78
|
+
(a,cc,prec,end,l) => a!==nil && (l=is(cc)|0) && [skip(l), a] :
|
|
79
|
+
(a,cc,prec,end,l) => a===nil && (l=is(cc)|0) && [skip(l), expr(prec-1,end)],
|
|
80
|
+
|
|
81
|
+
operator = parse.operator = [
|
|
82
|
+
// ','
|
|
83
|
+
binary(c => c==COMMA),
|
|
84
|
+
// '||' '&&'
|
|
85
|
+
binary(c => c==OR && code(1)==c && 2),
|
|
86
|
+
binary(c => c==AND && code(1)==c && 2),
|
|
87
|
+
// '|' '^' '&'
|
|
88
|
+
binary(c => c==OR),
|
|
89
|
+
binary(c => c==HAT),
|
|
90
|
+
binary(c => c==AND),
|
|
91
|
+
// '==' '!='
|
|
92
|
+
binary(c => (c==EQ || c==EXCL) && code(1)==EQ && (code(1)==code(2) ? 3 : 2)),
|
|
93
|
+
// '<' '>' '<=' '>='
|
|
94
|
+
binary(c => (c==GT || c==LT) && c!=code(1)),
|
|
95
|
+
// '<<' '>>' '>>>'
|
|
96
|
+
binary(c => (c==LT || c==GT) && c==code(1) && (c==code(2) ? 3 : 2)),
|
|
97
|
+
// '+' '-'
|
|
98
|
+
binary(c => (c==PLUS || c==MINUS) && code(1)!=c),
|
|
99
|
+
// '*' '/' '%'
|
|
100
|
+
binary(c => (c==MUL && code(1) != MUL) || c==DIV || c==MOD),
|
|
101
|
+
// -- ++ unaries
|
|
102
|
+
unary(c => (c==PLUS || c==MINUS) && code(1) == c && 2, true),
|
|
103
|
+
// - + ! unaries
|
|
104
|
+
unary(c => (c==PLUS || c==MINUS || c==EXCL) && (code(1)==c ? 2 : 1)),
|
|
105
|
+
// '()', '[]', '.'
|
|
106
|
+
(a,cc,prec,end,b) => (
|
|
107
|
+
// a.b[c](d)
|
|
108
|
+
cc==PERIOD ? [skip(), (a), typeof (b = (expr(prec,end))) === 'string' ? '"' + b + '"' : b] :
|
|
109
|
+
cc==OBRACK ? (idx++, a = ['.', (a), (expr(0,CBRACK))], idx++, a) :
|
|
110
|
+
cc==OPAREN ? (
|
|
111
|
+
idx++, b=expr(0,CPAREN), idx++,
|
|
112
|
+
Array.isArray(b) && b[0]===',' ? (b[0]=a, b) :
|
|
113
|
+
b === nil ? [a] :
|
|
114
|
+
[a, b]
|
|
115
|
+
) : nil
|
|
116
|
+
)
|
|
113
117
|
],
|
|
114
118
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
// fast operator lookup table
|
|
120
|
+
lookup = []
|
|
121
|
+
|
|
122
|
+
lookup[COMMA] = 0
|
|
123
|
+
lookup[OR] = 1
|
|
124
|
+
lookup[AND] = 2
|
|
125
|
+
lookup[HAT] = 4
|
|
126
|
+
lookup[EQ] = lookup[EXCL] = 6
|
|
127
|
+
lookup[LT] = lookup[GT] = 7
|
|
128
|
+
lookup[PLUS] = lookup[MINUS] = 9
|
|
129
|
+
lookup[MUL] = lookup[DIV] = lookup[MOD] = 10
|
|
130
|
+
lookup[PERIOD] = lookup[OBRACK] = lookup[OPAREN] = 13
|
|
122
131
|
|
|
123
|
-
binary = parse.binary = {
|
|
124
|
-
',': 1,
|
|
125
|
-
'||': 6, '&&': 7, '|': 8, '^': 9, '&': 10,
|
|
126
|
-
'==': 11, '!=': 11,
|
|
127
|
-
'<': 12, '>': 12, '<=': 12, '>=': 12,
|
|
128
|
-
'<<': 13, '>>': 13, '>>>': 13,
|
|
129
|
-
'+': 14, '-': 14,
|
|
130
|
-
'*': 15, '/': 15, '%': 15
|
|
131
|
-
}
|
|
132
132
|
|
|
133
133
|
|
|
134
134
|
export default parse
|
package/subscript.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var e,r,
|
|
1
|
+
var e,r,a=(a,o)=>(r=a,e=0,o=c(),e<r.length?t():o),t=(a="Bad syntax")=>{throw Error(a+" `"+r[e]+"` at "+e)},o=(a=1,t=e)=>{if("number"==typeof a)e+=a;else for(;a(s());)e++;return r.slice(t,e)||p},s=(a=0)=>r.charCodeAt(e+a),n=(a=1)=>r.substr(e,a),p="",c=(r=0,o,n=a.space(),c,i=e,u=0,f)=>{for(;i===e&&u<a.token.length;)c=a.token[u++](n);for(u=Math.max(0|l[n=a.space()],r);u<a.operator.length&&!(n===o||u<r);)(f=a.operator[u++](c,n,u,o))&&(f.indexOf(p)>=0&&t("Bad expression"),c=f,u=Math.max(0|l[n=a.space()],r));return!r&&o&&s()!=o&&t("Unclosed paren"),c},i=(a.space=r=>{for(;(r=s())<=32;)e++;return r},a.token=[e=>(e=o((e=>e>47&&e<58||46===e)))?((69===s()||101===s())&&(e+=o(2)+o((e=>e>=48&&e<=57))),isNaN(e=parseFloat(e))?t("Bad number"):e):p,(e,r)=>34===e?o()+o((r=>r-e))+o():p,r=>40===r?(++e,r=c(0,41),++e,r===p?t():r):p,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))],e=>(r,t,s,i,u,l,f)=>{if(r!==p&&(l=0|e(t))){u=[f=n(l),r];do{o(l),u.push(c(s,i))}while(a.space()==t&&n(l)==f);return u}}),u=(e,r=!1)=>r?(r,a,t,s,n)=>r!==p&&(n=0|e(a))&&[o(n),r]:(r,a,t,s,n)=>r===p&&(n=0|e(a))&&[o(n),c(t-1,s)],l=(a.operator=[i((e=>44==e)),i((e=>124==e&&s(1)==e&&2)),i((e=>38==e&&s(1)==e&&2)),i((e=>124==e)),i((e=>94==e)),i((e=>38==e)),i((e=>(61==e||33==e)&&61==s(1)&&(s(1)==s(2)?3:2))),i((e=>(62==e||60==e)&&e!=s(1))),i((e=>(60==e||62==e)&&e==s(1)&&(e==s(2)?3:2))),i((e=>(43==e||45==e)&&s(1)!=e)),i((e=>42==e&&42!=s(1)||47==e||37==e)),u((e=>(43==e||45==e)&&s(1)==e&&2),!0),u((e=>(43==e||45==e||33==e)&&(s(1)==e?2:1))),(r,a,t,s,n)=>46==a?[o(),r,"string"==typeof(n=c(t,s))?'"'+n+'"':n]:91==a?(e++,r=[".",r,c(0,93)],e++,r):40==a?(e++,n=c(0,41),e++,Array.isArray(n)&&","===n[0]?(n[0]=r,n):n===p?[r]:[r,n]):p],[]);l[44]=0,l[124]=1,l[38]=2,l[94]=4,l[61]=l[33]=6,l[60]=l[62]=7,l[43]=l[45]=9,l[42]=l[47]=l[37]=10,l[46]=l[91]=l[40]=13;var f=a,d=e=>Array.isArray(e)&&("string"==typeof e[0]||d(e[0])),y=(e,r={},a,t)=>d(e)?("string"==typeof(a=e[0])&&(t=h[a]),"function"!=typeof(a=t||y(a,r))?a:a.call(...e.map((e=>y(e,r))))):e&&"string"==typeof e?'"'===e[0]?e.slice(1,-1):"@"===e[0]?e.slice(1):e in r?r[e]:e:e,h=y.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))},g=y,m=e=>(e="string"==typeof e?f(e):e,r=>g(e,r));export{m as default,g as evaluate,f as parse};
|