subscript 8.0.0 → 8.0.1

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.
@@ -0,0 +1,11 @@
1
+ import { token, expr, err } from '../src/parse.js'
2
+ import { operator, compile, access } from '../src/compile.js'
3
+ import { CBRACK, CPAREN, PREC_ACCESS } from '../src/const.js'
4
+
5
+ // a[b]
6
+ token('[', PREC_ACCESS, a => a && ['[', a, expr(0, CBRACK) || err()])
7
+ operator('[', (a, b) => b && (a = compile(a), b = compile(b), ctx => a(ctx)[b(ctx)]))
8
+
9
+ // a.b
10
+ token('.', PREC_ACCESS, (a, b) => a && (b = expr(PREC_ACCESS)) && ['.', a, b])
11
+ operator('.', (a, b) => (a = compile(a), b = !b[0] ? b[1] : b, ctx => a(ctx)[b])) // a.true, a.1 → needs to work fine
package/feature/add.js ADDED
@@ -0,0 +1,22 @@
1
+
2
+ import { binary, unary } from '../src/parse.js'
3
+ import { PREC_ADD, PREC_PREFIX, PREC_ASSIGN } from '../src/const.js'
4
+ import { compile, access, operator } from '../src/compile.js'
5
+
6
+ unary('+', PREC_PREFIX), operator('+', (a, b) => !b && (a = compile(a), ctx => +a(ctx)))
7
+ unary('-', PREC_PREFIX), operator('-', (a, b) => !b && (a = compile(a), ctx => -a(ctx)))
8
+
9
+ binary('+', PREC_ADD), operator('+', (a, b) => b && (a = compile(a), b = compile(b), ctx => a(ctx) + b(ctx)))
10
+ binary('-', PREC_ADD), operator('-', (a, b) => b && (a = compile(a), b = compile(b), ctx => a(ctx) - b(ctx)))
11
+
12
+ binary('+=', PREC_ASSIGN, true)
13
+ operator('+=', (a, b) => (
14
+ b = compile(b),
15
+ access(a, (container, path, ctx) => container(ctx)[path(ctx)] += b(ctx))
16
+ ))
17
+
18
+ binary('-=', PREC_ASSIGN, true)
19
+ operator('-=', (a, b) => (
20
+ b = compile(b),
21
+ access(a, (container, path, ctx) => (container(ctx)[path(ctx)] -= b(ctx)))
22
+ ))
@@ -0,0 +1,11 @@
1
+ import { token, expr } from '../src/parse.js'
2
+ import { operator, compile } from '../src/compile.js'
3
+ import { CBRACK, PREC_TOKEN } from '../src/const.js'
4
+
5
+ // [a,b,c]
6
+ token('[', PREC_TOKEN, (a) => !a && ['[', expr(0, CBRACK) || ''])
7
+ operator('[', (a, b) => !b && (
8
+ !a ? () => [] : // []
9
+ a[0] === ',' ? (a = a.slice(1).map(compile), ctx => a.map(a => a(ctx))) : // [a,b,c]
10
+ (a = compile(a), ctx => [a(ctx)]) // [a]
11
+ ))
@@ -0,0 +1,11 @@
1
+ import { binary, err } from "../src/parse.js";
2
+ import { compile, operator, operators, access } from "../src/compile.js";
3
+ import { PREC_ASSIGN } from "../src/const.js";
4
+
5
+ // assignments
6
+ binary('=', PREC_ASSIGN, true)
7
+ operator('=', (a, b) => (
8
+ b = compile(b),
9
+ // a = x, ((a)) = x, a.b = x, a['b'] = x
10
+ access(a, (container, path, ctx) => container(ctx)[path(ctx)] = b(ctx))
11
+ ))
@@ -0,0 +1,15 @@
1
+ import { PREC_OR, PREC_AND, PREC_SHIFT, PREC_XOR, PREC_PREFIX } from "../src/const.js"
2
+ import { unary, binary } from "../src/parse.js"
3
+ import { operator, compile } from "../src/compile.js"
4
+
5
+ unary('~', PREC_PREFIX), operator('~', (a, b) => !b && (a = compile(a), ctx => ~a(ctx)))
6
+
7
+ binary('|', PREC_OR), operator('|', (a, b) => b && (a = compile(a), b = compile(b), ctx => a(ctx) | b(ctx)))
8
+
9
+ binary('&', PREC_AND), operator('&', (a, b) => b && (a = compile(a), b = compile(b), ctx => a(ctx) & b(ctx)))
10
+
11
+ binary('^', PREC_XOR), operator('^', (a, b) => b && (a = compile(a), b = compile(b), ctx => a(ctx) ^ b(ctx)))
12
+
13
+ binary('>>', PREC_SHIFT), operator('>>', (a, b) => b && (a = compile(a), b = compile(b), ctx => a(ctx) >> b(ctx)))
14
+
15
+ binary('<<', PREC_SHIFT), operator('<<', (a, b) => b && (a = compile(a), b = compile(b), ctx => a(ctx) << b(ctx)))
@@ -0,0 +1,5 @@
1
+ import { token } from "../src/parse.js"
2
+ import { PREC_TOKEN } from "../src/const.js"
3
+
4
+ token('true', PREC_TOKEN, a => a ? err() : [, true])
5
+ token('false', PREC_TOKEN, a => a ? err() : [, false])
@@ -0,0 +1,15 @@
1
+ import { token, expr, err } from '../src/parse.js'
2
+ import { operator, compile, access } from '../src/compile.js'
3
+ import { CBRACK, CPAREN, PREC_ACCESS } from '../src/const.js'
4
+
5
+ // a(b,c,d), a()
6
+ token('(', PREC_ACCESS, (a, b) => a && (b = expr(0, CPAREN), b ? ['(', a, b] : ['(', a, '']))
7
+ operator('(', (a, b, args) => (
8
+ args = b == '' ? () => [] : // a()
9
+ b[0] === ',' ? (b = b.slice(1).map(compile), ctx => b.map(arg => arg(ctx))) : // a(b,c)
10
+ (b = compile(b), ctx => [b(ctx)]), // a(b)
11
+
12
+ // a(...args), a.b(...args), a[b](...args)
13
+ access(a, (obj, path, ctx) => obj(ctx)[path(ctx)](...args(ctx)), true)
14
+ )
15
+ )
@@ -0,0 +1,6 @@
1
+ import { SPACE, STAR, PREC_TOKEN } from "../src/const.js"
2
+ import { token, skip, cur, idx, expr } from "../src/parse.js"
3
+
4
+ // /**/, //
5
+ token('/*', PREC_TOKEN, (a, prec) => (skip(c => c !== STAR && cur.charCodeAt(idx + 1) !== 47), skip(2), a || expr(prec) || ['']))
6
+ token('//', PREC_TOKEN, (a, prec) => (skip(c => c >= SPACE), a || expr(prec) || ['']))
@@ -0,0 +1,11 @@
1
+ import { PREC_EQ } from '../src/const.js'
2
+ import { unary, binary } from "../src/parse.js"
3
+ import { operator, compile } from "../src/compile.js"
4
+
5
+
6
+ binary('==', PREC_EQ), operator('==', (a, b) => b && (a = compile(a), b = compile(b), ctx => a(ctx) == b(ctx)))
7
+ binary('!=', PREC_EQ), operator('!=', (a, b) => b && (a = compile(a), b = compile(b), ctx => a(ctx) != b(ctx)))
8
+ binary('>', PREC_EQ), operator('>', (a, b) => b && (a = compile(a), b = compile(b), ctx => a(ctx) > b(ctx)))
9
+ binary('<', PREC_EQ), operator('<', (a, b) => b && (a = compile(a), b = compile(b), ctx => a(ctx) < b(ctx)))
10
+ binary('>=', PREC_EQ), operator('>=', (a, b) => b && (a = compile(a), b = compile(b), ctx => a(ctx) >= b(ctx)))
11
+ binary('<=', PREC_EQ), operator('<=', (a, b) => b && (a = compile(a), b = compile(b), ctx => a(ctx) <= b(ctx)))
@@ -0,0 +1,12 @@
1
+ import { token, expr, err, nary } from '../src/parse.js'
2
+ import { compile, operator } from '../src/compile.js'
3
+ import { CPAREN, PREC_ACCESS, PREC_GROUP, PREC_SEQ } from '../src/const.js'
4
+
5
+ // (a,b,c), (a)
6
+ // FIXME: try raising group precedence (it causes conflict in ?. though)
7
+ token('(', PREC_ACCESS, (a) => (!a && ['()', expr(0, CPAREN) || err('Empty group')]))
8
+ operator('()', (a) => (compile(a)))
9
+
10
+ const last = (...args) => (args = args.map(compile), ctx => args.map(arg => arg(ctx)).pop())
11
+ nary(',', PREC_SEQ), operator(',', last)
12
+ nary(';', PREC_SEQ, true), operator(';', last)
package/feature/in.js ADDED
@@ -0,0 +1,6 @@
1
+ import { binary } from "../src/parse.js";
2
+ import { compile, operator } from "../src/compile.js";
3
+ import { PREC_COMP } from "../src/const.js";
4
+
5
+ // a in b
6
+ binary('in', PREC_COMP), operator('in', (a, b) => b && (a = compile(a), b = compile(b), ctx => a(ctx) in b(ctx)))
@@ -0,0 +1,16 @@
1
+ import { token, expr } from "../src/parse.js"
2
+ import { operator, compile, access } from "../src/compile.js"
3
+ import { PREC_POSTFIX } from "../src/const.js"
4
+
5
+ let inc, dec
6
+ token('++', PREC_POSTFIX, a => a ? ['-', ['++', a], [, 1]] : ['++', expr(PREC_POSTFIX - 1)])
7
+ operator('++', inc = (a) =>
8
+ // ++a, ++((a)), ++a.b, ++a[b]
9
+ access(a, (obj, path, ctx) => ++obj(ctx)[path(ctx)])
10
+ )
11
+
12
+ token('--', PREC_POSTFIX, a => a ? ['+', ['--', a], [, 1]] : ['--', expr(PREC_POSTFIX - 1)])
13
+ operator('--', dec = (a) => (
14
+ // --a, --a.b, --a[b]
15
+ access(a, (obj, path, ctx) => --obj(ctx)[path(ctx)])
16
+ ))
@@ -0,0 +1,15 @@
1
+ import { PREC_LOR, PREC_LAND, PREC_PREFIX } from '../src/const.js';
2
+ import { unary, binary, nary } from "../src/parse.js"
3
+ import { operator, compile } from "../src/compile.js"
4
+
5
+ unary('!', PREC_PREFIX), operator('!', (a, b) => !b && (a = compile(a), ctx => !a(ctx)))
6
+
7
+ nary('||', PREC_LOR), operator('||', (...args) => (
8
+ args = args.map(compile),
9
+ ctx => { let arg, res; for (arg of args) if (res = arg(ctx)) return res; return res }
10
+ ))
11
+
12
+ nary('&&', PREC_LAND), operator('&&', (...args) => (
13
+ args = args.map(compile),
14
+ ctx => { let arg, res; for (arg of args) if (!(res = arg(ctx))) return res; return res }
15
+ ))
@@ -0,0 +1,25 @@
1
+ import { binary } from '../src/parse.js'
2
+ import { operator, compile, access } from '../src/compile.js'
3
+ import { PREC_MULT, PREC_ASSIGN } from '../src/const.js'
4
+
5
+ binary('*', PREC_MULT), operator('*', (a, b) => b && (a = compile(a), b = compile(b), ctx => a(ctx) * b(ctx)))
6
+ binary('/', PREC_MULT), operator('/', (a, b) => b && (a = compile(a), b = compile(b), ctx => a(ctx) / b(ctx)))
7
+ binary('%', PREC_MULT), operator('%', (a, b) => b && (a = compile(a), b = compile(b), ctx => a(ctx) % b(ctx)))
8
+
9
+ binary('*=', PREC_ASSIGN, true)
10
+ operator('*=', (a, b) => (
11
+ b = compile(b),
12
+ access(a, (container, path, ctx) => container(ctx)[path(ctx)] *= b(ctx))
13
+ ))
14
+
15
+ binary('/=', PREC_ASSIGN, true)
16
+ operator('/=', (a, b) => (
17
+ b = compile(b),
18
+ access(a, (container, path, ctx) => container(ctx)[path(ctx)] /= b(ctx))
19
+ ))
20
+
21
+ binary('%=', PREC_ASSIGN, true)
22
+ operator('%=', (a, b) => (
23
+ b = compile(b),
24
+ access(a, (container, path, ctx) => container(ctx)[path(ctx)] %= b(ctx))
25
+ ))
@@ -0,0 +1,11 @@
1
+ import { lookup, skip, err } from "../src/parse.js"
2
+ import { PERIOD, _0, _E, _e, _9 } from "../src/const.js"
3
+
4
+ // parse number
5
+ const num = a => a ? err() : [, (a = +skip(c => c === PERIOD || (c >= _0 && c <= _9) || (c === _E || c === _e ? 2 : 0))) != a ? err() : a]
6
+
7
+ // .1
8
+ lookup[PERIOD] = a => (!a && num())
9
+
10
+ // 0-9
11
+ for (let i = _0; i <= _9; i++) lookup[i] = num
@@ -0,0 +1,17 @@
1
+ import { token, expr } from '../src/parse.js'
2
+ import { operator, compile } from '../src/compile.js'
3
+ import { CBRACE, PREC_SEQ, PREC_TOKEN } from '../src/const.js'
4
+
5
+
6
+ // {a:1, b:2, c:3}
7
+ token('{', PREC_TOKEN, a => !a && (['{', expr(0, CBRACE) || '']))
8
+ operator('{', (a, b) => (
9
+ !a ? ctx => ({}) : // {}
10
+ a[0] === ',' ? (a = a.slice(1).map(compile), ctx => Object.fromEntries(a.map(a => a(ctx)))) : // {a:1,b:2}
11
+ a[0] === ':' ? (a = compile(a), ctx => Object.fromEntries([a(ctx)])) : // {a:1}
12
+ (b = compile(a), ctx => ({ [a]: b(ctx) }))
13
+ ))
14
+
15
+ // FIXME: mb we don't need this seq raise
16
+ token(':', PREC_SEQ + 0.1, (a, b) => (b = expr(PREC_SEQ + 0.1) || err(), [':', a, b]))
17
+ operator(':', (a, b) => (b = compile(b), a = Array.isArray(a) ? compile(a) : (a => a).bind(0, a), ctx => [a(ctx), b(ctx)]))
package/feature/pow.js ADDED
@@ -0,0 +1,5 @@
1
+ import { binary } from "../src/parse.js";
2
+ import { compile, operator } from "../src/compile.js";
3
+ import { PREC_EXP } from "../src/const.js";
4
+
5
+ binary('**', PREC_EXP, true), operator('**', (a, b) => b && (a = compile(a), b = compile(b), ctx => a(ctx) ** b(ctx)))
@@ -0,0 +1,19 @@
1
+ import { skip, err, cur, idx, lookup } from '../src/parse.js'
2
+ import { DQUOTE, QUOTE, BSLASH } from '../src/const.js'
3
+
4
+ const escape = { n: '\n', r: '\r', t: '\t', b: '\b', f: '\f', v: '\v' },
5
+ string = q => (qc, c, str = '') => {
6
+ qc && err('Unexpected string') // must not follow another token
7
+ skip() // first quote
8
+ while (c = cur.charCodeAt(idx), c - q) {
9
+ if (c === BSLASH) skip(), c = skip(), str += escape[c] || c
10
+ else str += skip()
11
+ }
12
+ skip() || err('Bad string')
13
+ return [, str]
14
+ }
15
+
16
+
17
+ // "' with /
18
+ lookup[DQUOTE] = string(DQUOTE)
19
+ lookup[QUOTE] = string(QUOTE)
@@ -0,0 +1,7 @@
1
+ import { token, expr } from '../src/parse.js'
2
+ import { operator, compile } from '../src/compile.js'
3
+ import { PREC_ASSIGN, COLON } from '../src/const.js'
4
+
5
+ // ?:
6
+ token('?', PREC_ASSIGN, (a, b, c) => a && (b = expr(PREC_ASSIGN, COLON)) && (c = expr(PREC_ASSIGN + 1), ['?', a, b, c]))
7
+ operator('?', (a, b, c) => (a = compile(a), b = compile(b), c = compile(c), ctx => a(ctx) ? b(ctx) : c(ctx)))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "subscript",
3
- "version": "8.0.0",
3
+ "version": "8.0.1",
4
4
  "description": "Fast and tiny expression evaluator with minimal syntax.",
5
5
  "main": "subscript.js",
6
6
  "module": "subscript.js",
@@ -8,15 +8,15 @@
8
8
  "types": "./subscript.d.ts",
9
9
  "exports": {
10
10
  ".": "./subscript.js",
11
- "./parse.js": "./parse.js",
12
- "./compile.js": "./compile.js",
11
+ "./parse.js": "./src/parse.js",
12
+ "./compile.js": "./src/compile.js",
13
13
  "./subscript.js": "./subscript.js",
14
14
  "./justin.js": "./justin.js"
15
15
  },
16
16
  "type": "module",
17
17
  "files": [
18
- "parse.js",
19
- "compile.js",
18
+ "src",
19
+ "feature",
20
20
  "subscript.js",
21
21
  "subscript.min.js",
22
22
  "justin.js",
@@ -0,0 +1,17 @@
1
+ export type OperatorFunction = (...args: any[]) => any;
2
+ export type OperatorMap = {
3
+ [key: string]: OperatorFunction;
4
+ };
5
+ export type Node = string | [string | undefined, ...Node[]];
6
+ export function compile(node: Node): ((ctx?: any) => any) | OperatorFunction;
7
+ export const operators: OperatorMap;
8
+ export function operator(op: string, fn: OperatorFunction): void;
9
+
10
+ type AccessorFunction = (ctx: any) => any;
11
+ export function access(
12
+ a: [string, ...any[]] | string,
13
+ fn: Function,
14
+ generic: boolean
15
+ ): AccessorFunction;
16
+
17
+ export default compile;
package/src/compile.js ADDED
@@ -0,0 +1,26 @@
1
+ import { err } from "./parse.js"
2
+
3
+ // build optimized evaluator for the tree
4
+ export const compile = (node) => !Array.isArray(node) ? ctx => ctx?.[node] : !node[0] ? () => node[1] : operators[node[0]](...node.slice(1)),
5
+
6
+ // registered operators
7
+ operators = {},
8
+
9
+ // register an operator
10
+ operator = (op, fn, prev = operators[op]) => (operators[op] = (...args) => fn(...args) || prev && prev(...args)),
11
+
12
+ // takes node and returns evaluator depending on the case with passed params (container, path, ctx) =>
13
+ access = (a, fn, generic) => (
14
+ // (((x))) => x
15
+ a[0] === '()' ? access(a[1], fn, generic) :
16
+ // (_, name, ctx) => ctx[path]
17
+ typeof a === 'string' ? fn.bind(0, ctx => ctx, () => a) :
18
+ // (container, path, ctx) => container(ctx)[path]
19
+ a[0] === '.' ? fn.bind(0, compile(a[1]), (a = a[2], () => a)) :
20
+ // (container, path, ctx) => container(ctx)[path(ctx)]
21
+ a[0] === '[' ? fn.bind(0, compile(a[1]), compile(a[2])) :
22
+ // (src, _, ctx) => src(ctx)
23
+ generic ? (a = compile(a), fn.bind(0, ctx => [a(ctx)], () => 0)) : () => err('Bad left value')
24
+ )
25
+
26
+ export default compile
package/src/const.js ADDED
@@ -0,0 +1,40 @@
1
+ export const
2
+ PERIOD = 46,
3
+ OPAREN = 40,
4
+ CPAREN = 41,
5
+ OBRACK = 91,
6
+ CBRACK = 93,
7
+ OBRACE = 123,
8
+ CBRACE = 125,
9
+ SPACE = 32,
10
+ COLON = 58,
11
+ DQUOTE = 34,
12
+ QUOTE = 39,
13
+ _0 = 48,
14
+ _9 = 57,
15
+ _E = 69,
16
+ _e = 101,
17
+ BSLASH = 92,
18
+ SLASH = 47,
19
+ STAR = 42
20
+
21
+ // ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_precedence
22
+ export const
23
+ PREC_SEQ = 1,
24
+ PREC_ASSIGN = 2,
25
+ PREC_LOR = 3,
26
+ PREC_LAND = 4,
27
+ PREC_OR = 5,
28
+ PREC_XOR = 6,
29
+ PREC_AND = 7,
30
+ PREC_EQ = 8,
31
+ PREC_COMP = 9,
32
+ PREC_SHIFT = 10,
33
+ PREC_ADD = 11,
34
+ PREC_MULT = 12,
35
+ PREC_EXP = 13,
36
+ PREC_PREFIX = 14,
37
+ PREC_POSTFIX = 15,
38
+ PREC_ACCESS = 17,
39
+ PREC_GROUP = 18,
40
+ PREC_TOKEN = 20
package/src/parse.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ export let idx: any;
2
+ export let cur: any;
3
+ export function parse(s: string): any;
4
+ export namespace parse {
5
+ function space(cc: any): any;
6
+ function id(n: any): any;
7
+ }
8
+ export function err(msg?: string, frag?: string): never;
9
+ export function skip(is: number | ((c: number) => number)): string;
10
+ export const lookup: ((a: any, b: any) => any)[];
11
+ export function token(op: string, prec: number, map: (a: any, curPrec: number, from: number) => any): (a: any, curPrec: number, from?: any) => any;
12
+ export function binary(op: string, prec: number, right?: boolean | undefined): (a: any, curPrec: number, from?: any) => any;
13
+ export function unary(op: string, prec: number, post?: boolean | undefined): (a: any, curPrec: number, from?: any) => any;
14
+ export function nary(op: string, prec: number, skips?: boolean | undefined): (a: any, curPrec: number, from?: any) => any;
15
+ export function expr(prec: number, end?: string | undefined): any;
16
+ export function isId(c: number): boolean;
17
+ export function space(): number;
18
+ export function id(): string;
19
+ export default parse;
package/src/parse.js ADDED
@@ -0,0 +1,94 @@
1
+ import { SPACE } from "./const.js"
2
+
3
+ // current string, index and collected ids
4
+ export let idx, cur,
5
+
6
+ // no handling tagged literals since easily done on user side with cache, if needed
7
+ parse = s => (idx = 0, cur = s, s = expr(), cur[idx] ? err() : s || ''),
8
+
9
+ err = (msg = 'Bad syntax',
10
+ lines = cur.slice(0, idx).split('\n'),
11
+ last = lines.pop()
12
+ ) => {
13
+ let before = cur.slice(idx - 108, idx).split('\n').pop()
14
+ let after = cur.slice(idx, idx + 108).split('\n').shift()
15
+ throw EvalError(`${msg} at ${lines.length}:${last.length} \`${idx >= 108 ? '…' : ''}${before}▶${after}\``, 'font-weight: bold')
16
+ },
17
+
18
+ skip = (is = 1, from = idx, l) => {
19
+ if (typeof is == 'number') idx += is
20
+ else while (l = is(cur.charCodeAt(idx))) idx += l
21
+ return cur.slice(from, idx)
22
+ },
23
+
24
+ // a + b - c
25
+ expr = (prec = 0, end, cc, token, newNode, fn) => {
26
+ // chunk/token parser
27
+ while (
28
+ (cc = parse.space()) && // till not end
29
+ // FIXME: extra work is happening here, when lookup bails out due to lower precedence -
30
+ // it makes extra `space` call for parent exprs on the same character to check precedence again
31
+ (newNode =
32
+ ((fn = lookup[cc]) && fn(token, prec)) ?? // if operator with higher precedence isn't found
33
+ (!token && parse.id()) // parse literal or quit. token seqs are forbidden: `a b`, `a "b"`, `1.32 a`
34
+ )
35
+ ) token = newNode;
36
+
37
+ // check end character
38
+ // FIXME: can't show "Unclosed paren", because can be unknown operator within group as well
39
+ if (end) cc == end ? idx++ : err()
40
+
41
+ return token
42
+ },
43
+
44
+ isId = c =>
45
+ (c >= 48 && c <= 57) || // 0..9
46
+ (c >= 65 && c <= 90) || // A...Z
47
+ (c >= 97 && c <= 122) || // a...z
48
+ c == 36 || c == 95 || // $, _,
49
+ (c >= 192 && c != 215 && c != 247), // any non-ASCII
50
+
51
+ // parse identifier (configurable)
52
+ id = parse.id = n => skip(isId),
53
+
54
+ // skip space chars, return first non-space character
55
+ space = parse.space = cc => { while ((cc = cur.charCodeAt(idx)) <= SPACE) idx++; return cc },
56
+
57
+ // operator/token lookup table
58
+ // lookup[0] is id parser to let configs redefine it
59
+ lookup = [],
60
+
61
+
62
+ // create operator checker/mapper (see examples)
63
+ token = (
64
+ op,
65
+ prec = SPACE,
66
+ map,
67
+ c = op.charCodeAt(0),
68
+ l = op.length,
69
+ prev = lookup[c],
70
+ word = op.toUpperCase() !== op // make sure word boundary comes after word operator
71
+ ) => lookup[c] = (a, curPrec, from = idx) =>
72
+ (curPrec < prec && (l < 2 || cur.substr(idx, l) == op) && (!word || !isId(cur.charCodeAt(idx + l))) && (idx += l, map(a, curPrec))) ||
73
+ (idx = from, prev?.(a, curPrec)),
74
+
75
+ // right assoc is indicated by negative precedence (meaning go from right to left)
76
+ binary = (op, prec, right = 0) => token(op, prec, (a, b) => a && (b = expr(prec - right / 2)) && [op, a, b]),
77
+
78
+ // post indicates postfix rather than prefix operator
79
+ unary = (op, prec, post) => token(op, prec, a => post ? (a && [op, a]) : (!a && (a = expr(prec - .5)) && [op, a])),
80
+
81
+ // skips means ,,, ;;; are allowed
82
+ nary = (op, prec, skips) => {
83
+ token(op, prec, (a, b) => (
84
+ (a || skips) && // if lhs exists or we're ok to skip
85
+ (b = expr(prec), b || skips) && // either rhs exists or we're ok to skip rhs
86
+ (
87
+ (!a || a[0] !== op) && (a = [op, a]), // if beginning of sequence - init node
88
+ (b || skips) && a.push(b),
89
+ a
90
+ ))
91
+ )
92
+ }
93
+
94
+ export default parse