diff --git a/meta/src/optimizer/mod.rs b/meta/src/optimizer/mod.rs index e1cc263b..b3bea513 100644 --- a/meta/src/optimizer/mod.rs +++ b/meta/src/optimizer/mod.rs @@ -30,10 +30,11 @@ mod unroller; /// Takes pest's ASTs and optimizes them pub fn optimize(rules: Vec) -> Vec { + let map = to_hash_map(&rules); let optimized: Vec = rules .into_iter() .map(rotater::rotate) - .map(skipper::skip) + .map(|rule| skipper::skip(rule, &map)) .map(unroller::unroll) .map(concatenator::concatenate) .map(factorizer::factor) @@ -41,10 +42,10 @@ pub fn optimize(rules: Vec) -> Vec { .map(rule_to_optimized_rule) .collect(); - let rules = to_hash_map(&optimized); + let optimized_map = to_optimized_hash_map(&optimized); optimized .into_iter() - .map(|rule| restorer::restore_on_err(rule, &rules)) + .map(|rule| restorer::restore_on_err(rule, &optimized_map)) .collect() } @@ -87,12 +88,18 @@ fn rule_to_optimized_rule(rule: Rule) -> OptimizedRule { } } -fn to_hash_map(rules: &[OptimizedRule]) -> HashMap { - rules - .iter() - .map(|r| (r.name.clone(), r.expr.clone())) - .collect() +macro_rules! to_hash_map { + ($func_name:ident, $rule:ty, $expr:ty) => { + fn $func_name(rules: &[$rule]) -> HashMap { + rules + .iter() + .map(|r| (r.name.clone(), r.expr.clone())) + .collect() + } + }; } +to_hash_map!(to_hash_map, Rule, Expr); +to_hash_map!(to_optimized_hash_map, OptimizedRule, OptimizedExpr); /// The optimized version of the pest AST's `Rule`. #[derive(Clone, Debug, Eq, PartialEq)] @@ -1040,6 +1047,37 @@ mod tests { ); } + #[test] + fn inline_skip() { + use crate::ast::Expr::*; + let rules = vec![ + Rule { + name: "inline".to_owned(), + ty: RuleType::Atomic, + expr: Str("a".to_owned()), + }, + Rule { + name: "skip".to_owned(), + ty: RuleType::Atomic, + expr: box_tree!(Rep(Seq( + NegPred(Choice( + Ident(String::from("inline")), + Str(String::from("b")) + )), + Ident("ANY".to_owned()) + ))), + }, + ]; + let map = to_hash_map(&rules); + let rule = skipper::skip(rules[1].clone(), &map); + assert!(matches!(rule, Rule { expr: Skip(..), .. })); + let choices = match rule.expr { + Skip(choices) => choices, + _ => unreachable!(), + }; + assert_eq!(choices, vec!["a".to_owned(), "b".to_owned()]); + } + #[test] fn push() { assert_eq!( diff --git a/meta/src/optimizer/restorer.rs b/meta/src/optimizer/restorer.rs index e128e03f..47c28ed7 100644 --- a/meta/src/optimizer/restorer.rs +++ b/meta/src/optimizer/restorer.rs @@ -103,7 +103,7 @@ mod tests { }]; assert_eq!( - restore_on_err(rules[0].clone(), &to_hash_map(&rules)), + restore_on_err(rules[0].clone(), &to_optimized_hash_map(&rules)), rules[0].clone() ); } @@ -123,7 +123,7 @@ mod tests { }; assert_eq!( - restore_on_err(rules[0].clone(), &to_hash_map(&rules)), + restore_on_err(rules[0].clone(), &to_optimized_hash_map(&rules)), restored ); } @@ -146,7 +146,7 @@ mod tests { }; assert_eq!( - restore_on_err(rules[0].clone(), &to_hash_map(&rules)), + restore_on_err(rules[0].clone(), &to_optimized_hash_map(&rules)), restored ); } diff --git a/meta/src/optimizer/skipper.rs b/meta/src/optimizer/skipper.rs index 8300309b..46033dba 100644 --- a/meta/src/optimizer/skipper.rs +++ b/meta/src/optimizer/skipper.rs @@ -7,15 +7,32 @@ // option. All files in the project carrying such notice may not be copied, // modified, or distributed except according to those terms. +use std::collections::HashMap; + use crate::ast::*; -pub fn skip(rule: Rule) -> Rule { - fn populate_choices(expr: Expr, mut choices: Vec) -> Option { +pub fn skip(rule: Rule, map: &HashMap) -> Rule { + fn populate_choices( + expr: Expr, + map: &HashMap, + mut choices: Vec, + ) -> Option { match expr { Expr::Choice(lhs, rhs) => { if let Expr::Str(string) = *lhs { choices.push(string); - populate_choices(*rhs, choices) + populate_choices(*rhs, map, choices) + } else if let Expr::Ident(name) = *lhs { + // Try inlining rule in choices + if let Some(Expr::Skip(mut inlined_choices)) = map + .get(&name) + .and_then(|expr| populate_choices(expr.clone(), map, vec![])) + { + choices.append(&mut inlined_choices); + populate_choices(*rhs, map, choices) + } else { + None + } } else { None } @@ -24,6 +41,10 @@ pub fn skip(rule: Rule) -> Rule { choices.push(string); Some(Expr::Skip(choices)) } + // Try inlining single rule + Expr::Ident(name) => map + .get(&name) + .and_then(|expr| populate_choices(expr.clone(), map, choices)), _ => None, } } @@ -38,7 +59,7 @@ pub fn skip(rule: Rule) -> Rule { if let Expr::Seq(lhs, rhs) = *expr { if let (Expr::NegPred(expr), Expr::Ident(ident)) = (*lhs, *rhs) { if ident == "ANY" { - if let Some(expr) = populate_choices(*expr, vec![]) { + if let Some(expr) = populate_choices(*expr, map, vec![]) { return expr; } }