nytramr-executor v1.0.0
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.
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 executer. |
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 executer = engine.compile('IF(PP(value), CL(CT("true")), CL(CT("false")))');
executer({ value: true }); // prints "true"
executer({ value: 'hello' }); // prints "true"
executer({ value: 0 }); // prints "false"
executer({}); // prints "false"
Recipes
Please consider be exception free, if your new component can throw and exception, try to avoid it as much as possible.
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 executer = 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 executer = 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 executer = 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 executer = 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 executer = engine.compile('FT(PP("singers"), EQ(SL(PP("band")), CT("The Beatles")))');
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 executer = 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 executer = 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 executer = 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 executer = 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 executer = engine.compile('CT("someText")');
executer({}); // 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 executer = 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
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 executer = 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
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 executer = 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 executer = 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 executer = 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 executer = 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 executer = 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 executer = 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
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
4 years ago