• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // pest. The Elegant Parser
2 // Copyright (c) 2018 Dragoș Tiselice
3 //
4 // Licensed under the Apache License, Version 2.0
5 // <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
6 // license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7 // option. All files in the project carrying such notice may not be copied,
8 // modified, or distributed except according to those terms.
9 
10 use crate::ast::*;
11 
factor(rule: Rule) -> Rule12 pub fn factor(rule: Rule) -> Rule {
13     let Rule { name, ty, expr } = rule;
14     Rule {
15         name,
16         ty,
17         expr: expr.map_top_down(|expr| {
18             match expr {
19                 Expr::Choice(lhs, rhs) => match (*lhs, *rhs) {
20                     (Expr::Seq(l1, r1), Expr::Seq(l2, r2)) => {
21                         if l1 == l2 {
22                             Expr::Seq(l1, Box::new(Expr::Choice(r1, r2)))
23                         } else {
24                             Expr::Choice(Box::new(Expr::Seq(l1, r1)), Box::new(Expr::Seq(l2, r2)))
25                         }
26                     }
27                     // Converts `(rule ~ rest) | rule` to `rule ~ rest?`, avoiding trying to match `rule` twice.
28                     // This is only done for atomic rules, because other rule types have implicit whitespaces.
29                     // FIXME: "desugar" implicit whitespace rules before applying any optimizations
30                     (Expr::Seq(l1, l2), r)
31                         if matches!(ty, RuleType::Atomic | RuleType::CompoundAtomic) =>
32                     {
33                         if *l1 == r {
34                             Expr::Seq(l1, Box::new(Expr::Opt(l2)))
35                         } else {
36                             Expr::Choice(Box::new(Expr::Seq(l1, l2)), Box::new(r))
37                         }
38                     }
39                     // Converts `rule | (rule ~ rest)` to `rule` since `(rule ~ rest)`
40                     // will never match if `rule` didn't.
41                     (l, Expr::Seq(r1, r2)) => {
42                         if l == *r1 {
43                             l
44                         } else {
45                             Expr::Choice(Box::new(l), Box::new(Expr::Seq(r1, r2)))
46                         }
47                     }
48                     (lhs, rhs) => Expr::Choice(Box::new(lhs), Box::new(rhs)),
49                 },
50                 expr => expr,
51             }
52         }),
53     }
54 }
55