illogical
A micro conditional javascript engine used to parse the raw logical and comparison expressions, evaluate the expression in the given data context, and provide access to a text form of the given expressions.
Revision: August 6, 2020.
About
This project has been developed to provide a shared conditional logic between front-end and back-end code, stored in JSON or in any other data serialization format.
Code documentation could be found here: https://briza-insurance.github.io/illogical/index.html.
The library is being build as CommonJS module and ESM.
Installation via NPM or Yarn
npm install -D @briza/illogicalyarn add @briza/illogical -DTable of Content
Basic Usage
// Import the illogical engine
import Engine from '@briza/illogical';
// Create a new instance of the engine
const engine = new Engine();
// Evaluate the raw expression
const result = engine.evaluate(['==', 5, 5]);For advanced usage, please Engine Options.
Evaluate
Evaluate comparison or logical expression as TRUE or FALSE result:
engine.evaluate(Comparison Expression or Logical Expression, Evaluation Data Context) => boolean
Data context is optional.
Example
// Comparison expression
engine.evaluate(['==', 5, 5]);
engine.evaluate(['==', 'circle', 'circle']);
engine.evaluate(['==', true, true]);
engine.evaluate(['==', '$name', 'peter'], {name: 'peter'});
engine.evaluate(['UNDEFINED', '$RefA'], {});
// Logical expression
engine.evaluate([
'AND',
['==', 5, 5],
['==', 10, 10]
]);
engine.evaluate([
'AND',
['==', 'circle', 'circle'],
['==', 10, 10]
]);
engine.evaluate([
'OR',
['==', '$name', 'peter'],
['==', 5, 10]
], {name: 'peter'});Statement
Get expression string representation:
engine.statement(Comparison Expression or Logical Expression) => string
Example
/* Comparison expression */
engine.statement(['==', 5, 5]);
// (5 == 5)
engine.statement(['==', 'circle', 'circle']);
// ("circle" == "circle")
engine.statement(['==', true, true]);
// (true == true)
engine.statement(['==', '$name', 'peter'], {name: 'peter'});
// ({name} == "peter")
engine.statement(['UNDEFINED', '$RefA']);
// ({RefA} is UNDEFINED)
/* Logical expression */
engine.statement([
'AND',
['==', 5, 5],
['==', 10, 10]
]);
// ((5 == 5) AND (10 == 10))
engine.statement([
'AND',
['==', 'circle', 'circle'],
['==', 10, 10]
]);
// (("circle" == "circle") AND (10 == 10))
engine.statement([
'OR',
['==', '$name', 'peter'],
['==', 5, 10]
], {name: 'peter'});
// (({name} == "peter") OR (5 == 10))Parse
Parse the expression into a evaluable object, i.e. it returns the parsed self-evaluable condition expression.
engine.parse(Comparison Expression or Logical Expression) => evaluable
Evaluate Function
evaluable.evaluate(context)please see Evaluation Data Context.evaluable.toString()please see Statement.
Example
let evaluable = engine.parse(['==', '$name', 'peter']);
evaluable.evaluate({name: 'peter'}); // true
evaluable.toString();
// ({name} == "peter")Simplify
Simplifies an expression with a given context. This is useful when you already have some of the properties of context and wants to try to eveluate the expression.
Example
engine.simplify(['AND', ['==', '$a', 10], ['==', '$b', 20]], { a: 10 }) // ['==', '$b', 20]
engine.simplify(['AND', ['==', '$a', 10], ['==', '$b', 20]], { a: 20 }) // falseValues not found in the context will cause the parent operand not to be evaluated and returned as part of the simplified expression.
In some situations we might want to evaluate the expression even if referred value is not present. You can provide a list of keys that will be evaluated even if they are not present in the context.
Example
engine.simplify(
['AND', ['==', '$a', 10], ['==', '$b', 20]],
{ a: 10 },
['b'] // '$b' will be evaluted to undefined.
) // falseWorking with Expressions
Evaluation Data Context
The evaluation data context is used to provide the expression with variable references, i.e. this allows for the dynamic expressions. The data context is object with properties used as the references keys, and its values as reference values.
Valid reference values: object, string, number, boolean, string[], number[].
To reference the nested reference, please use "." delimiter, e.g.:
$address.city
Accessing Array Element:
$options[1]
Accessing Array Element via Reference:
$options[{index}]
- The index reference is resolved within the data context as an array index.
Nested Referencing
$address.{segment}
- The segment reference is resolved within the data context as a property key.
Composite Reference Key
$shape{shapeType}
- The shapeType reference is resolved within the data context, and inserted into the outer reference key.
- E.g. shapeType is resolved as "B" and would compose the $shapeB outer reference.
- This resolution could be n-nested.
Example
// Data context
const ctx = {
name: 'peter',
country: 'canada',
age: 21,
options: [1,2,3],
address: {
city: 'Toronto',
country: 'Canada',
},
index: 2,
segment: 'city',
shapeA: 'box',
shapeB: 'circle',
shapeType: 'B'
}
// Evaluate an expression in the given data context
engine.evaluate(['>', '$age', 20], ctx); // true
// Evaluate an expression in the given data context
engine.evaluate(['==', '$address.city', 'Toronto'], ctx); // true
// Accessing Array Element
engine.evaluate(['==', '$options[1]', 2], ctx) // true
// Accessing Array Element via Reference
engine.evaluate(['==', '$options[{index}]', 3], ctx) // true
// Nested Referencing
engine.evaluate(['==', '$address.{segment}', 'Toronto'], ctx) // true
// Composite Reference Key
engine.evaluate(['==', '$shape{shapeType}', 'circle'], ctx) // trueOperand Types
The Comparison Expression expect operands to be one of the below:
Value
Simple value types: string, number, boolean.
Example
['==', 5, 5]
['==', 'circle', 'circle']
['==', true, true]Reference
The reference operand's value is resolved from the Evaluation Data Context, where the the operands name is used as key in the context.
The reference operand must be prefixed with $ symbol, e.g.: $name. This might be customized via Reference Predicate Parser Option.
Example
| Expression | Data Context |
|---|---|
['==', '$age', 21] |
{age: 21} |
['==', 'circle', '$shape'] |
{age: 'circle'} |
['==', '$visible', true] |
{visible: true} |
Collection
The operand could be an array mixed from Value and Reference.
Example
| Expression | Data Context |
|---|---|
['IN', [1, 2], 1] |
{} |
['IN', 'circle', ['$shapeA', $shapeB] |
{shapeA: 'circle', shapeB: 'box'} |
['IN', [$number, 5], 5] |
{number: 3} |
Comparison Expressions
Equal
Expression format: ["==", Left Operand, Right Operand].
Valid operand types: string, number, boolean.
["==", 5, 5]engine.evaluate(['==', 5, 5]); // trueNot Equal
Expression format: ["!=", Left Operand, Right Operand].
Valid operand types: string, number, boolean.
["!=", "circle", "square"]engine.evaluate(['!=', 'circle', 'square']); // trueGreater Than
Expression format: [">", Left Operand, Right Operand].
Valid operand types: number.
[">", 10, 5]engine.evaluate(['>', 10, 5]); // trueGreater Than or Equal
Expression format: [">=", Left Operand, Right Operand].
Valid operand types: number.
[">=", 5, 5]engine.evaluate(['>=', 5, 5]); // trueLess Than
Expression format: ["<", Left Operand, Right Operand].
Valid operand types: number.
["<", 5, 10]engine.evaluate(['<', 5, 10]); // trueLess Than or Equal
Expression format: ["<=", Left Operand, Right Operand].
Valid operand types: number.
["<=", 5, 5]engine.evaluate(['<=', 5, 5]); // trueIn
Expression format: ["IN", Left Operand, Right Operand].
Valid operand types: number and number[] or string and string[].
["IN", 5, [1,2,3,4,5]]
["IN", ["circle", "square", "triangle"], "square"]engine.evaluate(['IN', 5, [1,2,3,4,5]]); // true
engine.evaluate(['IN', ['circle', 'square', 'triangle'], 'square']); // trueNot In
Expression format: ["NOT IN", Left Operand, Right Operand].
Valid operand types: number and number[] or string and string[].
["IN", 10, [1,2,3,4,5]]
["IN", ["circle", "square", "triangle"], "oval"]engine.evaluate(['NOT IN', 10, [1,2,3,4,5]]); // true
engine.evaluate(['NOT IN', ['circle', 'square', 'triangle'], 'oval']); // truePrefix
Expression format: ["PREFIX", Left Operand, Right Operand].
Valid operand types: string.
- Left operand is the PREFIX term.
- Right operand is the tested word.
["PREFIX", "hemi", "hemisphere"]engine.evaluate(['PREFIX', "hemi", "hemisphere"]); // true
engine.evaluate(['PREFIX', "hemi", "sphere"]); // falseSuffix
Expression format: ["SUFFIX", Left Operand, Right Operand].
Valid operand types: string.
- Left operand is the tested word.
- Right operand is the SUFFIX term.
["SUFFIX", "establishment", "ment"]engine.evaluate(['SUFFIX', "establishment", "ment"]); // true
engine.evaluate(['SUFFIX', "establish", "ment"]); // falseOverlap
Expression format: ["OVERLAP", Left Operand, Right Operand].
Valid operand types number[] or string[].
["OVERLAP", [1, 2], [1, 2, 3, 4, 5]]
["OVERLAP", ["circle", "square", "triangle"], ["square"]]engine.evaluate(['OVERLAP', [1, 2, 6], [1, 2, 3, 4, 5]]); // true
engine.evaluate(['OVERLAP', ['circle', 'square', 'triangle'], ['square', 'oval']]); // trueUndefined
Expression format: ["UNDEFINED", Reference Operand].
["UNDEFINED", "$RefA"]engine.evaluate(['UNDEFINED', 'RefA'], {}); // true
engine.evaluate(['UNDEFINED', 'RefA'], { RefA: undefined }); // true
engine.evaluate(['UNDEFINED', 'RefA'], { RefA: 10 }); // falsePresent
Evaluates as FALSE when the operand is UNDEFINED or NULL.
Expression format: ["PRESENT", Reference Operand].
["PRESENT", "$RefA"]engine.evaluate(['PRESENT', 'RefA'], {}); // false
engine.evaluate(['PRESENT', 'RefA'], { RefA: undefined }); // false
engine.evaluate(['PRESENT', 'RefA'], { RefA: null }); // false
engine.evaluate(['PRESENT', 'RefA'], { RefA: 10 }); // true
engine.evaluate(['PRESENT', 'RefA'], { RefA: false }); // true
engine.evaluate(['PRESENT', 'RefA'], { RefA: 0 }); // trueLogical Expressions
And
The logical AND operator (&&) returns the boolean value TRUE if both operands are TRUE and returns FALSE otherwise.
Expression format: ["AND", Left Operand 1, Right Operand 2, ... , Right Operand N].
Valid operand types: Comparison Expression or Nested Logical Expression.
[
"AND",
["==", 5, 5],
["==", 10, 10]
]engine.evaluate(['AND', ['==', 5, 5], ['==', 10, 10]]); // trueOr
The logical OR operator (||) returns the boolean value TRUE if either or both operands is TRUE and returns FALSE otherwise.
Expression format: ["OR", Left Operand 1, Right Operand 2, ... , Right Operand N].
Valid operand types: Comparison Expression or Nested Logical Expression.
[
"OR",
["==", 5, 5],
["==", 10, 5]
]engine.evaluate(['OR', ['==', 5, 5], ['==', 10, 5]]); // trueNor
The logical NOR operator returns the boolean value TRUE if both operands are FALSE and returns FALSE otherwise.
Expression format: ["NOR", Left Operand 1, Right Operand 2, ... , Right Operand N]
Valid operand types: Comparison Expression or Nested Logical Expression.
[
"NOR",
["==", 5, 1],
["==", 10, 5]
]engine.evaluate(['NOR', ['==', 5, 1], ['==', 10, 5]]); // trueXor
The logical NOR operator returns the boolean value TRUE if both operands are FALSE and returns FALSE otherwise.
Expression format: ["XOR", Left Operand 1, Right Operand 2, ... , Right Operand N]
Valid operand types: Comparison Expression or Nested Logical Expression.
[
"XOR",
["==", 5, 5],
["==", 10, 5],
]engine.evaluate(['XOR', ['==', 5, 5], ['==', 10, 5]]); // true[
"XOR",
["==", 5, 5],
["==", 10, 10],
]engine.evaluate(['XOR', ['==', 5, 5], ['==', 10, 10]]); // falseNot
The logical NOT operator returns the boolean value TRUE if the operand is FALSE, TRUE otherwise.
Expression format: ["NOT", Operand]
Valid operand types: Comparison Expression or Nested Logical Expression.
[
"NOT",
["==", 5, 5],
]engine.evaluate(['NOT', ['==', 5, 5]]); // trueEngine Options
Parser Options
Below described, are individual options object properties which could be used individually. Any missing options will be substituted with the default options.
Usage
// Import the illogical engine
import Engine from '@briza/illogical';
// Create a new instance of the engine
const opts = {
referencePredicate: (operand) => operand.startsWith('$'),
};
const engine = new Engine(opts);Reference Predicate
A function used to determine if the operand is a reference type, otherwise evaluated as a static value.
referencePredicate: (operand: string) => booleanReturn value:
true= reference typefalse= value type
Default reference predicate:
The
$symbol at the begging of the operand is used to predicate the reference type., E.g.$State,$Country.
Reference Transform
A function used to transform the operand into the reference annotation stripped form. I.e. remove any annotation used to detect the reference type. E.g. "$Reference" => "Reference".
referenceTransform: (operand: string) => stringDefault reference transform:
It removes the$symbol at the begging of the operand name.
Operator Mapping
Mapping of the operators. The key is unique operator key, and the value is the key used to represent the given operator in the raw expression.
operatorMapping: Map<symbol, string>Default operator mapping:
// Comparison
[OPERATOR_EQ, '==']
[OPERATOR_NE, '!=']
[OPERATOR_GT, '>']
[OPERATOR_GE, '>=']
[OPERATOR_LT, '<']
[OPERATOR_LE, '<=']
[OPERATOR_IN, 'IN']
[OPERATOR_NOT_IN, 'NOT IN']
[OPERATOR_PREFIX, 'PREFIX']
[OPERATOR_SUFFIX, 'SUFFIX']
[OPERATOR_OVERLAP, 'OVERLAP']
[OPERATOR_UNDEFINED, 'UNDEFINED']
[OPERATOR_PRESENT, 'PRESENT']
// Logical
[OPERATOR_AND, 'AND']
[OPERATOR_OR, 'OR']
[OPERATOR_NOR, 'NOR']
[OPERATOR_XOR, 'XOR']
[OPERATOR_NOT, 'NOT']The operator keys are unique symbols which could be imported from the engine package:
import {
OPERATOR_EQ,
OPERATOR_NE,
OPERATOR_GT,
OPERATOR_GE,
OPERATOR_LT,
OPERATOR_LE,
OPERATOR_IN,
OPERATOR_NOT_IN,
OPERATOR_PREFIX,
OPERATOR_SUFFIX,
OPERATOR_OVERLAP,
OPERATOR_UNDEFINED,
OPERATOR_PRESENT,
OPERATOR_AND,
OPERATOR_OR,
OPERATOR_NOR,
OPERATOR_XOR,
OPERATOR_NOT
} from '@briza/illogical'Breaking Changes
v1.2.0
- Removed strict mode from the Engine constructor options.
const engine = new Engine(strictMode, opts);->const engine = new Engine(opts);
Contributing
See contributing.md.
License
Illogical is released under the MIT license. See license.txt.