Я хочу закодировать функцию для обработки определяемых условий.
Справочные данные содержатся в объекте, а простое условие хранится в массиве из 3 элементов, например:
["name", "= = ", "John Doe"]
вот код, который хорошо работает для проверки простого условия:
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.info( getResultOfSimpleCondition(myData, condition_0) )
я ищу, как реализовать более сложные условия по тому же принципу.
Например:
на 2 уровнях:
[ condition_0, "OR", condition_1 ]
// результат истинный
или
[ condition_1, "AND", condition_2 ]
// результат ложный
на большем количестве уровней:
[[ condition_0, "OR", condition_1 ], "AND", condition_3]
// результат истинный
или
[[ condition_0, "OR", condition_1 ], "AND", condition_3, "AND NOT", [condition_5, "OR", condition_23 ] ]
код будет выглядеть
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.info( getResultOfComplexCondition(myData, complexCondition) )
заранее спасибо
Для «читаемого» решения здесь может работать рекурсия. Функция, которая проходит через каждый дочерний массив (или дочерний элемент дочернего элемента и т. д.) в сложных условиях, пока не найдет только строковые элементы, вызовет вашу функцию simpleConditions, а затем создаст эти результаты для резервного копирования дерева условий до верхнего уровня. Это, вероятно, не будет очень производительным — возможно, вы захотите изучить создание небольшого механизма правил.
Скорее всего, вы захотите реализовать свои " = " и "! = ", используя настоящие операторы javascript ===
и !==
вместо ==
и !=
, чтобы избежать ловушек, таких как 0 == false
Я немного упростил ваше выражение, но для демонстрации рекурсивного обхода AST (абстрактного синтаксического дерева). Я использовал Acorn для анализа упрощенной версии предоставленных вами выражений.
Что касается ваших «нравится» и «не нравится», вам нужно будет разработать собственную грамматику. См. «Абстрактные синтаксические деревья с парсером рекурсивного спуска» для логики токенизации с использованием регулярных выражений.
const evaluateExpression = (context, expression) => {
const visitor = new NodeVisitor({ context });
const ast = acorn.parse(expression, { ecmaVersion: '2020' });
//console.info(ast);
return visitor.visit(ast.body[0].expression);
};
const main = () => {
const myData = { name: 'John Doe', age: 28, town: 'PARIS', qty: 5, uptodate: true };
console.info(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"
}
-->
так же, как и с функцией getResultOfSimpleCondition, я хочу иметь возможность решать более сложные логические выражения (условия). Я привел несколько примеров выше