4

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

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 对象,由网格中的一个字段传递,它包含以下变量:

我目前无法让 JSFiddle 正常工作。

4

2 回答 2

6

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

我在 jsFiddle 上放了一个演示,它可以解析你给定的公式

在演示中,我使用此类和令牌从公式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)
于 2013-08-27T17:43:22.063 回答
1

您不能真正使用正则表达式来匹配公式。公式是一种上下文无关的语言,正则表达式仅限于正则语言,后者是前者的子集。有许多算法可以识别上下文无关语言,例如CYKLL 解析器。如果您还没有,我不建议您学习这些,因为该主题非常大。

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

于 2013-08-27T09:40:23.867 回答