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.
- package/feature/access.js +11 -0
- package/feature/add.js +22 -0
- package/feature/array.js +11 -0
- package/feature/assign.js +11 -0
- package/feature/bitwise.js +15 -0
- package/feature/bool.js +5 -0
- package/feature/call.js +15 -0
- package/feature/comment.js +6 -0
- package/feature/compare.js +11 -0
- package/feature/group.js +12 -0
- package/feature/in.js +6 -0
- package/feature/increment.js +16 -0
- package/feature/logic.js +15 -0
- package/feature/mult.js +25 -0
- package/feature/number.js +11 -0
- package/feature/object.js +17 -0
- package/feature/pow.js +5 -0
- package/feature/string.js +19 -0
- package/feature/ternary.js +7 -0
- package/package.json +5 -5
- package/src/compile.d.ts +17 -0
- package/src/compile.js +26 -0
- package/src/const.js +40 -0
- package/src/parse.d.ts +19 -0
- package/src/parse.js +94 -0
|
@@ -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
|
+
))
|
package/feature/array.js
ADDED
|
@@ -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)))
|
package/feature/bool.js
ADDED
package/feature/call.js
ADDED
|
@@ -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)))
|
package/feature/group.js
ADDED
|
@@ -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
|
+
))
|
package/feature/logic.js
ADDED
|
@@ -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
|
+
))
|
package/feature/mult.js
ADDED
|
@@ -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,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.
|
|
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
|
-
"
|
|
19
|
-
"
|
|
18
|
+
"src",
|
|
19
|
+
"feature",
|
|
20
20
|
"subscript.js",
|
|
21
21
|
"subscript.min.js",
|
|
22
22
|
"justin.js",
|
package/src/compile.d.ts
ADDED
|
@@ -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
|