GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — GitHub - dy/subscript: Expression parser / evaluator.

BranchesTags
Open more actions menu

Latest commit

 

History

1,272 Commits
1,272 Commits

Folders and files

NameName
Last commit message
Last commit date
.github
.github
 
 
.work
.work
 
 
eval
eval
 
 
feature
feature
 
 
test
test
 
 
util
util
 
 
.gitignore
.gitignore
 
 
LICENSE
LICENSE
 
 
README.md
README.md
 
 
docs.md
docs.md
 
 
index.html
index.html
 
 
jessie.js
jessie.js
 
 
justin.js
justin.js
 
 
meta.json
meta.json
 
 
package-lock.json
package-lock.json
 
 
package.json
package.json
 
 
parse.js
parse.js
 
 
playwright.config.js
playwright.config.js
 
 
spec.md
spec.md
 
 
subscript.d.ts
subscript.d.ts
 
 
subscript.js
subscript.js
 
 
tea.yaml
tea.yaml
 
 

Repository files navigation

subscript

Modular expression parser & evaluator

build npm size microjs

import subscript from 'subscript'

let fn = subscript('a + b * 2')
fn({ a: 1, b: 3 })  // 7
  • Modular — 40+ pluggable syntax features, see playground
  • Universal — minimal syntax tree, see spec
  • Fast — efficient parser, see benchmarks
  • Small — ~2KB core, runs in browser/node
  • Safe — sandboxed, no prototype access
  • Metacircular — parses and compiles itself

Useful for: templates, calculators, sandboxes, safe eval, language subsets, custom DSLs, preprocessors.

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

Each preset has a parse-only entry — subscript/feature/{subscript,justin,jessie} — that drops the runtime (~50% smaller) for consumers that only need the AST. See docs for full description.

Syntax tree

Expressions parse to minimal JSON-compatible tree structure:

import { parse } from 'subscript'

parse('a + b * 2')
// ['+', 'a', ['*', 'b', [, 2]]]

// node kinds
'x'             // identifier — resolve from context
[, value]       // literal — return as-is (empty slot = data)
[op, ...args]   // operation — apply operator

See spec.md.

Extension

Add operators, literals or custom syntax. binary/unary/token/keyword register parsers; operator registers compilers — pair them as needed:

import justin, { 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))
))

justin('[1,2,3] ∩ [2,3,4]')({})  // [2, 3]

For parse-only consumers, register only the parse half:

import { binary } from 'subscript/feature/justin.js'
binary('∩', 80)

See docs.md for full API.

Safety

Blocked by default:

  • __proto__ and any other __* property
  • constructor, prototype
  • Globals outside the supplied context
subscript('constructor')({})                          // undefined
subscript('constructor.constructor("alert(1)")')({}) // undefined

Context values are capabilities. Do not pass reflection helpers such as Reflect.get or Object.getOwnPropertyDescriptor to untrusted expressions.

Performance

Parsing a + b * c - d / e + f.g[0](h) + i.j, 30k iterations — each iteration parses an unseen source, so source-keyed compile caches (new Function, angular-expressions) don't help:

Parse:
  subscript     26ms
  justin        35ms
  cel-js        45ms
  jsep          46ms
  jessie        51ms   ← JS subset (statements + functions)
  expr-eval     74ms
  oxc           86ms   ← full JS parser (native Rust)
  mathjs       156ms
  new Function 166ms
  jexl         304ms
  angular-expr  48s

Eval:
  new Function 0.2ms
  subscript    1.7ms
  cel-js        10ms
  mathjs        10ms
  expression-eval 24ms
  angular-expr  44ms

Run via node --import ./test/https-loader.js test/benchmark.js.

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:

// Node.js
import { bundleFile } from 'subscript/util/bundle.js'
console.log(await bundleFile('jessie.js'))

// Browser / custom sources
import { bundle } from 'subscript/util/bundle.js'
console.log(await bundle('main.js', {
  'main.js': `import { x } from './lib.js'; export default x * 2`,
  'lib.js': `export const x = 21`
}))
// → "const x = 21;\nexport { x as default }"

Used by

  • jz — JS subset → WASM compiler
  • sprae — DOM framework

Alternatives

cel-js, jsep, jexl, expr-eval, math.js, mozjexl, jexpr, expression-eval, string-math, nerdamer, math-codegen, math-parser, nx-compile, built-in-math-eval


ॐ

About

Expression parser / evaluator.

dy.github.io/subscript/

Topics

astdslevalevaluatorexpressionexpression-evaluatorexpression-parserformulajessiejustinlanguagemathparserprattsafe-evalsyntax

Resources

Readme
ISC license
Activity

Stars

150 stars

Watchers

5 watching

Forks

8 forks
Report repository

Releases

Packages

Used by

Contributors

Languages

You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.