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, Option<usize>)> {
14    if pattern.is_empty() {
15        return Ok(vec![]);
16    }
17
18    let ast = Parser::new().parse(pattern).map_err(|e| {
19        let offset = pattern[..e.span().start.offset].chars().count();
20        (format!("Parse error: {e}"), Some(offset))
21    })?;
22
23    let mut visitor = ExplainVisitor::new();
24    visitor.visit(&ast);
25    Ok(visitor.into_nodes())
26}
27
28#[cfg(test)]
29mod tests {
30    use super::*;
31
32    #[test]
33    fn test_empty_pattern() {
34        let result = explain("").unwrap();
35        assert!(result.is_empty());
36    }
37
38    #[test]
39    fn test_simple_literal() {
40        let result = explain("hello").unwrap();
41        assert!(!result.is_empty());
42    }
43
44    #[test]
45    fn test_digit_class() {
46        let result = explain(r"\d+").unwrap();
47        assert!(!result.is_empty());
48        let text: String = result.iter().map(|n| n.description.clone()).collect();
49        assert!(text.to_lowercase().contains("digit"));
50    }
51
52    #[test]
53    fn test_capture_group() {
54        let result = explain(r"(\w+)@(\w+)").unwrap();
55        assert!(!result.is_empty());
56        let text: String = result.iter().map(|n| n.description.clone()).collect();
57        assert!(text.to_lowercase().contains("group"));
58    }
59
60    #[test]
61    fn test_invalid_pattern() {
62        let result = explain(r"(unclosed");
63        assert!(result.is_err());
64        let (msg, offset) = result.unwrap_err();
65        assert!(msg.contains("Parse error"));
66        assert!(offset.is_some());
67    }
68}