Code Monkey home page Code Monkey logo

subscript's Introduction

subscript npm bundle size

Subscript is fast, tiny & extensible expression evaluator / microlanguage with standard syntax.

Used for:

  • templates (eg. sprae, templize)
  • expressions evaluators, calculators
  • subsets of languages (eg. justin)
  • sandboxes, playgrounds, safe eval
  • custom DSL (eg. mell)
  • preprocessors (eg. prepr)

Subscript has 3.5kb footprint (compare to 11.4kb jsep + 4.5kb expression-eval), good performance and wide test coverage.

Usage

import subscript from './subscript.js'

// parse expression
const fn = subscript('a.b + Math.sqrt(c - 1)')

// evaluate with context
fn({ a: { b:1 }, c: 5, Math })
// 3

Operators

Subscript supports common syntax (shared by JavaScript,C, C++, Java, C#, PHP, Swift, Objective-C, Kotlin, Perl etc.):

  • a.b, a[b], a(b)
  • a++, a--, ++a, --a
  • a * b, a / b, a % b
  • +a, -a, a + b, a - b
  • a < b, a <= b, a > b, a >= b, a == b, a != b
  • ~a, a & b, a ^ b, a | b, a << b, a >> b
  • !a, a && b, a || b
  • a = b, a += b, a -= b, a *= b, a /= b, a %= b
  • (a, (b)), a; b;
  • "abc", 'abc'
  • 0.1, 1.2e+3

Justin

Just-in is no-keywords JS subset, JSON + expressions (see thread).
It extends subscript with:

  • a === b, a !== b
  • a ** b, a **= b
  • a ?? b, a ??= b
  • a ||= b, a &&= b
  • a ? b : c, a?.b
  • ...a
  • [a, b] Array
  • {a: b} Object
  • (a, b) => c Function
  • // foo, /* bar */
  • true, false, null, NaN, undefined
  • a in b
import jstin from './justin.js'

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

Parse / Compile

Subscript exposes parse to build AST and compile to create evaluators.

import { parse, compile } from 'subscript'

// parse expression
let tree = parse('a.b + c - 1')
tree // ['-', ['+', ['.', 'a', 'b'], 'c'], [,1]]

// compile tree to evaluable function
fn = compile(tree)
fn({ a: {b: 1}, c: 2 }) // 3

Syntax Tree

AST has simplified lispy tree structure (inspired by frisk / nisp), opposed to ESTree:

  • not limited to particular language (JS), can be compiled to different targets;
  • reflects execution sequence, rather than code layout;
  • has minimal overhead, directly maps to operators;
  • simplifies manual evaluation and debugging;
  • has conventional form and one-liner docs:
import { compile } from 'subscript.js'

const fn = compile(['+', ['*', 'min', [,60]], [,'sec']])
fn({min: 5}) // min*60 + "sec" == "300sec"

// node kinds
['+', a];       // unary prefix or postfix operator `+a`
['+', a, b];    // binary operator `a + b`
['+', a, b, c]; // n-ary operator `a + b + c`
['()', a];      // group operator `(a)`
['(', a, b];    // access operator `a(b)`
[, a];          // literal value `'a'`
a;              // variable (from scope)

Extending

Subscript provides premade language features and API to customize syntax:

  • unary(str, precedence, postfix=false) − register unary operator, either prefix ⚬a or postfix a⚬.
  • binary(str, precedence, rassoc=false) − register binary operator a ⚬ b, optionally right-associative.
  • nary(str, precedence) − register n-ary (sequence) operator like a; b; or a, b, allows missing args.
  • group(str, precedence) - register group, like [a], {a}, (a) etc.
  • access(str, precedence) - register access operator, like a[b], a(b) etc.
  • token(str, precedence, lnode => node) − register custom token or literal. Callback takes left-side node and returns complete expression node.
  • operator(str, (a, b) => ctx => value) − register evaluator for an operator. Callback takes node arguments and returns evaluator function.
import script, { compile, operator, unary, binary, token } from './subscript.js'

// enable objects/arrays syntax
import 'subscript/feature/array.js';
import 'subscript/feature/object.js';

// add identity operators (precedence of comparison)
binary('===', 9), binary('!==', 9)
operator('===', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx)===b(ctx)))
operator('===', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx)!==b(ctx)))

// add nullish coalescing (precedence of logical or)
binary('??', 3)
operator('??', (a, b) => b && (a = compile(a), b = compile(b), ctx => a(ctx) ?? b(ctx)))

// add JS literals
token('undefined', 20, a => a ? err() : [, undefined])
token('NaN', 20, a => a ? err() : [, NaN])

See ./feature/* or ./justin.js for examples.

Performance

Subscript shows good performance within other evaluators. Example expression:

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

Parse 30k times:

subscript: ~150 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: ~15 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

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

🕉

subscript's People

Contributors

dy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

Forkers

ayufeng erights

subscript's Issues

Import doesn't work (node, jest)

I have a Nx repository.

The import in TypeScript/Node code works, but the build is broken.

The import in Jest Scripts didn't work at all, no matter what I tried:

   /var/www/example/node_modules/subscript/justin.js:2
    import { skip, cur, idx, err, expr } from './parse.js'
    ^^^^^^

    SyntaxError: Cannot use import statement outside a module

    > 1 | import jstin from 'subscript/justin.js';

Typescript support?

It will be great if this library supports typescript by exposing typescript typings.

How to implement assignment operator (=)?

I'm trying (desperatly 😢) to implement an assignment operator. I'd like to be able to update my context with the result of expressions.

This is what I have:

binary('=', 0);
operator('=', (left: any, right: any) => (ctx: any) => {
	// Make sure the left side is a variable reference
	if (typeof left !== 'string') {
		throw new Error('Left side of assignment must be a variable name');
	}

	
	// Evaluate the right side in the context
	right = compile(right);
	const value = right(ctx);
	// Assign the value in the context
	ctx[left] = value;
	// Return the value
	return value;
});

It works for simple expressions like
contextVar = anotherContextVar

But as soon as I use a more complicated expression like
contextVar = anotherContextVar + 2
it fails since my right varible will get an array of values 🤷‍♂️. I feel I'm making a simple mistake here, but I can't seem to figure it out.
Any suggestions?

is async supported?

nice work!

I was wondering if there is an approach that would handle async?

let fn = createFn('a + b(3)')
fn({ a: 2,b:x=>Promise.resolve(x)}) // 5

also, it isn't clear why github repo uses createFn and npm uses script in the first example

Math.* support?

Would be nice to have the typical Math functions available, or is this out of scope of this project?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.