JSPM

  • Created
  • Published
  • Downloads 7226
  • Score
    100M100P100Q128130F
  • License ISC

Tiny expression parser & evaluator

Package Exports

  • subscript
  • subscript/feature/access.js
  • subscript/feature/accessor.js
  • subscript/feature/asi.js
  • subscript/feature/async.js
  • subscript/feature/block.js
  • subscript/feature/class.js
  • subscript/feature/collection.js
  • subscript/feature/comment.js
  • subscript/feature/control.js
  • subscript/feature/destruct.js
  • subscript/feature/function.js
  • subscript/feature/group.js
  • subscript/feature/if.js
  • subscript/feature/literal.js
  • subscript/feature/loop.js
  • subscript/feature/module.js
  • subscript/feature/number.js
  • subscript/feature/op/arithmetic.js
  • subscript/feature/op/arrow.js
  • subscript/feature/op/assign-logical.js
  • subscript/feature/op/assignment.js
  • subscript/feature/op/bitwise-unsigned.js
  • subscript/feature/op/bitwise.js
  • subscript/feature/op/comparison.js
  • subscript/feature/op/defer.js
  • subscript/feature/op/equality.js
  • subscript/feature/op/identity.js
  • subscript/feature/op/increment.js
  • subscript/feature/op/logical.js
  • subscript/feature/op/membership.js
  • subscript/feature/op/nullish.js
  • subscript/feature/op/optional.js
  • subscript/feature/op/pow.js
  • subscript/feature/op/range.js
  • subscript/feature/op/spread.js
  • subscript/feature/op/ternary.js
  • subscript/feature/op/type.js
  • subscript/feature/op/unary.js
  • subscript/feature/prop.js
  • subscript/feature/regex.js
  • subscript/feature/seq.js
  • subscript/feature/string.js
  • subscript/feature/switch.js
  • subscript/feature/template.js
  • subscript/feature/try.js
  • subscript/feature/unit.js
  • subscript/feature/var.js
  • subscript/jessie
  • subscript/justin
  • subscript/parse
  • subscript/util/bundle.js
  • subscript/util/stringify.js

Readme

subscript

Tiny expression parser & evaluator.

build npm size microjs

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

Presets

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.

Extension

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.

Syntax Tree

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.

Safety

Blocked by default:

  • __proto__, __defineGetter__, __defineSetter__
  • constructor, prototype
  • Global access (only context is visible)
subscript('constructor.constructor("alert(1)")()')({})
// undefined (blocked)

Performance

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

Utils

Codegen

Convert tree back to code:

import { codegen } from 'subscript/util/stringify.js'

codegen(['+', ['*', 'min', [,60]], [,'sec']])
// 'min * 60 + "sec"'

Bundle

Bundle imports into a single file:

import { bundle } from 'subscript/util/bundle.js'

const code = await bundle('subscript/jessie.js')
// → self-contained ES module

Used by

  • jz — JS subset → WASM compiler