【问题标题】:Convert Java tokenizing regex into Javascript将 Java 标记正则表达式转换为 Javascript
【发布时间】:2014-04-02 19:47:34
【问题描述】:

作为对我的问题Tokenizing an infix string in Java 的回答,我得到了正则表达式(?<=[^\.a-zA-Z\d])|(?=[^\.a-zA-Z\d]。但是,现在我正在用 Javascript 编写相同的代码,我不知道如何让 Javascript 正则表达式来做同样的事情。

例如,如果我有字符串sin(4+3)*2,我需要将其解析为["sin","(","4","+","3",")","*","2"]

我将使用什么正则表达式将字符串标记为每个单独的部分。

之前,我所做的只是对每个可能的标记进行字符串替换,并在其周围放置一个空格,然后在该空格上拆分。但是,该代码很快变得非常臃肿。

我需要拆分的运算符将是标准数学运算符(+,-,*,/,^),以及函数名称(sin,cos,tan,abs,etc...) 和逗号

什么是快速有效的方法?

【问题讨论】:

    标签: javascript regex infix-notation


    【解决方案1】:

    您可以利用正则表达式分组来做到这一点。您需要一个组合不同可能标记的正则表达式,然后重复应用它。

    我喜欢把不同的部分分开;它使维护和扩展更容易:

    var tokens = [
      "sin",
      "cos",
      "tan",
      "\\(",
      "\\)",
      "\\+",
      "-",
      "\\*",
      "/",
      "\\d+(?:\\.\\d*)?"
    ];
    

    你将它们粘合成一个大的正则表达式,在每个标记之间使用|

    var rtok = new RegExp( "\\s*(?:(" + tokens.join(")|(") + "))\\s*", "g" );
    

    然后您可以对源字符串使用正则表达式操作进行标记化:

    function tokenize( expression ) {
      var toks = [], p;
    
      rtok.lastIndex = p = 0; // reset the regex
      while (rtok.lastIndex < expression.length) {
        var match = rtok.exec(expression);
    
        // Make sure we found a token, and that we found
        // one without skipping garbage
    
        if (!match || rtok.lastIndex - match[0].length !== p)
          throw "Oops - syntax error";
    
        // Figure out which token we matched by finding the non-null group
        for (var i = 1; i < match.length; ++i) {
          if (match[i]) {
            toks.push({
              type: i,
              txt: match[i]
            });
            // remember the new position in the string
            p = rtok.lastIndex;
            break;
          }
        }
      }
      return toks;
    }
    

    这只是重复将标记正则表达式与字符串匹配。正则表达式是使用“g”标志创建的,因此正则表达式机器将在我们进行每次匹配后自动跟踪从哪里开始匹配。当它没有看到匹配时,或者当它看到但必须跳过无效的东西来找到它时,我们知道存在语法错误。当它匹配时,它会在令牌数组中记录它匹配的令牌(非空组的索引)和匹配的文本。通过记住匹配的标记索引,它省去了在标记化后必须弄清楚每个标记字符串 含义 的麻烦;你只需要做一个简单的数字比较。

    因此调用tokenize( "sin(4+3) * cos(25 / 3)" ) 返回:

    [ { type: 1, txt: 'sin' },
      { type: 4, txt: '(' },
      { type: 10, txt: '4' },
      { type: 6, txt: '+' },
      { type: 10, txt: '3' },
      { type: 5, txt: ')' },
      { type: 8, txt: '*' },
      { type: 2, txt: 'cos' },
      { type: 4, txt: '(' },
      { type: 10, txt: '25' },
      { type: 9, txt: '/' },
      { type: 10, txt: '3' },
      { type: 5, txt: ')' } ]
    

    令牌类型 1 是 sin 函数,类型 4 是左括号,类型 10 是数字,等等。

    edit——如果你想匹配像“x”和“y”这样的标识符,那么我可能会使用一组不同的标记模式,其中一个只是为了匹配任何标识符。这意味着解析器不会直接从词法分析器中找到“sin”和“cos”等,但这没关系。以下是令牌模式的替代列表:

    var tokens = [
      "[A-Za-z_][A-Za-z_\d]*",
      "\\(",
      "\\)",
      "\\+",
      "-",
      "\\*",
      "/",
      "\\d+(?:\\.\\d*)?"
    ];
    

    现在任何标识符都将是类型 1 令牌。

    【讨论】:

    • 对不起,但是当我在jsfiddle.net/P5vmY 运行它时,它只是不断抛出语法错误。我是不是执行错了?
    • @scrblnrd3 好吧,我没有包含任何匹配“x”的内容。它必须是一组稍微不同的令牌模式;而不是显式查找“sin”等,您只需查找标识符,并在解析器中单独限定它们。
    • 谢谢。我对此稍作修改,在其中添加了一个属性,该属性指定某事物是否为数字、变量、运算符、括号等
    • @scrblnrd3 是的,好主意!始终让知道内容的代码将内容传达给其他代码!
    【解决方案2】:

    我不知道这是否会完成您想要实现的所有目标,但它对我有用:

    'sin(4+3)*2'.match(/\d+\.?\d*|[a-zA-Z]+|\S/g);
    
    // ["sin", "(", "4", "+", "3", ")", "*", "2"]
    

    您可以将[a-zA-Z]+ 部分替换为sin|cos|tan|etc 以仅支持数学函数。

    【讨论】:

      【解决方案3】:

      只提供一些可能性:

      [a-zA-Z]+|\d+(?:\.\d+)?|.
      

      【讨论】:

        猜你喜欢
        • 2011-10-02
        • 1970-01-01
        • 2019-05-11
        • 2023-03-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-02-03
        相关资源
        最近更新 更多