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

时间:2013-08-27 07:45:47

标签: javascript regex

我一直在研究一个解析公式的函数已经有一段时间了,但是还没能使它正常工作。它似乎并不总是有效 - 它会过滤文本的某些部分,但不是全部。

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;
    }
}

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

(我现在正在使用eval,但它仅用于开发)

任何帮助表示感谢。

修改

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

编辑2:

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

目前无法让JSFiddle正常工作。

2 个答案:

答案 0 :(得分:6)

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

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)

答案 1 :(得分:0)

您无法真正使用正则表达式来匹配公式。公式是context-free language,正则表达式仅限于regular languages,后者是前者的子集。有许多算法可用于识别无上下文的语言,例如CYKLL parsers。如果您已经没有,那么我不推荐研究那些,因为主题非常大。

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

相关问题