pomsky/exprs/
mod.rs

1use crate::{
2    capturing_groups::CapturingGroupsCollector,
3    compile::{CompileResult, CompileState},
4    diagnose::{CompileErrorKind, Diagnostic},
5    options::CompileOptions,
6    regex::Count,
7    validation::Validator,
8    visitor::RuleVisitor,
9};
10
11pub(crate) mod alternation;
12pub(crate) mod boundary;
13pub(crate) mod char_class;
14pub(crate) mod codepoint;
15pub(crate) mod dot;
16pub(crate) mod grapheme;
17pub(crate) mod group;
18pub(crate) mod intersection;
19pub(crate) mod literal;
20pub(crate) mod lookaround;
21pub(crate) mod range;
22pub(crate) mod recursion;
23pub(crate) mod reference;
24pub(crate) mod regex;
25pub(crate) mod repetition;
26pub(crate) mod rule;
27pub(crate) mod stmt;
28pub(crate) mod var;
29
30use pomsky_syntax::Span;
31use pomsky_syntax::exprs::{test::Test, *};
32
33pub(crate) trait Compile {
34    fn compile<'c>(
35        &'c self,
36        options: CompileOptions,
37        state: &mut CompileState<'c>,
38    ) -> CompileResult;
39}
40
41/// A parsed pomsky expression, which might contain more sub-expressions.
42#[derive(Clone)]
43#[cfg_attr(not(feature = "dbg"), derive(Debug))]
44#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
45pub struct Expr(Rule);
46
47impl Expr {
48    /// Parse a `Expr` without generating code.
49    ///
50    /// The parsed `Expr` can be displayed with `Debug` if the `dbg` feature is
51    /// enabled.
52    pub fn parse(input: &str) -> (Option<Self>, impl Iterator<Item = Diagnostic> + '_) {
53        let (rule, diagnostics) = pomsky_syntax::parse(input, 256);
54        (rule.map(Expr), diagnostics.into_iter().map(|d| Diagnostic::from_parser(&d, input)))
55    }
56
57    /// Compile a `Expr` that has been parsed, to a regex
58    pub fn compile(
59        &self,
60        input: &str,
61        options: CompileOptions,
62    ) -> (Option<String>, Vec<Diagnostic>) {
63        let mut validator = Validator::new(options);
64        if let Err(e) = validator.visit_rule(&self.0) {
65            return (None, vec![e.diagnostic(input)]);
66        }
67
68        let mut capt_groups = CapturingGroupsCollector::new();
69        if let Err(e) = capt_groups.visit_rule(&self.0) {
70            return (None, vec![e.diagnostic(input)]);
71        }
72
73        let no_span = Span::empty();
74
75        let start = Rule::Boundary(Boundary::new(BoundaryKind::Start, true, no_span));
76        let end = Rule::Boundary(Boundary::new(BoundaryKind::End, true, no_span));
77        let grapheme = Rule::Grapheme;
78        let codepoint = Rule::Codepoint;
79
80        let builtins = vec![
81            ("Start", &start),
82            ("End", &end),
83            ("Grapheme", &grapheme),
84            ("G", &grapheme),
85            ("Codepoint", &codepoint),
86            ("C", &codepoint),
87        ];
88
89        let mut state = CompileState::new(capt_groups, builtins);
90        let mut compiled = match self.0.compile(options, &mut state) {
91            Ok(compiled) => compiled,
92            Err(e) => return (None, vec![e.diagnostic(input)]),
93        };
94        if let Some(rec_span) = validator.first_recursion
95            && !compiled.terminates()
96        {
97            let error = CompileErrorKind::InfiniteRecursion.at(rec_span);
98            return (None, vec![error.diagnostic(input)]);
99        }
100        let count = compiled.optimize();
101
102        let mut buf = String::new();
103        if count != Count::Zero {
104            compiled.codegen(&mut buf, options.flavor);
105        }
106        (Some(buf), state.diagnostics)
107    }
108
109    /// Extracts top-level all unit tests from the Pomsky expression
110    pub fn extract_tests(self) -> Vec<Test> {
111        let mut rule = self.0;
112        let mut tests = Vec::new();
113        while let Rule::StmtExpr(expr) = rule {
114            if let Stmt::Test(test) = expr.stmt {
115                tests.push(test);
116            }
117            rule = expr.rule;
118        }
119        tests
120    }
121
122    /// Extracts top-level all unit tests from the Pomsky expression
123    pub fn extract_tests_ref(&self) -> Vec<&Test> {
124        let mut rule = &self.0;
125        let mut tests = Vec::new();
126        while let Rule::StmtExpr(expr) = rule {
127            if let Stmt::Test(test) = &expr.stmt {
128                tests.push(test);
129            }
130            rule = &expr.rule;
131        }
132        tests
133    }
134
135    /// Parse a string to a `Expr` and compile it to a regex.
136    pub fn parse_and_compile(
137        input: &str,
138        options: CompileOptions,
139    ) -> (Option<String>, Vec<Diagnostic>, Vec<Test>) {
140        match Self::parse(input) {
141            (Some(parsed), warnings1) => match parsed.compile(input, options) {
142                (Some(compiled), warnings2) => {
143                    let mut diagnostics =
144                        Vec::with_capacity(warnings1.size_hint().0 + warnings2.len());
145                    diagnostics.extend(warnings1);
146                    diagnostics.extend(warnings2);
147                    (Some(compiled), diagnostics, parsed.extract_tests())
148                }
149                (None, errors) => {
150                    let mut diagnostics =
151                        Vec::with_capacity(warnings1.size_hint().0 + errors.len());
152                    diagnostics.extend(errors);
153                    diagnostics.extend(warnings1);
154                    (None, diagnostics, parsed.extract_tests())
155                }
156            },
157            (None, diagnostics) => (None, diagnostics.collect(), vec![]),
158        }
159    }
160}
161
162#[cfg(feature = "dbg")]
163impl core::fmt::Debug for Expr {
164    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
165        if f.alternate() {
166            core::fmt::Debug::fmt(&self.0, f)
167        } else {
168            core::fmt::Display::fmt(&self.0, f)
169        }
170    }
171}