Javascript:解析公式的正则表达式

Posted

技术标签:

【中文标题】Javascript:解析公式的正则表达式【英文标题】:Javascript: Regular Expression to parse a formula 【发布时间】:2013-08-27 07:45:47 【问题描述】:

一段时间以来,我一直在研究解析公式的函数,但无法使其正常工作。它似乎并不总是有效 - 它过滤了文本的某些部分,但不是全部。

parseFormula(e) 
    var formula = e.value, value = 0.00, tValue = 0.00, tFormula = '', dObj = ;
    if(formula !== undefined && formula !== "") 
        dObj._formulaIn = formula;
        var f = formula.split(/\s/g);    

        for(var i = 0; i < f.length; i++) 
            tFormula = f[i];
            // Replacing PI
            tFormula = tFormula.replace(/(pi)/gi,Math.PI);
            dObj._1_pi_done = tFormula;

            // Replacing Squareroot with placeholder
            tFormula = tFormula.replace(/(sqrt)/gi,"__sqrt__");
            tFormula = tFormula.replace(/(sqr)/gi,"__sqrt__");
            tFormula = tFormula.replace(/(kvrt)/gi,"__sqrt__");
            tFormula = tFormula.replace(/(kvr)/gi,"__sqrt__");
            dObj._2_sqrt_done = tFormula;

            // Removing units that may cause trouble
            tFormula = tFormula.replace(/(m2||m3||t2||t3||c2||c3)/gi,"");
            dObj._3_units_done = tFormula;

            // Removing text
            tFormula = tFormula.replace(/\D+[^\*\/\+\-]+[^\,\.]/gi,"");
            dObj._4_text_done = tFormula;

            // Removing language specific decimals
            if(Language.defaultLang === "no_NB") 
                tFormula = tFormula.replace(/(\.)/gi,"");
                tFormula = tFormula.replace(/(\,)/gi,".");
             else 
                tFormula = tFormula.replace(/(\,)/gi,"");           
            
            dObj._5_lang_done = tFormula;

            // Re-applying Squareroot
            tFormula = tFormula.replace(/(__sqrt__)/g,"Math.sqrt");
            dObj._6_sqrt_done = tFormula;

            if(tFormula === "") 
                f.splice(i,1);
             else 
                f[i] = tFormula;
            
            dObj._7_splice_done = tFormula;
            console.log(dObj);
        

        formula = "";
        for(var j = 0; j < f.length; j++) 
            formula += f[j];   
        

        try 
            value = eval(formula);
         
        catch(err) 
        return value === 0 ? 0 : value.toFixed(4);
     else 
        return 0;
    

我不确定此函数中使用的任何正则表达式,因此我寻求帮助。例如,我不确定/(pi)/ 是否是获取确切文本“pi”并将其替换为 3.141 的正确方法。

(我现在用的是eval,不过只是用于开发)

任何帮助表示赞赏。

编辑:

我试图解析的公式是用户输入公式。他/她会在哪里输入类似:2/0.6 pcs of foo * pi bar + sqrt(4) foobar。我希望它去除所有非数学字母并计算其余部分。这意味着上面的公式将被解释为(2/0.6) * 3.141 + Math.sqrt(4) => 12.47

编辑 2:

e是一个ExtJS对象,由网格中的一个字段传递,它包含以下变量:

colIdx (int) column (Ext.grid.column.Column) field(字符串) grid (Ext.grid.Panel) originalValue(字符串) record (Ext.data.Model) row(CSS 选择器) rowIdx (int) store (Ext.data.Store) value(字符串) view (Ext.grid.View)

目前无法让 JSFiddle 正常工作。

【问题讨论】:

问题是什么? 这和 Ext JS 有什么关系? 我想知道我在 RegEx 中做错了什么 - 如果有任何明显的错误。 这是我在 ExtJS 4.2.x 中使用的函数 - 用于评估网格中的字段。 一个建议:在您的问题中添加一个您想要解析的示例formula。没有它,很难验证您尝试使用的正则表达式。这可能会帮助每个人更好地了解您想要完成的工作,并为您提供更多帮助。 【参考方案1】:

对要解析的表达式进行标记可能更容易。标记化后,更容易阅读该标记流并构建自己的表达式。

I've put up a demo on jsFiddle which can parse your given formula

在演示中,我使用这个 Tokenizer 类和令牌从公式中构建了一个 TokenStream

function Tokenizer() 
    this.tokens = ;
    // The regular expression which matches a token per group.
    this.regex = null;
    // Holds the names of the tokens. Index matches group. See buildExpression()
    this.tokenNames = [];


Tokenizer.prototype = 
    addToken: function(name, expression) 
        this.tokens[name] = expression;
    ,

    tokenize: function(data) 
        this.buildExpression(data);
        var tokens = this.findTokens(data);
        return new TokenStream(tokens);
    ,

    buildExpression: function (data) 
        var tokenRegex = [];
        for (var tokenName in this.tokens) 
            this.tokenNames.push(tokenName);
            tokenRegex.push('('+this.tokens[tokenName]+')');
        

        this.regex = new RegExp(tokenRegex.join('|'), 'g');
    ,

    findTokens: function(data) 
        var tokens = [];
        var match;

        while ((match = this.regex.exec(data)) !== null) 
            if (match == undefined) 
                continue;
            

            for (var group = 1; group < match.length; group++) 
                if (!match[group]) continue;

                tokens.push(
                    name: this.tokenNames[group - 1],
                    data: match[group]
                );
            
        

        return tokens;
    



TokenStream = function (tokens) 
    this.cursor = 0;
    this.tokens = tokens;

TokenStream.prototype = 
    next: function () 
        return this.tokens[this.cursor++];
    ,
    peek: function (direction) 
        if (direction === undefined) 
            direction = 0;
        

        return this.tokens[this.cursor + direction];
    

定义的令牌

tokenizer.addToken('whitespace', '\\s+');
tokenizer.addToken('l_paren', '\\(');
tokenizer.addToken('r_paren', '\\)');
tokenizer.addToken('float', '[0-9]+\\.[0-9]+');
tokenizer.addToken('int', '[0-9]+');
tokenizer.addToken('div', '\\/');
tokenizer.addToken('mul', '\\*');
tokenizer.addToken('add', '\\+');
tokenizer.addToken('constant', 'pi|PI');
tokenizer.addToken('id', '[a-zA-Z_][a-zA-Z0-9_]*');

通过定义上述标记,标记器可以识别公式中的所有内容。当公式

2/0.6 pcs of foo * pi bar + sqrt(4) foobar

被标记化,结果将是一个类似于

的标记流
int(2), div(/), float(0.6), whitespace( ), id(pcs), whitespace( ), id(of), whitespace( ), id(foo), whitespace( ), mul(*), whitespace( ), constant(pi), whitespace( ), id(bar), whitespace( ), add(+), whitespace( ), id(sqrt), l_paren((), int(4), r_paren()), whitespace( ), id(foobar)

【讨论】:

你应该把它放到图书馆里 @AriPorad 谢谢你的建议。这是我正在从事的个人项目的一部分,所以也许将来会:-) 这太棒了,在高级搜索字段上实现了它,而不是一个无法正常工作的巨大正则表达式,这很有吸引力!【参考方案2】:

您不能真正使用正则表达式来匹配公式。公式是context-free language,正则表达式仅限于regular languages,后者是前者的子集。有许多算法可以识别上下文无关语言,例如CYK 和LL parsers。如果你还没有学习过,我不建议你学习这些,因为这个主题相当大。

您可以快速、高效且轻松地尝试使用Reverse Polish Notation (RPN) 计算公式(使用Shunting Yard algorithm 将公式转换为RPN)。如果尝试失败(由于括号不匹配,无效的函数/常量,w/e),显然文本不是公式,否则一切都很好。调车场不是一个特别困难的算法,你应该没有问题实现它。即使你这样做了,我上面链接的***页面也有伪代码,并且 SO 中还有很多问题可以帮助你。

【讨论】:

感谢您的回答,但我不是尝试使用 RegEx 来识别上下文无关语言,而是使用预定义的调用来解析公式。我最初的想法是用 ASCII 码替换对 SQRT 的调用,在去掉未使用的字符后,我将用 Math.sqrt 替换它。我也打算将相同的函数用于其他类型的函数,例如 IF、LOG、COS、SIN、ARCSIN 等。 这就是为什么我建议使用调车场。你可以使用任何你想要的函数和常量。此外,它是一种广为人知的算法,因此与您当前基于替换不同部分中的某些魔术字符串的尝试相比,实现它的错误率要低得多(并且可维护,成像必须在一年后掌握您的代码)功能。

以上是关于Javascript:解析公式的正则表达式的主要内容,如果未能解决你的问题,请参考以下文章

计算器——python正则表达式

JavaScript 正则表达式Ⅰ

使用正则表达式删除 JavaScript

JavaScript正则表达式的学习

用于解析路径字符串的 Javascript 正则表达式

用于解析单个键的正则表达式:Javascript 中 JSON 中的值