use std::collections::HashMap;
use crate::optimizer::*;
pub fn restore_on_err(
rule: OptimizedRule,
rules: &HashMap<String, OptimizedExpr>,
) -> OptimizedRule {
let OptimizedRule { name, ty, expr } = rule;
let expr = expr.map_bottom_up(|expr| wrap_branching_exprs(expr, rules));
OptimizedRule { name, ty, expr }
}
fn wrap_branching_exprs(
expr: OptimizedExpr,
rules: &HashMap<String, OptimizedExpr>,
) -> OptimizedExpr {
match expr {
OptimizedExpr::Opt(expr) => {
if child_modifies_state(&expr, rules, &mut HashMap::new()) {
OptimizedExpr::Opt(Box::new(OptimizedExpr::RestoreOnErr(expr)))
} else {
OptimizedExpr::Opt(expr)
}
}
OptimizedExpr::Choice(lhs, rhs) => {
let wrapped_lhs = if child_modifies_state(&lhs, rules, &mut HashMap::new()) {
Box::new(OptimizedExpr::RestoreOnErr(lhs))
} else {
lhs
};
let wrapped_rhs = if child_modifies_state(&rhs, rules, &mut HashMap::new()) {
Box::new(OptimizedExpr::RestoreOnErr(rhs))
} else {
rhs
};
OptimizedExpr::Choice(wrapped_lhs, wrapped_rhs)
}
OptimizedExpr::Rep(expr) => {
if child_modifies_state(&expr, rules, &mut HashMap::new()) {
OptimizedExpr::Rep(Box::new(OptimizedExpr::RestoreOnErr(expr)))
} else {
OptimizedExpr::Rep(expr)
}
}
_ => expr,
}
}
fn child_modifies_state(
expr: &OptimizedExpr,
rules: &HashMap<String, OptimizedExpr>,
cache: &mut HashMap<String, Option<bool>>,
) -> bool {
expr.iter_top_down().any(|expr| match expr {
OptimizedExpr::Push(_) => true,
OptimizedExpr::Ident(ref name) if name == "DROP" => true,
OptimizedExpr::Ident(ref name) if name == "POP" => true,
OptimizedExpr::Ident(ref name) => match cache.get(name).cloned() {
Some(option) => match option {
Some(cached) => cached,
None => {
cache.insert(name.to_owned(), Some(false));
false
}
},
None => {
cache.insert(name.to_owned(), None);
let result = match rules.get(name) {
Some(expr) => child_modifies_state(expr, rules, cache),
None => false,
};
cache.insert(name.to_owned(), Some(result));
result
}
},
_ => false,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::optimizer::OptimizedExpr::*;
#[test]
fn restore_no_stack_children() {
let rules = vec![OptimizedRule {
name: "rule".to_owned(),
ty: RuleType::Normal,
expr: box_tree!(Opt(Str("a".to_string()))),
}];
assert_eq!(
restore_on_err(rules[0].clone(), &to_hash_map(&rules)),
rules[0].clone()
);
}
#[test]
fn restore_with_child_stack_ops() {
let rules = vec![OptimizedRule {
name: "rule".to_owned(),
ty: RuleType::Normal,
expr: box_tree!(Rep(Push(Str("a".to_string())))),
}];
let restored = OptimizedRule {
name: "rule".to_owned(),
ty: RuleType::Normal,
expr: box_tree!(Rep(RestoreOnErr(Push(Str("a".to_string()))))),
};
assert_eq!(
restore_on_err(rules[0].clone(), &to_hash_map(&rules)),
restored
);
}
#[test]
fn restore_choice_branch_with_and_branch_without() {
let rules = vec![OptimizedRule {
name: "rule".to_owned(),
ty: RuleType::Normal,
expr: box_tree!(Choice(Push(Str("a".to_string())), Str("a".to_string()))),
}];
let restored = OptimizedRule {
name: "rule".to_owned(),
ty: RuleType::Normal,
expr: box_tree!(Choice(
RestoreOnErr(Push(Str("a".to_string()))),
Str("a".to_string())
)),
};
assert_eq!(
restore_on_err(rules[0].clone(), &to_hash_map(&rules)),
restored
);
}
}