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 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>![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,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 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:
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 via `parse.binary`, `parse.unary` and `evaluate.operator`.
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
- parse.binary['=>'] = 10
90
+ // TODO
91
+ // parse.operator['=>'] = 10
63
92
 
64
93
  // define evaluators
65
- evaluate.operator['=>'] = ( args, body ) => evaluate(body, args)
66
- evaluate.operator['|'] = ( a, ...b ) => a.pipe(...b)
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 for:
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
- <!-- + `//, /* */` comments -->
109
- <!-- + `undefined` literal -->
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: ~280 ms
267
- jsep: ~281 ms
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
@@ -12,8 +12,8 @@ evaluate = (s, ctx={}, c, op) => {
12
12
  }
13
13
  if (s && typeof s === 'string')
14
14
  return s[0] === '"' ? s.slice(1,-1)
15
- : s[0]==='@' ? s.slice(1)
16
- : s in ctx ? ctx[s] : s
15
+ : s[0]==='@' ? s.slice(1)
16
+ : s in ctx ? ctx[s] : s
17
17
 
18
18
  return s
19
19
  },
package/justin.js CHANGED
@@ -1,13 +1,21 @@
1
1
  // justin lang https://github.com/endojs/Jessie/issues/66
2
- import {evaluate, operator} from './evaluate.js'
3
- import {parse, binary, unary, postfix, token, literal,
4
- code, char, skip, space, expr} from './parse.js'
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
- literal['undefined'] = undefined
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[2] = (q, qc, c, str) => {
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
- binary['**'] = 16
38
- operator['**'] = (...args)=>args.reduceRight((a,b)=>Math.pow(b,a))
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['~'] = 17
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
- postfix.push(node => {
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 (code() !== 63) return node
55
- skip(), space(), a = expr(-1,58)
56
- if (code() !== 58) return node
57
- skip(), space(), b = expr()
58
- return ['?:',node, a, b]
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
- // comments['/*']='*/'
63
- // comments['//']='\n'
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.postfix.unshift(node => (char(2) === 'in' ? (skip(2), ['in', '"' + node + '"', expr()]) : node))
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.push((node, arg) =>
72
- code() === 91 ?
71
+ evaluate.operator['['] = (...args) => Array(...args)
72
+ parse.token.unshift((cc, node, arg) =>
73
+ cc === 91 &&
73
74
  (
74
- skip(), arg=expr(-1,93),
75
- node = arg==null ? ['['] : arg[0] === ',' ? (arg[0]='[',arg) : ['[',arg],
75
+ skip(), arg=expr(0,93),
76
+ node = arg===nil ? ['['] : arg[0] === ',' ? (arg[0]='[',arg) : ['[',arg],
76
77
  skip(), node
77
- ) : null
78
+ )
78
79
  )
79
80
 
80
81
  // {}
81
- binary[':'] = 2
82
- token.unshift((node) => code() === 123 ? (skip(), node = map(['{',expr(-1,125)]), skip(), node) : null)
83
- operator['{'] = (...args)=>Object.fromEntries(args)
84
- operator[':'] = (a,b)=>[a,b]
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]==null) args = []
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 { default } from './subscript.js';
98
+ export default parse
97
99
  export { parse, evaluate }
package/justin.min.js CHANGED
@@ -1 +1 @@
1
- var r,e,n,t,l=r=>Array.isArray(r)&&("string"==typeof r[0]||l(r[0])),o=(r,e={},n,t)=>l(r)?("string"==typeof(n=r[0])&&(t=u[n]),"function"!=typeof(n=t||o(n,e))?n:n.call(...r.map((r=>o(r,e))))):r&&"string"==typeof r?'"'===r[0]?r.slice(1,-1):"@"===r[0]?r.slice(1):r in e?e[r]:r:r,u=o.operator={"!":r=>!r,"++":r=>++r,"--":r=>--r,".":(...r)=>r.reduce(((r,e)=>r&&r[e])),"%":(...r)=>r.reduce(((r,e)=>r%e)),"/":(...r)=>r.reduce(((r,e)=>r/e)),"*":(...r)=>r.reduce(((r,e)=>r*e)),"+":(...r)=>r.reduce(((r,e)=>r+e)),"-":(...r)=>r.length<2?-r:r.reduce(((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,"&&":(...r)=>r.every(Boolean),"||":(...r)=>r.some(Boolean),",":(...r)=>r.reduce(((r,e)=>e))},i=o,a=(t,l)=>(e=t,r=n=0,l=c(),r<e.length?p():l),s=()=>e.charCodeAt(r),f=(n=1)=>e.substr(r,n),p=(e="Bad syntax "+f())=>{throw Error(e+" at "+r)},d=(n=1,t=r)=>{if("number"==typeof n)r+=n;else for(;n(s());)++r>e.length&&p("End by "+n);return r>t?e.slice(t,r):null},h=()=>{for(;s()<=32;)r++},y=(e,t,l,o=3)=>{if(r&&n&&n[3]===r)return n;for(;o;)if(null!=(l=e[t=f(o--)]))return n=[t,l,t.length,r]},c=(e=-1,n)=>{h();let l,o,u,i,a=s(),d=0,g=r;if(n){if(a===n)return;i=t,t=n}for(;g===r&&d<b.length;)o=b[d++](a);if(g===r)(l=y(m))&&(r+=l[2],o=[l[0],c(l[1])]);else for(h(),a=s(),d=0;d<A.length;)(u=A[d](o,a))!==o?(o=u,h(),a=s()):d++;for(;a=s()&&a!==t&&(l=y(w))&&l[1]>e;){o=[l[0],o];do{r+=l[2],o.push(c(l[1]))}while(f(l[2])===l[0]);h()}return n&&(t=s()!==n?p("Unclosed paren"):i),o},g=r=>d((r=>r>=48&&r<=57||r>=65&&r<=90||r>=97&&r<=122||36==r||95==r||r>=192)),b=a.token=[r=>{if(r=d((r=>r>=48&&r<=57||46===r)))return(69===s()||101===s())&&(r+=d(2)+d((r=>r>=48&&r<=57))),isNaN(r=parseFloat(r))?p("Bad number"):r},(e,n)=>40===e?(r++,n=c(-1,41),r++,n):null,(e,n)=>34===e?(n=f(),r++,n+d((r=>r-e))+(r++,n)):null,g],v=a.literal={true:!0,false:!1,null:null},A=a.postfix=[(n,t,l)=>(46===t?(r++,h(),n=[".",n,'"'+g()+'"']):91===t?(r++,n=[".",n,c(-1,93)],r++):40===t?(r++,l=c(-1,41),n=Array.isArray(l)&&","===l[0]?(l[0]=n,l):null==l?[n]:[n,l],r++):43!==t&&45!==t||e.charCodeAt(r+1)!==t?"string"==typeof n&&v.hasOwnProperty(n)&&(n=v[n]):n=[d(2),n],n)],m=a.unary={"-":17,"!":17,"+":17,"++":17,"--":17},w=a.binary={",":1,"||":6,"&&":7,"|":8,"^":9,"&":10,"==":11,"!=":11,"<":12,">":12,"<=":12,">=":12,"<<":13,">>":13,">>>":13,"+":14,"-":14,"*":15,"/":15,"%":15},x=a,B=r=>(r="string"==typeof r?x(r):r,e=>i(r,e));v.undefined=void 0,b[2]=(r,e,n,t)=>{if(34===r||39===r){for(e=f(),d(),t="";(n=s())-r;)92===n?(d(),t+=C[f()]||f()):t+=f(),d();return d(),e+t+e}};var C={n:"\n",r:"\r",t:"\t",b:"\b",f:"\f",v:"\v"};A.push(((r,e)=>"string"==typeof r&&(e=m[r])?[r,c(e)]:r)),b[3]=r=>d((r=>(r>=48&&r<=57||r>=65&&r<=90||r>=97&&r<=122||36==r||95==r||r>=192)&&!w[String.fromCharCode(r)])),w["**"]=16,u["**"]=(...r)=>r.reduceRight(((r,e)=>Math.pow(e,r))),m["~"]=17,u["~"]=r=>~r,w[";"]=1,u["?:"]=(r,e,n)=>r?e:n,A.push((r=>{let e,n;return 63!==s()||(d(),h(),e=c(-1,58),58!==s())?r:(d(),h(),n=c(),["?:",r,e,n])})),o.operator.in=(r,e)=>r in e,a.postfix.unshift((r=>"in"===f(2)?(d(2),["in",'"'+r+'"',c()]):r)),u["["]=(...r)=>Array(...r),b.push(((r,e)=>91===s()?(d(),r=null==(e=c(-1,93))?["["]:","===e[0]?(e[0]="[",e):["[",e],d(),r):null)),w[":"]=2,b.unshift((r=>123===s()?(d(),r=E(["{",c(-1,125)]),d(),r):null)),u["{"]=(...r)=>Object.fromEntries(r),u[":"]=(r,e)=>[r,e];var E=(r,e)=>(null==r[1]?e=[]:":"==r[1][0]?e=[r[1]]:","==r[1][0]&&(e=r[1].slice(1)),["{",...e]);export{B as default,o as evaluate,a as parse};
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "subscript",
3
- "version": "3.0.3",
3
+ "version": "4.0.0",
4
4
  "description": "Microlanguage with common syntax for JS/C++/Python/Rust",
5
5
  "main": "subscript.js",
6
6
  "type": "module",
package/parse.js CHANGED
@@ -1,134 +1,134 @@
1
- const PERIOD = 46, OPAREN = 40, CPAREN = 41, OBRACK = 91, CBRACK = 93, PLUS = 43, MINUS = 45
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
- export let index, current, lastOp, end
4
+ // current string & index
5
+ let idx, cur
4
6
 
5
- export const parse = (str, tree) => (current=str, index=lastOp=0, tree=expr(), index < current.length ? err() : tree),
7
+ export const parse = (str, tree) => (cur=str, idx=0, tree=expr(), idx<cur.length ? err() : tree),
6
8
 
7
- // ------------ util
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
- // ascending lookup is faster for 1-char operators, longer for 2+ char ops, so we use descending
25
- while (l) if ((prec=ops[op=char(l--)])!=null) return lastOp = [op, prec, op.length, index] //opinfo
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
- expr = (prec=-1, curEnd) => {
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
- if (curEnd) if (cc === curEnd) return; else prevEnd = end, end = curEnd // global end marker saves operator lookups
19
+ char = (n=1) => cur.substr(idx, n),
34
20
 
35
- // parse node by token parsers (direct loop is faster than token.find)
36
- while (from===index && i < token.length) node = token[i++](cc)
21
+ nil = '',
37
22
 
38
- // unary prefix
39
- if (from===index) (op = operator(unary)) && (index += op[2], node = [op[0], expr(op[1])])
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 handlers
42
- else {
43
- for (space(), cc=code(), i=0; i < postfix.length;)
44
- if ((mapped = postfix[i](node, cc)) !== node) node = mapped, space(), cc=code(); else i++
45
- }
46
- // ALT: seems to be slower
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 (curEnd) end = code() !== curEnd ? err('Unclosed paren') : prevEnd
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
- // ------------------- tokens
64
- // 1.2e+3, .5 - fast & small version, but consumes corrupted nums as well
65
- float = (number) => {
66
- if (number = skip(c => (c >= 48 && c <= 57) || c === PERIOD)) {
67
- if (code() === 69 || code() === 101) number += skip(2) + skip(c => c >= 48 && c <= 57)
68
- return isNaN(number = parseFloat(number)) ? err('Bad number') : number
69
- }
70
- },
71
-
72
- // "a"
73
- string = (q, qc) => q === 34 ? (qc = char(), index++, qc) + skip(c => c-q) + (index++, qc) : null,
74
-
75
- // (...exp)
76
- group = (c, node) => c === OPAREN ? (index++, node = expr(-1,CPAREN), index++, node) : null,
77
-
78
- // var or literal
79
- id = name => (name = skip(c =>
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
- return node
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
- unary = parse.unary = {
116
- '-': 17,
117
- '!': 17,
118
- '+': 17,
119
- '++': 17,
120
- '--': 17
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,t,n,l=(n,l)=>(r=n,e=t=0,l=y(),e<r.length?u():l),a=()=>r.charCodeAt(e),o=(t=1)=>r.substr(e,t),u=(r="Bad syntax "+o())=>{throw Error(r+" at "+e)},s=(t=1,n=e)=>{if("number"==typeof t)e+=t;else for(;t(a());)++e>r.length&&u("End by "+t);return e>n?r.slice(n,e):null},f=()=>{for(;a()<=32;)e++},i=(r,n,l,a=3)=>{if(e&&t&&t[3]===e)return t;for(;a;)if(null!=(l=r[n=o(a--)]))return t=[n,l,n.length,e]},y=(r=-1,t)=>{f();let l,s,p,d,b=a(),m=0,B=e;if(t){if(b===t)return;d=n,n=t}for(;B===e&&m<c.length;)s=c[m++](b);if(B===e)(l=i(g))&&(e+=l[2],s=[l[0],y(l[1])]);else for(f(),b=a(),m=0;m<h.length;)(p=h[m](s,b))!==s?(s=p,f(),b=a()):m++;for(;b=a()&&b!==n&&(l=i(A))&&l[1]>r;){s=[l[0],s];do{e+=l[2],s.push(y(l[1]))}while(o(l[2])===l[0]);f()}return t&&(n=a()!==t?u("Unclosed paren"):d),s},p=e=>s((e=>e>=48&&e<=57||e>=65&&e<=90||e>=97&&e<=122||36==e||95==e||e>=192)),c=l.token=[e=>{if(e=s((e=>e>=48&&e<=57||46===e)))return(69===a()||101===a())&&(e+=s(2)+s((e=>e>=48&&e<=57))),isNaN(e=parseFloat(e))?u("Bad number"):e},(r,t)=>40===r?(e++,t=y(-1,41),e++,t):null,(r,t)=>34===r?(t=o(),e++,t+s((e=>e-r))+(e++,t)):null,p],d=l.literal={true:!0,false:!1,null:null},h=l.postfix=[(t,n,l)=>(46===n?(e++,f(),t=[".",t,'"'+p()+'"']):91===n?(e++,t=[".",t,y(-1,93)],e++):40===n?(e++,l=y(-1,41),t=Array.isArray(l)&&","===l[0]?(l[0]=t,l):null==l?[t]:[t,l],e++):43!==n&&45!==n||r.charCodeAt(e+1)!==n?"string"==typeof t&&d.hasOwnProperty(t)&&(t=d[t]):t=[s(2),t],t)],g=l.unary={"-":17,"!":17,"+":17,"++":17,"--":17},A=l.binary={",":1,"||":6,"&&":7,"|":8,"^":9,"&":10,"==":11,"!=":11,"<":12,">":12,"<=":12,">=":12,"<<":13,">>":13,">>>":13,"+":14,"-":14,"*":15,"/":15,"%":15},b=l,m=e=>Array.isArray(e)&&("string"==typeof e[0]||m(e[0])),B=(e,r={},t,n)=>m(e)?("string"==typeof(t=e[0])&&(n=v[t]),"function"!=typeof(t=n||B(t,r))?t:t.call(...e.map((e=>B(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=B.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=B,x=e=>(e="string"==typeof e?b(e):e,r=>w(e,r));export{x as default,w as evaluate,b as parse};
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};