Giter VIP home page Giter VIP logo

bcx-expression-evaluator's Introduction

bcx-expression-evaluator CI

Safely evaluate a JavaScript-like expression in given context.

In Buttonwood, we heavily use meta-data (JSON format) to deliver business logic from backend to front-end. We don't want to design a meta-data format too complex to maintain, this tool allows us to define some light logic in pure string, way more flexible than rigid meta-data, much safer and more maintainable than passing js function as string (we did that) from backend to front-end.

This tool was mainly extracted, modified and extended from the expression parser of aurelia-binding.

Install

npm install bcx-expression-evaluator

Document

function evaluate(expression, context, helper, opts)

  • expression: the expression string to be evaluated
  • context: the input model object
  • helper: optional helper object
  • opts: optional hashmap, currently only support rejectAssignment and stringInterpolationMode
    • rejectAssignment rejects assignment in expression
    • stringInterpolationMode treats the whole expression like if it's in backticks `expression`

function evaluateStringInterpolation is a short-cut to call evaluate with stringInterpolationMode option.

Usage (in es6 syntax)

import {evaluate, evaluateStringInterpolation} from 'bcx-expression-evaluator';

const context = {
    a: 1,
    b: 2,
    c: {
    one: 'one',
    two: 'two'
    },
    avg: function() { return (this.a + this.b) / 2; }
};

evaluate('avg() > a ? c.one : c.two', context);  // => 'one';

use some helper

const helper = {
    limit: 5,
    sum: (v1, v2) => v1 + v2
};

evaluate('sum(a, b) > limit', context, helper);  // => false;

access context object itself with special $this variable

evaluate('$this', context); // => the context object
evaluate('$this.a', context); // => 1

explicitly access helper object with special $parent variable

(carried over from aurelia-binding, might change $parent to $helper in future releases.)

evaluate('a', {a:1}, {a:2}); // => 1
evaluate('$this.a', {a:1}, {a:2}); // => 1
evaluate('$parent.a', {a:1}, {a:2}); // => 2

support es6 string interpolation

evaluate('`${a+1}`', {a:1}); // => '2'

You can evaluate a string interpolation without backtick "`"

evaluate('${a+1}', {a:1}, null, {stringInterpolationMode: true}); // => '2'
evaluateStringInterpolation('${a+1}', {a:1}); // => '2'

You don't have to escape backtick in stringInterpolationMode

evaluate('`\\`${a+1}\\``', {a:1}); // => '`2`', beware you need double escape as we are writing expression in string quotes
evaluate('`${a+1}`', {a:1}, null, {stringInterpolationMode: true}); // => '`2`'
evaluateStringInterpolation('`${a+1}`', {a:1}); // => '`2`'

safe. It is not an eval in JavaScript, doesn't have access to global JavaScript objects

evaluate('parseInt(a, 10)', {a:"7"}) // => undefined

// only have access to context and helper
evaluate('parseInt(a, 10)', {a:"7"}, {parseInt: parseInt}) // => 7

silent most of the time

evaluate('a.b', {}) // => undefined, no error thrown
evaluate('a.b || c', {c: 'lorem'}) // => 'lorem', no error thrown

you can use assignment to mutate context object (or even helper object)

let obj = {a: 1, b: 2};
evaluate('a = 3', obj); // obj is now {a: 3, b: 2}
evaluate('b > 3 ? (a = true) : (a = false)', obj); // obj is now {a: false, b: 2}

disable assignment if you don't need it

This doesn't eliminate side effect, it would not prevent any function you called in bcx-expression to mutate something.

evaluate('a=1', {a:0}, null, {rejectAssignment: true}); // throws error

Difference from real JavaScript expression

bcx-expression looks like JavaScript expression, but there are some difference.

wrong reference results undefined instead of error

let obj = {a: 1};
obj.b.a // => error
evaluate('b.a', obj); // => undefined

default result for +/- operators

Behaviour carried over from aurelia-binding.

// +/- behaviour in normal JavaScript expression
undefined + 1 // => NaN
1 + undefined // => NaN
null + 1 // => 1
1 + null // => 1
undefined + undefined // => NaN
null + null // => 0

// in bcx-expression, + and - ignores undefined/null value,
// if both left and right parts are (evaluated to) undefined/null, result default to 0
evaluate('undefined + 1'); // => 1
evaluate('1 + undefined'); // => 1
evaluate('null + 1'); // => 1
evaluate('1 + null'); // => 1
evaluate('undefined + undefined'); // => 0
evaluate('null + null'); // => 0

no function expression

// all following JavaScript expressions would not work in bcx-expression
(function(){return 1})()
(() => 1)()
arr.sort((a, b) => a > b)

// but this would work
arr.sort(aHelperFunc)

no regular expression support

Regex syntax is too complex to be supported for our AST (abstract syntax tree).

// regex literal would not work in bcx-expression.
/\w/.test(string)

One way to bypass this is to supply regex literal in helper object.

evaluate('tester.test(str)', {str: '%'}, {tester: /\w/});

some JavaScript operators would not work

typeof, instanceof, delete would not work, because bcx-expression is not real JavaScript.

BUTTONWOODCX™ PTY LTD.

bcx-expression-evaluator's People

Contributors

3cp avatar

Stargazers

 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

bcx-expression-evaluator's Issues

JSON key with dash does not work

it( "recognizes dash in key", () => {
  const context = { foo: { bar: 23 }, "foo-baz": 42 };

  expect( evaluate('foo.bar', context) ).toBe( 23 );
  expect( evaluate('foo-baz', context) ).toBe( 42 );             // fails
  expect( evaluate('$this["foo-baz"]', context) ).toBe( 42 );    // passes
});

Lexer Error: Unterminated quote

I tried to evaluate this '"${a+1}"' expression but it failed with error:
Lexer Error: Unterminated quote at column 8 in expression ["${a+1}"]

the test was:
t.equal(evaluate('"${a+1}"', {a:1}), "${a+1}");

In my opinion, the expression '"${a+1}"' is a string and the evaluator should not evaluate it.
If I'm right, this could be a bug.

Thanks

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.