반응형
아래와 같은 조건식이 있다고 가정하자.
이 경우, if문 내에서 true나 false가 반환되어 if문이 실행되거나 실행되지 않는다.
if((((condition1) && (condition2 || condition3))) == condition4) { ... }
그런데 만약 위의 조건식이 문자열(string)로 주어진다면? if 문 내에서는 판단할 방법이 없다.
const condition = "(((condition1) && (condition2 || condition3))) == condition4";
if(condition ???) { ... } // if문에서 사용 불가능
자바스크립트에서는 acorn을 이용해서 AST(Abstract Syntax Tree)를 생성하고 문자열 조건식을 판단할 수 있다.
npm install acorn
acorn을 import하고 parse를 이용해 조건식을 파싱한다.
const acorn = require("acorn");
const ast = acorn.parse(condition, { ecmaVersion: "latest" });
그리고 type별 / operator별 구체적인 사항을 아래와 같이 구현한다.
function evaluateCondition(condition, context) {
const ast = acorn.parse(condition, { ecmaVersion: "latest" });
function evaluateNode(node) {
if (node.type === "LogicalExpression") {
const leftValue = evaluateNode(node.left);
const rightValue = evaluateNode(node.right);
if (node.operator === "||") { // or
return leftValue || rightValue;
} else if (node.operator === "&&") { // and
return leftValue && rightValue;
}
} else if (node.type === "BinaryExpression") { // ==, !=
const leftValue = evaluateNode(node.left);
const rightValue = evaluateNode(node.right);
if (node.operator === "==") {
return leftValue == rightValue;
} else if (node.operator === "===") {
return leftValue === rightValue;
} else if (node.operator === "!=") {
return leftValue != rightValue;
} else if (node.operator === "!==") {
return leftValue !== rightValue;
}
} else if (node.type === "Identifier") {
return context[node.name];
} else if (node.type === "Literal") {
return node.value;
} else if (node.type === "UnaryExpression" && node.operator === "!") {
return !evaluateNode(node.argument);
}
throw new Error("Unsupported node type: " + node.type);
}
return evaluateNode(ast.body[0].expression);
}
사용 방법 예시는 아래와 같다.
const condition = "(condition1 || (condition2 && condition3))";
const result = evaluateCondition(condition, {
condition1: true,
condition2: false,
condition3: true,
});
console.log(result);
evaluateNode의 node를 로그로 출력하면 아래와 같이 문자열 조건식에 대한 정보를 parsing하여 가지고 있다.
Node {
type: 'LogicalExpression',
start: 1,
end: 41,
left: Node { type: 'Identifier', start: 1, end: 11, name: 'condition1' },
operator: '||',
right: Node {
type: 'LogicalExpression',
start: 16,
end: 40,
left: Node { type: 'Identifier', start: 16, end: 26, name: 'condition2' },
operator: '&&',
right: Node { type: 'Identifier', start: 30, end: 40, name: 'condition3' }
}
}
Node { type: 'Identifier', start: 1, end: 11, name: 'condition1' }
Node {
type: 'LogicalExpression',
start: 16,
end: 40,
left: Node { type: 'Identifier', start: 16, end: 26, name: 'condition2' },
operator: '&&',
right: Node { type: 'Identifier', start: 30, end: 40, name: 'condition3' }
}
Node { type: 'Identifier', start: 16, end: 26, name: 'condition2' }
Node { type: 'Identifier', start: 30, end: 40, name: 'condition3' }
이제 아래의 조건문도 실행해보자.
(((condition1) && (condition2 || condition3))) == condition4
4가지 조건이므로 for문을 4번 돌려서 모든 경우의 수에 대해 출력을 하였다.
let tf = [true, false];
const condition =
"(((condition1) && (condition2 || condition3))) == condition4";
for (let a = 0; a < 2; a++) {
for (let b = 0; b < 2; b++) {
for (let c = 0; c < 2; c++) {
for (let d = 0; d < 2; d++) {
let str = `(((${tf[a]}) && (${tf[b]} || ${tf[c]}))) == ${tf[d]} => `;
const result = evaluateCondition(condition, {
condition1: tf[a],
condition2: tf[b],
condition3: tf[c],
condition4: tf[d],
});
console.log(str + result);
}
}
}
}
출력 결과는 다음과 같으며, 수식을 제대로 판단하는 것을 알 수 있다.
(((true) && (true || true))) == true => true
(((true) && (true || true))) == false => false
(((true) && (true || false))) == true => true
(((true) && (true || false))) == false => false
(((true) && (false || true))) == true => true
(((true) && (false || true))) == false => false
(((true) && (false || false))) == true => false
(((true) && (false || false))) == false => true
(((false) && (true || true))) == true => false
(((false) && (true || true))) == false => true
(((false) && (true || false))) == true => false
(((false) && (true || false))) == false => true
(((false) && (false || true))) == true => false
(((false) && (false || true))) == false => true
(((false) && (false || false))) == true => false
(((false) && (false || false))) == false => true
만약 리액트에서 사용하고 싶다면 require을 이용해 함수 내에 추가하면 된다.
import React from "react";
function evaluateCondition(condition, context) {
const acorn = require("acorn"); // 추가
const ast = acorn.parse(condition, { ecmaVersion: "latest" });
...
전체 코드는 다음과 같다.
Node
const acorn = require("acorn");
function evaluateCondition(condition, context) {
const ast = acorn.parse(condition, { ecmaVersion: "latest" });
function evaluateNode(node) {
if (node.type === "LogicalExpression") {
const leftValue = evaluateNode(node.left);
const rightValue = evaluateNode(node.right);
if (node.operator === "||") { // or
return leftValue || rightValue;
} else if (node.operator === "&&") { // and
return leftValue && rightValue;
}
} else if (node.type === "BinaryExpression") { // ==, !=
const leftValue = evaluateNode(node.left);
const rightValue = evaluateNode(node.right);
if (node.operator === "==") {
return leftValue == rightValue;
} else if (node.operator === "===") {
return leftValue === rightValue;
} else if (node.operator === "!=") {
return leftValue != rightValue;
} else if (node.operator === "!==") {
return leftValue !== rightValue;
}
} else if (node.type === "Identifier") {
return context[node.name];
} else if (node.type === "Literal") {
return node.value;
} else if (node.type === "UnaryExpression" && node.operator === "!") {
return !evaluateNode(node.argument);
}
throw new Error("Unsupported node type: " + node.type);
}
return evaluateNode(ast.body[0].expression);
}
let tf = [true, false];
const condition =
"(((condition1) && (condition2 || condition3))) == condition4";
for (let a = 0; a < 2; a++) {
for (let b = 0; b < 2; b++) {
for (let c = 0; c < 2; c++) {
for (let d = 0; d < 2; d++) {
let str = `(((${tf[a]}) && (${tf[b]} || ${tf[c]}))) == ${tf[d]} => `;
const result = evaluateCondition(condition, {
condition1: tf[a],
condition2: tf[b],
condition3: tf[c],
condition4: tf[d],
});
console.log(str + result);
}
}
}
}
React
import React, { useEffect } from "react";
const acorn = require("acorn");
const App = () => {
function evaluateCondition(condition, context) {
const ast = acorn.parse(condition, { ecmaVersion: "latest" });
function evaluateNode(node) {
if (node.type === "LogicalExpression") {
const leftValue = evaluateNode(node.left);
const rightValue = evaluateNode(node.right);
if (node.operator === "||") {
// or
return leftValue || rightValue;
} else if (node.operator === "&&") {
// and
return leftValue && rightValue;
}
} else if (node.type === "BinaryExpression") {
// ==, !=
const leftValue = evaluateNode(node.left);
const rightValue = evaluateNode(node.right);
if (node.operator === "==") {
return leftValue == rightValue;
} else if (node.operator === "===") {
return leftValue === rightValue;
} else if (node.operator === "!=") {
return leftValue != rightValue;
} else if (node.operator === "!==") {
return leftValue !== rightValue;
}
} else if (node.type === "Identifier") {
return context[node.name];
} else if (node.type === "Literal") {
return node.value;
} else if (node.type === "UnaryExpression" && node.operator === "!") {
return !evaluateNode(node.argument);
}
throw new Error("Unsupported node type: " + node.type);
}
return evaluateNode(ast.body[0].expression);
}
const test = () => {
let tf = [true, false];
const condition =
"(((condition1) && (condition2 || condition3))) == condition4";
for (let a = 0; a < 2; a++) {
for (let b = 0; b < 2; b++) {
for (let c = 0; c < 2; c++) {
for (let d = 0; d < 2; d++) {
let str = `(((${tf[a]}) && (${tf[b]} || ${tf[c]}))) == ${tf[d]} => `;
const result = evaluateCondition(condition, {
condition1: tf[a],
condition2: tf[b],
condition3: tf[c],
condition4: tf[d],
});
console.log(str + result);
}
}
}
}
};
useEffect(test, []);
return <div>test</div>;
};
export default App;
반응형
댓글