subscript 2.0.0 → 3.0.3
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 +73 -74
- package/evaluate.js +57 -0
- package/justin.js +72 -43
- package/justin.min.js +1 -1
- package/package.json +21 -5
- package/parse.js +134 -0
- package/subscript.js +2 -174
- package/subscript.min.js +1 -1
package/README.md
CHANGED
|
@@ -1,12 +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
|
-
*
|
|
6
|
-
* Any _subscript_ fragment can be copy-pasted to
|
|
5
|
+
* Well-known syntax
|
|
6
|
+
* Any _subscript_ fragment can be copy-pasted to any target language
|
|
7
7
|
* It's tiny <sub></sub>
|
|
8
|
-
* It's fast ([see performance](#performance))
|
|
9
|
-
* Enables operators overloading
|
|
8
|
+
* It's very fast ([see performance](#performance))
|
|
10
9
|
* Configurable & extensible
|
|
11
10
|
* Trivial to use...
|
|
12
11
|
|
|
@@ -16,65 +15,33 @@ let fn = subscript(`a.b + c(d - 1)`)
|
|
|
16
15
|
fn({ a: { b:1 }, c: x => x * 2, d: 3 }) // 5
|
|
17
16
|
```
|
|
18
17
|
|
|
19
|
-
##
|
|
18
|
+
## Motivation
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
* expressions evaluators (math, arithmetic)
|
|
23
|
-
* subsets of languages (eg. jessie, justin) <!-- see sonr -->
|
|
24
|
-
* prototyping language features (eg. pipe operator)
|
|
25
|
-
* simulating languages (eg. glsl <!--, FORTRAN?, COBOL?-->)
|
|
26
|
-
* sandboxes, playgrounds
|
|
27
|
-
* safe, secure eval
|
|
28
|
-
* custom DSL
|
|
20
|
+
_Subscript_ is designed to be useful for:
|
|
29
21
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
## Lispy tree
|
|
38
|
-
|
|
39
|
-
It compiles code to lispy calltree (compatible with [frisk](https://npmjs.com/frisk)). Why?
|
|
22
|
+
* templates (perfect match with [template parts](https://github.com/github/template-parts))
|
|
23
|
+
* expressions evaluators, calculators
|
|
24
|
+
* subsets of languages (eg. [justin](#justin)) <!-- see sonr, mineural -->
|
|
25
|
+
* mocking language features (eg. pipe operator)
|
|
26
|
+
* sandboxes, playgrounds, safe eval
|
|
27
|
+
* custom DSL
|
|
40
28
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
+ overloading operators by context
|
|
44
|
-
+ simple manual evaluation, debugging
|
|
45
|
-
+ conventional form
|
|
46
|
-
+ one-liner docs...
|
|
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:
|
|
47
31
|
|
|
48
32
|
```js
|
|
49
33
|
import {evaluate} from 'subscript.js'
|
|
50
|
-
evaluate(['+', ['*', 'min', 60], '"sec"'], { min: 5 }) // min*60 + "sec" == "300sec"
|
|
51
|
-
```
|
|
52
34
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
By default subscript reserves:
|
|
56
|
-
|
|
57
|
-
* `[]`, `()` groups
|
|
58
|
-
* `true`, `false`, `null` literals
|
|
59
|
-
* `"` quotes.
|
|
60
|
-
|
|
61
|
-
All primitives are extensible via `parse.literal`, `parse.quote`, `parse.group`, `parse.comment` dicts.
|
|
62
|
-
|
|
63
|
-
```js
|
|
64
|
-
import { parse } from 'subscript.js'
|
|
35
|
+
evaluate(['+', ['*', 'min', 60], '"sec"'], { min: 5 }) // min*60 + "sec" == "300sec"
|
|
36
|
+
```
|
|
65
37
|
|
|
66
|
-
parse.quote["'"] = "'"
|
|
67
|
-
parse.comment["//"] = "\n"
|
|
68
|
-
|
|
69
|
-
parse(`'a' + 'b' // concat`) // ['+', 'a':String, 'b':String]
|
|
70
|
-
```
|
|
71
38
|
|
|
72
39
|
## Operators
|
|
73
40
|
|
|
74
41
|
Default operators include common operators for the listed languages in the following precedence:
|
|
75
42
|
|
|
76
|
-
*
|
|
77
|
-
* `! + - ++ --` prefix
|
|
43
|
+
* `++ --` unary postfix
|
|
44
|
+
* `! + - ++ --` unary prefix
|
|
78
45
|
* `* / %`
|
|
79
46
|
* `+ -`
|
|
80
47
|
* `<< >> >>>`
|
|
@@ -86,7 +53,7 @@ Default operators include common operators for the listed languages in the follo
|
|
|
86
53
|
* `&&`
|
|
87
54
|
* `||`
|
|
88
55
|
|
|
89
|
-
All other operators can be extended via `parse.binary`, `parse.
|
|
56
|
+
All other operators can be extended via `parse.binary`, `parse.unary` and `evaluate.operator`.
|
|
90
57
|
|
|
91
58
|
```js
|
|
92
59
|
import { parse, evaluate } from 'subscript.js'
|
|
@@ -107,24 +74,21 @@ let tree = parse(`
|
|
|
107
74
|
evaluate(tree, { Math, map, take, interval, gaussian })
|
|
108
75
|
```
|
|
109
76
|
|
|
110
|
-
##
|
|
111
|
-
|
|
112
|
-
Transform rules are applied to raw parsed calltree groups, eg.:
|
|
77
|
+
## Extending
|
|
113
78
|
|
|
114
|
-
|
|
115
|
-
* Property access `a.b.c` → `['.', 'a', 'b', 'c']` → `['.', 'a', '"b"', '"c"']`
|
|
79
|
+
By default subscript detects the following tokens:
|
|
116
80
|
|
|
117
|
-
|
|
81
|
+
* `"` strings
|
|
82
|
+
* `1.2e+3` floats
|
|
83
|
+
* `true`, `false`, `null` literals
|
|
84
|
+
* `()` expression groups or fn calls
|
|
85
|
+
* `.`, `[]` property access
|
|
118
86
|
|
|
119
|
-
|
|
120
|
-
import { parse, evaluate } from 'subscript.js'
|
|
87
|
+
Literals can be extended via `parse.literal` dict.
|
|
121
88
|
|
|
122
|
-
|
|
123
|
-
parse.binary[':'] = parse.binary['?'] = 5
|
|
124
|
-
parse.map[':'] = node => node[1][0]=='?' ? ['?:',node[1][1],node[1][2],node[2]] : node // [:, [?, a, b], c] → [?:, a, b, c]
|
|
89
|
+
Token parsers are extensible via `parse.token` list, can be added support of _regex_, _array_, _object_, _interpolated string_ and others.
|
|
125
90
|
|
|
126
|
-
|
|
127
|
-
```
|
|
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`.
|
|
128
92
|
|
|
129
93
|
|
|
130
94
|
## Justin
|
|
@@ -134,20 +98,24 @@ It adds support for:
|
|
|
134
98
|
|
|
135
99
|
+ `**` binary operator
|
|
136
100
|
+ `~` unary operator
|
|
101
|
+
+ `'` strings
|
|
137
102
|
+ `?:` ternary operator
|
|
138
103
|
+ `[...]` Array literal
|
|
139
104
|
+ `{...}` Object literal
|
|
140
105
|
+ `in` binary operator
|
|
141
106
|
+ `;` expression separator
|
|
142
|
-
+
|
|
143
|
-
+
|
|
107
|
+
+ unary word operators
|
|
108
|
+
<!-- + `//, /* */` comments -->
|
|
109
|
+
<!-- + `undefined` literal -->
|
|
110
|
+
<!-- + `?` chaining operator -->
|
|
144
111
|
<!-- + `...x` unary operator -->
|
|
145
112
|
<!-- + strings interpolation -->
|
|
146
113
|
|
|
147
114
|
```js
|
|
148
|
-
import { parse } from 'subscript/justin.js'
|
|
115
|
+
import { parse, evaluate } from 'subscript/justin.js'
|
|
149
116
|
|
|
150
|
-
let
|
|
117
|
+
let xy = parse('{ x: 1, "y": 2+2 }["x"]') // ['[', {x:1, y: ['+', 2, 2]}, '"x"']
|
|
118
|
+
evaluate(xy) // 1
|
|
151
119
|
```
|
|
152
120
|
|
|
153
121
|
<!--
|
|
@@ -155,6 +123,27 @@ let tree = parse('{x:1, "y":2+2}["x"]') // ['[', {x:1, y: ['+', 2, 2]}, '"x"']
|
|
|
155
123
|
|
|
156
124
|
These are custom DSL operators snippets for your inspiration:
|
|
157
125
|
|
|
126
|
+
|
|
127
|
+
```html
|
|
128
|
+
template-parts proposal
|
|
129
|
+
<template id="timer">
|
|
130
|
+
<time datetime="{{ date.toUTCString() }}">{{ date.toLocaleTimeString() }}</time>
|
|
131
|
+
</template>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
// a.b.c
|
|
135
|
+
// (node, c) => c === PERIOD ? (index++, space(), ['.', node, '"'+id()+'"']) : node,
|
|
136
|
+
|
|
137
|
+
// a[b][c]
|
|
138
|
+
// (node, c) => c === OBRACK ? (index++, node=['.', node, expr(CBRACK)], index++, node) : node,
|
|
139
|
+
|
|
140
|
+
// a(b)(c)
|
|
141
|
+
// (node, c, arg) => c === OPAREN ? (
|
|
142
|
+
// index++, arg=expr(CPAREN),
|
|
143
|
+
// node = Array.isArray(arg) && arg[0]===',' ? (arg[0]=node, arg) : arg == null ? [node] : [node, arg],
|
|
144
|
+
// index++, node
|
|
145
|
+
// ) : node,
|
|
146
|
+
|
|
158
147
|
<details>
|
|
159
148
|
<summary>Keyed arrays <code>[a:1, b:2, c:3]</code></summary>
|
|
160
149
|
|
|
@@ -254,6 +243,16 @@ These are custom DSL operators snippets for your inspiration:
|
|
|
254
243
|
```
|
|
255
244
|
|
|
256
245
|
</details>
|
|
246
|
+
|
|
247
|
+
like versions, units, hashes, urls, regexes etc
|
|
248
|
+
|
|
249
|
+
2a as `2*a`
|
|
250
|
+
|
|
251
|
+
string interpolation ` ${} 1 ${} `
|
|
252
|
+
|
|
253
|
+
keyed arrays? [a:1, b:2, c:3]
|
|
254
|
+
|
|
255
|
+
Examples: sonr, template-parts, neural-chunks
|
|
257
256
|
-->
|
|
258
257
|
|
|
259
258
|
## Performance
|
|
@@ -264,11 +263,11 @@ Subscript shows relatively good performance within other evaluators:
|
|
|
264
263
|
// 1 + (a * b / c % d) - 2.0 + -3e-3 * +4.4e4 / f.g[0] - i.j(+k == 1)(0)
|
|
265
264
|
// parse 30k times
|
|
266
265
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
266
|
+
subscript: ~280 ms
|
|
267
|
+
jsep: ~281 ms
|
|
268
|
+
expr-eval: ~480 ms
|
|
270
269
|
jexl: ~1200 ms
|
|
271
|
-
new Function:
|
|
270
|
+
new Function: ~1400 ms
|
|
272
271
|
```
|
|
273
272
|
|
|
274
273
|
## See also
|
|
@@ -280,6 +279,6 @@ new Function: 1466 ms
|
|
|
280
279
|
* [expression-eval](https://github.com/donmccurdy/expression-eval)
|
|
281
280
|
* [jsep](https://github.com/EricSmekens/jsep)
|
|
282
281
|
* [string-math](https://github.com/devrafalko/string-math)
|
|
283
|
-
|
|
282
|
+
* [nerdamer](https://github.com/jiggzson/nerdamer)
|
|
284
283
|
|
|
285
284
|
<p align=center>🕉</p>
|
package/evaluate.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export const isCmd = a => Array.isArray(a) && (typeof a[0] === 'string' || isCmd(a[0])),
|
|
2
|
+
|
|
3
|
+
// calltree → result
|
|
4
|
+
evaluate = (s, ctx={}, c, op) => {
|
|
5
|
+
if (isCmd(s)) {
|
|
6
|
+
c = s[0]
|
|
7
|
+
if (typeof c === 'string') op = operator[c]
|
|
8
|
+
c = op || evaluate(c, ctx) // [[a,b], c]
|
|
9
|
+
if (typeof c !== 'function') return c
|
|
10
|
+
|
|
11
|
+
return c.call(...s.map(a => evaluate(a,ctx)))
|
|
12
|
+
}
|
|
13
|
+
if (s && typeof s === 'string')
|
|
14
|
+
return s[0] === '"' ? s.slice(1,-1)
|
|
15
|
+
: s[0]==='@' ? s.slice(1)
|
|
16
|
+
: s in ctx ? ctx[s] : s
|
|
17
|
+
|
|
18
|
+
return s
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
// op evaluators
|
|
22
|
+
// multiple args allows shortcuts, lisp compatible, easy manual eval, functions anyways take multiple arguments
|
|
23
|
+
operator = evaluate.operator = {
|
|
24
|
+
'!':a=>!a,
|
|
25
|
+
'++':a=>++a,
|
|
26
|
+
'--':a=>--a,
|
|
27
|
+
|
|
28
|
+
'.':(...a)=>a.reduce((a,b)=>a?a[b]:a),
|
|
29
|
+
|
|
30
|
+
'%':(...a)=>a.reduce((a,b)=>a%b),
|
|
31
|
+
'/':(...a)=>a.reduce((a,b)=>a/b),
|
|
32
|
+
'*':(...a)=>a.reduce((a,b)=>a*b),
|
|
33
|
+
|
|
34
|
+
'+':(...a)=>a.reduce((a,b)=>a+b),
|
|
35
|
+
'-':(...a)=>a.length < 2 ? -a : a.reduce((a,b)=>a-b),
|
|
36
|
+
|
|
37
|
+
'>>>':(a,b)=>a>>>b,
|
|
38
|
+
'>>':(a,b)=>a>>b,
|
|
39
|
+
'<<':(a,b)=>a<<b,
|
|
40
|
+
|
|
41
|
+
'>=':(a,b)=>a>=b,
|
|
42
|
+
'>':(a,b)=>a>b,
|
|
43
|
+
'<=':(a,b)=>a<=b,
|
|
44
|
+
'<':(a,b)=>a<b,
|
|
45
|
+
|
|
46
|
+
'!=':(a,b)=>a!=b,
|
|
47
|
+
'==':(a,b)=>a==b,
|
|
48
|
+
|
|
49
|
+
'&':(a,b)=>a&b,
|
|
50
|
+
'^':(a,b)=>a^b,
|
|
51
|
+
'|':(a,b)=>a|b,
|
|
52
|
+
'&&':(...a)=>a.every(Boolean),
|
|
53
|
+
'||':(...a)=>a.some(Boolean),
|
|
54
|
+
',':(...a)=>a.reduce((a,b)=>(a,b))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export default evaluate
|
package/justin.js
CHANGED
|
@@ -1,68 +1,97 @@
|
|
|
1
1
|
// justin lang https://github.com/endojs/Jessie/issues/66
|
|
2
|
-
import {
|
|
2
|
+
import {evaluate, operator} from './evaluate.js'
|
|
3
|
+
import {parse, binary, unary, postfix, token, literal,
|
|
4
|
+
code, char, skip, space, expr} from './parse.js'
|
|
3
5
|
|
|
4
6
|
// undefined
|
|
5
|
-
|
|
7
|
+
literal['undefined'] = undefined
|
|
8
|
+
|
|
9
|
+
// "' with /
|
|
10
|
+
token[2] = (q, qc, c, str) => {
|
|
11
|
+
if (q !== 34 && q !== 39) return
|
|
12
|
+
qc = char(), skip(), str = ''
|
|
13
|
+
while (c=code(), c-q) {
|
|
14
|
+
if (c === 92) skip(), str += escape[char()] || char(); else str+=char()
|
|
15
|
+
skip()
|
|
16
|
+
}
|
|
17
|
+
return skip(), qc + str + qc
|
|
18
|
+
}
|
|
19
|
+
const escape = {n:'\n', r:'\r', t:'\t', b:'\b', f:'\f', v:'\v'}
|
|
20
|
+
|
|
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
|
+
|
|
6
35
|
|
|
7
36
|
// **
|
|
8
|
-
binary
|
|
37
|
+
binary['**'] = 16
|
|
38
|
+
operator['**'] = (...args)=>args.reduceRight((a,b)=>Math.pow(b,a))
|
|
9
39
|
|
|
10
40
|
// ~
|
|
11
|
-
unary[
|
|
41
|
+
unary['~'] = 17
|
|
42
|
+
operator['~'] = a=>~a
|
|
12
43
|
|
|
13
44
|
// ...
|
|
14
45
|
// unary[1]['...']=true
|
|
15
46
|
|
|
16
47
|
// ;
|
|
17
|
-
binary[
|
|
48
|
+
binary[';'] = 1
|
|
18
49
|
|
|
19
50
|
// ?:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
51
|
+
operator['?:']=(a,b,c)=>a?b:c
|
|
52
|
+
postfix.push(node => {
|
|
53
|
+
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]
|
|
59
|
+
})
|
|
25
60
|
|
|
26
61
|
// /**/, //
|
|
27
|
-
comments['/*']='*/'
|
|
28
|
-
comments['//']='\n'
|
|
62
|
+
// comments['/*']='*/'
|
|
63
|
+
// comments['//']='\n'
|
|
29
64
|
|
|
30
65
|
// in
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
66
|
+
evaluate.operator['in'] = (a,b)=>a in b
|
|
67
|
+
parse.postfix.unshift(node => (char(2) === 'in' ? (skip(2), ['in', '"' + node + '"', expr()]) : node))
|
|
68
|
+
|
|
69
|
+
// []
|
|
70
|
+
operator['['] = (...args) => Array(...args)
|
|
71
|
+
token.push((node, arg) =>
|
|
72
|
+
code() === 91 ?
|
|
73
|
+
(
|
|
74
|
+
skip(), arg=expr(-1,93),
|
|
75
|
+
node = arg==null ? ['['] : arg[0] === ',' ? (arg[0]='[',arg) : ['[',arg],
|
|
76
|
+
skip(), node
|
|
77
|
+
) : null
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
// {}
|
|
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]
|
|
85
|
+
|
|
86
|
+
const map = (n, args) => {
|
|
87
|
+
if (n[1]==null) args = []
|
|
88
|
+
else if (n[1][0]==':') args = [n[1]]
|
|
89
|
+
else if (n[1][0]==',') args = n[1].slice(1)
|
|
46
90
|
return ['{', ...args]
|
|
47
91
|
}
|
|
48
92
|
|
|
49
|
-
// groups ['{']='}'
|
|
50
|
-
// binary.unshift({'[':a=>Array(...a), '{':a=>Object.fromEntries(a)})
|
|
51
|
-
// transforms['['] = s => s.length > 2 ? s
|
|
52
|
-
// : s[1] === undefined
|
|
53
|
-
// ? [Array]
|
|
54
|
-
// // ['[',[',',a,[',',b],c]] → ['[',[',',a,,b,c]]
|
|
55
|
-
// : [Array].concat(isnode(s[1]) ? s[1].slice(1).reduce((a,b)=>a.push(...(isnode(b)&&b[0]==','?(b[0]=undefined,b):[b]))&&a,[])
|
|
56
|
-
// : s[1])
|
|
57
|
-
// transforms['{'] = (s,entries) => {
|
|
58
|
-
// if (s[1]===undefined) return {}
|
|
59
|
-
// if (s[1][0]==':') entries = [s[1].slice(1)]
|
|
60
|
-
// else entries = s[1].slice(1).map(n=>n.slice(1))
|
|
61
|
-
// entries = entries.map(n=>quotes[n[0][0]]?[n[0].slice(1,-1),n[1]]:n)
|
|
62
|
-
// return Object.fromEntries(entries)
|
|
63
|
-
// }
|
|
64
93
|
|
|
65
94
|
// TODO: strings interpolation
|
|
66
95
|
|
|
67
96
|
export { default } from './subscript.js';
|
|
68
|
-
export
|
|
97
|
+
export { parse, evaluate }
|
package/justin.min.js
CHANGED
|
@@ -1 +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};
|
package/package.json
CHANGED
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "subscript",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.3",
|
|
4
4
|
"description": "Microlanguage with common syntax for JS/C++/Python/Rust",
|
|
5
5
|
"main": "subscript.js",
|
|
6
|
+
"type": "module",
|
|
6
7
|
"files": [
|
|
8
|
+
"parse.js",
|
|
9
|
+
"evaluate.js",
|
|
10
|
+
"subscript.js",
|
|
7
11
|
"subscript.min.js",
|
|
8
12
|
"justin.js",
|
|
9
13
|
"justin.min.js"
|
|
10
14
|
],
|
|
11
15
|
"directories": {
|
|
12
|
-
"lib": "lib"
|
|
16
|
+
"lib": "lib",
|
|
17
|
+
"src": "src",
|
|
18
|
+
"test": "test"
|
|
13
19
|
},
|
|
14
20
|
"scripts": {
|
|
15
|
-
"
|
|
21
|
+
"build": "esbuild subscript.js --bundle --format=esm --minify --outfile=subscript.min.js",
|
|
22
|
+
"minify": "terser subscript.min.js -o subscript.min.js --module -c passes=3 -m",
|
|
23
|
+
"build-justin": "esbuild justin.js --bundle --format=esm --minify --outfile=justin.min.js",
|
|
24
|
+
"minify-justin": "terser justin.min.js -o justin.min.js --module -c passes=3 -m",
|
|
25
|
+
"test": "node test"
|
|
16
26
|
},
|
|
17
27
|
"repository": {
|
|
18
28
|
"type": "git",
|
|
@@ -33,12 +43,18 @@
|
|
|
33
43
|
"dsl",
|
|
34
44
|
"json",
|
|
35
45
|
"calculator",
|
|
36
|
-
"calc"
|
|
46
|
+
"calc",
|
|
47
|
+
"lisp",
|
|
48
|
+
"frisk"
|
|
37
49
|
],
|
|
38
50
|
"author": "Dmitry Iv.",
|
|
39
51
|
"license": "ISC",
|
|
40
52
|
"bugs": {
|
|
41
53
|
"url": "https://github.com/spectjs/subscript/issues"
|
|
42
54
|
},
|
|
43
|
-
"homepage": "https://github.com/spectjs/subscript#readme"
|
|
55
|
+
"homepage": "https://github.com/spectjs/subscript#readme",
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"esbuild": "^0.13.14",
|
|
58
|
+
"terser": "^5.10.0"
|
|
59
|
+
}
|
|
44
60
|
}
|
package/parse.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
const PERIOD = 46, OPAREN = 40, CPAREN = 41, OBRACK = 91, CBRACK = 93, PLUS = 43, MINUS = 45
|
|
2
|
+
|
|
3
|
+
export let index, current, lastOp, end
|
|
4
|
+
|
|
5
|
+
export const parse = (str, tree) => (current=str, index=lastOp=0, tree=expr(), index < current.length ? err() : tree),
|
|
6
|
+
|
|
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
|
|
23
|
+
|
|
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
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
expr = (prec=-1, curEnd) => {
|
|
29
|
+
space()
|
|
30
|
+
|
|
31
|
+
let cc = code(), op, node, i=0, mapped, from=index, prevEnd
|
|
32
|
+
|
|
33
|
+
if (curEnd) if (cc === curEnd) return; else prevEnd = end, end = curEnd // global end marker saves operator lookups
|
|
34
|
+
|
|
35
|
+
// parse node by token parsers (direct loop is faster than token.find)
|
|
36
|
+
while (from===index && i < token.length) node = token[i++](cc)
|
|
37
|
+
|
|
38
|
+
// unary prefix
|
|
39
|
+
if (from===index) (op = operator(unary)) && (index += op[2], node = [op[0], expr(op[1])])
|
|
40
|
+
|
|
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()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (curEnd) end = code() !== curEnd ? err('Unclosed paren') : prevEnd
|
|
58
|
+
// if (node == null) err('Missing argument')
|
|
59
|
+
|
|
60
|
+
return node;
|
|
61
|
+
},
|
|
62
|
+
|
|
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
|
+
(
|
|
81
|
+
(c >= 48 && c <= 57) || // 0..9
|
|
82
|
+
(c >= 65 && c <= 90) || // A...Z
|
|
83
|
+
(c >= 97 && c <= 122) || // a...z
|
|
84
|
+
c == 36 || c == 95 || // $, _,
|
|
85
|
+
c >= 192 // any non-ASCII
|
|
86
|
+
)
|
|
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]
|
|
110
|
+
|
|
111
|
+
return node
|
|
112
|
+
}
|
|
113
|
+
],
|
|
114
|
+
|
|
115
|
+
unary = parse.unary = {
|
|
116
|
+
'-': 17,
|
|
117
|
+
'!': 17,
|
|
118
|
+
'+': 17,
|
|
119
|
+
'++': 17,
|
|
120
|
+
'--': 17
|
|
121
|
+
},
|
|
122
|
+
|
|
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
|
+
|
|
133
|
+
|
|
134
|
+
export default parse
|
package/subscript.js
CHANGED
|
@@ -1,177 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
(c >= 97 && c <= 122) || // a...z
|
|
4
|
-
(c == 36 || c == 95) || // $, _,
|
|
5
|
-
c >= 192, // any non-ASCII
|
|
6
|
-
isIdentifierPart = c => isDigit(c) || isIdentifierStart(c),
|
|
7
|
-
isSpace = c => c <= 32,
|
|
8
|
-
isCmd = (a,op) => Array.isArray(a) && a.length && a[0] && (op ? a[0]===op : typeof a[0] === 'string' || isCmd(a[0])),
|
|
9
|
-
map = (node, t) => isCmd(node) ? (t = parse.map[node[0]], t?t(node):node) : node
|
|
10
|
-
|
|
11
|
-
const parse = (expr, index=0, curOp, curEnd) => {
|
|
12
|
-
const char = (n=1) => expr.substr(index, n), // get next n chars (as fast as expr[index])
|
|
13
|
-
code = () => expr.charCodeAt(index),
|
|
14
|
-
|
|
15
|
-
err = msg => {throw Error(msg + ' at ' + index)},
|
|
16
|
-
|
|
17
|
-
// skip index until condition matches
|
|
18
|
-
skip = is => { while (index < expr.length && is(code())) index++ },
|
|
19
|
-
|
|
20
|
-
// skip index, return skipped part
|
|
21
|
-
consume = is => expr.slice(index, (skip(is), index)),
|
|
22
|
-
|
|
23
|
-
// consume operator that resides within current group by precedence
|
|
24
|
-
consumeOp = (ops, op, prec, l=3) => {
|
|
25
|
-
if (index >= expr.length) return
|
|
26
|
-
|
|
27
|
-
// memoize by index - saves 20% to perf
|
|
28
|
-
if (index && curOp[3] === index) return curOp
|
|
29
|
-
|
|
30
|
-
// don't look up for end characters - saves 5-10% to perf
|
|
31
|
-
if (curEnd && curEnd === char(curEnd.length)) return
|
|
32
|
-
|
|
33
|
-
// ascending lookup is faster 1-char operators, longer for 2+ char ops
|
|
34
|
-
// for (let i=0, prec0, op0; i < l;) if (prec0=ops[op0=char(++i)]) prec=prec0,op=op0; else if (prec) return opinfo(op, prec)
|
|
35
|
-
while (l) if ((prec=ops[op=char(l--)])!=null) return curOp = [op, prec, parse.group[op], index] //opinfo
|
|
36
|
-
},
|
|
37
|
-
|
|
38
|
-
// `foo.bar(baz)`, `1`, `"abc"`, `(a % 2)`
|
|
39
|
-
consumeGroup = (group, end=curEnd) => {
|
|
40
|
-
index += group[0].length // group always starts with an operator +-b, a(b, +(b, a+b+c, so we skip it
|
|
41
|
-
if (group[2]) curEnd = group[2] // also we write root end marker
|
|
42
|
-
|
|
43
|
-
skip(isSpace);
|
|
44
|
-
|
|
45
|
-
let cc = code(), op, c = char(),
|
|
46
|
-
node='' // indicates "nothing", or "empty", as in [a,,b] - impossible to get as result of parsing
|
|
47
|
-
|
|
48
|
-
// `.` can start off a numeric literal
|
|
49
|
-
if (isDigit(cc)) node = parseInt(consume(isDigit));
|
|
50
|
-
else if (parse.quote[c]) index++, node = c + consume(c=>c!=cc) + c, index++
|
|
51
|
-
else if (isIdentifierStart(cc)) node = (node = consume(isIdentifierPart)) in parse.literal ? parse.literal[node] : node
|
|
52
|
-
// unaries can't be mixed in binary expressions loop due to operator names conflict, must be parsed before
|
|
53
|
-
else if (op = consumeOp(parse.prefix)) node = map([op[0], consumeGroup(op)])
|
|
54
|
-
|
|
55
|
-
skip(isSpace)
|
|
56
|
-
|
|
57
|
-
// consume expression for current precedence or group (== highest precedence)
|
|
58
|
-
while ((op = consumeOp(parse.binary)) && (group[2] || op[1] < group[1])) {
|
|
59
|
-
node = [op[0], node, consumeGroup(op)]
|
|
60
|
-
// consume same-op group, that also saves op lookups
|
|
61
|
-
while (char(op[0].length) === op[0]) node.push(consumeGroup(op))
|
|
62
|
-
node = map(node)
|
|
63
|
-
skip(isSpace)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// if group has end operator eg + a ) or + a ]
|
|
67
|
-
if (group[2]) index+=group[2].length, curEnd=end
|
|
68
|
-
|
|
69
|
-
return node;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return consumeGroup(curOp = ['', 108])
|
|
73
|
-
},
|
|
74
|
-
|
|
75
|
-
// calltree → result
|
|
76
|
-
evaluate = (s, ctx={}, c, op) => {
|
|
77
|
-
if (isCmd(s)) {
|
|
78
|
-
c = s[0]
|
|
79
|
-
if (typeof c === 'string') op = evaluate.operator[c]
|
|
80
|
-
c = op || evaluate(c, ctx) // [[a,b], c]
|
|
81
|
-
if (typeof c !== 'function') return c
|
|
82
|
-
|
|
83
|
-
return c.call(...s.map(a=>evaluate(a,ctx)))
|
|
84
|
-
}
|
|
85
|
-
if (s && typeof s === 'string')
|
|
86
|
-
return parse.quote[s[0]] ? s.slice(1,-1)
|
|
87
|
-
: s[0]==='@' ? s.slice(1)
|
|
88
|
-
: s in ctx ? ctx[s] : s
|
|
89
|
-
|
|
90
|
-
return s
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
Object.assign(parse, {
|
|
94
|
-
literal: {
|
|
95
|
-
true: true,
|
|
96
|
-
false: false,
|
|
97
|
-
null: null
|
|
98
|
-
},
|
|
99
|
-
group: {'(':')','[':']'},
|
|
100
|
-
quote: {'"':'"'},
|
|
101
|
-
comment: {},
|
|
102
|
-
prefix: {
|
|
103
|
-
'-': 2,
|
|
104
|
-
'!': 2,
|
|
105
|
-
'+': 2,
|
|
106
|
-
'(': 2,
|
|
107
|
-
'++': 2,
|
|
108
|
-
'--': 2,
|
|
109
|
-
'.': 1
|
|
110
|
-
},
|
|
111
|
-
postfix: {
|
|
112
|
-
'++': 2,
|
|
113
|
-
'--': 2
|
|
114
|
-
},
|
|
115
|
-
binary: {
|
|
116
|
-
',': 12,
|
|
117
|
-
'||': 11, '&&': 10, '|': 9, '^': 8, '&': 7,
|
|
118
|
-
'==': 6, '!=': 6,
|
|
119
|
-
'<': 5, '>': 5, '<=': 5, '>=': 5,
|
|
120
|
-
'<<': 4, '>>': 4, '>>>': 4,
|
|
121
|
-
'+': 3, '-': 3,
|
|
122
|
-
'*': 2, '/': 2, '%': 2,
|
|
123
|
-
'.': 1, '(': 1, '[': 1,
|
|
124
|
-
'e': 1, 'E': 1
|
|
125
|
-
},
|
|
126
|
-
map: {
|
|
127
|
-
'(': n => n.length < 3 ? n[1] : n.slice(1).reduce(
|
|
128
|
-
(a,b)=>[a].concat(b==='' ? [] : b[0]==',' ? b.slice(1).map(x=>x===''?undefined:x) : [b]),
|
|
129
|
-
), // [(,a,args1,args2] → [[a,...args1],...args2]
|
|
130
|
-
'[': n => (n[0]='.',n),
|
|
131
|
-
'.': n => typeof n[1] === 'number' ? parseFloat(n.length < 3 ? '.'+n[1] : n[1]+n[0]+n[2]) : // [.,2,1] → 2.1
|
|
132
|
-
['.',n[1],...n.slice(2).map(s=>typeof s === 'string' ? '"'+s+'"' : s)], // [.,a,b] → [.,a,"b"]
|
|
133
|
-
'e': n => parseFloat(n[1]+'e'+(Array.isArray(n[2])?n[2].join(''):n[2])),
|
|
134
|
-
'E': n => parse.map['e'](n)
|
|
135
|
-
}
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
// op evaluators
|
|
139
|
-
// multiple args allows shortcuts, lisp compatible, easy manual eval, functions anyways take multiple arguments
|
|
140
|
-
evaluate.operator = {
|
|
141
|
-
'!':a=>!a,
|
|
142
|
-
'++':a=>++a,
|
|
143
|
-
'--':a=>--a,
|
|
144
|
-
|
|
145
|
-
'.':(...a)=>a.reduce((a,b)=>a?a[b]:a),
|
|
146
|
-
'(':(a,...args)=>a(...args),
|
|
147
|
-
'[':(a,...args)=>a[args.pop()],
|
|
148
|
-
|
|
149
|
-
'%':(...a)=>a.reduce((a,b)=>a%b),
|
|
150
|
-
'/':(...a)=>a.reduce((a,b)=>a/b),
|
|
151
|
-
'*':(...a)=>a.reduce((a,b)=>a*b),
|
|
152
|
-
|
|
153
|
-
'+':(...a)=>a.reduce((a,b)=>a+b),
|
|
154
|
-
'-':(...a)=>a.length < 2 ? -a : a.reduce((a,b)=>a-b),
|
|
155
|
-
|
|
156
|
-
'>>>':(a,b)=>a>>>b,
|
|
157
|
-
'>>':(a,b)=>a>>b,
|
|
158
|
-
'<<':(a,b)=>a<<b,
|
|
159
|
-
|
|
160
|
-
'>=':(a,b)=>a>=b,
|
|
161
|
-
'>':(a,b)=>a>b,
|
|
162
|
-
'<=':(a,b)=>a<=b,
|
|
163
|
-
'<':(a,b)=>a<b,
|
|
164
|
-
|
|
165
|
-
'!=':(a,b)=>a!=b,
|
|
166
|
-
'==':(a,b)=>a==b,
|
|
167
|
-
|
|
168
|
-
'&':(a,b)=>a&b,
|
|
169
|
-
'^':(a,b)=>a^b,
|
|
170
|
-
'|':(a,b)=>a|b,
|
|
171
|
-
'&&':(...a)=>a.every(Boolean),
|
|
172
|
-
'||':(...a)=>a.some(Boolean),
|
|
173
|
-
',':(...a)=>a.reduce((a,b)=>(a,b))
|
|
174
|
-
}
|
|
1
|
+
import parse from './parse.js'
|
|
2
|
+
import evaluate from './evaluate.js'
|
|
175
3
|
|
|
176
4
|
export { parse, evaluate }
|
|
177
5
|
|
package/subscript.min.js
CHANGED
|
@@ -1 +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};
|