【问题标题】:javascript array parsing matchingjavascript数组解析匹配
【发布时间】:2014-01-13 08:22:08
【问题描述】:

假设我有一个数组。

["red", "blue", "neon", "black", "orange"]

我想评估某个匹配模式是否为真。

我想用逗号表示 OR,用 && 表示 AND。

"red&&blue" -> true
"blue&&white" -> false
"red,white" -> true
"(red&&blue),(red&&white)" -> true
"(red&&blue)&&(red&&white)" -> false
"(red&&blue)&&(red&&neon)" -> true

我应该使用什么样的匹配方案?我不想从头开始实现解析器,如果有任何现有的会很棒,但否则我希望逻辑像它在具有无限复杂性的 javascript 中一样工作。

我基本上是在寻找这样的东西,但对于 javascript: Java library for parsing & building logical expressions

【问题讨论】:

标签: javascript logic pattern-matching expression


【解决方案1】:

对于未来的读者,这里有一个更可靠的方法来完成这项工作:

基本上就是这样 - 无需重新发明轮子。

对于您发布的示例,语法可以是这样的:

{
var props = ["red", "blue", "neon", "black", "orange"]; 
}


start
  = additive

additive
  = left:multiplicative "," right:additive { return left || right }
  / multiplicative

multiplicative
  = left:primary "&&" right:multiplicative { return left && right }
  / primary

primary
  = atom
  / "(" additive:additive ")" { return additive; }

atom 
  = letters:[a-z]+ { return props.indexOf(letters.join("")) >= 0 }

【讨论】:

  • 漂亮!我无法抗拒地看到实现哈利的想法是多么困难。这是微不足道的。 :-)
【解决方案2】:

几乎可以肯定,最好写一个解析器或使用一个别人已经写好的。正如您在 cmets 中指出的那样,对于这种 非常 受限的输入,它实际上非常简单:

  • 在运算符上拆分字符串
  • 遍历生成的拆分字符串:
    • 验证运算符
    • , 转换为||
    • 可选择验证名称
    • true(如果在数组中)或false(如果不在)替换名称
  • 再次将结果重新加入字符串
  • 通过eval 运行结果(因为您现在知道它只有具有您已列入白名单的操作员和文本truefalse

这是一个快速的概念验证:Live Copy | Live Source

<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>Expression Thingy</title>
  <style>
    .good {
      color: green;
    }
    .bad {
      color: #d22;
    }
  </style>
</head>
<body>
  <script>
    (function() {
      var array = ["red", "blue", "neon", "black", "orange"];
      var tests = [
        {expr: "red&&blue",                 expect: true},
        {expr: "blue&&white",               expect: false},
        {expr: "red,white",                 expect: true},
        {expr: "(red&&blue),(red&&white)",  expect: true},
        {expr: "(red&&blue)&&(red&&white)", expect: false},
        {expr: "(red&&blue)&&(red&&neon)",  expect: true},
        {expr: "(red+blue)&&(red!neon)",    expectInvalid: true}
      ];
      var data;

      // Turn data into an object with named properties, to make lookups
      // faster
      data = {};
      array.forEach(function(entry) {
        data[entry] = true;
      });

      // Run the tests
      tests.forEach(runTest);

      function runTest(test) {
        var parts, invalid = false;

        // Get an array of tokens: We'll get `(`, `)`, `,`, `&&`, whitespace, or a name in each array slot
        parts = test.expr.match(/&&|,|\(|\)|\s+|[^()&,]+/g);

        // Validate the operators and turn the names into "true" or "false"
        parts.forEach(function(part, index) {
          switch (part) {
            case ",":
              // Valid operator, replace with ||
              parts[index] = "||";
              break;
            case "&&":
            case "(":
            case ")":
              // Valid operator
              break;
            default:
              // Name or whitespace
              if (!part.replace(/\s+/g, "")) {
                // Whitespace
              }
              else {
                // Name, validate it -- obviously apply whatever rule works
                // for your data, the rule below allows A-Z, $, and _ in
                // the first position and those plus digits in subsequent
                // positions.
                if (!/^[A-Za-z$_][A-Za-z0-9$_]*$/.test(part)) {
                  // Invalid
                  display("Invalid name: " + part, test.expectInvalid);
                  invalid = true;
                }
                else {
                  // Valid, replace it
                  parts[index] = data[part] ? "true" : "false";
                }
              }
              break;
          }
        });
        if (!invalid) {
          // Now we know parts only has valid stuff we can trust in it, rejoin
          // and eval it
          result = !!eval(parts.join(""));
          display(test.expr + ": Got " + result + ", expected " + test.expect, result === test.expect);
        }
      }

      function display(msg, good) {
        var p = document.createElement('p');
        p.innerHTML = String(msg);
        if (typeof good !== "undefined") {
          p.className = good ? "good" : "bad";
        }
        document.body.appendChild(p);
      }
    })();
</script>
</body>
</html>

您可能希望至少稍微修改一下验证规则。


旧答案,主要假设您可以信任输入:

将这些输入转换为有效的 JavaScript 表达式很容易。然后你可以:

  1. 使用别人已经写过的解析器,like this one(详情在this blog post (那个似乎不支持&amp;&amp;和@987654336 @,但也许您可以将其扩展为),或

  2. 将数组转换为对象属性并使用eval从不相信 eval 不安全或不能制作安全的输入。但如果输入是安全的或可以变得安全,eval 就可以了。

假设数组中的值是有效的 JavaScript 标识符,您只需将 , 更改为 || 即可将这些表达式转换为有效的 JavaScript 表达式:

str = str.replace(/,/g, "||");

同样,这会将数组转换为具有这些命名属性的对象:

var obj = {};
data.forEach(function(entry) {
    obj[entry] = true;
});

...您可能会随后将其传递给表达式评估器。

如果你要走eval 路线,你必须对字符串做更多的准备,将"(red&amp;&amp;blue),(red&amp;&amp;white)" 变成'(obj["red"]&amp;&amp;obj["blue"])||(obj["red"]&amp;&amp;obj["white"])',如下所示:

str = str.replace(/,/g, "||").replace(/\b([a-zA-Z0-9_]+)\b/g, 'obj["$1"]');

我不会做一个使用表达式评估器库的示例,但这里是eval 的基础知识:Live Copy | Live Source

<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>Expression Thingy</title>
  <style>
    .good {
      color: green;
    }
    .bad {
      color: #d22;
    }
  </style>
</head>
<body>
  <script>
    (function() {
      var data = ["red", "blue", "neon", "black", "orange"];
      var tests = [
        {expr: "red&&blue",                 expect: true},
        {expr: "blue&&white",               expect: false},
        {expr: "red,white",                 expect: true},
        {expr: "(red&&blue),(red&&white)",  expect: true},
        {expr: "(red&&blue)&&(red&&white)", expect: false},
        {expr: "(red&&blue)&&(red&&neon)",  expect: true}
      ];
      var obj;

      // Turn data into an object with named properties
      obj = {};
      data.forEach(function(entry) {
        obj[entry] = true;
      });

      // Turn the expressions into eval strings
      tests.forEach(createEvalString);

      // Run the tests
      tests.forEach(runTest);

      function createEvalString(test) {
        test.evalStr = test.expr.replace(/,/g, "||").replace(/\b([a-zA-Z0-9_]+)\b/g, 'obj["$1"]');
      }

      function runTest(test) {
        var result;

        display(test.evalStr);
        result = !!eval(test.evalStr); // Relies on us closing over `obj`
        display(test.expr + ": Got " + result + ", expected " + test.expect, result === test.expect);
      }

      function display(msg, good) {
        var p = document.createElement('p');
        p.innerHTML = String(msg);
        if (typeof good !== "undefined") {
          p.className = good ? "good" : "bad";
        }
        document.body.appendChild(p);
      }
    })();
  </script>
</body>
</html>

这只是一个起点。一方面,您需要在转换字符串并将其与eval 一起使用之前仔细检查字符串。

【讨论】:

  • 非常感谢,但输入不安全,这是有问题的。
  • 我想到了一个主意,你怎么看?:用(),&&将每个表达式一个一个地分割,直到你得到最小的组件。然后为每个部分检查它是否在数组中。给它一个真值或假值。完成后,使用您之前拆分的相同值再次重新加入它们。然后你应该能够安全地评估(因为它只是真/假表达式)
  • @Harry:你启发了我,让我明白这有多容易。这真的非常简单,请参阅更新的答案。此外,我们应该清理这些 cmets,因为它们不再添加任何内容。
【解决方案3】:

我认为这个特殊情况,可以用这个简单的函数来解决

var whiteList = ["red", "blue", "neon", "black", "orange"];
function evaluator(inputString) {
    var data = whiteList.reduce(function(previous, current) {
        return previous.split(current).join("####");
    }, inputString);
    data = data.replace(",", "||").replace(/[a-zA-Z]+/g, "false");
    return eval(data.replace(/####/g, "true"));
}

Sample run,带有测试用例(感谢@T.J. Crowder :)

【讨论】:

  • 愚蠢的问题,但你为什么使用####
  • @Harry 那只是一个占位符。我本可以将其设置为 true 本身,但 [a-zA-Z]+ 也会将所有 trues 更改为 false
猜你喜欢
  • 1970-01-01
  • 2018-05-15
  • 1970-01-01
  • 2012-03-09
  • 2012-04-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多