diff --git a/src/build_tables/inline_variables.rs b/src/build_tables/inline_variables.rs index d201519f..affbe163 100644 --- a/src/build_tables/inline_variables.rs +++ b/src/build_tables/inline_variables.rs @@ -108,10 +108,25 @@ impl InlinedProductionMap { .into_iter() .map(|production_to_add| { let mut inlined_production = item.production(grammar, &self).clone(); - inlined_production.steps.splice( - step_index..step_index + 1, - production_to_add.steps.iter().cloned(), - ); + let removed_step = inlined_production + .steps + .splice( + step_index..step_index + 1, + production_to_add.steps.iter().cloned(), + ) + .next() + .unwrap(); + let inserted_steps = &mut inlined_production.steps + [step_index..step_index + production_to_add.steps.len()]; + if let Some(alias) = removed_step.alias { + for inserted_step in inserted_steps.iter_mut() { + inserted_step.alias = Some(alias.clone()); + } + } + if let Some(last_inserted_step) = inserted_steps.last_mut() { + last_inserted_step.precedence = removed_step.precedence; + last_inserted_step.associativity = removed_step.associativity; + } self.inlined_productions .iter() .position(|p| *p == inlined_production) @@ -129,8 +144,9 @@ impl InlinedProductionMap { #[cfg(test)] mod tests { use super::*; - use crate::grammars::{ProductionStep, SyntaxVariable, VariableType}; - use crate::rules::Symbol; + use crate::grammars::{LexicalGrammar, ProductionStep, SyntaxVariable, VariableType}; + use crate::rules::{Alias, Associativity, Symbol}; + use std::borrow::Borrow; #[test] fn test_basic_inlining() { @@ -142,7 +158,7 @@ mod tests { variables_to_inline: vec![Symbol::non_terminal(1)], variables: vec![ SyntaxVariable { - name: "var0".to_string(), + name: "non-terminal-0".to_string(), kind: VariableType::Named, productions: vec![Production { dynamic_precedence: 0, @@ -154,7 +170,7 @@ mod tests { }], }, SyntaxVariable { - name: "var1".to_string(), + name: "non-terminal-1".to_string(), kind: VariableType::Named, productions: vec![ Production { @@ -176,34 +192,32 @@ mod tests { let inline_map = InlinedProductionMap::new(&grammar); // Nothing to inline at step 0. - assert_eq!( - display_items( - inline_map.inlined_items(ParseItem::Normal { - variable_index: 0, - production_index: 0, - step_index: 0 - }), - &grammar, - &inline_map - ), - None - ); + assert!(inline_map + .inlined_items(ParseItem::Normal { + variable_index: 0, + production_index: 0, + step_index: 0 + }) + .is_none()); // Inlining variable 1 yields two productions. assert_eq!( display_items( - inline_map.inlined_items(ParseItem::Normal { - variable_index: 0, - production_index: 0, - step_index: 1 - }), + inline_map + .inlined_items(ParseItem::Normal { + variable_index: 0, + production_index: 0, + step_index: 1 + }) + .unwrap(), &grammar, &inline_map ), - Some(vec![ - "terminal-10 • terminal-12 terminal-13 terminal-11".to_string(), - "terminal-10 • terminal-14 terminal-11".to_string(), - ]) + vec![ + "non-terminal-0 → terminal-10 • terminal-12 terminal-13 terminal-11" + .to_string(), + "non-terminal-0 → terminal-10 • terminal-14 terminal-11".to_string(), + ] ); } @@ -212,23 +226,21 @@ mod tests { let grammar = SyntaxGrammar { variables: vec![ SyntaxVariable { - name: "var0".to_string(), + name: "non-terminal-0".to_string(), kind: VariableType::Named, - productions: vec![ - Production { - dynamic_precedence: 0, - steps: vec![ - ProductionStep::new(Symbol::terminal(10)), - ProductionStep::new(Symbol::non_terminal(1)), // inlined - ProductionStep::new(Symbol::terminal(11)), - ProductionStep::new(Symbol::non_terminal(2)), // inlined - ProductionStep::new(Symbol::terminal(12)), - ], - }, - ], + productions: vec![Production { + dynamic_precedence: 0, + steps: vec![ + ProductionStep::new(Symbol::terminal(10)), + ProductionStep::new(Symbol::non_terminal(1)), // inlined + ProductionStep::new(Symbol::terminal(11)), + ProductionStep::new(Symbol::non_terminal(2)), // inlined + ProductionStep::new(Symbol::terminal(12)), + ], + }], }, SyntaxVariable { - name: "var1".to_string(), + name: "non-terminal-1".to_string(), kind: VariableType::Named, productions: vec![ Production { @@ -245,7 +257,7 @@ mod tests { ], }, SyntaxVariable { - name: "var2".to_string(), + name: "non-terminal-2".to_string(), kind: VariableType::Named, productions: vec![Production { dynamic_precedence: 0, @@ -253,7 +265,7 @@ mod tests { }], }, SyntaxVariable { - name: "var3".to_string(), + name: "non-terminal-3".to_string(), kind: VariableType::Named, productions: vec![Production { dynamic_precedence: 0, @@ -274,45 +286,156 @@ mod tests { let inline_map = InlinedProductionMap::new(&grammar); - let items = inline_map.inlined_items(ParseItem::Normal { - variable_index: 0, - production_index: 0, - step_index: 1 - }).unwrap().collect::>(); + let items = inline_map + .inlined_items(ParseItem::Normal { + variable_index: 0, + production_index: 0, + step_index: 1, + }) + .unwrap() + .collect::>(); assert_eq!( - display_items(Some(items.iter().cloned()), &grammar, &inline_map), - Some(vec![ - "terminal-10 • terminal-13 terminal-11 non-terminal-2 terminal-12".to_string(), - "terminal-10 • terminal-16 terminal-14 terminal-11 non-terminal-2 terminal-12".to_string() - ]) + display_items(&items, &grammar, &inline_map), + vec![ + "non-terminal-0 → terminal-10 • terminal-13 terminal-11 non-terminal-2 terminal-12".to_string(), + "non-terminal-0 → terminal-10 • terminal-16 terminal-14 terminal-11 non-terminal-2 terminal-12".to_string() + ] ); let item = items[0].successor().successor(); assert_eq!( - display_items(Some([item].iter().cloned()), &grammar, &inline_map), - Some(vec![ - "terminal-10 terminal-13 terminal-11 • non-terminal-2 terminal-12".to_string(), - ]) + display_items(&[item], &grammar, &inline_map), + vec![ + "non-terminal-0 → terminal-10 terminal-13 terminal-11 • non-terminal-2 terminal-12".to_string(), + ] ); assert_eq!( - display_items(inline_map.inlined_items(item), &grammar, &inline_map), - Some(vec![ - "terminal-10 terminal-13 terminal-11 • terminal-15 terminal-12".to_string(), - ]) + display_items(inline_map.inlined_items(item).unwrap(), &grammar, &inline_map), + vec![ + "non-terminal-0 → terminal-10 terminal-13 terminal-11 • terminal-15 terminal-12".to_string(), + ] ); } + #[test] + fn test_inlining_with_precedence_and_alias() { + let grammar = SyntaxGrammar { + variables_to_inline: vec![Symbol::non_terminal(1), Symbol::non_terminal(2)], + variables: vec![ + SyntaxVariable { + name: "non-terminal-0".to_string(), + kind: VariableType::Named, + productions: vec![Production { + dynamic_precedence: 0, + steps: vec![ + ProductionStep::new(Symbol::non_terminal(1)) // inlined + .with_prec(1, Some(Associativity::Left)), + ProductionStep::new(Symbol::terminal(10)), + ProductionStep::new(Symbol::non_terminal(2)), // inlined + ], + }], + }, + SyntaxVariable { + name: "non-terminal-1".to_string(), + kind: VariableType::Named, + productions: vec![Production { + dynamic_precedence: 0, + steps: vec![ + ProductionStep::new(Symbol::terminal(11)) + .with_prec(2, None) + .with_alias("inner_alias", true), + ProductionStep::new(Symbol::terminal(12)).with_prec(3, None), + ], + }], + }, + SyntaxVariable { + name: "non-terminal-2".to_string(), + kind: VariableType::Named, + productions: vec![Production { + dynamic_precedence: 0, + steps: vec![ProductionStep::new(Symbol::terminal(13)) + .with_alias("outer_alias", true)], + }], + }, + ], + expected_conflicts: Vec::new(), + extra_tokens: Vec::new(), + external_tokens: Vec::new(), + word_token: None, + }; + + let inline_map = InlinedProductionMap::new(&grammar); + + let items = inline_map + .inlined_items(ParseItem::Normal { + variable_index: 0, + production_index: 0, + step_index: 0, + }) + .unwrap() + .collect::>(); + assert_eq!( + display_items(&items, &grammar, &inline_map)[0], + "non-terminal-0 → • terminal-11 terminal-12 terminal-10 non-terminal-2".to_string(), + ); + + // The first step in the inlined production retains its precedence and alias. + let item = items[0].successor(); + assert_eq!( + display_items(&[item], &grammar, &inline_map)[0], + "non-terminal-0 → terminal-11 • terminal-12 terminal-10 non-terminal-2".to_string(), + ); + assert_eq!(item.precedence(&grammar, &inline_map), 2); + assert_eq!( + items[0].step(&grammar, &inline_map).unwrap().alias, + Some(Alias { + value: "inner_alias".to_string(), + is_named: true, + }) + ); + + // The final terminal of the inlined production inherits the precedence of + // the inlined step. + let item = item.successor(); + assert_eq!( + display_items(&[item], &grammar, &inline_map)[0], + "non-terminal-0 → terminal-11 terminal-12 • terminal-10 non-terminal-2".to_string(), + ); + assert_eq!(item.precedence(&grammar, &inline_map), 1); + + let item = item.successor(); + assert_eq!( + display_items(&[item], &grammar, &inline_map)[0], + "non-terminal-0 → terminal-11 terminal-12 terminal-10 • non-terminal-2".to_string(), + ); + + // All steps of the inlined production inherit their alias from the + // inlined step. + let items = inline_map.inlined_items(item).unwrap().collect::>(); + assert_eq!( + display_items(&items, &grammar, &inline_map)[0], + "non-terminal-0 → terminal-11 terminal-12 terminal-10 • terminal-13".to_string(), + ); + assert_eq!( + items[0].step(&grammar, &inline_map).unwrap().alias, + Some(Alias { + value: "outer_alias".to_string(), + is_named: true, + }) + ) + } + fn display_items( - items: Option>, + items: impl IntoIterator>, grammar: &SyntaxGrammar, inline_map: &InlinedProductionMap, - ) -> Option> { - items.map(|items| { - items - .map(|item| format!("{}", item.with(grammar, inline_map))) - .collect() - }) + ) -> Vec { + let lex = LexicalGrammar::default(); + items + .into_iter() + .map(|item| format!("{}", item.borrow().display_with(grammar, &lex, inline_map))) + .collect() } }