Tiny expression parser & evaluator.
import subscript from 'subscript' let fn = subscript('a + b * 2') fn({ a: 1, b: 3 }) // 7
- Safe — sandboxed, blocks
__proto__,constructor, no global access - Fast — Pratt parser engine, see benchmarks
- Portable — universal expression format, see spec
- Extensible — pluggable syntax, see DSL builder
- Metacircular — can parse and compile itself
Subscript: common expressions:
import subscript from 'subscript' subscript('a.b + c * 2')({ a: { b: 1 }, c: 3 }) // 7
Justin: JSON + expressions + templates + arrows:
import justin from 'subscript/justin.js' justin('{ x: a?.b ?? 0, y: [1, ...rest] }')({ a: null, rest: [2, 3] }) // { x: 0, y: [1, 2, 3] }
Jessie: JSON + expressions + statements, functions (JS subset):
import jessie from 'subscript/jessie.js' let fn = jessie(` function factorial(n) { if (n <= 1) return 1 return n * factorial(n - 1) } factorial(5) `) fn({}) // 120
See docs for full description.
import { binary, operator, compile } from 'subscript/justin.js' // add intersection operator binary('∩', 80) // register parser operator('∩', (a, b) => ( // register compiler a = compile(a), b = compile(b), ctx => a(ctx).filter(x => b(ctx).includes(x)) ))
import justin from 'subscript/justin.js' justin('[1,2,3] ∩ [2,3,4]')({}) // [2, 3]
See docs.md for full API.
Expressions parse to a minimal JSON-compatible syntax tree:
import { parse } from 'subscript' parse('a + b * 2') // ['+', 'a', ['*', 'b', [, 2]]]
Three forms:
'x' // identifier — resolve from context [, value] // literal — return as-is (empty slot = data) [op, ...args] // operation — apply operator
See spec.md.
Blocked by default:
__proto__,__defineGetter__,__defineSetter__constructor,prototype- Global access (only context is visible)
subscript('constructor.constructor("alert(1)")()')({}) // undefined (blocked)
Parse 30k: subscript 150ms · justin 183ms · jsep 270ms · expr-eval 480ms · jexl 1056ms
Eval 30k: new Function 7ms · subscript 15ms · jsep+eval 30ms · expr-eval 72ms
Convert tree back to code:
import { codegen } from 'subscript/util/stringify.js' codegen(['+', ['*', 'min', [,60]], [,'sec']]) // 'min * 60 + "sec"'
Bundle imports into a single file:
import { bundle } from 'subscript/util/bundle.js' const code = await bundle('subscript/jessie.js') // → self-contained ES module
- jz — JS subset → WASM compiler