Handle precedence and aliases properly when inlining variables
This commit is contained in:
parent
d078c263b0
commit
988dc7de35
1 changed files with 193 additions and 70 deletions
|
|
@ -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::<Vec<_>>();
|
||||
let items = inline_map
|
||||
.inlined_items(ParseItem::Normal {
|
||||
variable_index: 0,
|
||||
production_index: 0,
|
||||
step_index: 1,
|
||||
})
|
||||
.unwrap()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
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::<Vec<_>>();
|
||||
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::<Vec<_>>();
|
||||
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<impl Iterator<Item = ParseItem>>,
|
||||
items: impl IntoIterator<Item = impl Borrow<ParseItem>>,
|
||||
grammar: &SyntaxGrammar,
|
||||
inline_map: &InlinedProductionMap,
|
||||
) -> Option<Vec<String>> {
|
||||
items.map(|items| {
|
||||
items
|
||||
.map(|item| format!("{}", item.with(grammar, inline_map)))
|
||||
.collect()
|
||||
})
|
||||
) -> Vec<String> {
|
||||
let lex = LexicalGrammar::default();
|
||||
items
|
||||
.into_iter()
|
||||
.map(|item| format!("{}", item.borrow().display_with(grammar, &lex, inline_map)))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue