【发布时间】:2014-04-20 00:43:26
【问题描述】:
为了尝试在 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 时,它从根表达式开始(与它同名,因此是“数学”)。然后它遍历每个匹配器,然后是每个标记,如果标记是表达式,则递归。基本上这个(解析器将跟踪它匹配模式的字符串的偏移量/位置;这只是伪代码):
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 循环,但在 for 循环中它大部分时间调用一个函数,所以它结束了几乎一样)。
问题是,你如何“把它弄平”?你不做递归,而是如何做到这一点,所以它基本上是这样的:
while (token = stack.pop()) {
val = token.parse(val);
if (val) result.push(val);
}
我不是在寻找如何为这个特定的 PEG 问题实施解决方案的细节,我只是在寻找以非递归方式跟踪递归状态的一般方式。
【问题讨论】:
-
在某种传递对象(如
new Context(grammar))中,您似乎必须分别跟踪每种对象的深度。所以它将具有属性expressionStack、matcherStack和tokenStack,并跟踪每个属性的索引。但这似乎很复杂,所以不确定是不是这样...... -
最通用的解决方案是从 Javascript 到 Javascript 的源到源编译器,它将输入程序转换为带有蹦床的连续传递样式。我不知道是否有人写过这样的野兽。
-
这看起来也很复杂 :)。这是我到目前为止所拥有的,但是(a)它开始变得更加复杂到难以推理的程度,并且(b)它使得处理边缘情况变得更加困难。 github.com/parsejs/grammar/blob/master/lib/context.js
-
对比带有递归的旧版本:github.com/parsejs/grammar/blob/…
-
@LancePollard 越展平它就越复杂。递归可以使用 Stack\Queue + Loop 来完成,构建一个语言和一个解析器,构建一个复杂的有限状态机,构建一个更复杂的复杂的图灵机
标签: javascript algorithm parsing recursion state-machine