본문 바로가기
개발/Node JS

Node JS - 문자열로 된 조건식 판단하기 (Evaluate String into Conditions with Acorn)

by 피로물든딸기 2023. 8. 6.
반응형

Node JS 전체 링크

 

아래와 같은 조건식이 있다고 가정하자.

이 경우, if문 내에서 truefalse가 반환되어 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;

 

 

반응형

댓글