为了尝试在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问题的解决方案的细节,我更倾向于寻找以非递归方式跟踪递归状态的一般方法。

===============>>#1 票数:1

通常,您所做的是在代码中编写堆栈,并将“本地”变量放在“堆栈框架”上下文对象中,并保留在该堆栈中。 然后,在您将进行“递归调用”的地方,存储当前堆栈帧并为新的当前上下文创建一个新堆栈帧。 做“返回”只是扭转操作的问题。 它并不是特别复杂,但它确实使代码有点混乱。 唯一需要注意的是,当你完成解析表达式时,你会到达堆栈的底部(这样尾随令牌和丢失的令牌不会导致问题)。

这非常类似于机器代码中维护的堆栈所发生的情况,除了您不限于原始值并且因此可以使事情变得更加整洁(在数据结构级别)。

如果你有时间,考虑写(或使用别人的)LR(1)解析器。 那些维护很少的系统堆栈并且在语法中处理许多恶意案例比你的家庭卷LL(k)语法更好。 然而,他们中相当大 ,他们比你现在的本事如何工作更加神秘。

===============>>#2 票数:1

我更倾向于寻找以非递归方式跟踪递归状态的一般方法。

在堆栈(数组)中使用推送和弹出。
如果你有goto的话会更容易。
VBA中的(阶乘)方法(因为goto而更清晰)。

Option Explicit
Sub Main()
  MsgBox fac(1)
  MsgBox fac(5)
End Sub
Function fac(n&)
  Dim answer&, level&, stackn&(99)
  level = 0
zentry:
  If n = 1 Then answer = 1: GoTo zreturn
  level = level + 1 ' push n
  stackn(level) = n
  n = n - 1 ' call fac(n-1)
  GoTo zentry
zreturn:
  If level = 0 Then fac = answer: Exit Function
  n = stackn(level) ' pop n
  level = level - 1
  answer = n * answer ' factorial
  GoTo zreturn
End Function

javascript中的相同方法。

console.log(fac(1));
console.log(fac(5));
function fac(n) { // non-recursive
  var answer, level; 
  var stackn = []; 
  level = 0;
  while (true) { // no goto's
    if (n == 1) { answer = 1; break; }
    level = level + 1; // push n
    stackn[level] = n;
    n = n - 1; } // call fac(n-1) 
  while (true) { // no goto's
    if (level == 0) { return answer; }
    n = stackn[level]; // pop n
    level = level - 1;
    answer = n * answer; } // factorial
  }

  ask by Lance Pollard translate from so

未解决问题?本站智能推荐: