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