Skip to main content

rgx/explain/
mod.rs

1pub mod formatter;
2pub mod visitor;
3
4use regex_syntax::ast::parse::Parser;
5use visitor::ExplainVisitor;
6
7#[derive(Debug, Clone)]
8pub struct ExplainNode {
9    pub depth: usize,
10    pub description: String,
11}
12
13pub fn explain(pattern: &str) -> Result<Vec<ExplainNode>, String> {
14    if pattern.is_empty() {
15        return Ok(vec![]);
16    }
17
18    let ast = Parser::new()
19        .parse(pattern)
20        .map_err(|e| format!("Parse error: {e}"))?;
21
22    let mut visitor = ExplainVisitor::new();
23    visitor.visit(&ast);
24    Ok(visitor.into_nodes())
25}
26
27#[cfg(test)]
28mod tests {
29    use super::*;
30
31    #[test]
32    fn test_empty_pattern() {
33        let result = explain("").unwrap();
34        assert!(result.is_empty());
35    }
36
37    #[test]
38    fn test_simple_literal() {
39        let result = explain("hello").unwrap();
40        assert!(!result.is_empty());
41    }
42
43    #[test]
44    fn test_digit_class() {
45        let result = explain(r"\d+").unwrap();
46        assert!(!result.is_empty());
47        let text: String = result.iter().map(|n| n.description.clone()).collect();
48        assert!(text.to_lowercase().contains("digit"));
49    }
50
51    #[test]
52    fn test_capture_group() {
53        let result = explain(r"(\w+)@(\w+)").unwrap();
54        assert!(!result.is_empty());
55        let text: String = result.iter().map(|n| n.description.clone()).collect();
56        assert!(text.to_lowercase().contains("group"));
57    }
58
59    #[test]
60    fn test_invalid_pattern() {
61        let result = explain(r"(unclosed");
62        assert!(result.is_err());
63    }
64}