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:解析公式的正则表达式的主要内容,如果未能解决你的问题,请参考以下文章