An open API service indexing awesome lists of open source software.

https://github.com/dy/subscript

Fast and tiny expression parser / evaluator.
https://github.com/dy/subscript

compute dsl eval evaluator expression-evaluator expression-parser jessie jexl jsep justin math math-expression-evaluator math-expression-parser microlanguage template

Last synced: 5 days ago
JSON representation

Fast and tiny expression parser / evaluator.

Awesome Lists containing this project

README

          

subscript

Tiny expression parser & evaluator.


[![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)

```js
import subscript from 'subscript'

let fn = subscript('a + b * 2')
fn({ a: 1, b: 3 }) // 7
```

* **Fast** — Pratt parser engine, see [benchmarks](#performance)
* **Portable** — universal expression format, see [spec](./spec.md)
* **Extensible** — pluggable syntax, see [playground](https://dy.github.io/subscript/)
* **Metacircular** — can parse and compile itself
* **Safe** — sandboxed, blocks `__proto__`, `constructor`, no global access

## Presets

**Subscript** – common expressions:

```js
import subscript from 'subscript'

subscript('a.b + c * 2')({ a: { b: 1 }, c: 3 }) // 7
```

**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** – 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
```

See [docs](./docs.md#presets) for full description.

## Extension

Add operators, literals or custom syntax:

```js
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))
))
```

```js
import justin from 'subscript/justin.js'
justin('[1,2,3] ∩ [2,3,4]')({}) // [2, 3]
```

See [docs.md](./docs.md) for full API.

## Syntax Tree

Expressions parse to a minimal JSON-compatible syntax tree:

```js
import { parse } from 'subscript'

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

Three forms:

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

See [spec.md](./spec.md).

## Safety

Blocked by default:
- `__proto__`, `__defineGetter__`, `__defineSetter__`
- `constructor`, `prototype`
- Global access (only context is visible)

```js
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:

```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