I want to code a function to handle definable conditions.
The reference data is contained in an object and a simple condition is stored in a 3 elements array like this :
["name", "==", "John Doe"]
here is the code that works well to test a simple condition:
function getResultOfSimpleCondition(data, condition) {
let c1 = data[condition[0]],
operateur = condition[1],
c2 = condition[2], cond=true;
switch(operateur){
case "==" :
case "=" : cond = (c1 == c2 ); break;
case "!=" : cond = (c1 != c2 ); break;
case ">" : cond = (c1 > c2 ); break;
case "<" : cond = (c1 < c2 ); break;
case ">=" : cond = (c1 >= c2 ); break;
case "<=" : cond = (c1 <= c2 ); break;
case "like": cond = (c1.indexOf(c2) > -1); break;
case "not like": cond = (c1.indexOf(c2) == -1); break;
default : cond = (c1 == c2 ); break;
}
return cond
}
let myData = { name:'John Doe', age:'28', town:'PARIS', qty:5, uptodate: true},
condition_0 = ["name", "==", "Jack Sparrow"], // result false
condition_1 = ["age", ">=", "24"], // result true
condition_2 = ["uptodate", "==", false], // result false
condition_3 = ["town", "==", "PARIS"]; // result true
console.log( getResultOfSimpleCondition(myData, condition_0) )
what I'm looking for is how to implement more complex conditions on the same principle.
For example:
on 2 levels:
[ condition_0, "OR", condition_1 ]
// result true
or
[ condition_1, "AND", condition_2 ]
// result false
on more levels:
[[ condition_0, "OR", condition_1 ], "AND", condition_3]
// result true
or
[[ condition_0, "OR", condition_1 ], "AND", condition_3, "AND NOT", [condition_5, "OR", condition_23 ] ]
the code would look like
let myData = { name:'John Doe', age:'28', town:'PARIS', qty:5, uptodate: true},
complexCondition = [[ condition_0, "OR", condition_1 ], "AND", condition_3, "AND NOT", [condition_5, "OR", condition_23 ] ];
function getResultOfComplexCondition(data, condition){
...
}
console.log( getResultOfComplexCondition(myData, complexCondition) )
thank you in advance
I simplified your expression a bit, but to demonstrate a recursive walk through an AST (abstract syntax tree). I used Acorn to parse a simplified version of your provided expressions.
In regards to your 'like' and 'not like', you will need to devise your own grammar. See "Abstract Syntax Trees with a Recursive Descent Parser" for tokenization logic using regular expressions.
const evaluateExpression = (context, expression) => {
const visitor = new NodeVisitor({ context });
const ast = acorn.parse(expression, { ecmaVersion: '2020' });
//console.log(ast);
return visitor.visit(ast.body[0].expression);
};
const main = () => {
const myData = { name: 'John Doe', age: 28, town: 'PARIS', qty: 5, uptodate: true };
console.log(evaluateExpression(myData, 'age >= 24 && town == "PARIS"'));
};
// Adapted from:
// https://inspirnathan.com/posts/163-abstract-syntax-trees-with-recursive-descent-parser/
class NodeVisitor {
constructor({ context }) {
this.context = context;
}
visit(node) {
switch (node.type) {
case 'Literal':
return this.visitLiteral(node);
case 'Identifier':
return this.visitIdentifier(node);
case 'BinaryExpression':
return this.visitBinaryExpression(node);
case 'LogicalExpression':
return this.visitLogicalExpression(node);
}
}
visitLiteral(node) {
return node.value;
}
visitIdentifier(node) {
return node.name;
}
visitBinaryExpression(node) {
switch (node.operator) {
case '<':
return this.context[this.visit(node.left)] < this.visit(node.right);
case '<=':
return this.context[this.visit(node.left)] <= this.visit(node.right);
case '>':
return this.context[this.visit(node.left)] > this.visit(node.right);
case '>=':
return this.context[this.visit(node.left)] >= this.visit(node.right);
case '==':
return this.context[this.visit(node.left)] === this.visit(node.right);
case '!=':
return this.context[this.visit(node.left)] !== this.visit(node.right);
default:
throw new Error(`Invalid operation: ${node.operator}`);
}
}
visitLogicalExpression(node) {
switch (node.operator) {
case '&&':
return this.visit(node.left) && this.visit(node.right);
case '||':
return this.visit(node.left) || this.visit(node.right);
default:
throw new Error(`Invalid operation: ${node.operator}`);
}
}
}
main();
.as-console-wrapper { top: 0; max-height: 100% !important; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/acorn/8.8.2/acorn.min.js"></script>
<!--
// Acorn AST of parsed expression:
{
"type": "Program",
"start": 0,
"end": 28,
"body": [
{
"type": "ExpressionStatement",
"start": 0,
"end": 28,
"expression": {
"type": "LogicalExpression",
"start": 0,
"end": 28,
"left": {
"type": "BinaryExpression",
"start": 0,
"end": 9,
"left": {
"type": "Identifier",
"start": 0,
"end": 3,
"name": "age"
},
"operator": ">=",
"right": {
"type": "Literal",
"start": 7,
"end": 9,
"value": 24,
"raw": "24"
}
},
"operator": "&&",
"right": {
"type": "BinaryExpression",
"start": 13,
"end": 28,
"left": {
"type": "Identifier",
"start": 13,
"end": 17,
"name": "town"
},
"operator": "==",
"right": {
"type": "Literal",
"start": 21,
"end": 28,
"value": "PARIS",
"raw": "\"PARIS\""
}
}
}
}
],
"sourceType": "script"
}
-->