【问题标题】:Parsing operators and evaluating them in JavaScript [closed]解析运算符并在 JavaScript 中评估它们 [关闭]
【发布时间】:2013-12-20 20:15:12
【问题描述】:

我想解析一个包含if 语句的字符串并评估它的输出。

我可以使用评估部分,但其他部分对我来说太复杂了。

举以下例子:

  • if $a == 10 && ($b == '5' || $c == 'test')
  • if $x != 10 || $y == false
  • if $z < 10

我想把它们放在数组中——也就是说,我想要的相应示例的输出是:

  • [ [ '$a', '==', '10' ], '&&', [ [ '$b', '==', '5' ], '||', [ '$c', '==', 'test' ] ] ]
  • [ [ '$x', '!=', '10' ], '||', [ '$y', '==', 'false' ] ]
  • [ '$z', '<', '10' ]

我想我要求的逻辑/代码太多了,但如果有人能帮助我解决这个问题,那就太好了。使用正则表达式或普通字符串解析都可以。

【问题讨论】:

  • 你想要类似的东西,对于第一个条件,取两个数组(一个用于条件,另一个用于运算符“&&”,“||”)并将这两个数组添加到数组列表中......对其他查询(第二个和第三个查询)做同样的事情吗?请指教。
  • 你的用例是什么?我认为您尝试做的事情有更简单的替代方案。
  • 我想使用javascript将if语句逻辑转换为数组
  • 免责声明(我是作者).. 看看github.com/MichaelCereda/ConditionerJs

标签: javascript regex parsing operators


【解决方案1】:
var operators = ['==', '||', '&&']; // fill the list
var str = "$a == 10 && ($b == '5' || $c == 'test') ";
var expectedArr = [['$a','==','$b'], '&&', [['$b', '==', '5'], '||', ['$c', '==', 'test']]];

function parseStr(str)
{
    var output = [];
    var openParenthesis = str.indexOf('(');
    var toAppend = [];
    if ( openParenthesis > -1 ) {
        var closeParenthesis = str.lastIndexOf(')');
        var contents = str.substring(openParenthesis + 1, closeParenthesis - 1);
        toAppend = parseStr(contents);
    }
    else {
        openParenthesis = str.length;
    }

    var prefix = str.substring(0, openParenthesis);
    var regexp = makeOperatorsRegex();
    var res;
    while ( res = regexp.exec(prefix) ) {
        console.log(res);
        output.push(res[1].trim());
        output.push(res[2].trim());
    }
    var lastIdx = output.length - 1;
    if ( operators.indexOf(output[lastIdx]) > -1 ) {
        var tmpOutput = [[output.slice(0, lastIdx)], output[lastIdx]];
        output = tmpOutput;
    }
    for ( var i in toAppend ) {
        output.push(toAppend[i]);
    }
    return output;
}

function makeOperatorsRegex()
{
    var opRegex = '([a-zA-Z0-9\\$ ]*)(';
    var first = true;
    for ( var i in operators ) {
        if ( ! first ) {
            opRegex += '|';
        }
        opRegex += '(' + RegExp.quote(operators[i]) + ')';
        first = false;
    }
    opRegex += ')';

    return new RegExp(opRegex, 'g');
}

RegExp.quote = function(str) {
    return (str+'').replace(/([\=.?*+^$[\]\\(){}|-])/g, "\\$1");
};

function quote(value)
{
    if (value[0] == '$') {
        return value;
    }
    for ( var i in operators ) {
        if ( value == operators[i] ) {
            return value;
        }
    }
    return '\'' + value + '\'';
}

function buildExpr(arr, values) {
    var expr = '';
    for ( var i in arr ) {
        if( Object.prototype.toString.call( arr[i] ) === '[object Array]' ) {
            expr += '(' + buildExpr(arr[i]) + ')';
        }
        else {
            expr += quote(arr[i]);
        }
    }
    for (var k in values) {
        expr = expr.replace(new RegExp('\\$' + k, 'g'), quote(values[k]));
        expr = expr.replace(new RegExp('\\$' + k, 'g'), values[k]);
    }
    return expr
}

console.log(parseStr(str));

var values = {
    'a': 5,
    'b': 5,
    'c': 'test'
};
console.log(buildExpr(expectedArr, values));
console.log(eval(buildExpr(expectedArr, values)));

这几乎可行。你只需要在这里和那里打磨,它会的。完成后,您甚至可以像这样进行往返:

var str = "$a == 10 && ($b == '5' || $c == 'test') ";
var array = parseStr(str);
var newStr = buildExpr(array);
if ( newStr == str ) {
    console.log('ok');
}

【讨论】:

  • 我认为 OP 一开始是在问如何将该表达式解析为arr
  • 好的,你说得对,我一会儿更新。
  • 固定+之前的代码可用于往返。
  • 你怎么有耐心写这样的代码?
  • 您知道解决某事并告诉计算机它无法打败您的不可抗拒的冲动吗? :)
【解决方案2】:

如果您只是想解析您的字符串,那么有很多 JavaScript 解析库可以为您做到这一点。例如,您可以使用acorn有效 JavaScript 代码解析为Mozilla AST。您也可以使用escodegen 将其转换回字符串。

不幸的是,您的字符串似乎不是有效的 JavaScript 代码,但如果您删除每个字符串开头的 if,那么您绝对可以使用 acorn 解析字符串。输出将是 AST,这不是您要查找的内容,但您可以轻松地将其转换为您想要的格式。

但是,对于这样一个微不足道的用例,使用完整的解析器在我看来是一种矫枉过正的做法。例如,如果您只是想评估您的字符串,那么您可以使用 Function 构造函数,如下所示:

function read(expression) {
    var variables = expression.match(/\$\w+/g);
    var length = variables.length;
    var uniqueVariables = [];
    var index = 0;

    while (index < length) {
        var variable = variables[index++];
        if (uniqueVariables.indexOf(variable) < 0)
            uniqueVariables.push(variable);
    }

    return Function.apply(null, uniqueVariables.concat("return " + expression));
}

这个read 函数允许您读取如下表达式:

var condition = read("$a == 10 && ($b == '5' || $c == 'test')");

您现在可以按如下方式使用condition 函数:

alert(condition(10, "10", "test")); // true
alert(condition(5, "10", "test")); // false

亲自观看演示:http://jsfiddle.net/ZnUh2/

当然,您需要删除所有字符串开头的if 才能读取它们。这可以通过使用string.slice(2) 删除if 轻松完成。

如果您一心想将字符串转换为数组,那么这将需要更多的工作,但可以通过使用像 Lexer 这样的词法分析器轻松完成。您需要做的第一件事是为不同类型的令牌编写一些规则:

var lexer = new Lexer;

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

lexer.addRule(/if\b/g, function () { /* skip the if keyword */ });

// match opening parentheses
lexer.addRule(/\(/, function () { return "("; });

// match closing parentheses
lexer.addRule(/\)/, function () { return ")"; });

// match any other token
lexer.addRule(/[^\s\(\)]+/, function (lexeme) { return lexeme; });

请注意,此词法分析器希望每个标记(括号除外)之间都有空格。例如,$a==10 将被视为 1 个令牌,而 $a == 10 将被视为 3 个令牌。

接下来你需要一个基本的解析器。您可以手动实现,但自己编写所有运算符优先规则会很痛苦。相反,我建议您根据Dijkstrashunting yard algorithm 使用以下parser

我们现在可以如下创建解析器:

var relational = {
    precedence: 3,
    associativity: "left"
};

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

var parser = new Parser({
    "<":  relational,
    "<=": relational,
    ">":  relational,
    ">=": relational,
    "==": equality,
    "!=": equality,
    "&&": {
        precedence: 1,
        associativity: "right"
    },
    "||": {
        precedence: 0,
        associativity: "right"
    }
});

最后我们编写代码将词法分析器连接到解析器并生成所需的输出:

function parse(string) {
    lexer.setInput(string);

    var tokens = [], token;

    while (token = lexer.lex()) tokens.push(token);
    tokens = parser.parse(tokens);

    var stack = [], length = tokens.length, index = 0;

    while (index < length) {
        token = tokens[index++];

        switch (token) {
        case "<":
        case "<=":
        case ">":
        case ">=":
        case "==":
        case "!=":
        case "&&":
        case "||":
            var b = stack.pop();
            var a = stack.pop();
            stack.push([a, token, b]);
            break;
        default:
            stack.push(token);
        }
    }

    return stack.length && stack[0];
}

就是这样。现在您可以将字符串解析为数组,如下所示:

var array = parse("if $a == 10 && ($b == '5' || $c == 'test')");

要查看输出,您可以使用JSON.stringify。亲自观看演示:http://jsfiddle.net/d2UYZ/3/

【讨论】:

  • 很好的答案,词法分析器和解析器看起来也不错。我不明白为什么解析器需要并返回一个以空格分隔的标记字符串而不是一个数组,不过……只是针对其他代码量身定制的?
  • @DaggNabbit 确实如此。我在 2 个月前为解析器编写了代码以进行演示。因此,使用字符串而不是令牌数组并返回字符串而不是令牌数组是有意义的。由于代码已经作为要点发布在 GitHub 上,我决定不更改它,只更改 join/split parser.parse 函数的输入和输出。完全删除这种不必要的装箱/拆箱确实会更好,但为了一个简单的答案,我懒得这样做。
  • @DaggNabbit 我更新了要点、jsfiddle 和我的答案,并删除了不必要的split/join
  • 你和 Maciej Sz,都有很好的答案。感谢这个词法分析器。
  • 我专门为此创建了一个库,也许有人想看看github.com/MichaelCereda/ConditionerJs
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-03
  • 1970-01-01
  • 2019-01-12
  • 1970-01-01
  • 1970-01-01
  • 2016-02-12
相关资源
最近更新 更多