1

我试图在 Treesitter 语法中使运算符优先级正确。Treesitter 是一个 LR1 解析器生成器。

我有一个简单的算术语法,部分看起来像这样:

multiply_expression: $ => prec.left(2, seq(
    $._expression,
    '*',
    $._expression,
)),

addition_expression: $ => prec.left(1, seq(
    $._expression,
    '+',
    $._expression,
)),

这可以正常工作。multiply_expression确实比addition_expression.

但是,当我添加中间规则时,优先级会发生变化:

_partial_multi: $ => seq(
    $._expression,
    '*',
),

multiply_expression: $ => prec.left(2, seq(
    $._partial_multi,
    $._expression,
)),

我转向$.expression, '*'了它自己的规则。对我来说,这似乎是一个等效的语法,我预计不会有任何变化。但是,随着这种变化,优先级不再正确。addition_expression,保持不变,似乎比 具有更高的优先级multiply_expression

为什么引入额外的步骤会改变优先级?是否有此问题的名称,或者我在哪里可以找到有关它的更多信息?在编写语法或修复优先级问题时,是否有要遵循的规则或思考方式?

4

1 回答 1

1

为了重现性,这是您的完整语法:

module.exports = grammar({
  name: 'github_example',

  conflicts: $ => [],

  rules: {
    source_file: $ => $._expression,

    _expression: $ => choice(
      $.number,
      $.multiply_expression,
      $.addition_expression
    ),

    number: $ => /\d+/,

    _partial_multi: $ => seq(
        $._expression,
        '*',
    ),

    multiply_expression: $ => prec.left(2, seq(
        $._partial_multi,
        $._expression,
    )),

    addition_expression: $ => prec.left(1, seq(
        $._expression,
        '+',
        $._expression,
    )),
  }
});

您可以通过向规则添加优先级并从_partial_multi规则中删除左关联优先级来解决此问题multiply_expression

   _partial_multi: $ => prec(2, seq(
        $._expression,
        '*',
    )),

    multiply_expression: $ => seq(
        $._partial_multi,
        $._expression,
    ),

您在这里所做的是使乘法成为优先级 2 的右关联运算符。这就是您在不将其公开为原语的语法中定义左关联性或右关联性的方式。您可以通过如下编写乘法左结合:

    _partial_multi: $ => prec(2, seq(
        '*',
        $._expression,
    )),

    multiply_expression: $ => seq(
        $._expression,
        $._partial_multi,
    ),

您实际上偶然发现了一些非常有趣的东西,那就是您不需要显式的语言结构来定义语法中的优先级和关联性!它们只是“语法糖”,使语法更易于阅读和编写。有关如何通过分解规则指定优先级和关联性的更多信息,请参阅此页面。您可以看到,纯粹通过语法结构来指定优先级和关联性是令人困惑的,并且除非您仔细考虑,否则几乎会从您的期望中向后读取!正如您还发现的那样,混合这两种方法(通过语言结构语法结构指定优先级和关联性)可能会导致令人困惑的行为。最好坚持其中一个。

于 2021-04-20T12:12:06.237 回答