use crate::analysis::lookahead_dfa::ProductionIndex;
use crate::generate_name;
use crate::grammar::{ProductionAttribute, SymbolAttribute};
use crate::parser::{Alternation, Alternations, Factor, Production};
use crate::utils::combine;
use crate::{Pr, Symbol};
use anyhow::{bail, Result};
use parol_runtime::log::trace;
use parol_runtime::once_cell::sync::Lazy;
use regex::Regex;
use std::convert::TryFrom;
pub(crate) static RX_OPT_WITH_NUM_SUFFIX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"Opt[0-9]*$").expect("error parsing regex"));
struct TransformationOperand {
modified: bool,
productions: Vec<Production>,
}
fn finalize(productions: Vec<Production>) -> Result<Vec<Pr>> {
productions
.into_iter()
.map(|r| {
let Alternations(mut e) = r.rhs.clone();
if e.len() != 1 {
bail!(
"Expected one alternation per production after transformation but found {} at {}!",
e.len(), r
);
}
let single_alternative = e.pop().unwrap();
Ok(Pr(
Symbol::n(&r.lhs),
if single_alternative.0.is_empty() {
vec![]
} else {
let prod: Result<Vec<Symbol>> =
single_alternative
.0
.into_iter()
.try_fold(Vec::new(), |mut acc, f| {
let s = Symbol::try_from(f)?;
acc.push(s);
Ok(acc)
});
prod?
},
single_alternative.1
))
})
.collect()
}
fn variable_names(productions: &[Production]) -> Vec<String> {
let mut productions_vars = productions.iter().fold(vec![], |mut res, r| {
let variable = &r.lhs;
res.push(variable.clone());
let mut alternation_vars = r.rhs.0.iter().fold(vec![], |mut res, a| {
let mut factors_vars = a.0.iter().fold(vec![], |mut res, f| {
if let Factor::NonTerminal(n, ..) = f {
res.push(n.clone());
}
res
});
res.append(&mut factors_vars);
res
});
res.append(&mut alternation_vars);
res
});
productions_vars.sort();
productions_vars.dedup();
productions_vars
}
fn apply_production_transformation(
productions: &mut Vec<Production>,
index: usize,
trans: impl Fn(Production) -> Vec<Production>,
) {
let production_to_substitute = productions.remove(index);
let mut substitutes = trans(production_to_substitute);
productions.reserve(substitutes.len());
for i in index..(index + substitutes.len()) {
productions.insert(i, substitutes.remove(0));
}
}
fn find_production_with_factor(
productions: &[Production],
pred: impl Fn(&Factor) -> bool,
) -> Option<(ProductionIndex, usize)> {
let production_index = productions.iter().position(|r| {
let Alternations(e) = &r.rhs;
e.iter().any(|r| r.0.iter().any(&pred))
});
if let Some(production_index) = production_index {
let Alternations(e) = &productions[production_index].rhs;
Some((
production_index,
e.iter().position(|r| r.0.iter().any(&pred)).unwrap(),
))
} else {
None
}
}
fn format_productions(prods: &[Production]) -> String {
prods
.iter()
.fold(Vec::new(), |mut acc: Vec<String>, p| {
acc.push(p.to_string());
acc
})
.join("\n")
}
fn extract_options(opd: TransformationOperand) -> TransformationOperand {
fn extract_optional_in_alt(
alt: &mut Alternation,
non_terminal: String,
exclusions: &[String],
) -> Option<(String, Alternations)> {
for factor in &mut alt.0 {
match factor.clone() {
Factor::Group(_) | Factor::Repeat(_) => {
if let Some((name, alts)) = extract_optional_in_alts(
factor.inner_alts_mut().expect("Should always succeed"),
non_terminal.clone(),
exclusions,
) {
return Some((name, alts));
}
}
Factor::Optional(alts) => {
let preferred_name = if RX_OPT_WITH_NUM_SUFFIX.is_match(&non_terminal) {
non_terminal
} else {
format!("{}Opt", non_terminal)
};
let new_opt_production_name = generate_name(exclusions, preferred_name);
*factor = Factor::NonTerminal(
new_opt_production_name.clone(),
SymbolAttribute::Option,
None,
);
trace!(
"Extracting optional {} into production {}",
alts.to_par(),
new_opt_production_name
);
return Some((new_opt_production_name, alts));
}
_ => {}
}
}
None
}
fn extract_optional_in_alts(
alts: &mut Alternations,
non_terminal: String,
exclusions: &[String],
) -> Option<(String, Alternations)> {
for alt in &mut alts.0 {
if let Some((name, alts)) =
extract_optional_in_alt(alt, non_terminal.clone(), exclusions)
{
return Some((name, alts));
}
}
None
}
fn extract_optional_in_production(
prod: &mut Production,
non_terminal: String,
exclusions: &[String],
) -> Option<(String, Alternations)> {
for alt in &mut prod.rhs.0 {
if let Some((name, alts)) =
extract_optional_in_alt(alt, non_terminal.clone(), exclusions)
{
return Some((name, alts));
}
}
None
}
fn extract_optional_in_productions(
prods: &mut [Production],
exclusions: &[String],
) -> Option<(String, ProductionIndex, Alternations)> {
let mut prod_num = 0;
#[allow(clippy::explicit_counter_loop)]
for prod in prods {
if let Some((name, alts)) =
extract_optional_in_production(prod, prod.lhs.clone(), exclusions)
{
return Some((name, prod_num, alts));
}
prod_num += 1;
}
None
}
let mut modified = true;
let mut productions = opd.productions;
while modified {
let exclusions = variable_names(&productions);
if let Some((name, prod_num, alts)) =
extract_optional_in_productions(&mut productions, &exclusions)
{
modified = true;
productions.insert(
prod_num + 1,
Production::new(
name.clone(),
Alternations(vec![
Alternation::new().with_attribute(ProductionAttribute::OptionalNone)
]),
),
);
productions.insert(
prod_num + 1,
Production::new(
name,
Alternations(vec![Alternation(
vec![Factor::Group(alts)],
ProductionAttribute::OptionalSome,
)]),
),
);
} else {
modified = false;
}
}
TransformationOperand {
modified,
productions,
}
}
fn separate_alternatives(opd: TransformationOperand) -> TransformationOperand {
fn production_has_multiple_alts(r: &Production) -> bool {
let Alternations(e) = &r.rhs;
e.len() > 1
}
fn separate_production_with_multiple_alts(r: Production) -> Vec<Production> {
let Production { lhs, rhs } = r;
let Alternations(e) = rhs;
e.into_iter()
.map(|a| Production {
lhs: lhs.clone(),
rhs: Alternations(vec![a]),
})
.collect::<Vec<Production>>()
}
fn separate_single_production(productions: &mut Vec<Production>) -> bool {
let candidate_index = productions.iter().position(&production_has_multiple_alts);
if let Some(index) = candidate_index {
apply_production_transformation(
productions,
index,
separate_production_with_multiple_alts,
);
true
} else {
false
}
}
let mut modified = opd.modified;
let mut productions = opd.productions;
while separate_single_production(&mut productions) {
modified |= true;
}
TransformationOperand {
modified,
productions,
}
}
fn eliminate_single_rep(
exclusions: &[String],
alt_index: usize,
production: Production,
) -> Vec<Production> {
let production_name = production.lhs.clone();
if let Some(rpt_index_in_alt) = production.rhs.0[alt_index]
.0
.iter()
.position(|f| matches!(f, Factor::Repeat(_)))
{
let r_tick_name = generate_name(exclusions, production_name + "List");
if let Factor::Repeat(repeat) = production.rhs.0[alt_index].0[rpt_index_in_alt].clone() {
let mut production1 = production.clone();
production1.rhs.0[alt_index].0[rpt_index_in_alt] =
Factor::NonTerminal(r_tick_name.clone(), SymbolAttribute::RepetitionAnchor, None);
let production2 = if repeat.0.len() == 1 {
let mut rhs_p2 = repeat.0;
rhs_p2[0].push(Factor::default_non_terminal(r_tick_name.clone()));
rhs_p2[0].1 = ProductionAttribute::AddToCollection;
Production {
lhs: r_tick_name.clone(),
rhs: Alternations(rhs_p2),
}
} else {
Production {
lhs: r_tick_name.clone(),
rhs: Alternations(vec![Alternation::new()
.with_factors(if repeat.0.len() == 1 {
let mut fs = repeat.0[0].0.clone();
fs.push(Factor::default_non_terminal(r_tick_name.clone()));
fs
} else {
vec![
Factor::Group(repeat),
Factor::default_non_terminal(r_tick_name.clone()),
]
})
.with_attribute(ProductionAttribute::AddToCollection)]),
}
};
let production2a = Production {
lhs: r_tick_name,
rhs: Alternations(vec![
Alternation::new().with_attribute(ProductionAttribute::CollectionStart)
]),
};
vec![production1, production2, production2a]
} else {
panic!("Expected Factor::Repeat!");
}
} else {
vec![production]
}
}
fn eliminate_repetitions(opd: TransformationOperand) -> TransformationOperand {
fn find_production_with_repetition(
productions: &[Production],
) -> Option<(ProductionIndex, usize)> {
find_production_with_factor(productions, |f| matches!(f, Factor::Repeat(_)))
}
fn eliminate_repetition(productions: &mut Vec<Production>) -> bool {
if let Some((production_index, alt_index)) = find_production_with_repetition(productions) {
let exclusions = variable_names(productions);
apply_production_transformation(productions, production_index, |r| {
eliminate_single_rep(&exclusions, alt_index, r)
});
true
} else {
false
}
}
let mut modified = opd.modified;
let mut productions = opd.productions;
while eliminate_repetition(&mut productions) {
modified |= true;
}
TransformationOperand {
modified,
productions,
}
}
fn eliminate_single_opt(
exclusions: &[String],
alt_index: usize,
production: Production,
) -> Vec<Production> {
let production_name = production.lhs.clone();
if let Some(opt_index_in_alt) = production.rhs.0[alt_index]
.0
.iter()
.position(|f| matches!(f, Factor::Optional(_)))
{
if let Factor::Optional(optional) = production.rhs.0[alt_index].0[opt_index_in_alt].clone()
{
if optional.0.len() == 1 {
let mut production1 = production.clone();
production1.rhs.0[alt_index].0.remove(opt_index_in_alt);
optional.0[0].0.iter().rev().for_each(|fac| {
production1.rhs.0[alt_index]
.0
.insert(opt_index_in_alt, fac.clone())
});
let mut production1a = production.clone();
production1a.rhs.0[alt_index].0.remove(opt_index_in_alt);
vec![production1, production1a]
} else {
let r_tick_name = generate_name(exclusions, production_name + "Opt");
let mut production1 = production.clone();
production1.rhs.0[alt_index].0[opt_index_in_alt] =
Factor::default_non_terminal(r_tick_name.clone());
let mut production1a = production1.clone();
production1a.rhs.0[0].0.remove(opt_index_in_alt);
let production2 = Production {
lhs: r_tick_name,
rhs: if optional.0.len() == 1 {
Alternations(vec![
Alternation::new().with_factors(optional.0[0].0.clone())
])
} else {
optional
},
};
vec![production1, production1a, production2]
}
} else {
panic!("Expected Factor::Optional!");
}
} else {
vec![production]
}
}
fn eliminate_options(opd: TransformationOperand) -> TransformationOperand {
fn find_production_with_optional(
productions: &[Production],
) -> Option<(ProductionIndex, usize)> {
find_production_with_factor(productions, |f| matches!(f, Factor::Optional(_)))
}
fn eliminate_option(productions: &mut Vec<Production>) -> bool {
if let Some((production_index, alt_index)) = find_production_with_optional(productions) {
let exclusions = variable_names(productions);
apply_production_transformation(productions, production_index, |r| {
eliminate_single_opt(&exclusions, alt_index, r)
});
true
} else {
false
}
}
let mut modified = opd.modified;
let mut productions = opd.productions;
trace!(
"\nTry to remove options\n{}",
format_productions(&productions)
);
while eliminate_option(&mut productions) {
modified |= true;
trace!("\nRemoved options\n{}", format_productions(&productions));
}
TransformationOperand {
modified,
productions,
}
}
fn eliminate_single_grp(
exclusions: &[String],
alt_index: usize,
production: Production,
) -> Vec<Production> {
let production_name = production.lhs.clone();
if let Some(grp_index_in_alt) = production.rhs.0[alt_index]
.0
.iter()
.position(|f| matches!(f, Factor::Group(_)))
{
if let Factor::Group(group) = production.rhs.0[alt_index].0[grp_index_in_alt].clone() {
if group.0.len() == 1 {
let mut production1 = production.clone();
production1.rhs.0[alt_index].0.remove(grp_index_in_alt);
group.0[0].0.iter().rev().for_each(|fac| {
production1.rhs.0[alt_index]
.0
.insert(grp_index_in_alt, fac.clone())
});
vec![production1]
} else {
let g_name = generate_name(exclusions, production_name + "Group");
if let Factor::Group(group) =
production.rhs.0[alt_index].0[grp_index_in_alt].clone()
{
let mut production1 = production.clone();
production1.rhs.0[alt_index].0[grp_index_in_alt] =
Factor::default_non_terminal(g_name.clone());
let production2 = Production {
lhs: g_name,
rhs: group,
};
vec![production1, production2]
} else {
panic!("Expected Factor::Group!");
}
}
} else {
panic!("Expected Group here");
}
} else {
vec![production]
}
}
fn eliminate_groups(opd: TransformationOperand) -> TransformationOperand {
fn find_production_with_group(productions: &[Production]) -> Option<(ProductionIndex, usize)> {
find_production_with_factor(productions, |f| matches!(f, Factor::Group(_)))
}
fn eliminate_group(productions: &mut Vec<Production>) -> bool {
if let Some((production_index, alt_index)) = find_production_with_group(productions) {
let exclusions = variable_names(productions);
apply_production_transformation(productions, production_index, |r| {
eliminate_single_grp(&exclusions, alt_index, r)
});
true
} else {
false
}
}
let mut modified = opd.modified;
let mut productions = opd.productions;
while eliminate_group(&mut productions) {
modified |= true;
}
TransformationOperand {
modified,
productions,
}
}
fn eliminate_duplicates(opd: TransformationOperand) -> TransformationOperand {
fn find_productions_with_same_rhs(
productions: &[Production],
) -> Option<(ProductionIndex, ProductionIndex)> {
for i in 0..productions.len() {
for j in 0..productions.len() {
if i != j && productions[i].rhs == productions[j].rhs {
let (i, j) = if i < j { (i, j) } else { (j, i) };
let first = &productions[i].lhs;
let duplicate = &productions[j].lhs;
if productions.iter().filter(|pr| &pr.lhs == first).count() == 1
&& productions.iter().filter(|pr| &pr.lhs == duplicate).count() == 1
{
return Some((i, j));
}
}
}
}
None
}
fn eliminate_single_duplicate(
productions: &mut Vec<Production>,
production_index_1: ProductionIndex,
production_index_2: ProductionIndex,
) {
let to_find = productions[production_index_2].lhs.clone();
let replace_with = productions[production_index_1].lhs.clone();
#[allow(clippy::needless_range_loop)]
for pi in 0..productions.len() {
if pi != production_index_2 {
let pr = &mut productions[pi];
debug_assert!(pr.rhs.0.len() == 1, "Only one single Alternation expected!");
for s in &mut pr.rhs.0[0].0 {
if let Factor::NonTerminal(n, ..) = s {
if n == &to_find {
*s = Factor::default_non_terminal(replace_with.clone());
}
}
}
}
}
productions.remove(production_index_2);
}
fn eliminate_duplicate(productions: &mut Vec<Production>) -> bool {
if let Some((production_index_1, production_index_2)) =
find_productions_with_same_rhs(productions)
{
eliminate_single_duplicate(productions, production_index_1, production_index_2);
true
} else {
false
}
}
let mut modified = opd.modified;
let mut productions = opd.productions;
while eliminate_duplicate(&mut productions) {
modified |= true;
}
TransformationOperand {
modified,
productions,
}
}
pub(crate) fn transform_productions(productions: Vec<Production>) -> Result<Vec<Pr>> {
trace!(
"\nStarting transformation\n{}",
format_productions(&productions)
);
let mut operand = TransformationOperand {
modified: true,
productions,
};
while operand.modified {
operand.modified = false;
operand = extract_options(operand);
}
let trans_fn = combine(
combine(
combine(separate_alternatives, eliminate_repetitions),
eliminate_options,
),
eliminate_groups,
);
operand.modified = true;
while operand.modified {
operand.modified = false;
operand = trans_fn(operand);
}
operand.modified = true;
while operand.modified {
operand.modified = false;
operand = eliminate_duplicates(operand);
}
finalize(operand.productions)
}
#[cfg(test)]
mod test {
use super::{
eliminate_single_grp, eliminate_single_opt, eliminate_single_rep, Alternation,
Alternations, Factor, Production,
};
use crate::{
grammar::{ProductionAttribute, SymbolAttribute, TerminalKind},
transformation::canonicalization::apply_production_transformation,
};
macro_rules! terminal {
($term:literal) => {
Factor::Terminal(
$term.to_string(),
TerminalKind::Legacy,
vec![0],
SymbolAttribute::None,
None,
)
};
}
#[test]
fn eliminate_single_rep_case_1() {
let production = Production {
lhs: "Start".to_string(),
rhs: Alternations(vec![Alternation::new().with_factors(vec![
terminal!("x"),
Factor::Repeat(Alternations(vec![
Alternation::new().with_factors(vec![terminal!("r1"), terminal!("r2")])
])),
terminal!("y"),
])]),
};
let productions = eliminate_single_rep(&[production.lhs.clone()], 0, production);
assert_eq!(3, productions.len());
assert_eq!(
Production {
lhs: "Start".to_string(),
rhs: Alternations(vec![Alternation::new().with_factors(vec![
terminal!("x"),
Factor::NonTerminal(
"StartList".to_string(),
SymbolAttribute::RepetitionAnchor,
None
),
terminal!("y"),
])])
},
productions[0]
);
assert_eq!(
Production {
lhs: "StartList".to_string(),
rhs: Alternations(vec![Alternation::new()
.with_factors(vec![
terminal!("r1"),
terminal!("r2"),
Factor::default_non_terminal("StartList".to_string()),
])
.with_attribute(ProductionAttribute::AddToCollection)])
},
productions[1]
);
assert_eq!(
Production {
lhs: "StartList".to_string(),
rhs: Alternations(vec![
Alternation::new().with_attribute(ProductionAttribute::CollectionStart)
])
},
productions[2]
);
}
#[test]
fn eliminate_single_rep_case_2() {
let production = Production {
lhs: "Start".to_string(),
rhs: Alternations(vec![Alternation::new().with_factors(vec![
terminal!("x"),
Factor::Repeat(Alternations(vec![
Alternation::new().with_factors(vec![terminal!("r1")]),
Alternation::new().with_factors(vec![terminal!("r2")]),
])),
terminal!("x"),
])]),
};
let productions = eliminate_single_rep(&[production.lhs.clone()], 0, production);
assert_eq!(3, productions.len());
assert_eq!(
Production {
lhs: "Start".to_string(),
rhs: Alternations(vec![Alternation::new().with_factors(vec![
terminal!("x"),
Factor::NonTerminal(
"StartList".to_string(),
SymbolAttribute::RepetitionAnchor,
None
),
terminal!("x"),
])])
},
productions[0]
);
assert_eq!(
Production {
lhs: "StartList".to_string(),
rhs: Alternations(vec![Alternation::new()
.with_factors(vec![
Factor::Group(Alternations(vec![
Alternation::new().with_factors(vec![terminal!("r1"),]),
Alternation::new().with_factors(vec![terminal!("r2"),]),
])),
Factor::default_non_terminal("StartList".to_string()),
])
.with_attribute(ProductionAttribute::AddToCollection)])
},
productions[1]
);
assert_eq!(
Production {
lhs: "StartList".to_string(),
rhs: Alternations(vec![
Alternation::new().with_attribute(ProductionAttribute::CollectionStart)
])
},
productions[2]
);
}
#[test]
fn eliminate_single_opt_case_1() {
let production = Production {
lhs: "Start".to_string(),
rhs: Alternations(vec![Alternation::new().with_factors(vec![
terminal!("x"),
Factor::Optional(Alternations(vec![
Alternation::new().with_factors(vec![terminal!("o1"), terminal!("o2")])
])),
terminal!("y"),
])]),
};
let productions = eliminate_single_opt(&[production.lhs.clone()], 0, production);
assert_eq!(2, productions.len());
assert_eq!(
Production {
lhs: "Start".to_string(),
rhs: Alternations(vec![Alternation::new().with_factors(vec![
terminal!("x"),
terminal!("o1"),
terminal!("o2"),
terminal!("y"),
])])
},
productions[0]
);
assert_eq!(
Production {
lhs: "Start".to_string(),
rhs: Alternations(vec![
Alternation::new().with_factors(vec![terminal!("x"), terminal!("y"),])
])
},
productions[1]
);
}
#[test]
fn eliminate_single_opt_case_2() {
let production = Production {
lhs: "Start".to_string(),
rhs: Alternations(vec![Alternation::new().with_factors(vec![
terminal!("x"),
Factor::Optional(Alternations(vec![
Alternation::new().with_factors(vec![terminal!("o1")]),
Alternation::new().with_factors(vec![terminal!("o2")]),
])),
terminal!("y"),
])]),
};
let productions = eliminate_single_opt(&[production.lhs.clone()], 0, production);
assert_eq!(3, productions.len());
assert_eq!(
Production {
lhs: "Start".to_string(),
rhs: Alternations(vec![Alternation::new().with_factors(vec![
terminal!("x"),
Factor::default_non_terminal("StartOpt".to_string()),
terminal!("y"),
])])
},
productions[0]
);
assert_eq!(
Production {
lhs: "Start".to_string(),
rhs: Alternations(vec![
Alternation::new().with_factors(vec![terminal!("x"), terminal!("y"),]),
])
},
productions[1]
);
assert_eq!(
Production {
lhs: "StartOpt".to_string(),
rhs: Alternations(vec![
Alternation::new().with_factors(vec![terminal!("o1")]),
Alternation::new().with_factors(vec![terminal!("o2")]),
])
},
productions[2]
);
}
#[test]
fn eliminate_single_grp_case_1() {
let production = Production {
lhs: "Start".to_string(),
rhs: Alternations(vec![Alternation::new().with_factors(vec![
terminal!("x"),
Factor::Group(Alternations(vec![
Alternation::new().with_factors(vec![terminal!("g1"), terminal!("g2")])
])),
terminal!("y"),
])]),
};
let productions = eliminate_single_grp(&[production.lhs.clone()], 0, production);
assert_eq!(1, productions.len());
assert_eq!(
Production {
lhs: "Start".to_string(),
rhs: Alternations(vec![Alternation::new().with_factors(vec![
terminal!("x"),
terminal!("g1"),
terminal!("g2"),
terminal!("y"),
])])
},
productions[0]
);
}
#[test]
fn eliminate_single_grp_case_2() {
let production = Production {
lhs: "Start".to_string(),
rhs: Alternations(vec![Alternation::new().with_factors(vec![
terminal!("x"),
Factor::Group(Alternations(vec![
Alternation::new().with_factors(vec![terminal!("g1")]),
Alternation::new().with_factors(vec![terminal!("g2")]),
])),
terminal!("y"),
])]),
};
let productions = eliminate_single_grp(&[production.lhs.clone()], 0, production);
assert_eq!(2, productions.len());
assert_eq!(
Production {
lhs: "Start".to_string(),
rhs: Alternations(vec![Alternation::new().with_factors(vec![
terminal!("x"),
Factor::default_non_terminal("StartGroup".to_string()),
terminal!("y"),
])])
},
productions[0]
);
assert_eq!(
Production {
lhs: "StartGroup".to_string(),
rhs: Alternations(vec![
Alternation::new().with_factors(vec![terminal!("g1")]),
Alternation::new().with_factors(vec![terminal!("g2")]),
])
},
productions[1]
);
}
#[test]
fn test_apply_production_transformation1() {
let mut productions = vec![
Production::new("A".to_string(), Alternations::new()),
Production::new("B".to_string(), Alternations::new()),
];
apply_production_transformation(&mut productions, 1, |_| {
vec![
Production::new("C".to_string(), Alternations::new()),
Production::new("D".to_string(), Alternations::new()),
]
});
assert_eq!(
vec![
Production::new("A".to_string(), Alternations::new()),
Production::new("C".to_string(), Alternations::new()),
Production::new("D".to_string(), Alternations::new()),
],
productions
);
}
#[test]
fn test_apply_production_transformation2() {
let mut productions = vec![
Production::new("A".to_string(), Alternations::new()),
Production::new("B".to_string(), Alternations::new()),
];
apply_production_transformation(&mut productions, 0, |_| {
vec![
Production::new("C".to_string(), Alternations::new()),
Production::new("D".to_string(), Alternations::new()),
]
});
assert_eq!(
vec![
Production::new("C".to_string(), Alternations::new()),
Production::new("D".to_string(), Alternations::new()),
Production::new("B".to_string(), Alternations::new()),
],
productions
);
}
#[test]
fn test_apply_production_transformation_no_change() {
let mut productions = vec![
Production::new("A".to_string(), Alternations::new()),
Production::new("B".to_string(), Alternations::new()),
];
apply_production_transformation(&mut productions, 0, |_| {
vec![Production::new("A".to_string(), Alternations::new())]
});
assert_eq!(
vec![
Production::new("A".to_string(), Alternations::new()),
Production::new("B".to_string(), Alternations::new()),
],
productions
);
}
#[test]
fn test_apply_production_transformation_empty_substitute() {
let mut productions = vec![
Production::new("A".to_string(), Alternations::new()),
Production::new("B".to_string(), Alternations::new()),
];
apply_production_transformation(&mut productions, 0, |_| vec![]);
assert_eq!(
vec![Production::new("B".to_string(), Alternations::new()),],
productions
);
}
}