Natty Precise Maverick

    @nytramr/executor

    1.6.0 • Public • Published

    Executor

    A compact library to execute small scripts in a secure way and avoiding an eval expression. See also Never use eval()! or this Stackoverflow link.

    NOTE for version 1.6 and further:

    The way to access numerical properties had slightly changed. While in version 1.5.X and older, a property like : PP(obj.1.1) would return obj["1.1"], since version 1.6.x the same property will return obj[1][1].

    In my humble opinion I think this way is more intuitive an more predictable, that is why I didn't made a new mayor version of the library, trying to make everyone getting this important change.

    Install

    npm

    $> npm install @nytramr/executor
    

    yarn

    $> yarn add @nytramr/executor
    

    API

    constructor

    const engine = new Engine();

    compile

    Syntax

    compile(_textGraph_);

    Parameters

    name description
    textGraph The textGraph is a graph like string that represents the code to be "compiled" into an executor.

    Return value

    The executor function

    Example

    const engine = new Engine();
    
    const executor = engine.compile('GT(PP(first), PP(second))');
    
    executor({ first: 10, second: 5 }); // returns true
    executor({ first: -1, second: -12 }); // returns true
    executor({ first: 'b', second: 'a' }); // returns true
    executor({ first: 'a', second: 'A' }); // returns true
    executor({ first: 10, second: 10 }); // returns false
    executor({ first: -10, second: 10 }); // returns false
    executor({ first: 'a', second: 'b' }); // returns false

    define

    It allows the introduction of new operators into the compiler parser, in order to extend the functionality to meet special needs.

    Syntax

    define(_operator_, _executer_);

    Parameters

    name description
    operator The operator is a string to be used by the compiler to identify the new functionality.
    executer The executer is a special function to be used in order to compile the operator. The function must return another function that will receive the context as a parameter

    Return value

    undefined

    Example

    const engine = new Engine();
    
    // let's define an IF operator, that depending on the boolean result of pred, will execute and return the execution of trueResult or falseResult
    engine.define('IF', (pred, trueResult, falseResult) => (context) =>
      pred(context) ? trueResult(context) : falseResult(context),
    );
    
    // let's create an operator that prints in the console the value got by `valueGetter`
    engine.define('CL', (valueGetter) => (context) => console.log(valueGetter(context)));
    
    var executor = engine.compile('IF(PP(value), CL(CT("true")), CL(CT("false")))');
    
    executor({ value: true }); // prints "true"
    executor({ value: 'hello' }); // prints "true"
    executor({ value: 0 }); // prints "false"
    executor({}); // prints "false"

    Recipes

    Please consider be exception free, if your new component can throw and exception, try to avoid it as much as possible.

    Name Implementation Use

    conditional (if-else)

    const engine = new Engine();
    
    engine.define('IF', (pred, trueResult, falseResult) => (context) =>
      pred(context) ? trueResult(context) : falseResult(context),
    );

    or

    const engine = new Engine();
    
    engine.define('IF', (pred, trueResult, falseResult) => (context) =>
      (pred(context) && trueResult(context)) || falseResult(context),
    );
    var executor = engine.compile('IF(PP(value), CT("true")), CL(CT("false"))');

    console log

    const engine = new Engine();
    
    engine.define('CL', (valueGetter) => (context) => console.log(valueGetter(context)));
    var executor = engine.compile('CL(PP(value))');

    join

    const engine = new Engine();
    
    engine.define('JN', (arrayGetter, string) => (context) => {
      const array = arrayGetter(context);
      if (Array.isArray(array)) return array.join(string(context));
      return ''; // you may choice to return undefined instead.
    });
    var executor = engine.compile('JN(PP("myArray"), CT(","))');

    find

    const engine = new Engine();
    
    engine.define('FD', (arrayGetter, string) => (context) => {
      const array = arrayGetter(context);
      if (Array.isArray(array)) return array.find((element) => predicate(context, subContext, element));
      return undefined;
    });
    var executor = engine.compile('FN(PP("singers"), EQ(SL(PP("name")), CT("John")))');

    filter

    const engine = new Engine();
    
    engine.define('FT', (arrayGetter, string) => (context) => {
      const array = arrayGetter(context);
      if (Array.isArray(array)) return array.filter((element) => predicate(context, subContext, element));
      return []; // you may choice to return undefined instead.
    });
    var executor = engine.compile('FT(PP("singers"), EQ(SL(PP("band")), CT("The Beatles")))');

    getVariable

    Syntax

    getVariable(_name_);

    Parameters

    name description
    name The name of the value to be retrieved.

    Return value

    undefined

    Example

    const engine = new Engine();
    
    const value = engine.getVariable('variableName');

    setVariable

    Syntax

    setVariable(_name_, _value_);

    Parameters

    name description
    name The name under the value is going to be stored.
    value The value to be stored.

    Return value

    undefined

    Example

    const engine = new Engine();
    
    engine.setVariable('variableName', 'value of the variable');

    Language

    PP(path)

    It will return the part of the context object according to the given path. If at any point of the path a value cannot be resolved, it returns undefined.

    path

    Overall

    The path is a divided by dots ('.') string like property and can be expressed the same way that any object is accessed programmatically.

    Special Chars

    There is also the possibility to use a path-like string between quotes to access to a property which contains non allowed chars like ., -, etc.

    Dynamic Access

    The use of squarebrackets allows using literals or other values of the same context as part of the path.

    examples

    Simplest use

    It should return the value of the property name

    const engine = new Engine();
    
    const executor = engine.compile('PP(name)');
    
    executor({ name: 'John' }); // returns "John"
    executor({ name: 'Paul' }); // returns "Paul"

    Navigate in the object

    It should return the value of the property name of the object user

    const engine = new Engine();
    
    const executor = engine.compile('PP(user.name)');
    
    executor({ user: { name: 'John' } }); // returns "John"
    executor({ user: { name: 'Paul' } }); // returns "Paul"

    Index in array

    It should return the value of second position in the array

    const engine = new Engine();
    
    const executor = engine.compile('PP(1)');
    
    executor(['cero', 'uno', 'dos']); // returns "uno"
    executor([20, 30, 40]); // returns 30

    Use a property as a key

    It should return the value of the property specified by the property key

    const executor = engine.compile('PP([key])');
    
    executor({ value: 'name', key: 'value' }); // returns "name"
    executor({ 'another.value': 'another name', key: 'another.value' }); // returns "another name"
    executor({ value: 'name', keyNotFound: 'value' }); // returns undefined
    executor({}); // returns undefined

    or

    const executor = engine.compile('PP(PP(key))');
    
    executor({ value: 'name', key: 'value' }); // returns "name"
    executor({ 'another.value': 'another name', key: 'another.value' }); // returns "another name"
    executor({ value: 'name', keyNotFound: 'value' }); // returns undefined
    executor({}); // returns undefined

    Use a property path as a key

    It should return the value of the property specified by the property sub-key of key

    const executor = engine.compile('PP([key.sub-key])');
    
    executor({ value: 'name', key: { 'sub-key': 'value' } }); // returns "name"
    executor({ 'another value': 'another name', key: { 'sub-key': 'another value' } }); // returns "another name"
    executor({ value: 'name', keyNotFound: { 'sub-key': 'value' } }); // returns undefined
    executor({ value: 'name', key: { 'sub-keyNotFound': 'value' } }); // returns undefined
    executor({}); // returns undefined

    or

    const executor2 = engine.compile('PP(PP(key.sub-key))');
    
    executor2({ value: 'name', key: { 'sub-key': 'value' } }); // returns "name"
    executor2({ 'another value': 'another name', key: { 'sub-key': 'another value' } }); // returns "another name"
    executor2({ value: 'name', keyNotFound: { 'sub-key': 'value' } }); // returns undefined
    executor2({ value: 'name', key: { 'sub-keyNotFound': 'value' } }); // returns undefined
    executor2({}); // returns undefined

    AN(condition1, condition2)

    It will return true or false depending of the and evaluation of condition1 and condition2.

    The execution is lazy, therefore in case the first condition returns falsy value, the second condition is not evaluated.

    example

    const engine = new Engine();
    
    const executor = engine.compile('AN(PP(first), PP(second))');
    executor({ first: true, second: true }); // returns true
    executor({ first: true, second: 10 }); // returns 10
    executor({ first: true, second: 'true' }); // returns "true"
    executor({ first: false, second: true }); // returns false
    executor({ first: false, second: false }); // returns false
    executor({ first: true, second: false }); // returns false
    executor({ first: 0, second: false }); // returns 0
    executor({ first: null, second: false }); // returns null
    executor({ first: '', second: false }); // returns ''
    executor({ second: true }); // returns Undefined

    CT(value)

    It will return an executor that always returns value. Is the way we can define literals.

    example

    const engine = new Engine();
    
    const executor = engine.compile('CT("someText")');
    executor({}); // returns "someText"
    executor({ first: 10, second: '10' }); // returns "someText"

    EQ(value1, value2)

    It will return true when both values are equals, returns false otherwise.

    example

    const engine = new Engine();
    
    const executor = engine.compile('EQ(PP(first), PP(second))');
    executor({ first: 10, second: 10 }); // returns true
    executor({ first: '10', second: '10' }); // returns true
    executor({ first: true, second: true }); // returns true
    executor({}); // returns true
    executor({ first: 10, second: '10' }); // returns false
    executor({ first: false, second: true }); // returns false
    executor({ first: false, second: undefined }); // returns false
    executor({ first: false, second: 0 }); // returns false

    FLT(array, predicate)

    It returns a new array with every element from the array that the predicate executes in true. If none element meets the predicate, it returns an empty array ([])

    example

    const engine = new Engine();
    
    const executor = engine.compile('FLT(PP("myArray"), EQ(SL(PP("band")), PP("myBand")))');
    
    executor({
      myArray: [
        { name: 'John', band: 'The Beatles' },
        { name: 'Paul', band: 'The Beatles' },
        { name: 'Ringo', band: 'The Beatles' },
        { name: 'George', band: 'The Beatles' },
        { name: 'Yoko', band: 'None' },
      ],
      myBand: 'The Beatles',
    });
    /* returns [
      { name: 'John', band: 'The Beatles' },
      { name: 'Paul', band: 'The Beatles' },
      { name: 'Ringo', band: 'The Beatles' },
      { name: 'George', band: 'The Beatles' },
      { name: 'Yoko', band: 'None' },
    ]
    */

    FND(array, predicate)

    It returns the first element from the array that the predicate executes in true. It returns undefined if none element meets the predicate.

    example

    const engine = new Engine();
    
    const executor = engine.compile('FND(PP("myArray"), EQ(SL(PP("name")), PP("myName")))');
    
    executor({
      myArray: [
        { name: 'John', band: 'The Beatles' },
        { name: 'Paul', band: 'The Beatles' },
        { name: 'Ringo', band: 'The Beatles' },
        { name: 'George', band: 'The Beatles' },
        { name: 'Yoko', band: 'None' },
      ],
      myName: 'Ringo',
    }); // returns { name: 'Ringo', band: 'The Beatles' }

    GE(value1, value2)

    It will return true when the first value is greater or equals than the second value, returns false otherwise.

    example

    const engine = new Engine();
    
    const executor = engine.compile('GE(PP(first), PP(second))');
    executor({ first: 10, second: 5 }); // returns true
    executor({ first: -1, second: -12 }); // returns true
    executor({ first: 'b', second: 'a' }); // returns true
    executor({ first: 'a', second: 'A' }); // returns true
    executor({ first: 10, second: 10 }); // returns false
    executor({ first: -10, second: 10 }); // returns false
    executor({ first: 'a', second: 'b' }); // returns false
    executor({}); // returns false

    GET(valueName)

    It will return the stored value under the name valueName.

    example

    const engine = new Engine();
    
    const executor = engine.compile('GET(CT("someText"))');
    executor({}); // returns any previously stored valued under the name "someText"

    GT(value1, value2)

    It will return true when the first value is greater than the second value, returns false otherwise.

    example

    const engine = new Engine();
    
    const executor = engine.compile('GT(PP(first), PP(second))');
    executor({ first: 10, second: 5 }); // returns true
    executor({ first: -1, second: -12 }); // returns true
    executor({ first: 'b', second: 'a' }); // returns true
    executor({ first: 'a', second: 'A' }); // returns true
    executor({ first: 10, second: 10 }); // returns false
    executor({ first: -10, second: 10 }); // returns false
    executor({ first: 'a', second: 'b' }); // returns false
    executor({}); // returns false

    LE(value1, value2)

    It will return true when the first value is less or equals than the second value, returns false otherwise.

    example

    const engine = new Engine();
    
    const executor = engine.compile('LE(PP(first), PP(second))');
    executor({ first: 10, second: 10 }); // returns true
    executor({ first: 5, second: 10 }); // returns true
    executor({ first: -1, second: 0 }); // returns true
    executor({ first: 'a', second: 'b' }); // returns true
    executor({ first: 'A', second: 'a' }); // returns true
    executor({ first: 10, second: -10 }); // returns false
    executor({ first: 'b', second: 'a' }); // returns false
    executor({}); // returns false

    LT(value1, value2)

    It will return true when the first value is less than the second value, returns false otherwise.

    example

    const engine = new Engine();
    
    const executor = engine.compile('LT(PP(first), PP(second))');
    executor({ first: 5, second: 6 }); // returns true
    executor({ first: -2, second: 0 }); // returns true
    executor({ first: 'a', second: 'b' }); // returns true
    executor({ first: 'A', second: 'a' }); // returns true
    executor({ first: 10, second: 10 }); // returns false
    executor({ first: 10, second: -10 }); // returns false
    executor({ first: 'b', second: 'a' }); // returns false
    executor({}); // returns false

    NE(value1, value2)

    It will return true when both values are different, returns false otherwise.

    example

    const engine = new Engine();
    
    const executor = engine.compile('EQ(PP(first), PP(second))');
    executor({ first: 10, second: 10 }); // returns false
    executor({ first: '10', second: '10' }); // returns false
    executor({ first: true, second: true }); // returns false
    executor({}); // returns false
    executor({ first: 10, second: '10' }); // returns true
    executor({ first: false, second: true }); // returns true
    executor({ first: false, second: undefined }); // returns true
    executor({ first: false, second: 0 }); // returns true

    NT(condition)

    It will return true when the condition is false or false when the condition is true.

    example

    const engine = new Engine();
    
    const executor = engine.compile('NT(PP(first))');
    executor({ first: false }); // returns true
    executor({ first: 0 }); // returns true
    executor({ first: null }); // returns true
    executor({}); // returns true
    executor({ first: true }); // returns false
    executor({ first: 1 }); // returns false
    executor({ first: {} }); // returns false

    OR(condition1, condition2)

    It will return true or false depending of the or evaluation of condition1 and condition2.

    The execution is lazy, therefore in case the first condition returns a truly value, the second condition is not evaluated.

    example

    const engine = new Engine();
    
    const executor = engine.compile('OR(PP(first), PP(second))');
    executor({ first: true, second: true }); // returns true
    executor({ first: true, second: false }); // returns true
    executor({ first: false, second: true }); // returns true
    executor({ first: false, second: false }); // returns false
    executor({ first: 'true', second: true }); // returns "true"
    executor({ first: 10, second: false }); // returns 10
    executor({ first: 0, second: false }); // returns 0
    executor({ first: false, second: null }); // returns null
    executor({ first: false, second: '' }); // returns ''
    executor({ second: true }); // returns true

    SET(valueName, value)

    It will store the value under the name valueName.

    example

    const engine = new Engine();
    
    const executor = engine.compile('SET(PP(name), CT("artistName"))');
    executor({ name: 'John' }); // will store "John" under the key "artistName"

    Dev Setup

    Prerequisites

    In order to checkout project and build and run tests locally you need to meet the following requirements:

    • Node.js version >= 13.14.0, you can get the latest version of Node.js from http://nodejs.org/,
    • git, you can get git from http://git-scm.com/,
    • yarn, you can install yarn by running the following command: npm install yarn -g

    Install dev dependencies

    $> yarn install
    

    Build the package

    $> yarn build
    

    Test

    $> yarn test
    

    Install

    npm i @nytramr/executor

    DownloadsWeekly Downloads

    0

    Version

    1.6.0

    License

    MIT

    Unpacked Size

    23.6 kB

    Total Files

    3

    Last publish

    Collaborators

    • martinrubinsztein