【问题标题】:Adding rule to Treesitter LR1 grammar changes precedence向 Treesitter LR1 语法添加规则更改优先级
【发布时间】:2021-07-12 05:46:24
【问题描述】:

我正在尝试使 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

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

【问题讨论】:

  • 我不知道treesitter如何重新定位和解决冲突的细节,但在我看来你还没有告诉解析器生成器如何解决涉及_partial_multi的冲突。您应该为该产生式分配优先级。
  • 另外,像往常一样,如果您在问题中包含minimal reproducible example,会更容易为您提供帮助。提取物不容易测试。
  • 说了这么多,我模糊地尝试填写一个测试文件,并应用了我在第一条评论中提出的建议。它似乎工作。正如您所报告的,原始测试(基于您的摘录)只是解析不正确;不会发出歧义警告。 (对于使用不同配置优先级的方式的 bison,会为您的语法发出冲突报告。)

标签: parsing lr1 treesitter


【解决方案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,
    ),

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-06
    • 1970-01-01
    • 1970-01-01
    • 2021-03-29
    相关资源
    最近更新 更多