subscript

Subscript is expression evaluator / microlanguage with standard syntax

  • Any fragment can be copy-pasted to any language: C++, JS, Java, Python, Go, Rust etc.
  • Tiny size npm bundle size
  • :rocket: Fast performance
  • Configurable & extensible
  • Trivial to use
import script from './subscript.js'
let fn = script`a.b + c(d - 1)`
fn({ a: { b:1 }, c: x => x * 2, d: 3 }) // 5
fn.args // ['a', 'c', 'd']

Motivation

Subscript is designed to be useful for:

  • templates (perfect match with template parts, templize)
  • expressions evaluators, calculators
  • configurable subsets of languages (eg. justin)
  • pluggable/mock language features (eg. pipe operator)
  • sandboxes, playgrounds, safe eval
  • custom DSL

Subscript has 2kb footprint, compared to 11.4kb jsep + 4.5kb expression-eval, with better test coverage and better performance.

Design

Default operators are (same as JS precedence order):

  • ( a, b, c )
  • a.b, a[b], a(b, c)
  • a++, a-- unary postfix
  • !a, +a, -a, ++a, --a unary prefix
  • a * b, a / b, a % b
  • a + b, a - b
  • a << b, a >> b, a >>> b
  • a < b, a <= b, a > b, a >= b
  • a == b, a != b
  • a & b
  • a ^ b
  • a | b
  • a && b
  • a || b
  • a , b

Default literals:

  • "abc" strings
  • 1.2e+3 numbers

Everything else can be extended via parse.set(token, precedence, operator) for unary or binary operators (detected by number of arguments in operator), or via parse.set(token, parse, precedence) for custom tokens.

import script from './subscript.js'

// add ~ unary operator with precedence 15
script.set('~', 15, a => ~a)

// add === binary operator
script.set('===', 9, (a, b) => a===b)

// add literals
script.set('true', a => ()=>true)
script.set('false', a => ()=>false)

script`true === false`() // false

See subscript.js or justin.js for examples.

Justin

Justin is minimal JS subset βˆ’ JSON with JS expressions (see original thread).

It extends subscript with:

  • ===, !== operators
  • ** exponentiation operator (right-assoc)
  • ~ bit inversion operator
  • ' strings
  • ?: ternary operator
  • ?. optional chain operator
  • ?? nullish coalesce operator
  • [...] Array literal
  • {...} Object literal
  • in binary
  • ; expression separator
  • //, /* */ comments
  • true, false, null, undefined literals
import jstin from 'subscript/justin.js'

let xy = jstin('{ x: 1, "y": 2+2 }["x"]')
xy()  // 1

Performance

Subscript shows relatively good performance within other evaluators:

1 + (a * b / c % d) - 2.0 + -3e-3 * +4.4e4 / f.g[0] - i.j(+k == 1)(0)

Parse 30k times:

subscript: ~170 ms πŸ₯‡
justin: ~183 ms πŸ₯ˆ
jsep: ~270 ms πŸ₯‰
jexpr: ~297 ms
mr-parser: ~420 ms
expr-eval: ~480 ms
math-parser: ~570 ms
math-expression-evaluator: ~900ms
jexl: ~1056 ms
mathjs: ~1200 ms
new Function: ~1154 ms

Eval 30k times:

new Function: ~7 ms πŸ₯‡
subscript: ~17 ms πŸ₯ˆ
justin: ~17 ms πŸ₯ˆ
jexpr: ~23 ms πŸ₯‰
jsep (expression-eval): ~30 ms
math-expression-evaluator: ~50ms
expr-eval: ~72 ms
jexl: ~110 ms
mathjs: ~119 ms
mr-parser: -
math-parser: -

Alternatives

πŸ•‰

Subscript

Microlanguage / expression evaluator with common syntax

Subscript Info

⭐ Stars 42
πŸ”— Source Code github.com
πŸ•’ Last Update 5 months ago
πŸ•’ Created 9 months ago
🐞 Open Issues 0
βž— Star-Issue Ratio Infinity
😎 Author spectjs