Skip to main content

ast_visitor/
ast_visitor.rs

1//! # AST Visitor
2//!
3//! Shows how to walk the parsed AST recursively.  Two visitors are implemented:
4//!
5//! 1. **`count_nodes`** — counts every node in the tree.
6//! 2. **`collect_literals`** — harvests every literal character in order,
7//!    which lets you reconstruct the "fixed" parts of a pattern.
8//!
9//! Run with:
10//!   cargo run -p re-parser --example ast_visitor
11
12use re_parser::ast::{CharClassItem, Regex};
13use re_parser::parse;
14
15// visitor 1: count nodes
16
17fn count_nodes(node: &Regex) -> usize {
18    match node {
19        Regex::Literal(_) | Regex::AnyChar | Regex::Anchor(_) | Regex::EscapeClass(_) => 1,
20        Regex::CharClass(_) => 1,
21        Regex::Group(inner, _) => 1 + count_nodes(inner),
22        Regex::Quantifier(inner, _, _) => 1 + count_nodes(inner),
23        Regex::Concat(nodes) | Regex::Alternation(nodes) => {
24            1 + nodes.iter().map(count_nodes).sum::<usize>()
25        }
26    }
27}
28
29// visitor 2: collect literal characters (left-to-right, depth-first)
30
31fn collect_literals(node: &Regex, out: &mut Vec<char>) {
32    match node {
33        Regex::Literal(c) => out.push(*c),
34        Regex::AnyChar | Regex::Anchor(_) | Regex::EscapeClass(_) => {}
35        Regex::CharClass(cls) => {
36            // Collect literal items inside the class
37            for item in &cls.items {
38                if let CharClassItem::Literal(c) = item {
39                    out.push(*c);
40                }
41            }
42        }
43        Regex::Group(inner, _) => collect_literals(inner, out),
44        Regex::Quantifier(inner, _, _) => collect_literals(inner, out),
45        Regex::Concat(nodes) | Regex::Alternation(nodes) => {
46            for n in nodes {
47                collect_literals(n, out);
48            }
49        }
50    }
51}
52
53// visitor 3: pretty-print with indentation
54
55fn pretty_print(node: &Regex, indent: usize) {
56    let pad = "  ".repeat(indent);
57    match node {
58        Regex::Literal(c) => println!("{pad}Literal({c:?})"),
59        Regex::AnyChar => println!("{pad}AnyChar"),
60        Regex::Anchor(a) => println!("{pad}Anchor({a:?})"),
61        Regex::EscapeClass(ec) => println!("{pad}EscapeClass({ec:?})"),
62        Regex::CharClass(cls) => {
63            let neg = if cls.negated { "negated" } else { "" };
64            println!("{pad}CharClass[{neg}] ({} items)", cls.items.len());
65        }
66        Regex::Group(inner, kind) => {
67            println!("{pad}Group({kind:?})");
68            pretty_print(inner, indent + 1);
69        }
70        Regex::Quantifier(inner, kind, greedy) => {
71            let mode = if *greedy { "greedy" } else { "lazy" };
72            println!("{pad}Quantifier({kind:?}, {mode})");
73            pretty_print(inner, indent + 1);
74        }
75        Regex::Concat(nodes) => {
76            println!("{pad}Concat ({} children)", nodes.len());
77            for n in nodes {
78                pretty_print(n, indent + 1);
79            }
80        }
81        Regex::Alternation(nodes) => {
82            println!("{pad}Alternation ({} branches)", nodes.len());
83            for n in nodes {
84                pretty_print(n, indent + 1);
85            }
86        }
87    }
88}
89
90fn analyse(pattern: &str) {
91    println!("┌─ Pattern: {pattern}");
92    let ast = parse(pattern).unwrap();
93
94    let total = count_nodes(&ast);
95    let mut lits = Vec::new();
96    collect_literals(&ast, &mut lits);
97    let lit_str: String = lits.iter().collect();
98
99    println!("│  nodes:    {total}");
100    println!("│  literals: {lit_str:?}");
101    println!("└─ tree:");
102    pretty_print(&ast, 1);
103    println!();
104}
105
106fn main() {
107    analyse(r"\d{4}-\d{2}-\d{2}");          // ISO date skeleton
108    analyse(r"(?P<proto>https?)://\S+");      // simple URL prefix
109    analyse(r"^[A-Z][a-z]+(?: [A-Z][a-z]+)*$");  // capitalized words
110}