我尝试为一个给定的字符串生成一个语法树,使用简单的数学运算符(+、-、*、/和圆括号),给定字符串"1 + 2 * 3":
它应返回如下数组:
["+",
[1,
["*",
[2,3]
]
]
]
我在[1,"+",2,"*",3]中创建了一个函数来转换"1 + 2 * 3"。
问题是:我不想优先考虑某些行动。
我的代码是:
function isNumber(ch){
switch (ch) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '.':
return true;
break;
default:
return false;
break;
}
}
function generateSyntaxTree(text){
if (typeof text != 'string') return [];
var code = text.replace(new RegExp("[ \t\r\n\v\f]", "gm"), "");
var codeArray = [];
var syntaxTree = [];
// Put it in its on scope
(function(){
var lastPos = 0;
var wasNum = false;
for (var i = 0; i < code.length; i++) {
var cChar = code[i];
if (isNumber(cChar)) {
if (!wasNum) {
if (i != 0) {
codeArray.push(code.slice(lastPos, i));
}
lastPos = i;
wasNum = true;
}
} else {
if (wasNum) {
var n = Number(code.slice(lastPos, i));
if (isNaN(n)) {
throw new Error("Invalid Number");
return [];
} else {
codeArray.push(n);
}
wasNum = false;
lastPos = i;
}
}
}
if (wasNum) {
var n = Number(code.slice(lastPos, code.length));
if (isNaN(n)) {
throw new Error("Invalid Number");
return [];
} else {
codeArray.push(n);
}
}
})();
// At this moment, codeArray = [1,"+",2,"*",3]
return syntaxTree;
}
alert('Returned: ' + generateSyntaxTree("1 + 2 * 3"));
5条答案
按热度按时间zhte4eai1#
如果不使用FLEX/野牛或任何其他类似的包,那么执行自顶向下解析器的方法是首先编写一个可以解析输入并提供令牌的令牌化器。
基本上,您需要一个提供getNextToken、peekNextToken和skipNextToken的令牌化器。
然后你用这个结构一路向下。
正如您所看到的,您从请求最低权限的操作开始,它需要下一个更高权限的操作作为其左项和右项,以此类推。一元运算符的结构略有不同。最巧妙的是在遇到括号时在末尾递归。
这是一个使用解析器并呈现解析树的演示页面(它的代码位于......)
sbtkgmzw2#
要做的事情是使用像flex或ANTLR这样的解析器生成器(在谷歌上搜索会找到适合你语言的)。
但是如果你是为了好玩或者学习解析器是如何工作的,可以在维基百科上查找递归下降解析器。
一个简单的递归下降解析器可以很容易地为这样的简单表达式做。你可以定义语法为:
注意,通过使
<term>
的规则包含<factor>
的规则,该语法确保了所有乘除操作在解析树中比任何加/减操作都更低的位置出现,这确保了这些操作首先被求值。neekobn83#
你读过解析器背后的理论吗?维基百科(一如既往)有一些好文章可以读:
1aaf6o9v4#
与其他答案中的方法类似,这里是另一个递归实现。它具有以下显著特征:
-1
(没有中间空格)可以解释为文字,而不必解释为运算符。- -1
或--1
等。支持的语法可以描述为:
优先级是为了尽可能匹配作为
<literal>
一部分的减号。交互式片段
x一个一个一个一个x一个一个二个一个x一个一个三个一个
解释
tokens
是一个基于正则表达式的输入迭代器。该正则表达式有一个look-behindAssert,以确保减号(如果存在)不是 * binary * 运算符,并且可以包括在数字文本的匹配中。该正则表达式定义命名组,以便代码可以依赖于名称,而不必引用文本字符。get
使用此迭代器获取共享变量中的下一个标记(match
)并返回前一个。get
采用一个参数来指定哪个命名组需要匹配。如果确实如此,则读取下一个标记,否则get
检查匹配是否是强制的。如果是,则抛出异常,否则函数返回undefined,因此呼叫者可以尝试另一个语法规则。term
、factor
、expr
实现了对应名称的语法规则,它们依赖于get
(带参数)来决定语法规则的走向,这些函数都返回树(根节点)。node
在输出树中自下而上构造一个节点。如果树中的节点应该是不同于数组的东西,或者应该执行一些缩减(合并节点),那么这就是要更改的函数。jjjwad0x5#
我曾经做过一个有趣的小计算器,也遇到过和你一样的问题,我通过构建语法树来解决这个问题,首先,不考虑顺序优先级,每个节点都有一个优先级值,当对非常量求值时,我会检查左边的节点:如果它的优先级较低,我会顺时针旋转树:把它放到求值中,先求值,同样的,对正确的节点,然后我再试着求值,它看起来对我来说足够好了.