mirror of
https://github.com/tree-sitter/tree-sitter.git
synced 2026-09-10 07:36:22 -04:00
fix(generate): honor right associativity despite a lower-precedence shift
A SHIFT/REDUCE conflict can bundle several shift interpretations with different precedences against a single reduce. `handle_conflict` weighed them with only `shift_is_less` and `shift_is_more`, so a lone lower-precedence shift set `shift_is_less` and the REDUCE won outright, even when another interpretation tied the REDUCE in precedence and the REDUCE was declared right-associative. That tie's associativity should have won by shifting, so a right-associative rule silently became left-associative as soon as an unrelated lower-precedence rule was added to the grammar. Track the equal-precedence case explicitly, and in the reduce-wins branch shift instead when a tying interpretation exists and the reduce actions are purely right-associative. Co-authored-by: Will Lillis <will.lillis24@gmail.com>
This commit is contained in:
parent
4d0f126996
commit
0900f84eab
|
|
@ -770,6 +770,7 @@ impl<'a> ParseTableBuilder<'a> {
|
|||
|
||||
// If the SHIFT action has higher precedence, remove all the REDUCE actions.
|
||||
let mut shift_is_less = false;
|
||||
let mut shift_is_equal = false;
|
||||
let mut shift_is_more = false;
|
||||
for p in shift_precedence {
|
||||
match Self::compare_precedence(
|
||||
|
|
@ -781,7 +782,7 @@ impl<'a> ParseTableBuilder<'a> {
|
|||
) {
|
||||
Ordering::Greater => shift_is_more = true,
|
||||
Ordering::Less => shift_is_less = true,
|
||||
Ordering::Equal => {}
|
||||
Ordering::Equal => shift_is_equal = true,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -790,8 +791,28 @@ impl<'a> ParseTableBuilder<'a> {
|
|||
}
|
||||
// If the REDUCE actions have higher precedence, remove the SHIFT action.
|
||||
else if shift_is_less && !shift_is_more {
|
||||
entry.actions.pop();
|
||||
conflicting_items.retain(|item| item.is_done());
|
||||
// Exception: if one SHIFT interpretation ties the REDUCE actions in
|
||||
// precedence while another has lower precedence, and the REDUCE
|
||||
// actions are purely right associative, honor that right
|
||||
// associativity by shifting rather than reducing. The
|
||||
// lower-precedence interpretation coexists with the tying one, so on
|
||||
// its own it must not force a REDUCE that would flip the tie to left
|
||||
// associative.
|
||||
if shift_is_equal
|
||||
&& matches!(
|
||||
(
|
||||
reduction_info.has_left_assoc,
|
||||
reduction_info.has_non_assoc,
|
||||
reduction_info.has_right_assoc,
|
||||
),
|
||||
(false, false, true)
|
||||
)
|
||||
{
|
||||
entry.actions.drain(0..entry.actions.len() - 1);
|
||||
} else {
|
||||
entry.actions.pop();
|
||||
conflicting_items.retain(|item| item.is_done());
|
||||
}
|
||||
}
|
||||
// If the SHIFT and REDUCE actions have the same precedence, consider
|
||||
// the REDUCE actions' associativity.
|
||||
|
|
|
|||
37
test/fixtures/test_grammars/associativity_left_with_lower_precedence_shift/corpus.txt
vendored
Normal file
37
test/fixtures/test_grammars/associativity_left_with_lower_precedence_shift/corpus.txt
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
================================================================================
|
||||
Addition is left associative
|
||||
================================================================================
|
||||
|
||||
1 + 1 + 1 + 1
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
(expression
|
||||
(addition
|
||||
(expression
|
||||
(addition
|
||||
(expression
|
||||
(addition
|
||||
(expression
|
||||
(number))
|
||||
(expression
|
||||
(number))))
|
||||
(expression
|
||||
(number))))
|
||||
(expression
|
||||
(number))))
|
||||
|
||||
================================================================================
|
||||
Superaddition still parses
|
||||
================================================================================
|
||||
|
||||
1 + + 1
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
(expression
|
||||
(superaddition
|
||||
(expression
|
||||
(number))
|
||||
(expression
|
||||
(number))))
|
||||
16
test/fixtures/test_grammars/associativity_left_with_lower_precedence_shift/grammar.js
vendored
Normal file
16
test/fixtures/test_grammars/associativity_left_with_lower_precedence_shift/grammar.js
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
export default grammar({
|
||||
name: 'associativity_left_with_lower_precedence_shift',
|
||||
|
||||
rules: {
|
||||
expression: $ => choice(
|
||||
$.addition,
|
||||
$.superaddition,
|
||||
$.number,
|
||||
),
|
||||
|
||||
addition: $ => prec.left(1, seq($.expression, '+', $.expression)),
|
||||
superaddition: $ => prec.right(seq($.expression, '+', '+', $.expression)),
|
||||
|
||||
number: _ => '1',
|
||||
}
|
||||
});
|
||||
22
test/fixtures/test_grammars/associativity_right_with_lower_precedence_shift/corpus.txt
vendored
Normal file
22
test/fixtures/test_grammars/associativity_right_with_lower_precedence_shift/corpus.txt
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
================================================================================
|
||||
Addition is right associative
|
||||
================================================================================
|
||||
|
||||
1 + 1 + 1 + 1
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
(expression
|
||||
(addition
|
||||
(expression
|
||||
(number))
|
||||
(expression
|
||||
(addition
|
||||
(expression
|
||||
(number))
|
||||
(expression
|
||||
(addition
|
||||
(expression
|
||||
(number))
|
||||
(expression
|
||||
(number))))))))
|
||||
15
test/fixtures/test_grammars/associativity_right_with_lower_precedence_shift/grammar.js
vendored
Normal file
15
test/fixtures/test_grammars/associativity_right_with_lower_precedence_shift/grammar.js
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
module.exports = grammar({
|
||||
name: 'associativity_right_with_lower_precedence_shift',
|
||||
|
||||
rules: {
|
||||
expression: $ => choice(
|
||||
$.addition,
|
||||
$.superaddition,
|
||||
$.number
|
||||
),
|
||||
addition: $ => prec.right(1, seq($.expression, "+", $.expression)),
|
||||
superaddition: $ => prec.right(seq($.expression, "+", "+", $.expression)),
|
||||
number: _ => "1"
|
||||
}
|
||||
});
|
||||
|
||||
Loading…
Reference in a new issue