Search code examples
operator-precedencetreesitter

Treesitter precedence not taking effect


I'm learning how to use tree sitter and making a grammar for parsing simple predicate logic. I'll clearly need precedence to make negation, ~, bind more tightly than conjunction and dis-junction, (/\ and \/). I think I've applied precedence correctly, practically copying the example in the documentation.

Here is the grammar:

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

    rules: {
      // TODO: add the actual grammar rules
      source_file: $ => repeat($._expression),
      _expression: $ => choice($.identifier,
                               $._unary_expression,
                               $._binary_expression,
                               seq('(', $._expression, ')')),
      _binary_expression: $ => choice(
        $.and,
        $.or
      ),
      _unary_expression: $ => prec(2, choice(
        $.not
      )),
      not: $ => seq('~', $._expression),
      and: $ => prec.left(seq($._expression, "/\\", $._expression)),
      or: $ => prec.left(seq($._expression, "\\/", $._expression)),
      identifier: $ => /[a-z_]+/

    }
  });

However, when I run tree-sitter generate I get this error:

╰─➤ tree-sitter generate
Unresolved conflict for symbol sequence:

  '~'  _expression  •  '/\'  …

Possible interpretations:

  1:  '~'  (and  _expression  •  '/\'  _expression)  (precedence: 0, associativity: Left)
  2:  (not  '~'  _expression)  •  '/\'  …

Possible resolutions:

  1:  Specify a higher precedence in `and` than in the other rules.
  2:  Specify a higher precedence in `not` than in the other rules.
  3:  Specify a left or right associativity in `not`
  4:  Add a conflict for these rules: `not`, `and`

I believe what I've done with the prec(2 choice($.not)) is option 2 but it doesn't seem to be in effect in the grammar. I'm using tree-sitter 0.20.7 installed via cargo.


Solution

  • Okay, after experimenting with some more permutations I found that moving the precedence directive into the not definition fixes this issue.

          _unary_expression: $ => choice(
            $.not
          ),
          not: $ => prec(2, seq('~', $._expression)),
    

    Removing the precedence from _unary_expression and moving it to inside not.