为了尝试在JavaScript中实现PEG而不会使旧版浏览器从堆栈溢出中崩溃,我想制作一个解析表达式语法,以非递归方式解析字符串。 你怎么做到这一点? 感到心灵弯曲。
假设你有这样的结构:
-
grammar
有很多表达方式 -
expression
有很多matchers
-
matcher
有许多tokens
(或者其他任何更好的词) -
token
可以指向另一个expression
,也可以是原始字符串或正则表达式。 因此,如果它指向另一个表达式,则这是递归开始的位置。
所以说你定义这样的层次结构:
var grammar = new Grammar('math');
var expression = grammar.expression;
expression('math')
.match(':number', ':operator', ':number', function(left, operator, right){
switch (operator) {
case '+': return left + right;
case '-': return left - right;
case '*': return left * right;
case '/': return left / right;
}
});
expression('number')
.match(/\d+/, parseInt);
expression('operator')
.match('+')
.match('-')
.match('*')
.match('/');
var val = grammar.parse('6*8'); // 42
当你调用grammar.parse
,它从根表达式开始(它与它的名字相同,所以“math”)。 然后它遍历每个匹配器,然后遍历每个标记,如果标记是表达式,则递归。 基本上这个(解析器将跟踪它匹配模式的字符串的偏移/位置;这只是伪代码):
function parse(str, expression, position) {
var result = [];
expression.matchers.forEach(function(matcher){
matcher.tokens.forEach(function(token){
var val;
if (token.expression) {
val = parse(str, token.expression, position);
} else {
val = token.parse(str, position);
}
if (val) result.push(val);
});
});
return result;
}
parse('6*8', grammar.root, 0);
因此,对于像6*8
这样的简单表达式,递归非常少,但您可以快速找到具有多层嵌套的复杂表达式。 再加上嵌套for循环的嵌套,并且堆栈变大(我实际上并没有使用forEach
,我用于循环,但在for循环中它大部分时间调用一个函数,所以它最终很漂亮几乎相同)。
问题是, 你如何“扁平化”? 而不是做递归,你如何做到这一点,所以它基本上是这样的:
while (token = stack.pop()) {
val = token.parse(val);
if (val) result.push(val);
}
我不是在寻找如何实现这个特定PEG问题的解决方案的细节,我更倾向于寻找以非递归方式跟踪递归状态的一般方法。