Skip to main content

real_world_patterns/
real_world_patterns.rs

1//! # Real-world Patterns
2//!
3//! Parses several common real-world regex patterns and prints a summary of
4//! what each top-level AST node represents, showing how a real tool could use
5//! this library to analyse or validate patterns before compiling them.
6//!
7//! Run with:
8//!   cargo run -p re-parser --example real_world_patterns
9
10use re_parser::ast::{GroupKind, Regex};
11use re_parser::parse;
12
13/// Recursively counts capturing groups (both numbered and named).
14fn count_captures(node: &Regex) -> usize {
15    match node {
16        Regex::Group(inner, kind) => {
17            let self_count = matches!(kind, GroupKind::Capturing | GroupKind::Named(_)) as usize;
18            self_count + count_captures(inner)
19        }
20        Regex::Quantifier(inner, _, _) => count_captures(inner),
21        Regex::Concat(nodes) | Regex::Alternation(nodes) => {
22            nodes.iter().map(count_captures).sum()
23        }
24        _ => 0,
25    }
26}
27
28/// Returns true if the pattern is anchored at both ends (^ ... $).
29fn is_fully_anchored(node: &Regex) -> bool {
30    let Regex::Concat(nodes) = node else {
31        return false;
32    };
33    let first = nodes.first().map(|n| matches!(n, Regex::Anchor(re_parser::ast::Anchor::Start)));
34    let last = nodes.last().map(|n| matches!(n, Regex::Anchor(re_parser::ast::Anchor::End)));
35    first.unwrap_or(false) && last.unwrap_or(false)
36}
37
38/// Collect names of named groups.
39fn named_groups(node: &Regex, out: &mut Vec<String>) {
40    match node {
41        Regex::Group(inner, GroupKind::Named(name)) => {
42            out.push(name.clone());
43            named_groups(inner, out);
44        }
45        Regex::Group(inner, _) => named_groups(inner, out),
46        Regex::Quantifier(inner, _, _) => named_groups(inner, out),
47        Regex::Concat(nodes) | Regex::Alternation(nodes) => {
48            for n in nodes {
49                named_groups(n, out);
50            }
51        }
52        _ => {}
53    }
54}
55
56fn report(label: &str, pattern: &str) {
57    println!("── {label} ──");
58    println!("   pattern  : {pattern}");
59
60    match parse(pattern) {
61        Err(e) => println!("   ERROR     : {e}"),
62        Ok(ast) => {
63            let captures = count_captures(&ast);
64            let anchored = is_fully_anchored(&ast);
65            let mut names = Vec::new();
66            named_groups(&ast, &mut names);
67
68            println!("   captures  : {captures}");
69            println!("   anchored  : {anchored}");
70            if !names.is_empty() {
71                println!("   named     : {}", names.join(", "));
72            }
73        }
74    }
75    println!();
76}
77
78fn main() {
79    report(
80        "IPv4 address",
81        r"^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$",
82    );
83
84    report(
85        "ISO 8601 date",
86        r"^(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})$",
87    );
88
89    report(
90        "Email (simplified)",
91        r"^[\w.+-]+@[\w-]+\.[\w.]+$",
92    );
93
94    report(
95        "Semantic version",
96        r"^(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<pre>[a-zA-Z0-9.]+))?$",
97    );
98
99    report(
100        "HTTP method",
101        r"^(?:GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)$",
102    );
103
104    report(
105        "Hex colour",
106        r"^#(?:[0-9a-fA-F]{3}){1,2}$",
107    );
108
109    report(
110        "URL (basic)",
111        r"^(?P<scheme>https?)://(?P<host>[^/]+)(?P<path>/[^\s]*)?$",
112    );
113
114    // Example: intentionally broken pattern
115    report("Invalid escape", r"^\zip$");
116}