使用javascript解析算术表达式

时间:2014-04-27 16:22:54

标签: javascript parsing

使用javascript有一种简单的方法来转换以下表达式

e*((a*(b+c))+d)

类似

multiply(e, add(multiply(a, add(b,c)), d))

表达式将存储在字符串中。我愿意接受任何可以避免编写自己的解析器(库,buitl-in功能,......)的解决方案。

编辑:我应该已经预先确定我实际上并不想使用乘法添加函数,目的是为了定义我自己的函数来替换乘法添加并对变量执行自定义操作

5 个答案:

答案 0 :(得分:11)

您尝试解析为抽象语法树的表达式是一个无上下文的表达式。这意味着您需要一个无上下文语法才能解析它。所以让我们创建一个解析器。

为了简化解析,我们将分离词法分析阶段。因此,我们首先需要创建一个词法分析器。幸运的是,有很多方便的词法分析库可用。我们将使用这个:

https://github.com/aaditmshah/lexer

所以这是词法分析器:

var lexer = new Lexer;

lexer.addRule(/\s+/, function () {
    /* skip whitespace */
});

lexer.addRule(/[a-z]/, function (lexeme) {
    return lexeme; // symbols
});

lexer.addRule(/[\(\+\-\*\/\)]/, function (lexeme) {
    return lexeme; // punctuation (i.e. "(", "+", "-", "*", "/", ")")
});

接下来我们创建一个解析器。我们将使用Dijkstra的分流码算法的以下实现进行解析:

https://gist.github.com/aaditmshah/6683499

所以这是解析器:

var factor = {
    precedence: 2,
    associativity: "left"
};

var term = {
    precedence: 1,
    associativity: "left"
};

var parser = new Parser({
    "+": term,
    "-": term,
    "*": factor,
    "/": factor
});

最后,我们创建一个parse函数,如下所示:

function parse(input) {
    lexer.setInput(input);
    var tokens = [], token;
    while (token = lexer.lex()) tokens.push(token);
    return parser.parse(tokens);
}

现在您只需调用parse以后缀表示法获取已解析的标记流:

var output = parse("e*((a*(b+c))+d)");
alert(output.join(" "));               // "e a b c + * d + *"

后缀形式的优点是您可以使用堆栈轻松操作它:

  1. e推入堆栈。
  2. a推入堆栈。
  3. b推入堆栈。
  4. c推入堆栈。
  5. 弹出bc并将b + c推入堆栈。
  6. 弹出ab + c并将a * (b + c)推入堆栈。
  7. d推入堆栈。
  8. 弹出a * (b + c)d并将a * (b + c) + d推入堆栈。
  9. 弹出ea * (b + c) + d并将e * (a * (b + c) + d)推入堆栈。
  10. 同样,使用堆栈也可以轻松创建所需的输出。它的步骤相同。您只需将不同的值推回堆栈以进行不同的操作。

    请参阅演示:http://jsfiddle.net/d2UYZ/2/

    编辑1:我很无聊,我为你解决了这个问题:

    var stack = [];
    
    var operator = {
        "+": "add",
        "-": "subtract",
        "*": "multiply",
        "/": "divide"
    };
    
    parse("e*((a*(b+c))+d)").forEach(function (c) {
        switch (c) {
        case "+":
        case "-":
        case "*":
        case "/":
            var b = stack.pop();
            var a = stack.pop();
            stack.push(operator[c] + "(" + a + ", " + b + ")");
            break;
        default:
            stack.push(c);
        }
    });
    
    var output = stack.pop();
    
    alert(output);
    

    输出(如您所料)字符串"multiply(e, add(multiply(a, add(b,c)), d))"。请参阅演示:http://jsfiddle.net/d2UYZ/4/

    编辑2:如果您需要评估表达式,您也可以轻松完成。您所需要的只是为每个运算符创建值和函数的符号映射:

    var stack = [];
    
    var context = {
        "a": 1,
        "b": 2,
        "c": 3,
        "d": 4,
        "e": 5
    };
    
    var operator = {
        "+": function (a, b) { return a + b; },
        "-": function (a, b) { return a - b; },
        "*": function (a, b) { return a * b; },
        "/": function (a, b) { return a / b; }
    };
    
    parse("e*((a*(b+c))+d)").forEach(function (c) {
        switch (c) {
        case "+":
        case "-":
        case "*":
        case "/":
            var b =+ stack.pop();
            var a =+ stack.pop();
            stack.push(operator[c](a, b));
            break;
        default:
            stack.push(context[c]);
        }
    });
    
    var output = stack.pop();
    

    因此,表达式e*((a*(b+c))+d)变为5*((1*(2+3))+4),其评估为45。请参阅演示:http://jsfiddle.net/d2UYZ/6/

答案 1 :(得分:3)

您无法按照建议使用eval执行此操作,因为您无法以这种方式使用自定义addmultiply功能。您可以使用正则表达式解析所有运算符,但这并不容易。

您必须确保操作顺序正确,并将其分解为多个步骤。

它的基本逻辑是:

  1. 定义添加和乘法功能。

  2. 定义一个函数,它接受一串数字和运算符(没有括号)并将其拆分为二进制运算(即。' 1 + 7'或' 5 * 3&#39 ;)使用正则表达式,可以传递给add和multiply函数。这必须以递归方式完成,按照您自定义数学运算的预期操作顺序,换句话说,如果您得到“5 + 3 * 2'你将解析' 3 * 2'并在使用其结果添加到5之前评估(假设您的订单将首先相乘)。

  3. 定义一个查找括号的函数,递归查找最里面的集合并将其传递给上面的函数2进行评估。

  4. 将输入字符串传递给上面的函数3,并希望所有递归逻辑和正则表达式都正确。

答案 2 :(得分:0)

你可以eval

var result = eval('e*((a*(b+c))+d)');

如果声明了所有变量。

但请记住以下内容:

eval是邪恶的

答案 3 :(得分:0)

我认为更好的方法是定义你自己的函数:

multiply = function(a, b) {
    return a *  b;
}

add = function(a, b) {
    return a +  b;
}

此示例仅限于两个数字,但可以使用arguments进行缩放。

Fiddle

答案 4 :(得分:-1)

听起来你不只是寻找一个简单的eval()。如果您想要自定义功能,您将不得不自己解析它。另一种选择是以正确的顺序使用正则表达式替换。虽然从长远来看,这将极易出错并且难以管理。

用JavaScript编写的任何解析器最终都会比用C或其他低级语言编写的解析器慢。虽然这只应该用于更大的表达式,因为较小的简单表达式将更容易解析,无论如何。

对于正则表达式方法,您只需编写正则表达式即可获取a + b * c并将其转换为add(a, mul(b, c))。然后你将使用eval()来完成剩下的工作。 (请注意,abcaddmul都应该被定义。