请帮助我理解 craftinginterpreters.com 中的解析树示例

Please, help me to understand parsing trees example from craftinginterpreters.com

我正在尝试从头开始构建 C 编译器。谷歌搜索将我带到 https://craftinginterpreters.com/ site. There are a lot of explanations how source code parsers work in detail. I've reached "Parsing expressions" 章节并卡在了这一章。
他们提供了创建解析树的算法(递归下降)

Expr expression() {
    Expr left = equality();
    ...
}

Expr equality() {
    Expr left = comparison();
    ...
}

Expr comparison() {
    Expr left = term();
    ...
}

Expr term() {
    Expr left = factor();
    ...
}

Expr factor() {
    Expr left = unary();
    ...
}

Expr unary() {
    if (WeFoundToken('-')) {
        return Expr.Unary(prev(), unary());
    }
    return primary; // if there's no '-'
}

但是当我们解析这个简单的表达式时会发生什么:1 * 2 - 3
我无法理解这将如何工作,因为顶级 expression() 在被调用时会通过所有低优先级运算符解析函数到最低优先级 unary() 函数,该函数将遍历标记,找到 (-) 和 return 它作为一元 (-) 操作 (-3) 表达式
在那种情况下,将无法构建像这样的有效树:

   -
 *   3
1 2

如果没有根节点那将是无效的:

 *  
1 2  (-3)

让我们逐步完成示例表达式 1 * 2 - 3。解析器从头开始。

  • 第一个标记匹配 primary 中的 1。消费 1.
  • 控制returns为factorexpr设置为1。然后 while 条件匹配 *。消耗*
    • while循环中,它尝试接下来消费一个unary,这成功匹配了primary 2。消费 2.
    • 表达式设置为 1 * 2。不再有 [*/] 消耗,循环终止。 Return 这个表达式为 term.
  • term进入while循环,看到-。已消耗 -(意味着下一个令牌是 3,而不是 -
    • 尝试使用 factor,成功匹配 primary 中的 3。消费 3.
    • 表达式设置为 1 * 2 - 3

这导致树:

    -
  *   3
 1 2

也就是说,因为term已经消耗了1 * 2作为一个factor,所以term会进入while循环,不会调用factor 再次。这成功地将 - 识别为 term 中的运算符,而不是 unary 表达式的一部分。

unary() function which will iterate through tokens

不,不是。它只查看下一个输入标记,发现它不是 -。所以它 returns primary().

您在问题中链接的页面的实际代码是:

  private Expr unary() {
    if (match(BANG, MINUS)) {
      Token operator = previous();
      Expr right = unary();
      return new Expr.Unary(operator, right);
    }

    return primary();
  }

该函数调用 match,而不是 WeFoundToken。 (我不确定那是从哪里来的)。 match 在页面前面定义:

  private boolean match(TokenType... types) {
    for (TokenType type : types) {
      if (check(type)) {
        advance();
        return true;
      }
    }

    return false;
  }

该函数中的循环 (for (TokenType type : types)) 遍历调用的参数,依次将每个参数与下一个输入标记进行比较。它从不查看任何其他输入令牌。