subscript 5.5.2 → 6.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 CHANGED
@@ -1,20 +1,17 @@
1
- # <img alt="subscript" src="/subscript2.svg" height=42/> <!--sub͘<em>script</em>--> <!--<sub>SUB͘<em>SCRIPT</em></sub>-->
2
- <a href="https://github.com/spectjs/subscript/actions/workflows/node.js.yml"><img src="https://github.com/spectjs/subscript/actions/workflows/node.js.yml/badge.svg"/></a>
3
- <a href="http://npmjs.org/subscript"><img src="https://img.shields.io/npm/v/subscript?color=indianred"/></a>
4
- <a href="http://microjs.com/#subscript"><img src="https://img.shields.io/badge/microjs-subscript-blue?color=darkslateblue"/></a>
1
+ # <img alt="subscript" src="/subscript2.svg" height=28/> <!--sub͘<em>script</em>--> <!--<sub>SUB͘<em>SCRIPT</em></sub>--> <a href="https://github.com/spectjs/subscript/actions/workflows/node.js.yml"><img src="https://github.com/spectjs/subscript/actions/workflows/node.js.yml/badge.svg"/></a> <a href="http://npmjs.org/subscript"><img src="https://img.shields.io/npm/v/subscript"/></a> <a href="http://microjs.com/#subscript"><img src="https://img.shields.io/badge/microjs-subscript-blue?color=darkslateblue"/></a>
5
2
 
6
- _Subscript_ is micro-language with common syntax subset of C++, JS, Java, Python, Go, Rust, Swift, Objective C, Kotlin etc.<br/>
3
+ _Subscript_ is micro-language with common syntax subset of C++, JS, Java, Python, Go, Rust etc.<br/>
7
4
 
8
- * Well-known syntax
9
- * Any _subscript_ fragment can be copy-pasted to any target language
10
- * It's tiny <sub><a href="https://bundlephobia.com/package/subscript@5.5.0"><img alt="npm bundle size" src="https://img.shields.io/bundlephobia/minzip/subscript/latest?color=brightgreen&label=gzip"/></a></sub>
11
- * It's :rocket: fast ([see performance](#performance))
5
+ * Standard conventional syntax
6
+ * Any fragment can be copy-pasted to any target language
7
+ * Tiny size <sub><a href="https://bundlephobia.com/package/subscript@6.0.0"><img alt="npm bundle size" src="https://img.shields.io/bundlephobia/minzip/subscript/latest?color=brightgreen&label=gzip"/></a></sub>
8
+ * :rocket: Fast [performance](#performance)
12
9
  * Configurable & extensible
13
- * Trivial to use...
10
+ * Trivial to use
14
11
 
15
12
  ```js
16
- import subscript from 'subscript.js'
17
- let fn = subscript(`a.b + c(d - 1)`)
13
+ import script from 'subscript.js'
14
+ let fn = script`a.b + c(d - 1)`
18
15
  fn({ a: { b:1 }, c: x => x * 2, d: 3 }) // 5
19
16
  ```
20
17
 
@@ -29,115 +26,114 @@ _Subscript_ is designed to be useful for:
29
26
  * sandboxes, playgrounds, safe eval
30
27
  * custom DSL
31
28
 
32
- [_Jsep_](https://github.com/EricSmekens/jsep) is generally fine for the listed tasks, unless you need dependencies as small as possible.
33
- _Subscript_ has [2.4kb](https://npmfs.com/package/subscript/5.5.0/subscript.min.js) footprint vs [11.4kb](https://npmfs.com/package/jsep/1.2.0/dist/jsep.min.js) _jsep_ + [4.5kb](https://npmfs.com/package/expression-eval/5.0.0/dist/expression-eval.module.js) _expression-eval_, with _jsep_ test coverage and better performance.
29
+ _Subscript_ has [2kb](https://npmfs.com/package/subscript/6.0.0/subscript.min.js) footprint, compared to [11.4kb](https://npmfs.com/package/jsep/1.2.0/dist/jsep.min.js) _jsep_ + [4.5kb](https://npmfs.com/package/expression-eval/5.0.0/dist/expression-eval.module.js) _expression-eval_, with better test coverage and better performance.
34
30
 
35
31
 
36
- ## Evaluation
32
+ ## Design
37
33
 
38
- _Subscript_ parser generates lispy calltree (compatible with [frisk](https://npmjs.com/frisk)), which is compared to esprima AST has:
34
+ Default operators are (same as JS precedence order):
39
35
 
40
- + minimal possible overhead
41
- + clear precedence
42
- + overloading by context
43
- + manual evaluation and debugging
44
- + conventional form
45
- + one-liner docs:
36
+ * `( a, b, c )`
37
+ * `a.b`, `a[b]`, `a(b, c)`
38
+ * `a++`, `a--` unary postfix
39
+ * `!a`, `+a`, `-a`, `++a`, `--a` unary prefix
40
+ * `a * b`, `a / b`, `a % b`
41
+ * `a + b`, `a - b`
42
+ * `a << b`, `a >> b`, `a >>> b`
43
+ * `a < b`, `a <= b`, `a > b`, `a >= b`
44
+ * `a == b`, `a != b`
45
+ * `a & b`
46
+ * `a ^ b`
47
+ * `a | b`
48
+ * `a && b`
49
+ * `a || b`
50
+
51
+ Default literals:
52
+
53
+ * `"abc"` strings
54
+ * `1.2e+3` numbers
55
+
56
+ Everything else can be extended via `parse.set(token, precedence, operator)` for unary or binary operators (detected by number of arguments in `operator`), or via `parse.set(token, parse, precedence)` for custom tokens.
46
57
 
47
58
  ```js
48
- import {evaluate} from 'subscript.js'
59
+ import script from 'subscript.js'
49
60
 
50
- evaluate(['+', ['*', 'min', 60], '@sec'], { min: 5 }) // min*60 + "sec" == "300sec"
51
- ```
61
+ // add ~ unary operator with precedence 15
62
+ script.set('~', 15, a => ~a)
52
63
 
53
- ## Extending
64
+ // add === binary operator
65
+ script.set('===', 9, (a, b) => a===b)
54
66
 
55
- ### Operators
67
+ // add literals
68
+ script.set('true', a => ()=>true)
69
+ script.set('false', a => ()=>false)
56
70
 
57
- Default operators include common operators for the listed languages in the following precedence:
71
+ script`true === false`() // false
72
+ ```
58
73
 
59
- * `++ --` unary postfix
60
- * `! + - ++ --` unary prefix
61
- * `* / %`
62
- * `+ -`
63
- * `<< >> >>>`
64
- * `< <= > >=`
65
- * `== !=`
66
- * `&`
67
- * `^`
68
- * `|`
69
- * `&&`
70
- * `||`
74
+ See [subscript.js](subscript.js) or [justin.js](./justin.js) for examples.
71
75
 
72
- Operators can be extended via `parse.operator(str, prec, type)` and `evaluate.operator(str, fn)` functions for any unary/binary/postfix operators, calls, prop chains, groups etc.
76
+ <!--
77
+ Operators can be extended via .
73
78
 
74
79
  ```js
75
- import { parse, evaluate } from 'subscript.js'
76
-
77
- parse.operator('=>', 10) // precedence=10, type=default (0 - binary, 1 - postfix, -1 - prefix)
80
+ import script from 'subscript.js'
78
81
 
79
- evaluate.operator('=>', ( args, body ) => evaluate(body, args))
80
- evaluate.operator('|', ( a, b ) => a.pipe(b))
82
+ script.set('|', 10, ( a, b ) => a.pipe(b))
81
83
 
82
- let tree = parse(`
84
+ let evaluate = script(`
83
85
  interval(350)
84
86
  | take(25)
85
87
  | map(gaussian)
86
- | map(num => "•".repeat(Math.floor(num * 65)))
88
+ | "•".repeat(Math.floor(it * 65)))
87
89
  `)
88
- evaluate(tree, { Math, map, take, interval, gaussian })
90
+ evaluate({ Math, map, take, interval, gaussian })
89
91
  ```
90
92
 
91
- ### Tokens
92
-
93
- Default tokens include:
94
-
95
- * `"abc"` strings
96
- * `1.2e+3` floats
97
- * `name` identifiers
98
-
99
- Tokens are extensible via `parse.token` list, can be added support of _literals_, _regexes_, _strings_, _numbers_ and others.
93
+ Literals are extensible by providing custom parser to `lookup`, can be added support of _booleans_, function calls, prop chains, groups, _regexes_, _strings_, _numbers_ and any other constructs.
100
94
 
101
95
  ```js
102
- import parse, {char} from 'subscript/parse.js'
103
- import evaluate from 'subscript/evaluate.js'
96
+ import script from 'subscript.js'
104
97
 
105
- conts ctx = {x:1}
106
- parse.token.unshift(c => char(4) === 'this' ? ctx : null)
107
- evaluate(parse(`this.x`)) // 1
98
+ script.literal.unshift(c => skip('this') && {x:1})
99
+ script`this.x`() // 1
108
100
  ```
109
101
 
110
- ### Spaces/comments
102
+ ### Identifiers
103
+
104
+ Identifiers include
111
105
 
112
- Comments can be added via extending `parse.space`. See [justin.js](./justin.js) for more examples.
106
+ ### Spaces/comments
113
107
 
108
+ Comments can be added via extending `parse.space`.
109
+ -->
114
110
 
115
111
  ## Justin
116
112
 
117
- _Justin_ extension (original [thread](https://github.com/endojs/Jessie/issues/66)) is minimal JS subset − JSON with JS expressions.<br/>
118
- It adds support of:
113
+ _Justin_ is minimal JS subset − JSON with JS expressions (see original [thread](https://github.com/endojs/Jessie/issues/66)).<br/>
114
+
115
+ It extends _subscript_ with:
119
116
 
120
117
  + `===`, `!==` operators
121
- + `**` binary operator
122
- + `~` unary operator
118
+ + `**` exponentiation operator (right-assoc)
119
+ + `~` bit inversion operator
123
120
  + `'` strings
124
121
  + `?:` ternary operator
122
+ + `?.` optional chain operator
125
123
  + `[...]` Array literal
126
124
  + `{...}` Object literal
127
- + `in` binary operator
125
+ + `in` binary
128
126
  + `;` expression separator
129
- + unary word operators
130
127
  + `//`, `/* */` comments
131
128
  + `true`, `false`, `null`, `undefined` literals
132
- <!-- + `?` chaining operator -->
133
129
  <!-- + `...x` unary operator -->
134
130
  <!-- + strings interpolation -->
135
131
 
136
132
  ```js
137
- import { parse, evaluate } from 'subscript/justin.js'
133
+ import jstin from 'subscript/justin.js'
138
134
 
139
- let xy = parse('{ x: 1, "y": 2+2 }["x"]') // ['[', {x:1, y: ['+', 2, 2]}, '@x']
140
- evaluate(xy) // 1
135
+ let xy = jstin('{ x: 1, "y": 2+2 }["x"]')
136
+ xy() // 1
141
137
  ```
142
138
 
143
139
  <!--
@@ -282,19 +278,40 @@ Examples: sonr, template-parts, neural-chunks
282
278
  Subscript shows relatively good performance within other evaluators:
283
279
 
284
280
  ```
285
- // 1 + (a * b / c % d) - 2.0 + -3e-3 * +4.4e4 / f.g[0] - i.j(+k == 1)(0)
286
- // parse 30k times
281
+ 1 + (a * b / c % d) - 2.0 + -3e-3 * +4.4e4 / f.g[0] - i.j(+k == 1)(0)
282
+ ```
287
283
 
288
- subscript: ~230 ms
289
- jsep: ~280 ms
284
+ Parse 30k times:
285
+
286
+ ```
287
+ subscript: ~170 ms 🥇
288
+ justin: ~183 ms 🥈
289
+ jsep: ~270 ms 🥉
290
+ mr-parser: ~420 ms
290
291
  expr-eval: ~480 ms
291
- jexl: ~1200 ms
292
- new Function: ~1400 ms
292
+ math-parser: ~570 ms
293
+ math-expression-evaluator: ~900ms
294
+ jexl: ~1056 ms
295
+ mathjs: ~1200 ms
296
+ new Function: ~1154 ms
297
+ ```
298
+
299
+ Eval 30k times:
300
+ ```
301
+ new Function: ~7 ms 🥇
302
+ subscript: ~17 ms 🥈
303
+ justin: ~17 ms 🥈
304
+ jsep (expression-eval): ~30 ms 🥉
305
+ math-expression-evaluator: ~50ms
306
+ expr-eval: ~72 ms
307
+ jexl: ~110 ms
308
+ mathjs: ~119 ms
309
+ mr-parser: -
310
+ math-parser: -
293
311
  ```
294
312
 
295
- ## See also
313
+ ## Alternatives
296
314
 
297
- * [Jessie](https://github.com/endojs/Jessie) − Minimal JS subset.
298
315
  * [jexl](https://github.com/TomFrost/Jexl)
299
316
  * [mozjexl](https://github.com/mozilla/mozjexl)
300
317
  * [expr-eval](https://github.com/silentmatt/expr-eval)
@@ -302,5 +319,9 @@ new Function: ~1400 ms
302
319
  * [jsep](https://github.com/EricSmekens/jsep)
303
320
  * [string-math](https://github.com/devrafalko/string-math)
304
321
  * [nerdamer](https://github.com/jiggzson/nerdamer)
322
+ * [math-codegen](https://github.com/mauriciopoppe/math-codegen)
323
+ * [math-parser](https://www.npmjs.com/package/math-parser)
324
+ * [math.js](https://mathjs.org/docs/expressions/parsing.html)
325
+ * [Jessie](https://github.com/endojs/Jessie)
305
326
 
306
327
  <p align=center>🕉</p>
package/index.js ADDED
@@ -0,0 +1,71 @@
1
+ const SPACE=32
2
+
3
+ // current string & index
4
+ export let idx, cur,
5
+
6
+ parse = (s, ...fields) => !(cur=s.raw ? String.raw(s,...fields) : s, idx=0, s=expr()) || cur[idx] ? err('Unexpected end') : ctx=>s(ctx||{}),
7
+
8
+ isId = c =>
9
+ (c >= 48 && c <= 57) || // 0..9
10
+ (c >= 65 && c <= 90) || // A...Z
11
+ (c >= 97 && c <= 122) || // a...z
12
+ c == 36 || c == 95 || // $, _,
13
+ (c >= 192 && c != 215 && c != 247), // any non-ASCII
14
+
15
+ err = (msg='Unexpected token',c=cur[idx]) => { throw SyntaxError(msg + ' `' + c + '` at ' + idx) },
16
+
17
+ skip = (is=1, from=idx, l) => {
18
+ if (typeof is == 'number') idx += is
19
+ else while (is(cur.charCodeAt(idx))) idx++
20
+ return cur.slice(from, idx)
21
+ },
22
+
23
+ // a + b - c
24
+ expr = (prec=0, end, cc, token, newNode, fn) => {
25
+ // chunk/token parser
26
+ while (
27
+ ( cc=space() ) && // till not end
28
+ // FIXME: extra work is happening here, when lookup bails out due to lower precedence -
29
+ // it makes extra `space` call for parent exprs on the same character to check precedence again
30
+ ( newNode =
31
+ (fn=lookup[cc]) && fn(token, prec) || // if operator with higher precedence isn't found
32
+ (!token && id()) // parse literal or quit. token seqs are forbidden: `a b`, `a "b"`, `1.32 a`
33
+ )
34
+ ) token = newNode;
35
+
36
+ // check end character
37
+ if (end) cc==end?idx++:err('Missing', String.fromCharCode(end))
38
+
39
+ return token
40
+ },
41
+
42
+ // skip space chars, return first non-space character
43
+ space = cc => { while ((cc = cur.charCodeAt(idx)) <= SPACE) idx++; return cc },
44
+
45
+ // variable identifier
46
+ id = (name=skip(isId), fn) => name ? (fn=ctx => ctx[name], fn.id=()=>name, fn) : 0,
47
+
48
+ // operator/token lookup table
49
+ lookup = [],
50
+
51
+ // create operator checker/mapper (see examples)
52
+ set = parse.set = (
53
+ op,
54
+ opPrec, fn=SPACE, // if opPrec & fn come in reverse order - consider them raw parse fn case, still precedence possible
55
+ c=op.charCodeAt(0),
56
+ l=op.length,
57
+ prev=lookup[c],
58
+ arity=fn.length || ([fn,opPrec]=[opPrec,fn], 0),
59
+ word=op.toUpperCase()!==op, // make sure word boundary comes after word operator
60
+ map=
61
+ // binary
62
+ arity>1 ? (a,b) => a && (b=expr(opPrec)) && (
63
+ !a.length && !b.length ? (a=fn(a(),b()), ()=>a) : // static pre-eval like `"a"+"b"`
64
+ ctx => fn(a(ctx),b(ctx))
65
+ ) :
66
+ // unary prefix (0 args)
67
+ arity ? a => !a && (a=expr(opPrec-1)) && (ctx => fn(a(ctx))) :
68
+ fn // custom parser
69
+ ) =>
70
+ // FIXME: find out if that's possible to globalize precision and instead of passing it to map, just provide global
71
+ lookup[c] = (a, curPrec, from=idx) => curPrec<opPrec && (l<2||cur.substr(idx,l)==op) && (!word||!isId(cur.charCodeAt(idx+l))) && (idx+=l, map(a, curPrec)) || (idx=from, prev&&prev(a, curPrec))
package/justin.js CHANGED
@@ -1,212 +1,73 @@
1
1
  // justin lang https://github.com/endojs/Jessie/issues/66
2
- import {evaluate} from './evaluate.js'
3
- import {parse, code, char, skip, expr, err, val} from './parse.js'
2
+ import {parse, set, lookup, skip, cur, idx, err, expr, isId, space} from './index.js'
3
+ import './subscript.js'
4
+
5
+ const PERIOD=46, OPAREN=40, CPAREN=41, OBRACK=91, CBRACK=93, SPACE=32, DQUOTE=34, QUOTE=39, _0=48, _9=57, BSLASH=92,
6
+ PREC_SEQ=1, PREC_COND=3, PREC_SOME=4, PREC_EVERY=5, PREC_OR=6, PREC_XOR=7, PREC_AND=8,
7
+ PREC_EQ=9, PREC_COMP=10, PREC_SHIFT=11, PREC_SUM=12, PREC_MULT=13, PREC_EXP=14, PREC_UNARY=15, PREC_POSTFIX=16, PREC_CALL=18, PREC_GROUP=19
8
+
9
+
10
+ let u, list, op, prec, fn,
11
+ escape = {n:'\n', r:'\r', t:'\t', b:'\b', f:'\f', v:'\v'},
12
+ string = q => (qc, c, str='') => {
13
+ qc&&err('Unexpected string') // must not follow another token
14
+ while (c=cur.charCodeAt(idx), c-q) {
15
+ if (c === BSLASH) skip(), c=skip(), str += escape[c] || c
16
+ else str += skip()
17
+ }
18
+ return skip()||err('Bad string'), () => str
19
+ }
4
20
 
5
- const PERIOD=46, OPAREN=40, CPAREN=41, CBRACK=93, SPACE=32,
21
+ // operators
22
+ for (list=[
23
+ // "' with /
24
+ '"', string(DQUOTE),,
25
+ "'", string(QUOTE),,
6
26
 
7
- PREC_SEQ=1, PREC_TERN=3, PREC_SOME=4, PREC_EVERY=5, PREC_OR=6, PREC_XOR=7, PREC_AND=8,
8
- 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,
9
- PREC_EXP=14, PREC_TOKEN=20
27
+ // /**/, //
28
+ '/*', (a, prec) => (skip(c => c !== 42 && cur.charCodeAt(idx+1) !== 47), skip(2), a||expr(prec)),,
29
+ '//', (a, prec) => (skip(c => c >= 32), a||expr(prec)),,
10
30
 
31
+ // literals
32
+ 'null', a => a ? err('Unexpected literal') : ()=>null,,
33
+ 'true', a => a ? err('Unexpected literal') : ()=>true,,
34
+ 'false', a => a ? err('Unexpected literal') : ()=>false,,
35
+ 'undefined', a => a ? err('Unexpected literal') : ()=>undefined,,
11
36
 
12
- // tokens
13
- parse.token.push(
14
- // 1.2e+3, .5 - fast & small version, but consumes corrupted nums as well
15
- (number) => (
16
- (number = skip(c => (c > 47 && c < 58) || c == PERIOD)) && (
17
- (code() == 69 || code() == 101) && (number += skip(2) + skip(c => c >= 48 && c <= 57)),
18
- isNaN(number = new Number(number)) ? err('Bad number') : number
19
- )
20
- ),
37
+ ';', a => expr()||(()=>{}),,
21
38
 
22
- // "' with /
23
- (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(), '@' + str
31
- },
32
-
33
- // id
34
- c => skip(c =>
35
- (c >= 48 && c <= 57) || // 0..9
36
- (c >= 65 && c <= 90) || // A...Z
37
- (c >= 97 && c <= 122) || // a...z
38
- c == 36 || c == 95 || // $, _,
39
- (c >= 192 && c != 215 && c != 247) // any non-ASCII
40
- )
41
- )
42
-
43
- const escape = {n:'\n', r:'\r', t:'\t', b:'\b', f:'\f', v:'\v'}
44
-
45
-
46
- // /**/, //
47
- parse.space = cc => {
48
- while (cc = code(), cc <= 32 || cc === 47) {
49
- if (cc <= 32) skip()
50
- else if (cc === 47)
51
- // /**/
52
- if (code(1) === 42) skip(2), skip(c => c !== 42 && code(1) !== 47), skip(2)
53
- // //
54
- else if (code(1) === 47) skip(2), skip(c => c >= 32)
55
- else break
56
- }
57
- return cc
58
- }
39
+ // operators
40
+ '===', PREC_EQ, (a,b) => a===b,
41
+ '!==', PREC_EQ, (a,b) => a!==b,
42
+ '~', PREC_UNARY, (a) => ~a,
59
43
 
44
+ // right order
45
+ '**', (a,prec,b=expr(PREC_EXP-1)) => ctx=>a(ctx)**b(ctx), PREC_EXP,
46
+
47
+ // ?:
48
+ ':', 3.1, (a,b) => [a,b],
49
+ '?', 3, (a,b) => a ? b[2] : b[1],
50
+
51
+ // a?.[, a?.( - postfix operator
52
+ '?.', a => a && (ctx => a(ctx)||(()=>{})),,//(a) => a||(()=>{}),
53
+ // a?.b - optional chain operator
54
+ '?.', (a,id) => (space(), id=skip(isId)) && (ctx => a(ctx)?.[id]),,
55
+
56
+ 'in', PREC_COMP, (a,b) => a in b,
60
57
 
61
- // operators
62
- const addOps = (add, stride=2, list) => {
63
- for (let i = 0; i < list.length; i+=stride) add(list[i], list[i+1], list[i+2])
64
- }
65
-
66
- addOps(parse.operator, 3, [
67
- // subscript ones
68
- ',', PREC_SEQ,,
69
-
70
- '|', PREC_OR,,
71
- '||', PREC_SOME,,
72
-
73
- '&', PREC_AND,,
74
- '&&', PREC_EVERY,,
75
-
76
- '^', PREC_XOR,,
77
-
78
- // ==, !=
79
- '==', PREC_EQ,,
80
- '!=', PREC_EQ,,
81
-
82
- // > >= >> >>>, < <= <<
83
- '>', PREC_COMP,,
84
- '>=', PREC_COMP,,
85
- '>>', PREC_SHIFT,,
86
- '>>>', PREC_SHIFT,,
87
- '<', PREC_COMP,,
88
- '<=', PREC_COMP,,
89
- '<<', PREC_SHIFT,,
90
-
91
- // + ++ - --
92
- '+', PREC_SUM,,
93
- '+', PREC_UNARY, -1,
94
- '++', PREC_UNARY, -1,
95
- '++', PREC_POSTFIX, +1,
96
- '-', PREC_SUM,,
97
- '-', PREC_UNARY, -1,
98
- '--', PREC_UNARY, -1,
99
- '--', PREC_POSTFIX, +1,
100
-
101
- // ! ~
102
- '!', PREC_UNARY, -1,
103
-
104
- // * / %
105
- '*', PREC_MULT,,
106
- '/', PREC_MULT,,
107
- '%', PREC_MULT,,
108
-
109
- // a.b
110
- '.', PREC_CALL, (node,b) => node && [skip(), node, '@' + expr(PREC_CALL)],
111
-
112
- // a[b]
113
- '[', PREC_CALL, (node) => (skip(), node = ['.', node, val(expr(0,CBRACK))], node),
114
- ']',,,
115
-
116
- // a(b)
117
- '(', PREC_CALL, (node,b) => (skip(), b=expr(0,CPAREN),
118
- Array.isArray(b) && b[0]===',' ? (b[0]=node, b) : b ? [node, val(b)] : [node]
119
- ),
120
- // (a+b)
121
- '(', PREC_GROUP, (node,b) => !node && (skip(), b=expr(0,CPAREN) || err(), b),
122
- ')',,,
123
-
124
- // justin extension
125
- ';', PREC_SEQ,,
126
- '===', PREC_EQ,,
127
- '!==', PREC_EQ,,
128
- '**', PREC_EXP,,
129
- '~', PREC_UNARY, -1,
130
- '?', PREC_TERN, (node) => {
131
- if (!node) err('Expected expression')
132
- let a, b
133
- skip(), parse.space(), a = expr()
134
- return ['?:', node, a[1], a[2]]
135
- },
136
- ':', PREC_COMP,,
137
- 'in', PREC_COMP, (node) => code(2)<=32 && [skip(2), '@'+node, expr(PREC_COMP)],
138
-
139
- // as operator it's faster to lookup (no need to extra rule check), smaller and no conflict with word names
140
58
  // [a,b,c]
141
- '[', PREC_TOKEN, (node,arg) => !node && (
142
- skip(), arg=expr(0,93),
143
- !arg ? ['['] : arg[0] == ',' ? (arg[0]='[',arg) : ['[',arg]
144
- ),
59
+ '[', (a, args) => !a && (
60
+ a=expr(0,CBRACK),
61
+ !a ? ctx => [] : a.all ? ctx => a.all(ctx) : ctx => [a(ctx)]
62
+ ),,
145
63
 
146
- // {a:0, b:1}
147
- '{', PREC_TOKEN, (node,arg) => !node && (skip(), arg=expr(0,125),
148
- !arg ? ['{'] : arg[0] == ':' ? ['{',strkey(arg)] : arg[0] == ',' ? (arg[0]='{',arg.map(strkey)) : ['{',arg]),
149
- '}',,,
64
+ // {a:1, b:2, c:3}
65
+ '{', (a, args) => !a && (
66
+ a=expr(0,125),
67
+ !a ? ctx => ({}) : ctx => (args=(a.all||a)(ctx), Object.fromEntries(a.all?args:[args]))
68
+ ),,
69
+ ':', (a, prec, b) => (b=expr(3.1)||err(), ctx => [(a.id||a)(ctx), b(ctx), a(ctx)]), 3.1
150
70
 
71
+ ]; [op,prec,fn,...list]=list, op;) set(op,prec,fn)
151
72
 
152
- // literals
153
- 'null', PREC_TOKEN, node=>!node&&(skip(4),v(null)),
154
- 'false', PREC_TOKEN, node=>!node&&(skip(5),v(false)),
155
- 'true', PREC_TOKEN, node=>!node&&(skip(4),v(true)),
156
- 'undefined', PREC_TOKEN, node=>!node&&(skip(9),v(undefined)),
157
- ])
158
- const strkey = a => Array.isArray(a) ? (a[1]=(a[1][0]==='@'?'':'@')+a[1],a) : a
159
- const v = v => ({valueOf:()=>v})
160
-
161
- addOps(evaluate.operator, 2, [
162
- // subscript
163
- '!', a=>!a,
164
- '++', a=>++a,
165
- '--', a=>--a,
166
-
167
- '.', (a,b)=>a?a[b]:a,
168
-
169
- '%', (a,b)=>a%b,
170
- '/', (a,b)=>a/b,
171
- '*', (a,b)=>a*b,
172
-
173
- '+', (a,b)=>a+(b||0),
174
- '-', (a,b)=>b==null ? -a : a-b,
175
-
176
- '>>>', (a,b)=>a>>>b,
177
- '>>', (a,b)=>a>>b,
178
- '<<', (a,b)=>a<<b,
179
-
180
- '>=', (a,b)=>a>=b,
181
- '>', (a,b)=>a>b,
182
- '<=', (a,b)=>a<=b,
183
- '<', (a,b)=>a<b,
184
-
185
- '!=', (a,b)=>a!=b,
186
- '==', (a,b)=>a==b,
187
-
188
- '&', (a,b)=>a&b,
189
- '^', (a,b)=>a^b,
190
- '|', (a,b)=>a|b,
191
- '&&', (...a)=>a.every(Boolean),
192
- '||', (...a)=>a.some(Boolean),
193
- ',', (a,b)=>(a,b),
194
-
195
- // justin extension
196
- '**', (...args)=>args.reduceRight((a,b)=>Math.pow(b,a)),
197
- '~', a=>~a,
198
- '?:', (a,b,c)=>a?b:c,
199
- 'in', (a,b)=>a in b,
200
-
201
- '[', (...args) => Array(...args),
202
- '{', (...args)=>Object.fromEntries(args),
203
- ':', (a,b)=>[a,b]
204
- ])
205
-
206
- // TODO ...
207
- // TODO: strings interpolation
208
-
209
- export { parse, evaluate }
210
-
211
- // code → evaluator
212
- export default s => (s = typeof s == 'string' ? parse(s) : s, ctx => evaluate(s, ctx))
73
+ export default parse
package/justin.min.js CHANGED
@@ -1 +1 @@
1
- const r=(t,n={})=>{if("string"==typeof t)return"@"===t[0]?t.slice(1):n[t];if(Array.isArray(t)){let o=t[0],a=t.length,s=Array.isArray(o)?r(o,n):e[o]||n[o]||o,l=1,i=[];if(s.ab){i=r(t[l++],n);do{i=s(i,r(t[l++],n))}while(l<a)}else{for(;l<a;)i.push(r(t[l++],n));i=s.apply(o,i)}return i}return t},e={};let t,n;r.operator=(r,t)=>(e[r]=t,t.ab=2==t.length);const o=(r,e)=>(n=r,t=0,e=f(),t<n.length?a():y(e)),a=(r="Bad syntax")=>{throw Error(r+" `"+n[t]+"` at "+t)},s=(r=1,e=t)=>{if("number"==typeof r)t+=r;else for(;r(l());)t++;return n.slice(e,t)},l=(r=0)=>n.charCodeAt(t+r),i=(r=1)=>n.substr(t,r),f=(r=0,e,n,s,l=0,i,f)=>{for(;(n=o.space())&&(i=(f=c[n])&&f(s,r)||!s&&p(n));)s=i;return e&&(n!=e?a("Unclosed paren"):t++),s};o.space=r=>{for(;(r=l())<=32;)t++;return r};const u=o.token=[],p=(r,e=0,t)=>{for(;e<u.length;)if(t=u[e++](r))return t},c=[];o.operator=(r,e=0,n=0,a,l=r.charCodeAt(0),u=r.length,p=c[l])=>(a=n?n>0?r=>r&&[s(u),y(r)]:n<0?r=>!r&&[s(u),y(f(e-1))]:n:n=>{n=[r,y(n)];do{t+=u,n.push(y(f(e)))}while(o.space()==l&&(u<2||i(u)==r));return n},c[l]=(t,n)=>n<e&&(u<2||i(u)==r)&&a(t)||p&&p(t,n));const y=r=>Array.isArray(r)?r:(r||a()).valueOf();o.token.push((r=>(r=s((r=>r>47&&r<58||46==r)))&&((69==l()||101==l())&&(r+=s(2)+s((r=>r>=48&&r<=57))),isNaN(r=new Number(r))?a("Bad number"):r)),((r,e,t,n)=>{if(34===r||39===r){for(i(),s(),n="";(t=l())-r;)92===t?(s(),n+=h[i()]||i()):n+=i(),s();return s(),"@"+n}}),(r=>s((r=>r>=48&&r<=57||r>=65&&r<=90||r>=97&&r<=122||36==r||95==r||r>=192&&215!=r&&247!=r))));const h={n:"\n",r:"\r",t:"\t",b:"\b",f:"\f",v:"\v"};o.space=r=>{for(;(r=l())<=32||47===r;)if(r<=32)s();else if(47===r)if(42===l(1))s(2),s((r=>42!==r&&47!==l(1))),s(2);else{if(47!==l(1))break;s(2),s((r=>r>=32))}return r};const d=(r,e=2,t)=>{for(let n=0;n<t.length;n+=e)r(t[n],t[n+1],t[n+2])};d(o.operator,3,[",",1,,"|",6,,"||",4,,"&",8,,"&&",5,,"^",7,,"==",9,,"!=",9,,">",10,,">=",10,,">>",11,,">>>",11,,"<",10,,"<=",10,,"<<",11,,"+",12,,"+",15,-1,"++",15,-1,"++",16,1,"-",12,,"-",15,-1,"--",15,-1,"--",16,1,"!",15,-1,"*",13,,"/",13,,"%",13,,".",18,(r,e)=>r&&[s(),r,"@"+f(18)],"[",18,r=>(s(),[".",r,y(f(0,93))]),"]",,,"(",18,(r,e)=>(s(),e=f(0,41),Array.isArray(e)&&","===e[0]?(e[0]=r,e):e?[r,y(e)]:[r]),"(",19,(r,e)=>!r&&(s(),f(0,41)||a()),")",,,";",1,,"===",9,,"!==",9,,"**",14,,"~",15,-1,"?",3,r=>{let e;return r||a("Expected expression"),s(),o.space(),e=f(),["?:",r,e[1],e[2]]},":",10,,"in",10,r=>l(2)<=32&&[s(2),"@"+r,f(10)],"[",20,(r,e)=>!r&&(s(),(e=f(0,93))?","==e[0]?(e[0]="[",e):["[",e]:["["]),"{",20,(r,e)=>!r&&(s(),(e=f(0,125))?":"==e[0]?["{",A(e)]:","==e[0]?(e[0]="{",e.map(A)):["{",e]:["{"]),"}",,,"null",20,r=>!r&&(s(4),b(null)),"false",20,r=>!r&&(s(5),b(!1)),"true",20,r=>!r&&(s(4),b(!0)),"undefined",20,r=>!r&&(s(9),b(void 0))]);const A=r=>Array.isArray(r)?(r[1]=("@"===r[1][0]?"":"@")+r[1],r):r,b=r=>({valueOf:()=>r});d(r.operator,2,["!",r=>!r,"++",r=>++r,"--",r=>--r,".",(r,e)=>r?r[e]:r,"%",(r,e)=>r%e,"/",(r,e)=>r/e,"*",(r,e)=>r*e,"+",(r,e)=>r+(e||0),"-",(r,e)=>null==e?-r: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,e)=>e,"**",(...r)=>r.reduceRight(((r,e)=>Math.pow(e,r))),"~",r=>~r,"?:",(r,e,t)=>r?e:t,"in",(r,e)=>r in e,"[",(...r)=>Array(...r),"{",(...r)=>Object.fromEntries(r),":",(r,e)=>[r,e]]);var g=e=>(e="string"==typeof e?o(e):e,t=>r(e,t));export{g as default,r as evaluate,o as parse};
1
+ let e,r,t,l,n,a,d=(t,...l)=>(r=t.raw?String.raw(t,...l):t,e=0,!(t=p())||r[e]?i("Unexpected end"):e=>t(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,i=(t="Unexpected token",l=r[e])=>{throw SyntaxError(t+" `"+l+"` at "+e)},f=(t=1,l=e,n)=>{if("number"==typeof t)e+=t;else for(;t(r.charCodeAt(e));)e++;return r.slice(l,e)},p=(r=0,t,l,n,a,d)=>{for(;(l=c())&&(a=(d=u[l])&&d(n,r)||!n&&s());)n=a;return t&&(l==t?e++:i("Missing",String.fromCharCode(t))),n},c=t=>{for(;(t=r.charCodeAt(e))<=32;)e++;return t},s=(e=f(o),r)=>e?((r=r=>r[e]).id=()=>e,r):0,u=[],h=d.set=(t,l,n=32,a=t.charCodeAt(0),d=t.length,i=u[a],f=n.length||([n,l]=[l,n],0),c=t.toUpperCase()!==t,s=(f>1?(e,r)=>e&&(r=p(l))&&(e.length||r.length?t=>n(e(t),r(t)):(e=n(e(),r()),()=>e)):f?e=>!e&&(e=p(l-1))&&(r=>n(e(r))):n))=>u[a]=(n,a,f=e)=>a<l&&(d<2||r.substr(e,d)==t)&&(!c||!o(r.charCodeAt(e+d)))&&(e+=d,s(n,a))||(e=f,i&&i(n,a)),g=e=>e>=48&&e<=57,x=t=>(t&&i("Unexpected number"),t=f((e=>46==e||g(e))),(69==r.charCodeAt(e)||101==r.charCodeAt(e))&&(t+=f(2)+f(g)),(t=+t)!=t?i("Bad number"):()=>t),C=(e,r,t=e.of)=>l=>r(t?t(l):l,e.id());for(l=48;l<=57;)u[l++]=x;for(t=['"',e=>(e=e?i("Unexpected string"):f((e=>e-34)),f()||i("Bad string"),()=>e),,".",(e,r,t)=>e?(c(),r=f(o)||i(),(t=t=>e(t)[r]).id=()=>r,t.of=e,t):x(f(-1)),18,"[",(e,r,t)=>e&&(r=p(0,93)||i("Empty group"),(t=t=>e(t)[r(t)]).id=r,t.of=e,t),18,"(",(e,r,t)=>(r=p(0,41),e?t=>e(t).apply(e.of?.(t),r?r.all?r.all(t):[r(t)]:[]):r||i("Empty group")),18,",",(e,r,t=p(1))=>(t.all=e.all?(r,l,n=e.all(r))=>n.push(t(r))&&n:r=>[e(r),t(r)],t),1,"|",6,(e,r)=>e|r,"||",4,(e,r)=>e||r,"&",8,(e,r)=>e&r,"&&",5,(e,r)=>e&&r,"^",7,(e,r)=>e^r,"==",9,(e,r)=>e==r,"!=",9,(e,r)=>e!=r,">",10,(e,r)=>e>r,">=",10,(e,r)=>e>=r,">>",11,(e,r)=>e>>r,">>>",11,(e,r)=>e>>>r,"<",10,(e,r)=>e<r,"<=",10,(e,r)=>e<=r,"<<",11,(e,r)=>e<<r,"+",12,(e,r)=>e+r,"+",15,e=>+e,"++",e=>C(e||p(14),e?(e,r)=>e[r]++:(e,r)=>++e[r]),15,"-",12,(e,r)=>e-r,"-",15,e=>-e,"--",e=>C(e||p(14),e?(e,r)=>e[r]--:(e,r)=>--e[r]),15,"!",15,e=>!e,"*",13,(e,r)=>e*r,"/",13,(e,r)=>e/r,"%",13,(e,r)=>e%r];[l,n,a,...t]=t,l;)h(l,n,a);let U,A,b,m,y={n:"\n",r:"\r",t:"\t",b:"\b",f:"\f",v:"\v"},E=t=>(l,n,a="")=>{for(l&&i("Unexpected string");(n=r.charCodeAt(e))-t;)92===n?(f(),n=f(),a+=y[n]||n):a+=f();return f()||i("Bad string"),()=>a};for((U=['"',E(34),,"'",E(39),,"/*",(t,l)=>(f((t=>42!==t&&47!==r.charCodeAt(e+1))),f(2),t||p(l)),,"//",(e,r)=>(f((e=>e>=32)),e||p(r)),,"null",e=>e?i("Unexpected literal"):()=>null,,"true",e=>e?i("Unexpected literal"):()=>!0,,"false",e=>e?i("Unexpected literal"):()=>!1,,"undefined",e=>e?i("Unexpected literal"):()=>{},,";",e=>p()||(()=>{}),,"===",9,(e,r)=>e===r,"!==",9,(e,r)=>e!==r,"~",15,e=>~e,"**",(e,r,t=p(13))=>r=>e(r)**t(r),14,":",3.1,(e,r)=>[e,r],"?",3,(e,r)=>e?r[2]:r[1],"?.",e=>e&&(r=>e(r)||(()=>{})),,"?.",(e,r)=>(c(),(r=f(o))&&(t=>e(t)?.[r])),,"in",10,(e,r)=>e in r,"[",(e,r)=>!e&&((e=p(0,93))?e.all?r=>e.all(r):r=>[e(r)]:e=>[]),,"{",(e,r)=>!e&&((e=p(0,125))?t=>(r=(e.all||e)(t),Object.fromEntries(e.all?r:[r])):e=>({})),,":",(e,r,t)=>(t=p(3.1)||i(),r=>[(e.id||e)(r),t(r),e(r)]),3.1]);[A,b,m,...U]=U,A;)h(A,b,m);export{d as default};
package/package.json CHANGED
@@ -1,12 +1,11 @@
1
1
  {
2
2
  "name": "subscript",
3
- "version": "5.5.2",
4
- "description": "Microlanguage with common syntax for JS/C++/Python/Rust",
3
+ "version": "6.0.3",
4
+ "description": "Fast and tiny expression evaluator with common syntax microlanguage.",
5
5
  "main": "subscript.js",
6
6
  "type": "module",
7
7
  "files": [
8
- "parse.js",
9
- "evaluate.js",
8
+ "index.js",
10
9
  "subscript.js",
11
10
  "subscript.min.js",
12
11
  "justin.js",
@@ -32,11 +31,16 @@
32
31
  "jexl",
33
32
  "jsep",
34
33
  "expression",
34
+ "evaluator",
35
+ "parser",
35
36
  "evaluation",
36
37
  "math",
37
38
  "arithmetic",
38
- "evaluator",
39
39
  "justin",
40
+ "eval",
41
+ "math-eval",
42
+ "math-evaluator",
43
+ "calculation",
40
44
  "jessie",
41
45
  "jessica",
42
46
  "eval",
@@ -44,8 +48,13 @@
44
48
  "json",
45
49
  "calculator",
46
50
  "calc",
47
- "lisp",
48
- "frisk"
51
+ "math.js",
52
+ "mathjs",
53
+ "math-codegen",
54
+ "math-parser",
55
+ "formula",
56
+ "operator",
57
+ "overload"
49
58
  ],
50
59
  "author": "Dmitry Iv.",
51
60
  "license": "ISC",
package/subscript.js CHANGED
@@ -1,130 +1,88 @@
1
- import parse, {skip, expr, code, tokens, val, operator as parseOp, err} from './parse.js'
2
- import evaluate, {operator as evalOp} from './evaluate.js'
3
-
4
- const PERIOD=46, OPAREN=40, CPAREN=41, CBRACK=93, SPACE=32,
1
+ import {parse, set, lookup, skip, cur, idx, err, expr, isId, space} from './index.js'
5
2
 
3
+ const PERIOD=46, OPAREN=40, CPAREN=41, OBRACK=91, CBRACK=93, SPACE=32, DQUOTE=34, _0=48, _9=57,
6
4
  PREC_SEQ=1, PREC_SOME=4, PREC_EVERY=5, PREC_OR=6, PREC_XOR=7, PREC_AND=8,
7
5
  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
8
6
 
9
- tokens.push(
10
- // 1.2e+3, .5 - fast & small version, but consumes corrupted nums as well
11
- (number) => (
12
- (number = skip(c => (c > 47 && c < 58) || c == PERIOD)) && (
13
- (code() == 69 || code() == 101) && (number += skip(2) + skip(c => c >= 48 && c <= 57)),
14
- isNaN(number = new Number(number)) ? err('Bad number') : number
15
- )
16
- ),
7
+ let u, list, op, prec, fn,
8
+ isNum = c => c>=_0 && c<=_9,
9
+ // 1.2e+3, .5
10
+ num = n => (
11
+ n&&err('Unexpected number'),
12
+ n = skip(c=>c==PERIOD || isNum(c)),
13
+ (cur.charCodeAt(idx) == 69 || cur.charCodeAt(idx) == 101) && (n += skip(2) + skip(isNum)),
14
+ n=+n, n!=n ? err('Bad number') : () => n // 0 args means token is static
15
+ ),
16
+
17
+ inc = (a,fn,c=a.of) => ctx => fn(c?c(ctx):ctx, a.id())
18
+
19
+ // numbers
20
+ for (op=_0;op<=_9;) lookup[op++] = num
21
+
22
+ // operators
23
+ for (list=[
17
24
  // "a"
18
- (q, qc, v) => q == 34 && (skip(), v=skip(c => c-q), skip(), '@'+v),
19
- // id
20
- c => skip(c =>
21
- (c >= 48 && c <= 57) || // 0..9
22
- (c >= 65 && c <= 90) || // A...Z
23
- (c >= 97 && c <= 122) || // a...z
24
- c == 36 || c == 95 || // $, _,
25
- c >= 192 // any non-ASCII
26
- )
27
- )
25
+ '"', a => (a=a?err('Unexpected string'):skip(c => c-DQUOTE), skip()||err('Bad string'), ()=>a),,
26
+
27
+ // a.b, .2, 1.2 parser in one
28
+ '.', (a,id,fn) => !a ? num(skip(-1)) : // FIXME: .123 is not operator, so we skip back, but mb reorganizing num would be better
29
+ (space(), id=skip(isId)||err(), fn=ctx=>a(ctx)[id], fn.id=()=>id, fn.of=a, fn), PREC_CALL,
28
30
 
29
- const addOps = (add, stride=2, list) => {
30
- for (let i = 0; i < list.length; i+=stride) add(list[i], list[i+1], list[i+2])
31
- }
31
+ // a[b]
32
+ '[', (a,b,fn) => a && (b=expr(0,CBRACK)||err(), fn=ctx=>a(ctx)[b(ctx)], fn.id=b, fn.of=a, fn), PREC_CALL,
33
+
34
+ // a(), a(b), (a,b), (a+b)
35
+ '(', (a,b,fn) => (
36
+ b=expr(0,CPAREN),
37
+ // a(), a(b), a(b,c,d)
38
+ a ? ctx => a(ctx).apply(a.of?.(ctx), b ? b.all ? b.all(ctx) : [b(ctx)] : []) :
39
+ // (a+b)
40
+ b || err()
41
+ ), PREC_CALL,
32
42
 
33
- addOps(parseOp, 3, [
34
- ',', PREC_SEQ,,
43
+ // [a,b,c] or (a,b,c)
44
+ ',', (a,prec,b=expr(PREC_SEQ)) => (
45
+ b.all = a.all ? ctx => [...a.all(ctx), b(ctx)] : ctx => [a(ctx),b(ctx)],
46
+ b
47
+ ), PREC_SEQ,
35
48
 
36
- '|', PREC_OR,,
37
- '||', PREC_SOME,,
49
+ '|', PREC_OR, (a,b)=>a|b,
50
+ '||', PREC_SOME, (a,b)=>a||b,
38
51
 
39
- '&', PREC_AND,,
40
- '&&', PREC_EVERY,,
52
+ '&', PREC_AND, (a,b)=>a&b,
53
+ '&&', PREC_EVERY, (a,b)=>a&&b,
41
54
 
42
- '^', PREC_XOR,,
55
+ '^', PREC_XOR, (a,b)=>a^b,
43
56
 
44
57
  // ==, !=
45
- '==', PREC_EQ,,
46
- '!=', PREC_EQ,,
58
+ '==', PREC_EQ, (a,b)=>a==b,
59
+ '!=', PREC_EQ, (a,b)=>a!=b,
47
60
 
48
61
  // > >= >> >>>, < <= <<
49
- '>', PREC_COMP,,
50
- '>=', PREC_COMP,,
51
- '>>', PREC_SHIFT,,
52
- '>>>', PREC_SHIFT,,
53
- '<', PREC_COMP,,
54
- '<=', PREC_COMP,,
55
- '<<', PREC_SHIFT,,
62
+ '>', PREC_COMP, (a,b)=>a>b,
63
+ '>=', PREC_COMP, (a,b)=>a>=b,
64
+ '>>', PREC_SHIFT, (a,b)=>a>>b,
65
+ '>>>', PREC_SHIFT, (a,b)=>a>>>b,
66
+ '<', PREC_COMP, (a,b)=>a<b,
67
+ '<=', PREC_COMP, (a,b)=>a<=b,
68
+ '<<', PREC_SHIFT, (a,b)=>a<<b,
56
69
 
57
70
  // + ++ - --
58
- '+', PREC_SUM,,
59
- '+', PREC_UNARY, -1,
60
- '++', PREC_UNARY, -1,
61
- '++', PREC_POSTFIX, +1,
62
- '-', PREC_SUM,,
63
- '-', PREC_UNARY, -1,
64
- '--', PREC_UNARY, -1,
65
- '--', PREC_POSTFIX, +1,
71
+ '+', PREC_SUM, (a,b)=>a+b,
72
+ '+', PREC_UNARY, (a)=>+a,
73
+ '++', a => inc(a||expr(PREC_UNARY-1), a ? (a,b)=>a[b]++ : (a,b)=>++a[b]), PREC_UNARY,
74
+
75
+ '-', PREC_SUM, (a,b)=>a-b,
76
+ '-', PREC_UNARY, (a)=>-a,
77
+ '--', a => inc(a||expr(PREC_UNARY-1), a ? (a,b)=>a[b]-- : (a,b)=>--a[b]), PREC_UNARY,
66
78
 
67
79
  // ! ~
68
- '!', PREC_UNARY, -1,
80
+ '!', PREC_UNARY, (a)=>!a,
69
81
 
70
82
  // * / %
71
- '*', PREC_MULT,,
72
- '/', PREC_MULT,,
73
- '%', PREC_MULT,,
83
+ '*', PREC_MULT, (a,b)=>a*b,
84
+ '/', PREC_MULT, (a,b)=>a/b,
85
+ '%', PREC_MULT, (a,b)=>a%b
86
+ ]; [op,prec,fn,...list]=list, op;) set(op,prec,fn)
74
87
 
75
- // a.b
76
- '.', PREC_CALL, (node,b) => node && [skip(), node, '@' + expr(PREC_CALL)],
77
-
78
- // a[b]
79
- '[', PREC_CALL, (node) => (skip(), node = ['.', node, val(expr(0,CBRACK))], node),
80
- ']',,,
81
-
82
- // a(b)
83
- '(', PREC_CALL, (node,b) => (skip(), b=expr(0,CPAREN),
84
- Array.isArray(b) && b[0]===',' ? (b[0]=node, b) : b ? [node, val(b)] : [node]
85
- ),
86
- // (a+b)
87
- '(', PREC_GROUP, (node,b) => !node && (skip(), b=expr(0,CPAREN) || err(), b),
88
- ')',,,
89
- ])
90
-
91
-
92
- // evaluators
93
- addOps(evalOp, 2, [
94
- '!', a=>!a,
95
- '++', a=>++a,
96
- '--', a=>--a,
97
-
98
- '.', (a,b)=>a?a[b]:a,
99
-
100
- '%', (a,b)=>a%b,
101
- '/', (a,b)=>a/b,
102
- '*', (a,b)=>a*b,
103
-
104
- '+', (a,b)=>a+(b||0),
105
- '-', (a,b)=>b==null ? -a : a-b,
106
-
107
- '>>>', (a,b)=>a>>>b,
108
- '>>', (a,b)=>a>>b,
109
- '<<', (a,b)=>a<<b,
110
-
111
- '>=', (a,b)=>a>=b,
112
- '>', (a,b)=>a>b,
113
- '<=', (a,b)=>a<=b,
114
- '<', (a,b)=>a<b,
115
-
116
- '!=', (a,b)=>a!=b,
117
- '==', (a,b)=>a==b,
118
-
119
- '&', (a,b)=>a&b,
120
- '^', (a,b)=>a^b,
121
- '|', (a,b)=>a|b,
122
- '&&', (a,b)=>a&&b,
123
- '||', (a,b)=>a||b,
124
- ',', (a,b)=>(a,b)
125
- ])
126
-
127
- export { parse, evaluate }
128
-
129
- // code → evaluator
130
- export default s => (s = typeof s == 'string' ? parse(s) : s, ctx => evaluate(s, ctx))
88
+ export default parse
package/subscript.min.js CHANGED
@@ -1 +1 @@
1
- let r,e;const t=(t,n)=>(e=t,r=0,n=l(),r<e.length?a():h(n)),a=(t="Bad syntax")=>{throw Error(t+" `"+e[r]+"` at "+r)},n=(t=1,a=r)=>{if("number"==typeof t)r+=t;else for(;t(o());)r++;return e.slice(a,r)},o=(t=0)=>e.charCodeAt(r+t),s=(t=1)=>e.substr(r,t),l=(e=0,n,o,s,l=0,u,p)=>{for(;(o=t.space())&&(u=(p=i[o])&&p(s,e)||!s&&f(o));)s=u;return n&&(o!=n?a("Unclosed paren"):r++),s};t.space=e=>{for(;(e=o())<=32;)r++;return e};const u=t.token=[],f=(r,e=0,t)=>{for(;e<u.length;)if(t=u[e++](r))return t},i=[],p=t.operator=(e,a=0,o=0,u,f=e.charCodeAt(0),p=e.length,y=i[f])=>(u=o?o>0?r=>r&&[n(p),h(r)]:o<0?r=>!r&&[n(p),h(l(a-1))]:o:n=>{n=[e,h(n)];do{r+=p,n.push(h(l(a)))}while(t.space()==f&&(p<2||s(p)==e));return n},i[f]=(r,t)=>t<a&&(p<2||s(p)==e)&&u(r)||y&&y(r,t)),h=r=>Array.isArray(r)?r:(r||a()).valueOf(),y=(r,e={})=>{if("string"==typeof r)return"@"===r[0]?r.slice(1):e[r];if(Array.isArray(r)){let t=r[0],a=r.length,n=Array.isArray(t)?y(t,e):c[t]||e[t]||t,o=1,s=[];if(n.ab){s=y(r[o++],e);do{s=n(s,y(r[o++],e))}while(o<a)}else{for(;o<a;)s.push(y(r[o++],e));s=n.apply(e,s)}return s}return r},c={},A=y.operator=(r,e)=>(c[r]=e).ab=2==e.length;u.push((r=>(r=n((r=>r>47&&r<58||46==r)))&&((69==o()||101==o())&&(r+=n(2)+n((r=>r>=48&&r<=57))),isNaN(r=new Number(r))?a("Bad number"):r)),((r,e,t)=>34==r&&(n(),t=n((e=>e-r)),n(),"@"+t)),(r=>n((r=>r>=48&&r<=57||r>=65&&r<=90||r>=97&&r<=122||36==r||95==r||r>=192))));const d=(r,e=2,t)=>{for(let a=0;a<t.length;a+=e)r(t[a],t[a+1],t[a+2])};d(p,3,[",",1,,"|",6,,"||",4,,"&",8,,"&&",5,,"^",7,,"==",9,,"!=",9,,">",10,,">=",10,,">>",11,,">>>",11,,"<",10,,"<=",10,,"<<",11,,"+",12,,"+",15,-1,"++",15,-1,"++",16,1,"-",12,,"-",15,-1,"--",15,-1,"--",16,1,"!",15,-1,"*",13,,"/",13,,"%",13,,".",18,(r,e)=>r&&[n(),r,"@"+l(18)],"[",18,r=>(n(),[".",r,h(l(0,93))]),"]",,,"(",18,(r,e)=>(n(),e=l(0,41),Array.isArray(e)&&","===e[0]?(e[0]=r,e):e?[r,h(e)]:[r]),"(",19,(r,e)=>!r&&(n(),l(0,41)||a()),")",,,]),d(A,2,["!",r=>!r,"++",r=>++r,"--",r=>--r,".",(r,e)=>r?r[e]:r,"%",(r,e)=>r%e,"/",(r,e)=>r/e,"*",(r,e)=>r*e,"+",(r,e)=>r+(e||0),"-",(r,e)=>null==e?-r: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&&e,"||",(r,e)=>r||e,",",(r,e)=>e]);var g=r=>(r="string"==typeof r?t(r):r,e=>y(r,e));export{g as default,y as evaluate,t as parse};
1
+ let e,r,t,o,a,n,d=(t,...o)=>(r=t.raw?String.raw(t,...o):t,e=0,!(t=i())||r[e]?f("Unexpected end"):e=>t(e||{})),l=e=>e>=48&&e<=57||e>=65&&e<=90||e>=97&&e<=122||36==e||95==e||e>=192&&215!=e&&247!=e,f=(t="Unexpected token",o=r[e])=>{throw SyntaxError(t+" `"+o+"` at "+e)},h=(t=1,o=e,a)=>{if("number"==typeof t)e+=t;else for(;t(r.charCodeAt(e));)e++;return r.slice(o,e)},i=(r=0,t,o,a,n,d)=>{for(;(o=c())&&(n=(d=p[o])&&d(a,r)||!a&&s());)a=n;return t&&(o==t?e++:f("Missing",String.fromCharCode(t))),a},c=t=>{for(;(t=r.charCodeAt(e))<=32;)e++;return t},s=(e=h(l),r)=>e?((r=r=>r[e]).id=()=>e,r):0,p=[],g=d.set=(t,o,a=32,n=t.charCodeAt(0),d=t.length,f=p[n],h=a.length||([a,o]=[o,a],0),c=t.toUpperCase()!==t,s=(h>1?(e,r)=>e&&(r=i(o))&&(e.length||r.length?t=>a(e(t),r(t)):(e=a(e(),r()),()=>e)):h?e=>!e&&(e=i(o-1))&&(r=>a(e(r))):a))=>p[n]=(a,n,h=e)=>n<o&&(d<2||r.substr(e,d)==t)&&(!c||!l(r.charCodeAt(e+d)))&&(e+=d,s(a,n))||(e=h,f&&f(a,n)),C=e=>e>=48&&e<=57,u=t=>(t&&f("Unexpected number"),t=h((e=>46==e||C(e))),(69==r.charCodeAt(e)||101==r.charCodeAt(e))&&(t+=h(2)+h(C)),(t=+t)!=t?f("Bad number"):()=>t),x=(e,r,t=e.of)=>o=>r(t?t(o):o,e.id());for(o=48;o<=57;)p[o++]=u;for(t=['"',e=>(e=e?f("Unexpected string"):h((e=>e-34)),h()||f("Bad string"),()=>e),,".",(e,r,t)=>e?(c(),r=h(l)||f(),(t=t=>e(t)[r]).id=()=>r,t.of=e,t):u(h(-1)),18,"[",(e,r,t)=>e&&(r=i(0,93)||f(),(t=t=>e(t)[r(t)]).id=r,t.of=e,t),18,"(",(e,r,t)=>(r=i(0,41),e?t=>e(t).apply(e.of?.(t),r?r.all?r.all(t):[r(t)]:[]):r||f()),18,",",(e,r,t=i(1))=>(t.all=e.all?r=>[...e.all(r),t(r)]:r=>[e(r),t(r)],t),1,"|",6,(e,r)=>e|r,"||",4,(e,r)=>e||r,"&",8,(e,r)=>e&r,"&&",5,(e,r)=>e&&r,"^",7,(e,r)=>e^r,"==",9,(e,r)=>e==r,"!=",9,(e,r)=>e!=r,">",10,(e,r)=>e>r,">=",10,(e,r)=>e>=r,">>",11,(e,r)=>e>>r,">>>",11,(e,r)=>e>>>r,"<",10,(e,r)=>e<r,"<=",10,(e,r)=>e<=r,"<<",11,(e,r)=>e<<r,"+",12,(e,r)=>e+r,"+",15,e=>+e,"++",e=>x(e||i(14),e?(e,r)=>e[r]++:(e,r)=>++e[r]),15,"-",12,(e,r)=>e-r,"-",15,e=>-e,"--",e=>x(e||i(14),e?(e,r)=>e[r]--:(e,r)=>--e[r]),15,"!",15,e=>!e,"*",13,(e,r)=>e*r,"/",13,(e,r)=>e/r,"%",13,(e,r)=>e%r];[o,a,n,...t]=t,o;)g(o,a,n);export{d as default};
package/evaluate.js DELETED
@@ -1,27 +0,0 @@
1
- // calltree → result
2
- export const evaluate = (node, ctx={}) => {
3
- if (typeof node === 'string')
4
- return node[0]==='@' ? node.slice(1) : ctx[node]
5
-
6
- if (Array.isArray(node)) {
7
- // [[a,b], c] or ['+', a, b] or ['myfn', a, b], or
8
- let c = node[0], l = node.length,
9
- fn = Array.isArray(c) ? evaluate(c, ctx) : (lookup[c] || ctx[c] || c),
10
- i=1, res=[]
11
-
12
- if (fn.ab) { res=evaluate(node[i++],ctx); do { res = fn(res, evaluate(node[i++],ctx)) } while ( i < l ) }
13
- else { while ( i < l ) res.push(evaluate(node[i++], ctx)); res = fn.apply(ctx,res) }
14
-
15
- return res
16
- }
17
-
18
- return node
19
- },
20
- lookup = {},
21
-
22
- // op evaluators
23
- // multiple args (fn.r=false) are caused by:
24
- // ||&& shortcuts, lisp compatiblity, manual eval, functions multiple arguments, right precedence
25
- operator = evaluate.operator = (op, fn) => (lookup[op] = fn).ab = fn.length==2
26
-
27
- export default evaluate
package/parse.js DELETED
@@ -1,67 +0,0 @@
1
- const SPACE=32
2
-
3
- // current string & index
4
- let idx, cur
5
-
6
- export const parse = (str, tree) => (cur=str, idx=0, tree=expr(), idx<cur.length ? err() : val(tree)),
7
-
8
- err = (msg='Bad syntax') => { throw Error(msg + ' `' + cur[idx] + '` at ' + idx) },
9
-
10
- skip = (is=1, from=idx) => {
11
- if (typeof is === 'number') idx += is
12
- else while (is(code())) idx++;
13
- return cur.slice(from, idx)
14
- },
15
-
16
- code = (i=0) => cur.charCodeAt(idx+i),
17
-
18
- char = (n=1) => cur.substr(idx, n),
19
-
20
- // a + b - c
21
- expr = (prec=0, end, cc, node, i=0, newNode, op) => {
22
- // chunk/token parser
23
- while (
24
- (cc=parse.space()) && (newNode = (op=lookup[cc]) && op(node, prec) || (!node && token(cc)) )
25
- ) node = newNode;
26
-
27
- // skip end character, if expected
28
- if (end) cc != end ? err('Unclosed paren') : idx++
29
-
30
- return node
31
- },
32
-
33
- // can be extended with comments, so we export
34
- space = parse.space = cc => { while (cc = code(), cc <= SPACE) idx++; return cc },
35
-
36
- // tokens
37
- tokens = parse.token = [],
38
- token = (c,i=0,node) => { while(i<tokens.length) if (node = tokens[i++](c)) return node },
39
-
40
- // operator lookup table
41
- lookup = [],
42
-
43
- // create operator checker/mapper (see examples)
44
- // @param op is operator string
45
- // @param prec is operator precedenc to check
46
- // @param map is either number +1 - postfix unary, -1 prefix unary, 0 binary, else - custom mapper function
47
- operator = parse.operator = (
48
- op, prec=0, type=0, map, c=op.charCodeAt(0), l=op.length, prev=lookup[c]
49
- ) => (
50
- map = !type ? node => { // binary
51
- node = [op, val(node)]
52
- do { idx+=l, node.push(val(expr(prec))) } // consume same-op group
53
- while (parse.space()==c && (l<2||char(l)==op))
54
- return node
55
- } :
56
- type > 0 ? node => node && [skip(l), val(node)] : // postfix unary
57
- type < 0 ? node => !node && [skip(l), val(expr(prec-1))] : // prefix unary
58
- type,
59
-
60
- lookup[c] = (node, curPrec) =>
61
- curPrec < prec && (l<2||char(l)==op) && map(node) || (prev && prev(node, curPrec))
62
- ),
63
-
64
- // in order to support literal tokens, we call valueOf any time we create or modify calltree node
65
- val = node => Array.isArray(node) ? node : (node || err()).valueOf()
66
-
67
- export default parse