Skip to content

Commit e49a56e

Browse files
authored
Merge pull request tree-sitter#939 from tree-sitter/partial-order-precedence
Allow precedences to be specified using strings and a partial ordering relation
2 parents 225e15c + d8a235f commit e49a56e

File tree

21 files changed

+731
-246
lines changed

21 files changed

+731
-246
lines changed

cli/src/generate/build_tables/build_parse_table.rs

Lines changed: 111 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,15 @@ use crate::generate::grammars::{
55
InlinedProductionMap, LexicalGrammar, SyntaxGrammar, VariableType,
66
};
77
use crate::generate::node_types::VariableInfo;
8-
use crate::generate::rules::{Associativity, Symbol, SymbolType, TokenSet};
8+
use crate::generate::rules::{Associativity, Precedence, Symbol, SymbolType, TokenSet};
99
use crate::generate::tables::{
1010
FieldLocation, GotoAction, ParseAction, ParseState, ParseStateId, ParseTable, ParseTableEntry,
1111
ProductionInfo, ProductionInfoId,
1212
};
13-
use core::ops::Range;
14-
use std::collections::hash_map::Entry;
1513
use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
1614
use std::fmt::Write;
1715
use std::u32;
16+
use std::{cmp::Ordering, collections::hash_map::Entry};
1817

1918
// For conflict reporting, each parse state is associated with an example
2019
// sequence of symbols that could lead to that parse state.
@@ -29,6 +28,14 @@ struct AuxiliarySymbolInfo {
2928
parent_symbols: Vec<Symbol>,
3029
}
3130

31+
#[derive(Debug, Default)]
32+
struct ReductionInfo {
33+
precedence: Precedence,
34+
has_left_assoc: bool,
35+
has_right_assoc: bool,
36+
has_non_assoc: bool,
37+
}
38+
3239
struct ParseStateQueueEntry {
3340
state_id: ParseStateId,
3441
preceding_auxiliary_symbols: AuxiliarySymbolSequence,
@@ -118,8 +125,6 @@ impl<'a> ParseTableBuilder<'a> {
118125
)?;
119126
}
120127

121-
self.remove_precedences();
122-
123128
Ok((self.parse_table, self.parse_state_info_by_id))
124129
}
125130

@@ -179,6 +184,7 @@ impl<'a> ParseTableBuilder<'a> {
179184
let mut terminal_successors = BTreeMap::new();
180185
let mut non_terminal_successors = BTreeMap::new();
181186
let mut lookaheads_with_conflicts = TokenSet::new();
187+
let mut reduction_infos = HashMap::<Symbol, ReductionInfo>::new();
182188

183189
// Each item in the item set contributes to either or a Shift action or a Reduce
184190
// action in this state.
@@ -217,31 +223,50 @@ impl<'a> ParseTableBuilder<'a> {
217223
ParseAction::Reduce {
218224
symbol: Symbol::non_terminal(item.variable_index as usize),
219225
child_count: item.step_index as usize,
220-
precedence: item.precedence(),
221-
associativity: item.associativity(),
222226
dynamic_precedence: item.production.dynamic_precedence,
223227
production_id: self.get_production_id(item),
224228
}
225229
};
226230

231+
let precedence = item.precedence();
232+
let associativity = item.associativity();
227233
for lookahead in lookaheads.iter() {
228-
let entry = self.parse_table.states[state_id]
234+
let table_entry = self.parse_table.states[state_id]
229235
.terminal_entries
230-
.entry(lookahead);
231-
let entry = entry.or_insert_with(|| ParseTableEntry::new());
236+
.entry(lookahead)
237+
.or_insert_with(|| ParseTableEntry::new());
238+
let reduction_info = reduction_infos.entry(lookahead).or_default();
232239

233240
// While inserting Reduce actions, eagerly resolve conflicts related
234241
// to precedence: avoid inserting lower-precedence reductions, and
235242
// clear the action list when inserting higher-precedence reductions.
236-
if entry.actions.is_empty() {
237-
entry.actions.push(action);
238-
} else if action.precedence() > entry.actions[0].precedence() {
239-
entry.actions.clear();
240-
entry.actions.push(action);
241-
lookaheads_with_conflicts.remove(&lookahead);
242-
} else if action.precedence() == entry.actions[0].precedence() {
243-
entry.actions.push(action);
244-
lookaheads_with_conflicts.insert(lookahead);
243+
if table_entry.actions.is_empty() {
244+
table_entry.actions.push(action);
245+
} else {
246+
match Self::compare_precedence(
247+
&self.syntax_grammar,
248+
precedence,
249+
&reduction_info.precedence,
250+
) {
251+
Ordering::Greater => {
252+
table_entry.actions.clear();
253+
table_entry.actions.push(action);
254+
lookaheads_with_conflicts.remove(&lookahead);
255+
*reduction_info = ReductionInfo::default();
256+
}
257+
Ordering::Equal => {
258+
table_entry.actions.push(action);
259+
lookaheads_with_conflicts.insert(lookahead);
260+
}
261+
Ordering::Less => continue,
262+
}
263+
}
264+
265+
reduction_info.precedence = precedence.clone();
266+
match associativity {
267+
Some(Associativity::Left) => reduction_info.has_left_assoc = true,
268+
Some(Associativity::Right) => reduction_info.has_right_assoc = true,
269+
None => reduction_info.has_non_assoc = true,
245270
}
246271
}
247272
}
@@ -302,6 +327,7 @@ impl<'a> ParseTableBuilder<'a> {
302327
&preceding_symbols,
303328
&preceding_auxiliary_symbols,
304329
symbol,
330+
reduction_infos.get(&symbol).unwrap(),
305331
)?;
306332
}
307333

@@ -381,6 +407,7 @@ impl<'a> ParseTableBuilder<'a> {
381407
preceding_symbols: &SymbolSequence,
382408
preceding_auxiliary_symbols: &Vec<AuxiliarySymbolInfo>,
383409
conflicting_lookahead: Symbol,
410+
reduction_info: &ReductionInfo,
384411
) -> Result<()> {
385412
let entry = self.parse_table.states[state_id]
386413
.terminal_entries
@@ -393,9 +420,8 @@ impl<'a> ParseTableBuilder<'a> {
393420
// sorted out ahead of time in `add_actions`. But there can still be
394421
// REDUCE-REDUCE conflicts where all actions have the *same*
395422
// precedence, and there can still be SHIFT/REDUCE conflicts.
396-
let reduce_precedence = entry.actions[0].precedence();
397423
let mut considered_associativity = false;
398-
let mut shift_precedence: Option<Range<i32>> = None;
424+
let mut shift_precedence: Vec<&Precedence> = Vec::new();
399425
let mut conflicting_items = HashSet::new();
400426
for (item, lookaheads) in &item_set.entries {
401427
if let Some(step) = item.step() {
@@ -409,15 +435,9 @@ impl<'a> ParseTableBuilder<'a> {
409435
conflicting_items.insert(item);
410436
}
411437

412-
let precedence = item.precedence();
413-
if let Some(range) = &mut shift_precedence {
414-
if precedence < range.start {
415-
range.start = precedence;
416-
} else if precedence > range.end {
417-
range.end = precedence;
418-
}
419-
} else {
420-
shift_precedence = Some(precedence..precedence);
438+
let p = item.precedence();
439+
if let Err(i) = shift_precedence.binary_search(&p) {
440+
shift_precedence.insert(i, p);
421441
}
422442
}
423443
}
@@ -429,8 +449,6 @@ impl<'a> ParseTableBuilder<'a> {
429449
}
430450

431451
if let ParseAction::Shift { is_repetition, .. } = entry.actions.last_mut().unwrap() {
432-
let shift_precedence = shift_precedence.unwrap_or(0..0);
433-
434452
// If all of the items in the conflict have the same parent symbol,
435453
// and that parent symbols is auxiliary, then this is just the intentional
436454
// ambiguity associated with a repeat rule. Resolve that class of ambiguity
@@ -448,40 +466,37 @@ impl<'a> ParseTableBuilder<'a> {
448466
}
449467

450468
// If the SHIFT action has higher precedence, remove all the REDUCE actions.
451-
if shift_precedence.start > reduce_precedence
452-
|| (shift_precedence.start == reduce_precedence
453-
&& shift_precedence.end > reduce_precedence)
454-
{
469+
let mut shift_is_less = false;
470+
let mut shift_is_more = false;
471+
for p in shift_precedence {
472+
match Self::compare_precedence(&self.syntax_grammar, p, &reduction_info.precedence)
473+
{
474+
Ordering::Greater => shift_is_more = true,
475+
Ordering::Less => shift_is_less = true,
476+
Ordering::Equal => {}
477+
}
478+
}
479+
480+
if shift_is_more && !shift_is_less {
455481
entry.actions.drain(0..entry.actions.len() - 1);
456482
}
457483
// If the REDUCE actions have higher precedence, remove the SHIFT action.
458-
else if shift_precedence.end < reduce_precedence
459-
|| (shift_precedence.end == reduce_precedence
460-
&& shift_precedence.start < reduce_precedence)
461-
{
484+
else if shift_is_less && !shift_is_more {
462485
entry.actions.pop();
463486
conflicting_items.retain(|item| item.is_done());
464487
}
465488
// If the SHIFT and REDUCE actions have the same predence, consider
466489
// the REDUCE actions' associativity.
467-
else if shift_precedence == (reduce_precedence..reduce_precedence) {
490+
else if !shift_is_less && !shift_is_more {
468491
considered_associativity = true;
469-
let mut has_left = false;
470-
let mut has_right = false;
471-
let mut has_non = false;
472-
for action in &entry.actions {
473-
if let ParseAction::Reduce { associativity, .. } = action {
474-
match associativity {
475-
Some(Associativity::Left) => has_left = true,
476-
Some(Associativity::Right) => has_right = true,
477-
None => has_non = true,
478-
}
479-
}
480-
}
481492

482493
// If all Reduce actions are left associative, remove the SHIFT action.
483494
// If all Reduce actions are right associative, remove the REDUCE actions.
484-
match (has_left, has_non, has_right) {
495+
match (
496+
reduction_info.has_left_assoc,
497+
reduction_info.has_non_assoc,
498+
reduction_info.has_right_assoc,
499+
) {
485500
(true, false, false) => {
486501
entry.actions.pop();
487502
conflicting_items.retain(|item| item.is_done());
@@ -595,7 +610,7 @@ impl<'a> ParseTableBuilder<'a> {
595610
"(precedence: {}, associativity: {:?})",
596611
precedence, associativity
597612
))
598-
} else if precedence != 0 {
613+
} else if !precedence.is_none() {
599614
Some(format!("(precedence: {})", precedence))
600615
} else {
601616
None
@@ -714,6 +729,47 @@ impl<'a> ParseTableBuilder<'a> {
714729
Err(Error::new(msg))
715730
}
716731

732+
fn compare_precedence(
733+
grammar: &SyntaxGrammar,
734+
left: &Precedence,
735+
right: &Precedence,
736+
) -> Ordering {
737+
match (left, right) {
738+
// Integer precedences can be compared to other integer precedences,
739+
// and to the default precedence, which is zero.
740+
(Precedence::Integer(l), Precedence::Integer(r)) => l.cmp(r),
741+
(Precedence::Integer(l), Precedence::None) => l.cmp(&0),
742+
(Precedence::None, Precedence::Integer(r)) => 0.cmp(&r),
743+
744+
// Named precedences can be compared to other named precedences.
745+
(Precedence::Name(l), Precedence::Name(r)) => grammar
746+
.precedence_orderings
747+
.iter()
748+
.find_map(|list| {
749+
let mut saw_left = false;
750+
let mut saw_right = false;
751+
for name in list {
752+
if name == l {
753+
saw_left = true;
754+
if saw_right {
755+
return Some(Ordering::Less);
756+
}
757+
} else if name == r {
758+
saw_right = true;
759+
if saw_left {
760+
return Some(Ordering::Greater);
761+
}
762+
}
763+
}
764+
None
765+
})
766+
.unwrap_or(Ordering::Equal),
767+
768+
// Other combinations of precedence types are not comparable.
769+
_ => Ordering::Equal,
770+
}
771+
}
772+
717773
fn get_auxiliary_node_info(
718774
&self,
719775
item_set: &ParseItemSet,
@@ -739,26 +795,6 @@ impl<'a> ParseTableBuilder<'a> {
739795
}
740796
}
741797

742-
fn remove_precedences(&mut self) {
743-
for state in self.parse_table.states.iter_mut() {
744-
for (_, entry) in state.terminal_entries.iter_mut() {
745-
for action in entry.actions.iter_mut() {
746-
match action {
747-
ParseAction::Reduce {
748-
precedence,
749-
associativity,
750-
..
751-
} => {
752-
*precedence = 0;
753-
*associativity = None;
754-
}
755-
_ => {}
756-
}
757-
}
758-
}
759-
}
760-
}
761-
762798
fn get_production_id(&mut self, item: &ParseItem) -> ProductionInfoId {
763799
let mut production_info = ProductionInfo {
764800
alias_sequence: Vec::new(),

cli/src/generate/build_tables/item.rs

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
use crate::generate::grammars::{
2-
LexicalGrammar, Production, ProductionStep, SyntaxGrammar,
3-
};
4-
use crate::generate::rules::Associativity;
5-
use crate::generate::rules::{Symbol, SymbolType, TokenSet};
1+
use crate::generate::grammars::{LexicalGrammar, Production, ProductionStep, SyntaxGrammar};
2+
use crate::generate::rules::{Associativity, Precedence, Symbol, SymbolType, TokenSet};
63
use lazy_static::lazy_static;
74
use std::cmp::Ordering;
85
use std::fmt;
@@ -17,7 +14,7 @@ lazy_static! {
1714
index: 0,
1815
kind: SymbolType::NonTerminal,
1916
},
20-
precedence: 0,
17+
precedence: Precedence::None,
2118
associativity: None,
2219
alias: None,
2320
field_name: None,
@@ -82,8 +79,9 @@ impl<'a> ParseItem<'a> {
8279
self.prev_step().and_then(|step| step.associativity)
8380
}
8481

85-
pub fn precedence(&self) -> i32 {
86-
self.prev_step().map_or(0, |step| step.precedence)
82+
pub fn precedence(&self) -> &Precedence {
83+
self.prev_step()
84+
.map_or(&Precedence::None, |step| &step.precedence)
8785
}
8886

8987
pub fn prev_step(&self) -> Option<&'a ProductionStep> {
@@ -165,12 +163,12 @@ impl<'a> fmt::Display for ParseItemDisplay<'a> {
165163
if i == self.0.step_index as usize {
166164
write!(f, " •")?;
167165
if let Some(associativity) = step.associativity {
168-
if step.precedence != 0 {
166+
if !step.precedence.is_none() {
169167
write!(f, " ({} {:?})", step.precedence, associativity)?;
170168
} else {
171169
write!(f, " ({:?})", associativity)?;
172170
}
173-
} else if step.precedence != 0 {
171+
} else if !step.precedence.is_none() {
174172
write!(f, " ({})", step.precedence)?;
175173
}
176174
}
@@ -197,12 +195,12 @@ impl<'a> fmt::Display for ParseItemDisplay<'a> {
197195
write!(f, " •")?;
198196
if let Some(step) = self.0.production.steps.last() {
199197
if let Some(associativity) = step.associativity {
200-
if step.precedence != 0 {
198+
if !step.precedence.is_none() {
201199
write!(f, " ({} {:?})", step.precedence, associativity)?;
202200
} else {
203201
write!(f, " ({:?})", associativity)?;
204202
}
205-
} else if step.precedence != 0 {
203+
} else if !step.precedence.is_none() {
206204
write!(f, " ({})", step.precedence)?;
207205
}
208206
}
@@ -257,7 +255,7 @@ impl<'a> Hash for ParseItem<'a> {
257255
hasher.write_u32(self.step_index);
258256
hasher.write_i32(self.production.dynamic_precedence);
259257
hasher.write_usize(self.production.steps.len());
260-
hasher.write_i32(self.precedence());
258+
self.precedence().hash(hasher);
261259
self.associativity().hash(hasher);
262260

263261
// When comparing two parse items, the symbols that were already consumed by

0 commit comments

Comments
 (0)