1use crate::grammar::SymbolId;
2use crate::runtime::ErrorContext;
3
4#[doc(hidden)]
7#[derive(Debug, Clone, Copy)]
8pub struct ErrorInfo<'a> {
9 pub symbol_names: &'a [&'a str],
11 pub state_items: &'a [&'a [(u16, u8)]],
13 pub rule_rhs: &'a [&'a [u32]],
15 pub state_symbols: &'a [u32],
17}
18
19impl ErrorContext for ErrorInfo<'_> {
20 fn symbol_name(&self, id: SymbolId) -> &str {
21 self.symbol_names
22 .get(id.0 as usize)
23 .copied()
24 .unwrap_or("<?>")
25 }
26
27 fn state_symbol(&self, state: usize) -> SymbolId {
28 SymbolId(self.state_symbols.get(state).copied().unwrap_or(0))
29 }
30
31 fn state_items(&self, state: usize) -> &[(u16, u8)] {
32 self.state_items.get(state).copied().unwrap_or(&[])
33 }
34
35 fn rule_rhs(&self, rule: usize) -> &[u32] {
36 self.rule_rhs.get(rule).copied().unwrap_or(&[])
37 }
38}
39
40mod alloc_impl {
45 use alloc::collections::{BTreeMap, BTreeSet};
46 use alloc::{format, string::String, vec, vec::Vec};
47
48 use crate::grammar::{Grammar, SymbolId};
49 use crate::lr::{GrammarInternal, to_grammar_internal};
50 use crate::runtime::{ErrorContext, OpEntry, ParseTable};
51
52 type Row = Vec<(u32, u32)>;
53 type RowGroup = (Row, Vec<usize>);
54
55 #[derive(Debug, Clone, PartialEq, Eq)]
58 pub enum Conflict {
59 ShiftReduce {
60 terminal: SymbolId,
61 reduce_rule: usize,
62 example: String,
63 },
64 ReduceReduce {
65 terminal: SymbolId,
66 rule1: usize,
67 rule2: usize,
68 example: String,
69 },
70 }
71
72 #[derive(Debug)]
77 pub struct CompiledTable {
78 data: Vec<u32>,
80 check: Vec<u32>,
81 action_base: Vec<i32>, goto_base: Vec<i32>, rules: Vec<(u32, u8)>,
87
88 num_terminals: u32,
90
91 pub(crate) grammar: GrammarInternal,
93 pub(crate) num_states: usize,
95 pub(crate) conflicts: Vec<Conflict>,
97
98 state_items: Vec<Vec<(u16, u8)>>,
101 rule_rhs: Vec<Vec<u32>>,
103 state_symbols: Vec<u32>,
105 default_reduce: Vec<u32>,
107 default_goto: Vec<u32>,
109 }
110
111 fn most_frequent(iter: impl Iterator<Item = u32>) -> u32 {
113 let mut counts: BTreeMap<u32, usize> = BTreeMap::new();
114 for v in iter {
115 *counts.entry(v).or_default() += 1;
116 }
117 counts
118 .into_iter()
119 .max_by_key(|&(_, c)| c)
120 .map(|(v, _)| v)
121 .unwrap_or(u32::MAX)
122 }
123
124 fn compact_rows(rows: &[Row]) -> (Vec<u32>, Vec<u32>, Vec<i32>) {
128 let mut bases = vec![0i32; rows.len()];
129
130 let mut dedup: BTreeMap<Row, Vec<usize>> = BTreeMap::new();
132 for (i, row) in rows.iter().enumerate() {
133 dedup.entry(row.clone()).or_default().push(i);
134 }
135
136 let mut unique_rows: Vec<RowGroup> = dedup.into_iter().collect();
137 unique_rows.sort_by(|a, b| b.0.len().cmp(&a.0.len()).then_with(|| a.1[0].cmp(&b.1[0])));
138
139 let init_size = rows.len() * 2;
140 let mut data = vec![0u32; init_size];
141 let mut check: Vec<u32> = vec![u32::MAX; init_size];
142 let mut used_bases = BTreeSet::new();
143
144 for (row, members) in &unique_rows {
145 if row.is_empty() {
146 for &idx in members {
147 let mut displacement = 0i32;
148 while !used_bases.insert(displacement) {
149 displacement += 1;
150 }
151 bases[idx] = displacement;
152 }
153 continue;
154 }
155
156 let min_col = row.iter().map(|(c, _)| *c).min().unwrap_or(0) as i32;
157
158 let mut displacement = -min_col;
159 loop {
160 if !used_bases.contains(&displacement) {
161 let mut ok = true;
162 for &(col, _) in row {
163 let slot = (displacement + col as i32) as usize;
164 if slot >= check.len() {
165 let new_size = (slot + 1).max(data.len() * 2);
166 data.resize(new_size, 0);
167 check.resize(new_size, u32::MAX);
168 }
169 if check[slot] != u32::MAX {
170 ok = false;
171 break;
172 }
173 }
174 if ok {
175 break;
176 }
177 }
178 displacement += 1;
179 }
180
181 used_bases.insert(displacement);
182 for &(col, value) in row {
183 let slot = (displacement + col as i32) as usize;
184 data[slot] = value;
185 check[slot] = col;
186 }
187 for &idx in members {
188 bases[idx] = displacement;
189 }
190 }
191
192 (data, check, bases)
193 }
194
195 impl CompiledTable {
196 pub fn build(grammar: &Grammar) -> Result<Self, String> {
200 let internal = to_grammar_internal(grammar)?;
201 Ok(Self::build_from_internal(&internal))
202 }
203
204 pub(crate) fn build_from_internal(grammar: &GrammarInternal) -> Self {
206 let result = crate::lr::build_minimal_automaton(grammar);
207 let num_terminals = grammar.symbols.num_terminals();
208 let num_item_states = result.num_item_states;
209 let num_non_terminals = grammar.symbols.num_non_terminals() as usize;
210
211 let mut reduce_rules_per_state: Vec<Vec<u32>> = vec![Vec::new(); num_item_states];
215 let mut goto_targets_per_nt: Vec<Vec<u32>> = vec![Vec::new(); num_non_terminals];
216
217 for (state, transitions) in result
218 .dfa
219 .transitions
220 .iter()
221 .take(num_item_states)
222 .enumerate()
223 {
224 for &(sym, target) in transitions {
225 if result.reduce_to_real.contains_key(&sym) {
226 continue;
227 }
228 if sym < num_terminals && target >= num_item_states {
229 reduce_rules_per_state[state].push((target - num_item_states) as u32);
230 } else if sym >= num_terminals
231 && sym < grammar.symbols.num_symbols()
232 && target < num_item_states
233 {
234 let nt_idx = (sym - num_terminals) as usize;
235 goto_targets_per_nt[nt_idx].push(target as u32);
236 }
237 }
238 }
239
240 let default_reduce: Vec<u32> = reduce_rules_per_state
242 .iter()
243 .map(|rules| {
244 let default = most_frequent(rules.iter().filter(|&&r| r > 0).copied());
245 if default != u32::MAX { default } else { 0 }
246 })
247 .collect();
248
249 let default_goto: Vec<u32> = goto_targets_per_nt
251 .iter()
252 .map(|targets| most_frequent(targets.iter().copied()))
253 .collect();
254
255 let mut state_symbols = vec![0u32; num_item_states];
257 for state in 0..num_item_states {
258 for &(sym, target) in &result.dfa.transitions[state] {
259 if target < num_item_states {
260 state_symbols[target] = sym;
261 }
262 }
263 }
264
265 let mut real_to_virtual: BTreeMap<u32, u32> = BTreeMap::new();
267 for (&virtual_id, &real_id) in &result.reduce_to_real {
268 real_to_virtual.insert(real_id, virtual_id);
269 }
270
271 let find_target = |state: usize, sym: u32| -> Option<usize> {
273 result.dfa.transitions[state]
274 .iter()
275 .find(|&&(s, _)| s == sym)
276 .map(|&(_, t)| t)
277 };
278
279 let mut rows: Vec<Row> = Vec::with_capacity(num_item_states + num_non_terminals);
281
282 for (state, &dr) in default_reduce.iter().take(num_item_states).enumerate() {
283 let mut row: Row = Vec::new();
284
285 for sym in grammar.symbols.terminal_ids() {
286 let shift = find_target(state, sym.0).filter(|&t| t < num_item_states);
287 let reduce = if let Some(&virtual_id) = real_to_virtual.get(&sym.0) {
288 find_target(state, virtual_id)
290 .filter(|&t| t >= num_item_states)
291 .map(|t| t - num_item_states)
292 } else {
293 find_target(state, sym.0)
295 .filter(|&t| t >= num_item_states)
296 .map(|t| t - num_item_states)
297 };
298
299 let entry = match (shift, reduce) {
300 (Some(s), Some(r)) => {
301 use crate::grammar::TerminalKind;
302 match grammar.symbols.terminal_kind(sym) {
303 TerminalKind::Shift => OpEntry::shift(s),
304 TerminalKind::Reduce => OpEntry::reduce(r),
305 TerminalKind::Prec | TerminalKind::Conflict => {
306 OpEntry::shift_or_reduce(s, r)
307 }
308 TerminalKind::Plain => unreachable!(),
309 }
310 }
311 (Some(s), None) => OpEntry::shift(s),
312 (None, Some(r)) => {
313 if dr > 0 && r as u32 == dr {
314 continue;
315 }
316 OpEntry::reduce(r)
317 }
318 (None, None) => continue,
319 };
320 row.push((sym.0, entry.0));
321 }
322
323 rows.push(row);
324 }
325
326 for (nt_idx, &default_target) in default_goto.iter().take(num_non_terminals).enumerate()
328 {
329 let mut row: Row = Vec::new();
330 let sym = num_terminals + nt_idx as u32;
331 for state in 0..num_item_states {
332 if let Some(target) = find_target(state, sym)
333 && target < num_item_states
334 && target as u32 != default_target
335 {
336 row.push((state as u32, target as u32));
337 }
338 }
339 rows.push(row);
340 }
341
342 let (data, check, bases) = compact_rows(&rows);
343 let (action_base, goto_base) = bases.split_at(num_item_states);
344
345 let rules: Vec<(u32, u8)> = grammar
346 .rules
347 .iter()
348 .map(|r| (r.lhs.id().0, r.rhs.len() as u8))
349 .collect();
350
351 let rule_rhs: Vec<Vec<u32>> = grammar
352 .rules
353 .iter()
354 .map(|r| r.rhs.iter().map(|s| s.id().0).collect())
355 .collect();
356
357 CompiledTable {
358 data,
359 check,
360 action_base: action_base.to_vec(),
361 goto_base: goto_base.to_vec(),
362 num_terminals: grammar.symbols.num_terminals(),
363 grammar: grammar.clone(),
364 rules,
365 num_states: num_item_states,
366 conflicts: result.conflicts,
367 state_items: result.state_items,
368 rule_rhs,
369 state_symbols,
370 default_reduce,
371 default_goto,
372 }
373 }
374
375 pub fn table(&self) -> ParseTable<'_> {
377 ParseTable::new(
378 &self.data,
379 &self.check,
380 &self.action_base,
381 &self.goto_base,
382 &self.rules,
383 self.num_terminals,
384 &self.default_reduce,
385 &self.default_goto,
386 )
387 }
388
389 pub fn has_conflicts(&self) -> bool {
391 !self.conflicts.is_empty()
392 }
393
394 pub fn format_conflicts(&self) -> Vec<String> {
396 use alloc::collections::BTreeSet;
397 let mut seen = BTreeSet::new();
398 let mut messages = Vec::new();
399 for c in &self.conflicts {
400 let msg = match c {
401 Conflict::ShiftReduce {
402 terminal,
403 reduce_rule,
404 example,
405 } => {
406 let item =
407 self.format_item(*reduce_rule, self.rule_rhs[*reduce_rule].len());
408 let is_ambiguity = example.starts_with("Ambiguity");
409 let mut msg = if is_ambiguity {
410 "Shift/reduce conflict:".into()
411 } else {
412 let term_name = self.grammar.symbols.name(*terminal);
413 format!("Shift/reduce conflict on '{}':", term_name)
414 };
415 msg.push_str(&format!("\n Shift wins over:\n {}", item));
416 if !example.is_empty() {
417 let indented = example.replace('\n', "\n ");
418 msg.push_str(&format!("\n {}", indented));
419 }
420 msg
421 }
422 Conflict::ReduceReduce {
423 terminal,
424 rule1,
425 rule2,
426 example,
427 } => {
428 let item1 = self.format_item(*rule1, self.rule_rhs[*rule1].len());
429 let item2 = self.format_item(*rule2, self.rule_rhs[*rule2].len());
430 let is_ambiguity = example.starts_with("Ambiguity");
431 let mut msg = if is_ambiguity {
432 format!(
433 "Reduce/reduce conflict:\n \
434 Reduce: {} (wins)\n \
435 Reduce: {}",
436 item1, item2,
437 )
438 } else {
439 let term_name = self.grammar.symbols.name(*terminal);
440 format!(
441 "Reduce/reduce conflict on '{}':\n \
442 Reduce: {} (wins)\n \
443 Reduce: {}",
444 term_name, item1, item2,
445 )
446 };
447 if !example.is_empty() {
448 let indented = example.replace('\n', "\n ");
449 msg.push_str(&format!("\n {}", indented));
450 }
451 msg
452 }
453 };
454 if seen.insert(msg.clone()) {
455 messages.push(msg);
456 }
457 }
458 messages
459 }
460
461 fn format_item(&self, rule_idx: usize, dot: usize) -> String {
463 let rule = &self.grammar.rules[rule_idx];
464 let lhs_name = self.grammar.symbols.name(rule.lhs.id());
465 let rhs = &self.rule_rhs[rule_idx];
466 let mut s = format!("{} ->", lhs_name);
467 for (i, &sym_id) in rhs.iter().enumerate() {
468 if i == dot {
469 s.push_str(" \u{2022}");
470 }
471 s.push(' ');
472 s.push_str(self.grammar.symbols.name(SymbolId(sym_id)));
473 }
474 if dot == rhs.len() {
475 s.push_str(" \u{2022}");
476 }
477 s
478 }
479
480 pub fn symbol_id(&self, name: &str) -> Option<SymbolId> {
482 self.grammar.symbols.get_id(name)
483 }
484
485 pub fn symbol_name(&self, id: SymbolId) -> &str {
487 self.grammar.symbols.name(id)
488 }
489
490 pub fn num_symbols(&self) -> u32 {
492 self.grammar.symbols.num_symbols()
493 }
494
495 pub fn num_terminals(&self) -> u32 {
497 self.grammar.symbols.num_terminals()
498 }
499
500 pub fn num_states(&self) -> usize {
502 self.num_states
503 }
504
505 pub fn conflicts(&self) -> &[Conflict] {
507 &self.conflicts
508 }
509
510 #[doc(hidden)]
513 pub fn table_data(&self) -> &[u32] {
514 &self.data
515 }
516
517 #[doc(hidden)]
518 pub fn table_check(&self) -> &[u32] {
519 &self.check
520 }
521
522 #[doc(hidden)]
523 pub fn action_base(&self) -> &[i32] {
524 &self.action_base
525 }
526
527 #[doc(hidden)]
528 pub fn goto_base(&self) -> &[i32] {
529 &self.goto_base
530 }
531
532 #[doc(hidden)]
533 pub fn rules(&self) -> &[(u32, u8)] {
534 &self.rules
535 }
536
537 #[doc(hidden)]
538 pub fn state_items(&self) -> &[Vec<(u16, u8)>] {
539 &self.state_items
540 }
541
542 #[doc(hidden)]
543 pub fn rule_rhs(&self) -> &[Vec<u32>] {
544 &self.rule_rhs
545 }
546
547 #[doc(hidden)]
548 pub fn rule_name(&self, rule: usize) -> Option<&str> {
549 self.grammar.rules.get(rule).and_then(|r| {
550 if let crate::lr::AltAction::Named(name) = &r.action {
551 Some(name.as_str())
552 } else {
553 None
554 }
555 })
556 }
557
558 #[doc(hidden)]
559 pub fn state_symbols(&self) -> &[u32] {
560 &self.state_symbols
561 }
562
563 #[doc(hidden)]
564 pub fn default_reduce(&self) -> &[u32] {
565 &self.default_reduce
566 }
567
568 #[doc(hidden)]
569 pub fn default_goto(&self) -> &[u32] {
570 &self.default_goto
571 }
572 }
573
574 impl ErrorContext for CompiledTable {
575 fn symbol_name(&self, id: SymbolId) -> &str {
576 self.grammar.symbols.name(id)
577 }
578
579 fn state_symbol(&self, state: usize) -> SymbolId {
580 SymbolId(self.state_symbols.get(state).copied().unwrap_or(0))
581 }
582
583 fn state_items(&self, state: usize) -> &[(u16, u8)] {
584 self.state_items
585 .get(state)
586 .map(|v| v.as_slice())
587 .unwrap_or(&[])
588 }
589
590 fn rule_rhs(&self, rule: usize) -> &[u32] {
591 self.rule_rhs.get(rule).map(|v| v.as_slice()).unwrap_or(&[])
592 }
593 }
594}
595
596pub use alloc_impl::{CompiledTable, Conflict};
597
598#[cfg(test)]
599mod tests {
600 use super::*;
601 use crate::lr::{GrammarInternal, to_grammar_internal};
602 use crate::meta::parse_grammar;
603 use crate::runtime::ParserOp;
604
605 fn simple_grammar() -> GrammarInternal {
606 to_grammar_internal(
607 &parse_grammar(
608 r#"
609 start s; terminals { a } s = a => a;
610 "#,
611 )
612 .unwrap(),
613 )
614 .unwrap()
615 }
616
617 fn expr_grammar() -> GrammarInternal {
618 to_grammar_internal(
619 &parse_grammar(
620 r#"
621 start expr;
622 terminals { PLUS, NUM }
623 expr = expr PLUS term => add | term => term;
624 term = NUM => num;
625 "#,
626 )
627 .unwrap(),
628 )
629 .unwrap()
630 }
631
632 fn ambiguous_grammar() -> GrammarInternal {
633 to_grammar_internal(
634 &parse_grammar(
635 r#"
636 start expr;
637 terminals { PLUS, NUM }
638 expr = expr PLUS expr => add | NUM => num;
639 "#,
640 )
641 .unwrap(),
642 )
643 .unwrap()
644 }
645
646 fn prec_grammar() -> GrammarInternal {
647 to_grammar_internal(
648 &parse_grammar(
649 r#"
650 start expr;
651 terminals { prec OP, NUM }
652 expr = expr OP expr => binop | NUM => num;
653 "#,
654 )
655 .unwrap(),
656 )
657 .unwrap()
658 }
659
660 #[test]
661 fn test_simple_table() {
662 let grammar = simple_grammar();
663 let compiled = CompiledTable::build_from_internal(&grammar);
664 let table = compiled.table();
665
666 assert!(!compiled.has_conflicts());
667
668 let a_id = compiled.symbol_id("a").unwrap();
669 match table.action(0, a_id) {
670 ParserOp::Shift(_) => {}
671 other => panic!("Expected Shift, got {:?}", other),
672 }
673 }
674
675 #[test]
676 fn test_expr_table() {
677 let grammar = expr_grammar();
678 let compiled = CompiledTable::build_from_internal(&grammar);
679 let table = compiled.table();
680
681 assert!(!compiled.has_conflicts());
682
683 let num_id = compiled.symbol_id("NUM").unwrap();
684 match table.action(0, num_id) {
685 ParserOp::Shift(_) => {}
686 other => panic!("Expected Shift on NUM, got {:?}", other),
687 }
688 }
689
690 #[test]
691 fn test_ambiguous_grammar() {
692 let grammar = ambiguous_grammar();
693 let compiled = CompiledTable::build_from_internal(&grammar);
694
695 assert!(
696 compiled.has_conflicts(),
697 "Expected conflicts for ambiguous grammar"
698 );
699
700 let has_sr_conflict = compiled
701 .conflicts
702 .iter()
703 .any(|c| matches!(c, Conflict::ShiftReduce { .. }));
704 assert!(has_sr_conflict, "Expected shift/reduce conflict");
705
706 let messages = compiled.format_conflicts();
708 assert!(!messages.is_empty(), "Expected formatted conflict messages");
709 let msg = &messages[0];
710 assert!(
711 msg.contains("Shift/reduce conflict"),
712 "Should describe conflict type: {}",
713 msg
714 );
715 assert!(
717 !msg.contains("'PLUS'"),
718 "Ambiguity should not mention terminal: {}",
719 msg
720 );
721 assert!(
723 msg.contains("\u{2022}"),
724 "Should contain dot in item: {}",
725 msg
726 );
727 assert!(
729 msg.contains("Ambiguity in"),
730 "Should contain ambiguity: {}",
731 msg
732 );
733 assert!(msg.contains("expr"), "Should mention expr: {}", msg);
734 }
735
736 #[test]
737 fn test_conflict_example_rr() {
738 let grammar = to_grammar_internal(
739 &parse_grammar(
740 r#"
741 start s;
742 terminals { A }
743 s = x => x | y => y;
744 x = A => a;
745 y = A => a;
746 "#,
747 )
748 .unwrap(),
749 )
750 .unwrap();
751 let compiled = CompiledTable::build_from_internal(&grammar);
752
753 assert!(compiled.has_conflicts(), "Expected R/R conflict");
754 let has_rr = compiled
755 .conflicts
756 .iter()
757 .any(|c| matches!(c, Conflict::ReduceReduce { .. }));
758 assert!(has_rr, "Expected reduce/reduce conflict");
759
760 let messages = compiled.format_conflicts();
762 let msg = &messages[0];
763 assert!(
764 msg.contains("Reduce/reduce conflict"),
765 "Should describe R/R: {}",
766 msg
767 );
768 assert!(
769 msg.contains("Ambiguity in"),
770 "Should contain ambiguity info: {}",
771 msg,
772 );
773 }
774
775 #[test]
776 fn test_conflict_example_rr_epsilon_bracketing() {
777 let grammar = to_grammar_internal(
778 &parse_grammar(
779 r#"
780 start s;
781 terminals { A }
782 s = x => x | y => y;
783 x = _ => e;
784 y = _ => e;
785 "#,
786 )
787 .unwrap(),
788 )
789 .unwrap();
790 let compiled = CompiledTable::build_from_internal(&grammar);
791
792 let messages = compiled.format_conflicts();
793 let msg = &messages[0];
794 assert!(
795 msg.contains("Reduce/reduce conflict"),
796 "Should describe R/R: {}",
797 msg
798 );
799 assert!(
800 msg.contains("Ambiguity in"),
801 "Should contain ambiguity info: {}",
802 msg,
803 );
804 }
805
806 #[test]
807 fn test_shift_terminal_resolves_conflict() {
808 let grammar = to_grammar_internal(
810 &parse_grammar(
811 r#"
812 start expr;
813 terminals { shift PLUS, NUM }
814 expr = expr PLUS expr => add | NUM => num;
815 "#,
816 )
817 .unwrap(),
818 )
819 .unwrap();
820
821 let compiled = CompiledTable::build_from_internal(&grammar);
822 assert!(
823 !compiled.has_conflicts(),
824 "shift terminal should resolve S/R conflict"
825 );
826 }
827
828 #[test]
829 fn test_reduce_terminal_resolves_conflict() {
830 let grammar = to_grammar_internal(
832 &parse_grammar(
833 r#"
834 start expr;
835 terminals { reduce PLUS, NUM }
836 expr = expr PLUS expr => add | NUM => num;
837 "#,
838 )
839 .unwrap(),
840 )
841 .unwrap();
842
843 let compiled = CompiledTable::build_from_internal(&grammar);
844 assert!(
845 !compiled.has_conflicts(),
846 "reduce terminal should resolve S/R conflict"
847 );
848 }
849
850 #[test]
851 fn test_conflict_terminal_resolves_conflict() {
852 let grammar = to_grammar_internal(
854 &parse_grammar(
855 r#"
856 start expr;
857 terminals { conflict PLUS, NUM }
858 expr = expr PLUS expr => add | NUM => num;
859 "#,
860 )
861 .unwrap(),
862 )
863 .unwrap();
864
865 let compiled = CompiledTable::build_from_internal(&grammar);
866 assert!(
867 !compiled.has_conflicts(),
868 "conflict terminal should suppress S/R conflict"
869 );
870 }
871
872 #[test]
873 fn test_no_conflict_examples_for_clean_grammar() {
874 let grammar = expr_grammar();
875 let compiled = CompiledTable::build_from_internal(&grammar);
876 assert!(!compiled.has_conflicts());
877 assert!(compiled.conflicts().is_empty());
878 }
879
880 #[test]
881 fn test_prec_terminal_no_conflict() {
882 let grammar = prec_grammar();
883 let compiled = CompiledTable::build_from_internal(&grammar);
884 let table = compiled.table();
885
886 assert!(
887 !compiled.has_conflicts(),
888 "PrecTerminal should not report conflicts"
889 );
890
891 let op_id = compiled.symbol_id("OP").unwrap();
893 let mut found_shift_or_reduce = false;
894 for state in 0..compiled.num_states() {
895 if let ParserOp::ShiftOrReduce { .. } = table.action(state, op_id) {
896 found_shift_or_reduce = true;
897 break;
898 }
899 }
900 assert!(
901 found_shift_or_reduce,
902 "Expected ShiftOrReduce action for OP"
903 );
904 }
905
906 #[test]
907 fn test_conflict_example_lr2() {
908 let grammar = to_grammar_internal(
911 &parse_grammar(
912 r#"
913 start s;
914 terminals { X, SEMI, A, B }
915 s = aa SEMI A => sa | bb B => sb;
916 aa = X => x;
917 bb = X SEMI => xs;
918 "#,
919 )
920 .unwrap(),
921 )
922 .unwrap();
923 let compiled = CompiledTable::build_from_internal(&grammar);
924 assert!(compiled.has_conflicts(), "Expected R/R conflict");
926 }
927
928 #[test]
929 fn test_goto() {
930 let grammar = expr_grammar();
931 let compiled = CompiledTable::build_from_internal(&grammar);
932 let table = compiled.table();
933
934 let expr_id = compiled.symbol_id("expr").unwrap();
935 let term_id = compiled.symbol_id("term").unwrap();
936
937 assert!(
938 table.goto(0, expr_id).is_some(),
939 "Expected goto on expr from state 0"
940 );
941 assert!(
942 table.goto(0, term_id).is_some(),
943 "Expected goto on term from state 0"
944 );
945 }
946}