@nytramr/executor
v1.6.0
Published
A small library to 'compile' and execute simple expressions scripts.
Downloads
3
Maintainers
Readme
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 returnobj["1.1"]
, since version 1.6.x the same property will returnobj[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.
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