UNPKG

subscript

Version:

Modular expression parser & evaluator

200 lines (137 loc) 5.88 kB
<h1 align="center"><em>sub</em>script</h1> <p align="center">Modular expression parser & evaluator</p> <div align="center"> [![build](https://github.com/dy/subscript/actions/workflows/node.js.yml/badge.svg)](https://github.com/dy/subscript/actions/workflows/node.js.yml) [![npm](https://img.shields.io/npm/v/subscript)](http://npmjs.org/subscript) [![size](https://img.shields.io/bundlephobia/minzip/subscript?label=size)](https://bundlephobia.com/package/subscript) [![microjs](https://img.shields.io/badge/µjs-subscript-darkslateblue)](http://microjs.com/#subscript) <!--[![demo](https://img.shields.io/badge/play-%F0%9F%9A%80-white)](https://dy.github.io/subscript/)--> </div> ```js import subscript from 'subscript' let fn = subscript('a + b * 2') fn({ a: 1, b: 3 }) // 7 ``` * **Modular** — 40+ pluggable syntax features, see [playground](https://dy.github.io/subscript/) * **Universal** — minimal syntax tree, see [spec](./spec.md) * **Fast** — efficient parser, see [benchmarks](#performance) * **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**](./docs.md#subscript) – common expressions: ```js import subscript from 'subscript' subscript('a.b + c * 2')({ a: { b: 1 }, c: 3 }) // 7 ``` [**Justin**](./docs.md#justin) – JSON + expressions + templates + arrows: ```js 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**](./docs.md#jessie) – JSON + expressions + statements, functions (JS subset): ```js 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](./docs.md#presets) for full description. ## Syntax tree Expressions parse to minimal JSON-compatible tree structure: ```js 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](./spec.md). ## Extension Add operators, literals or custom syntax. `binary`/`unary`/`token`/`keyword` register parsers; `operator` registers compilers — pair them as needed: ```js 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: ```js import { binary } from 'subscript/feature/justin.js' binary('∩', 80) ``` See [docs.md](./docs.md) for full API. ## Safety Blocked by default: - `__proto__`, `__defineGetter__`, `__defineSetter__` - `constructor`, `prototype` - Global access (only context is visible) ```js subscript('constructor.constructor("alert(1)")()')({}) // undefined (blocked) ``` ## Performance Parsing `a + b * c - d / e + f.g[0](h) + i.j`, 30k iterations: ``` Parse: new Function 8ms subscript 34ms cel-js 39ms angular-expr 42ms justin 51ms jsep 54ms jessie 76ms ← JS subset (statements + functions) expr-eval 81ms oxc 84ms ← full JS parser (native Rust) mathjs 185ms jexl 403ms Eval: subscript 3ms new Function 4ms cel-js 13ms expression-eval 14ms mathjs 17ms angular-expr 62ms ``` Run via `node --import ./test/https-loader.js test/benchmark.js`. ## Utils ### Codegen Convert tree back to code: ```js import { codegen } from 'subscript/util/stringify.js' codegen(['+', ['*', 'min', [,60]], [,'sec']]) // 'min * 60 + "sec"' ``` ### Bundle Bundle imports into a single file: ```js // 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](https://github.com/dy/jz) — JS subset → WASM compiler * [sprae](https://github.com/dy/jz) — DOM framework <!-- * [prepr](https://github.com/dy/prepr) --> <!-- * [glsl-transpiler](https://github.com/stackgl/glsl-transpiler) --> <!-- * [piezo](https://github.com/dy/piezo) --> ## Alternatives <sup>[cel-js](https://github.com/marcbachmann/cel-js?tab=readme-ov-file), [jsep](https://github.com/EricSmekens/jsep), [jexl](https://github.com/TomFrost/Jexl), [expr-eval](https://github.com/silentmatt/expr-eval), [math.js](https://mathjs.org/), [mozjexl](https://github.com/mozilla/mozjexl), [jexpr](https://github.com/justinfagnani/jexpr), [expression-eval](https://github.com/donmccurdy/expression-eval), [string-math](https://github.com/devrafalko/string-math), [nerdamer](https://github.com/jiggzson/nerdamer), [math-codegen](https://github.com/mauriciopoppe/math-codegen), [math-parser](https://www.npmjs.com/package/math-parser), [nx-compile](https://github.com/nx-js/compiler-util), [built-in-math-eval](https://github.com/mauriciopoppe/built-in-math-eval)</sup> <br> <p align=center><a href="https://github.com/krsnzd/license/">ॐ</a></p>