subscript 3.0.1 → 5.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 +54 -37
- package/{src/evaluate.js → evaluate.js} +10 -8
- package/justin.js +69 -41
- package/justin.min.js +1 -1
- package/package.json +3 -2
- package/parse.js +148 -0
- package/subscript.js +2 -2
- package/subscript.min.js +1 -1
- package/src/parse.js +0 -134
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
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
|
-
*
|
|
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/
|
|
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/4.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:
|
|
57
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`.
|
|
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
|
|
@@ -104,8 +119,10 @@ It adds support for:
|
|
|
104
119
|
+ `{...}` Object literal
|
|
105
120
|
+ `in` binary operator
|
|
106
121
|
+ `;` expression separator
|
|
107
|
-
|
|
108
|
-
|
|
122
|
+
+ unary word operators
|
|
123
|
+
+ `//`, `/* */` comments
|
|
124
|
+
+ `true`, `false`, `null`, `undefined` literals
|
|
125
|
+
<!-- + `===`, `!==` operators -->
|
|
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: ~
|
|
266
|
-
jsep: ~
|
|
282
|
+
subscript: ~200 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>
|
|
@@ -12,12 +12,14 @@ evaluate = (s, ctx={}, c, op) => {
|
|
|
12
12
|
}
|
|
13
13
|
if (s && typeof s === 'string')
|
|
14
14
|
return s[0] === '"' ? s.slice(1,-1)
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
: s[0]==='@' ? s.slice(1)
|
|
16
|
+
: s in ctx ? ctx[s] : s
|
|
17
17
|
|
|
18
18
|
return s
|
|
19
19
|
},
|
|
20
20
|
|
|
21
|
+
reducer=fn=>(...a)=>a.reduce(fn),
|
|
22
|
+
|
|
21
23
|
// op evaluators
|
|
22
24
|
// multiple args allows shortcuts, lisp compatible, easy manual eval, functions anyways take multiple arguments
|
|
23
25
|
operator = evaluate.operator = {
|
|
@@ -25,13 +27,13 @@ operator = evaluate.operator = {
|
|
|
25
27
|
'++':a=>++a,
|
|
26
28
|
'--':a=>--a,
|
|
27
29
|
|
|
28
|
-
'.':(
|
|
30
|
+
'.':reducer((a,b)=>a?a[b]:a),
|
|
29
31
|
|
|
30
|
-
'%':(
|
|
31
|
-
'/':(
|
|
32
|
-
'*':(
|
|
32
|
+
'%':reducer((a,b)=>a%b),
|
|
33
|
+
'/':reducer((a,b)=>a/b),
|
|
34
|
+
'*':reducer((a,b)=>a*b),
|
|
33
35
|
|
|
34
|
-
'+':(
|
|
36
|
+
'+':reducer((a,b)=>a+b),
|
|
35
37
|
'-':(...a)=>a.length < 2 ? -a : a.reduce((a,b)=>a-b),
|
|
36
38
|
|
|
37
39
|
'>>>':(a,b)=>a>>>b,
|
|
@@ -51,7 +53,7 @@ operator = evaluate.operator = {
|
|
|
51
53
|
'|':(a,b)=>a|b,
|
|
52
54
|
'&&':(...a)=>a.every(Boolean),
|
|
53
55
|
'||':(...a)=>a.some(Boolean),
|
|
54
|
-
',':(
|
|
56
|
+
',':reducer((a,b)=>(a,b))
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
export default evaluate
|
package/justin.js
CHANGED
|
@@ -1,73 +1,101 @@
|
|
|
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, operator, err} from './parse.js'
|
|
4
|
+
|
|
5
|
+
// ;
|
|
6
|
+
operator(';', 1)
|
|
7
|
+
|
|
8
|
+
// ===, !==
|
|
9
|
+
operator('===', 9)
|
|
10
|
+
operator('!==', 9)
|
|
5
11
|
|
|
6
12
|
// undefined
|
|
7
|
-
|
|
13
|
+
const v = v => ({valueOf:()=>v})
|
|
14
|
+
parse.token.splice(2,0, c =>
|
|
15
|
+
c === 116 && char(4) === 'true' && skip(4) ? v(true) :
|
|
16
|
+
c === 102 && char(5) === 'false' && skip(5) ? v(false) :
|
|
17
|
+
c === 110 && char(4) === 'null' && skip(4) ? v(null) :
|
|
18
|
+
c === 117 && char(9) === 'undefined' && skip(9) ? v(undefined) :
|
|
19
|
+
null
|
|
20
|
+
)
|
|
8
21
|
|
|
9
|
-
// '
|
|
10
|
-
token
|
|
22
|
+
// "' with /
|
|
23
|
+
parse.token[1] = (q, qc, c, str) => {
|
|
24
|
+
if (q !== 34 && q !== 39) return
|
|
25
|
+
qc = char(), skip(), str = ''
|
|
26
|
+
while (c=code(), c-q) {
|
|
27
|
+
if (c === 92) skip(), str += escape[char()] || char(); else str+=char()
|
|
28
|
+
skip()
|
|
29
|
+
}
|
|
30
|
+
return skip(), qc + str + qc
|
|
31
|
+
}
|
|
32
|
+
const escape = {n:'\n', r:'\r', t:'\t', b:'\b', f:'\f', v:'\v'}
|
|
11
33
|
|
|
12
34
|
// **
|
|
13
|
-
|
|
14
|
-
operator
|
|
35
|
+
evaluate.operator['**'] = (...args)=>args.reduceRight((a,b)=>Math.pow(b,a))
|
|
36
|
+
operator('**', 14)
|
|
15
37
|
|
|
16
38
|
// ~
|
|
17
|
-
|
|
18
|
-
operator['~'] = a=>~a
|
|
39
|
+
operator('~', 13, -1)
|
|
40
|
+
evaluate.operator['~'] = a=>~a
|
|
19
41
|
|
|
20
|
-
// ...
|
|
21
|
-
// unary[1]['...']=true
|
|
22
|
-
|
|
23
|
-
// ;
|
|
24
|
-
binary[';'] = 1
|
|
42
|
+
// TODO ...
|
|
25
43
|
|
|
26
44
|
// ?:
|
|
27
|
-
operator['?:']=(a,b,c)=>a?b:c
|
|
28
|
-
|
|
45
|
+
evaluate.operator['?:']=(a,b,c)=>a?b:c
|
|
46
|
+
operator('?', 3, (node) => {
|
|
47
|
+
if (!node) err('Expected expression')
|
|
29
48
|
let a, b
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return ['?:',node, a, b]
|
|
49
|
+
skip(), parse.space(), a = expr()
|
|
50
|
+
if (code() !== 58) err('Expected :')
|
|
51
|
+
skip(), parse.space(), b = expr()
|
|
52
|
+
return ['?:', node, a, b]
|
|
35
53
|
})
|
|
54
|
+
// operator(':')
|
|
36
55
|
|
|
37
56
|
// /**/, //
|
|
38
|
-
|
|
39
|
-
|
|
57
|
+
parse.space = cc => {
|
|
58
|
+
while (cc = code(), cc <= 32) {
|
|
59
|
+
skip()
|
|
60
|
+
if (code() === 47)
|
|
61
|
+
// /**/
|
|
62
|
+
if (code(1) === 42) skip(2), skip(c => c !== 42 && code(1) !== 47), skip(2)
|
|
63
|
+
// //
|
|
64
|
+
else if (code(1) === 47) skip(2), skip(c => c >= 32)
|
|
65
|
+
}
|
|
66
|
+
return cc
|
|
67
|
+
}
|
|
40
68
|
|
|
41
69
|
// in
|
|
42
70
|
evaluate.operator['in'] = (a,b)=>a in b
|
|
43
|
-
|
|
71
|
+
operator('in', 10, (node) => code(2) <= 32 && [skip(2), '"'+node+'"', expr(10)])
|
|
44
72
|
|
|
45
73
|
// []
|
|
46
|
-
operator['['] = (...args) => Array(...args)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
skip(), node
|
|
53
|
-
) : null
|
|
54
|
-
)
|
|
74
|
+
evaluate.operator['['] = (...args) => Array(...args)
|
|
75
|
+
// as operator it's faster to lookup (no need to call extra rule check), smaller and no conflict with word names
|
|
76
|
+
operator('[', 20, (node,arg) => !node && (
|
|
77
|
+
skip(), arg=expr(0,93), skip(),
|
|
78
|
+
!arg ? ['['] : arg[0] === ',' ? (arg[0]='[',arg) : ['[',arg]
|
|
79
|
+
))
|
|
55
80
|
|
|
56
81
|
// {}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
82
|
+
parse.token.unshift((cc, node) => (
|
|
83
|
+
cc === 123 && (skip(), node = map(['{', expr(0,125)]), skip(), node)
|
|
84
|
+
))
|
|
85
|
+
|
|
86
|
+
operator('}')
|
|
87
|
+
operator(':', 0)
|
|
88
|
+
evaluate.operator['{'] = (...args)=>Object.fromEntries(args)
|
|
89
|
+
evaluate.operator[':'] = (a,b)=>[a,b]
|
|
61
90
|
|
|
62
91
|
const map = (n, args) => {
|
|
63
|
-
if (n[1]
|
|
92
|
+
if (!n[1]) args = []
|
|
64
93
|
else if (n[1][0]==':') args = [n[1]]
|
|
65
94
|
else if (n[1][0]==',') args = n[1].slice(1)
|
|
66
95
|
return ['{', ...args]
|
|
67
96
|
}
|
|
68
97
|
|
|
69
|
-
|
|
70
98
|
// TODO: strings interpolation
|
|
71
99
|
|
|
72
|
-
export
|
|
100
|
+
export default parse
|
|
73
101
|
export { parse, evaluate }
|
package/justin.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var e,r,
|
|
1
|
+
var e,r,t=e=>Array.isArray(e)&&("string"==typeof e[0]||t(e[0])),a=(e,r={},o,l)=>t(e)?("string"==typeof(o=e[0])&&(l=n[o]),"function"!=typeof(o=l||a(o,r))?o:o.call(...e.map((e=>a(e,r))))):e&&"string"==typeof e?'"'===e[0]?e.slice(1,-1):"@"===e[0]?e.slice(1):e in r?r[e]:e:e,o=e=>(...r)=>r.reduce(e),n=a.operator={"!":e=>!e,"++":e=>++e,"--":e=>--e,".":o(((e,r)=>e&&e[r])),"%":o(((e,r)=>e%r)),"/":o(((e,r)=>e/r)),"*":o(((e,r)=>e*r)),"+":o(((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),",":o(((e,r)=>r))},l=15,s=(t,a)=>(r=t,e=0,a=c(),e<r.length?p():a.valueOf()),p=(t="Bad syntax")=>{throw Error(t+" `"+r[e]+"` at "+e)},u=(t=1,a=e)=>{if("number"==typeof t)e+=t;else for(;t(f());)e++;return r.slice(a,e)},f=(t=0)=>r.charCodeAt(e+t),i=(t=1)=>r.substr(e,t),c=(e=0,r,t,a,o=0,n,l)=>{for(;(t=s.space())&&(l=h[t]?.(a,e)||!a&&d(t));)a=l;return r&&t!==r&&p("Unclosed paren"),a},v=(s.space=r=>{for(;(r=f())<=32;)e++;return r},s.token=[e=>(e=u((e=>e>47&&e<58||46==e)))&&((69==f()||101==f())&&(e+=u(2)+u((e=>e>=48&&e<=57))),isNaN(e=new Number(e))?p("Bad number"):e),(e,r)=>34==e&&u()+u((r=>r-e))+u(),e=>u((e=>e>=48&&e<=57||e>=65&&e<=90||e>=97&&e<=122||36==e||95==e||e>=192&&215!=e&&247!=e))]),d=(e,r=0,t)=>{for(;r<v.length;)if(t=v[r++](e))return t},h=[],y=(r,t=0,a=0,o,n=r.charCodeAt(0),l=r.length,v=h[n],d=r.toUpperCase()!==r,y)=>(y=l<2?d?e=>f(1)<=32:e=>1:d?e=>i(l)==r&&f(l)<=32:e=>i(l)==r,o=a?a>0?e=>e&&[u(l),e]:a<0?e=>!e&&[u(l),(c(t-1)||p()).valueOf()]:a:a=>{a=[r,a||p()];do{e+=l,a.push((c(t)||p()).valueOf())}while(s.space()==n&&y());return a},h[n]=(e,r)=>r<t&&y()&&o(e)||v&&v(e,r));for(let r=0,t=[",",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&&[u(),e,"string"==typeof(r=c(18))?'"'+r+'"':r.valueOf()],"[",18,r=>(e++,r=[".",r,c(0,93).valueOf()],e++,r),"]",,,"(",18,(r,t)=>(e++,t=c(0,41),e++,Array.isArray(t)&&","===t[0]?(t[0]=r,t):t?[r,t.valueOf()]:[r]),"(",19,(r,t)=>!r&&(++e,t=c(0,41)||p(),++e,t),")",,,];r<t.length;)y(t[r++],t[r++],t[r++]);y(";",1),y("===",9),y("!==",9);var g=e=>({valueOf:()=>e});s.token.splice(2,0,(e=>116===e&&"true"===i(4)&&u(4)?g(!0):102===e&&"false"===i(5)&&u(5)?g(!1):110===e&&"null"===i(4)&&u(4)?g(null):117===e&&"undefined"===i(9)&&u(9)?g(void 0):null)),s.token[1]=(e,r,t,a)=>{if(34===e||39===e){for(r=i(),u(),a="";(t=f())-e;)92===t?(u(),a+=O[i()]||i()):a+=i(),u();return u(),r+a+r}};var O={n:"\n",r:"\r",t:"\t",b:"\b",f:"\f",v:"\v"};a.operator["**"]=(...e)=>e.reduceRight(((e,r)=>Math.pow(r,e))),y("**",14),y("~",13,-1),a.operator["~"]=e=>~e,a.operator["?:"]=(e,r,t)=>e?r:t,y("?",3,(e=>{let r,t;return e||p("Expected expression"),u(),s.space(),r=c(),58!==f()&&p("Expected :"),u(),s.space(),t=c(),["?:",e,r,t]})),s.space=e=>{for(;(e=f())<=32;)u(),47===f()&&(42===f(1)?(u(2),u((e=>42!==e&&47!==f(1))),u(2)):47===f(1)&&(u(2),u((e=>e>=32))));return e},a.operator.in=(e,r)=>e in r,y("in",10,(e=>f(2)<=32&&[u(2),'"'+e+'"',c(10)])),a.operator["["]=(...e)=>Array(...e),y("[",20,((e,r)=>!e&&(u(),r=c(0,93),u(),r?","===r[0]?(r[0]="[",r):["[",r]:["["]))),s.token.unshift(((e,r)=>123===e&&(u(),r=b(["{",c(0,125)]),u(),r))),y("}"),y(":",0),a.operator["{"]=(...e)=>Object.fromEntries(e),a.operator[":"]=(e,r)=>[e,r];var b=(e,r)=>(e[1]?":"==e[1][0]?r=[e[1]]:","==e[1][0]&&(r=e[1].slice(1)):r=[],["{",...r]),A=s;export{A as default,a as evaluate,s as parse};
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "subscript",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.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
|
-
"
|
|
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 = (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
package/subscript.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var e,r,t,
|
|
1
|
+
var e,r,a=15,t=(a,t)=>(r=a,e=0,t=f(),e<r.length?o():t.valueOf()),o=(a="Bad syntax")=>{throw Error(a+" `"+r[e]+"` at "+e)},n=(a=1,t=e)=>{if("number"==typeof a)e+=a;else for(;a(s());)e++;return r.slice(t,e)},s=(a=0)=>r.charCodeAt(e+a),l=(a=1)=>r.substr(e,a),f=(e=0,r,a,n,s=0,l,f)=>{for(;(a=t.space())&&(f=i[a]?.(n,e)||!n&&p(a));)n=f;return r&&a!==r&&o("Unclosed paren"),n},u=(t.space=r=>{for(;(r=s())<=32;)e++;return r},t.token=[e=>(e=n((e=>e>47&&e<58||46==e)))&&((69==s()||101==s())&&(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))]),p=(e,r=0,a)=>{for(;r<u.length;)if(a=u[r++](e))return a},i=[],c=(r,a=0,u=0,p,c=r.charCodeAt(0),y=r.length,g=i[c],h=r.toUpperCase()!==r,v)=>(v=y<2?h?e=>s(1)<=32:e=>1:h?e=>l(y)==r&&s(y)<=32:e=>l(y)==r,p=u?u>0?e=>e&&[n(y),e]:u<0?e=>!e&&[n(y),(f(a-1)||o()).valueOf()]:u:n=>{n=[r,n||o()];do{e+=y,n.push((f(a)||o()).valueOf())}while(t.space()==c&&v());return n},i[c]=(e,r)=>r<a&&v()&&p(e)||g&&g(e,r));for(let r=0,t=[",",1,,"|",6,,"||",4,,"&",8,,"&&",5,,"^",7,,"==",9,,"!=",9,,">",10,,">=",10,,">>",11,,">>>",11,,"<",10,,"<=",10,,"<<",11,,"+",12,,"+",a,-1,"++",a,-1,"++",a,1,"-",12,,"-",a,-1,"--",a,-1,"--",a,1,"!",a,-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,a)=>(e++,a=f(0,41),e++,Array.isArray(a)&&","===a[0]?(a[0]=r,a):a?[r,a.valueOf()]:[r]),"(",19,(r,a)=>!r&&(++e,a=f(0,41)||o(),++e,a),")",,,];r<t.length;)c(t[r++],t[r++],t[r++]);var y=t,g=e=>Array.isArray(e)&&("string"==typeof e[0]||g(e[0])),h=(e,r={},a,t)=>g(e)?("string"==typeof(a=e[0])&&(t=d[a]),"function"!=typeof(a=t||h(a,r))?a:a.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=e=>(...r)=>r.reduce(e),d=h.operator={"!":e=>!e,"++":e=>++e,"--":e=>--e,".":v(((e,r)=>e&&e[r])),"%":v(((e,r)=>e%r)),"/":v(((e,r)=>e/r)),"*":v(((e,r)=>e*r)),"+":v(((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),",":v(((e,r)=>r))},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/parse.js
DELETED
|
@@ -1,134 +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
|
|
54
|
-
float = (number, c, isDigit) => {
|
|
55
|
-
number = skip(isDigit = c => c >= 48 && c <= 57) || ''
|
|
56
|
-
if (code() === PERIOD) index++, number += '.' + skip(isDigit)
|
|
57
|
-
if (number) {
|
|
58
|
-
if ((c = code()) === 69 || c === 101) { // e, E
|
|
59
|
-
index++, number += 'e'
|
|
60
|
-
if ((c=code()) === PLUS || c === MINUS) // +-
|
|
61
|
-
number += char(), index++
|
|
62
|
-
number += skip(isDigit)
|
|
63
|
-
}
|
|
64
|
-
return parseFloat(number)
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
|
|
68
|
-
// "a"
|
|
69
|
-
string = (q, qc) => q === 34 ? (qc = char(), index++, qc) + skip(c => c !== q) + (index++, qc) : null,
|
|
70
|
-
|
|
71
|
-
// (...exp)
|
|
72
|
-
group = (c, node) => c === OPAREN ? (index++, node = expr(CPAREN), index++, node) : null,
|
|
73
|
-
|
|
74
|
-
// var or literal
|
|
75
|
-
id = name => (name = skip(c =>
|
|
76
|
-
(c >= 48 && c <= 57) || // 0..9
|
|
77
|
-
(c >= 65 && c <= 90) || // A...Z
|
|
78
|
-
(c >= 97 && c <= 122) || // a...z
|
|
79
|
-
c == 36 || c == 95 || // $, _,
|
|
80
|
-
c >= 192 // any non-ASCII
|
|
81
|
-
)) && literal.hasOwnProperty(name) ? literal[name] : name,
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
// ------------ util
|
|
85
|
-
code = () => current.charCodeAt(index), // current char code
|
|
86
|
-
char = (n=1) => current.substr(index, n), // skip n chars
|
|
87
|
-
err = (msg) => { throw Error(msg + ' at ' + index) },
|
|
88
|
-
skip = (is=1, from=index) => { // consume N or until condition matches
|
|
89
|
-
if (typeof is === 'number') index += is
|
|
90
|
-
else while (is(code())) ++index > current.length && err('Unexpected end ' + is) // 1 + true === 2;
|
|
91
|
-
return index > from ? current.slice(from, index) : null
|
|
92
|
-
},
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
// ----------- config
|
|
96
|
-
token = parse.token = [ group, float, string, id ],
|
|
97
|
-
|
|
98
|
-
literal = parse.literal = {true:true, false:false, null:null},
|
|
99
|
-
|
|
100
|
-
postfix = parse.postfix = [
|
|
101
|
-
// a.b[c](d), 3 in 1 for performance
|
|
102
|
-
(node, cc, arg) => {
|
|
103
|
-
if (cc === PERIOD) index++, space(), node = ['.', node, '"'+id()+'"']
|
|
104
|
-
else if (cc === OBRACK) index++, node = ['.', node, expr(CBRACK)], index++
|
|
105
|
-
else if (cc === OPAREN)
|
|
106
|
-
index++, arg=expr(CPAREN),
|
|
107
|
-
node = Array.isArray(arg) && arg[0]===',' ? (arg[0]=node, arg) : arg == null ? [node] : [node, arg],
|
|
108
|
-
index++
|
|
109
|
-
return node
|
|
110
|
-
},
|
|
111
|
-
|
|
112
|
-
// a++, a--
|
|
113
|
-
(node, cc) => (cc===0x2b || cc===0x2d) && current.charCodeAt(index+1)===cc ? [skip(2), node] : node,
|
|
114
|
-
],
|
|
115
|
-
|
|
116
|
-
unary = parse.unary = {
|
|
117
|
-
'-': 17,
|
|
118
|
-
'!': 17,
|
|
119
|
-
'+': 17,
|
|
120
|
-
'++': 17,
|
|
121
|
-
'--': 17
|
|
122
|
-
},
|
|
123
|
-
|
|
124
|
-
binary = parse.binary = {
|
|
125
|
-
',': 1,
|
|
126
|
-
'||': 6, '&&': 7, '|': 8, '^': 9, '&': 10,
|
|
127
|
-
'==': 11, '!=': 11,
|
|
128
|
-
'<': 12, '>': 12, '<=': 12, '>=': 12,
|
|
129
|
-
'<<': 13, '>>': 13, '>>>': 13,
|
|
130
|
-
'+': 14, '-': 14,
|
|
131
|
-
'*': 15, '/': 15, '%': 15
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export default parse
|