Skip to main content

rexact_syntax/
ast.rs

1//! Syntax tree produced by the dialect parsers.
2//!
3//! Invariants: spans are half-open ranges of Unicode scalar offsets into the
4//! original pattern text. Code points are `u32` (not `char`) because ECMAScript
5//! without the Unicode flag can name lone surrogates such as `\uD83D`.
6
7use std::fmt;
8
9/// Half-open range `[start, end)` in scalar-value offsets.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub struct Span {
12    /// First offset covered.
13    pub start: usize,
14    /// One past the last offset covered.
15    pub end: usize,
16}
17
18/// `\d`, `\w`, `\s` and negations. Their meaning is per dialect and alphabet.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum Shorthand {
21    /// `\d`
22    Digit,
23    /// `\D`
24    NotDigit,
25    /// `\w`
26    Word,
27    /// `\W`
28    NotWord,
29    /// `\s`
30    Space,
31    /// `\S`
32    NotSpace,
33}
34
35impl Shorthand {
36    fn letter(self) -> char {
37        match self {
38            Shorthand::Digit => 'd',
39            Shorthand::NotDigit => 'D',
40            Shorthand::Word => 'w',
41            Shorthand::NotWord => 'W',
42            Shorthand::Space => 's',
43            Shorthand::NotSpace => 'S',
44        }
45    }
46}
47
48/// Which Unicode property family a `\p{...}` refers to.
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum PropKind {
51    /// General category (name is the canonical short name, e.g. `Lu`, or a group such as `L`).
52    GeneralCategory,
53    /// Script (canonical long name).
54    Script,
55    /// Script extensions (canonical long name).
56    ScriptExtensions,
57    /// Binary property (canonical long name).
58    Binary,
59}
60
61/// A resolved `\p{...}` / `\P{...}` reference: the name is already validated
62/// against the pinned Unicode data.
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub struct PropertyRef {
65    /// `\P{...}` or `\p{^...}`.
66    pub negated: bool,
67    /// Property family.
68    pub kind: PropKind,
69    /// Canonical name within the family.
70    pub name: &'static str,
71    /// Derived from a POSIX class: exempt from caseless widening (PCRE2).
72    pub exact: bool,
73}
74
75/// One member of a character class.
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub enum ClassItem {
78    /// A Unicode property.
79    Property(PropertyRef),
80    /// Inclusive range of code points (a single character has `lo == hi`).
81    Range(u32, u32),
82    /// A shorthand such as `\d`.
83    Shorthand(Shorthand),
84}
85
86/// A bracketed class, or a lone shorthand outside brackets.
87#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct Class {
89    /// `[^...]`.
90    pub negated: bool,
91    /// Members, in source order.
92    pub items: Vec<ClassItem>,
93}
94
95/// Zero-width assertions.
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97pub enum Assertion {
98    /// `^`
99    LineStart,
100    /// `$`
101    LineEnd,
102    /// `\A`
103    TextStart,
104    /// `\Z` (Python: end of text only)
105    TextEnd,
106    /// `\b`
107    WordBoundary,
108    /// `\B`
109    NotWordBoundary,
110    /// Rust `\<` or `\b{start}`: a word boundary with a word character next.
111    WordStart,
112    /// Rust `\>` or `\b{end}`: a word boundary with a word character before.
113    WordEnd,
114    /// PCRE2 `\Z`: end of text or just before a final line break.
115    TextEndOptionalNewline,
116}
117
118/// Set operators of Rust character classes.
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
120pub enum SetOp {
121    /// `&&`
122    Intersect,
123    /// `--`
124    Difference,
125    /// `~~`
126    SymmetricDifference,
127}
128
129/// A Rust bracketed class with nesting and set operations.
130#[derive(Debug, Clone, PartialEq, Eq)]
131pub enum ClassExpr {
132    /// Plain members.
133    Items(Vec<ClassItem>),
134    /// Union of operands.
135    Union(Vec<ClassExpr>),
136    /// `[...]` with optional negation applied after evaluating the inside.
137    Bracket {
138        /// `[^...]`.
139        negated: bool,
140        /// Contents.
141        inner: Box<ClassExpr>,
142    },
143    /// `lhs op rhs`, left-associative.
144    Op(SetOp, Box<ClassExpr>, Box<ClassExpr>),
145}
146
147/// Scoped inline flag changes, `(?i-m:...)`.
148#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
149pub struct FlagDelta {
150    /// Letters switched on.
151    pub on: crate::flags::Flags,
152    /// Letters switched off.
153    pub off: crate::flags::Flags,
154}
155
156/// Node shape.
157#[derive(Debug, Clone, PartialEq, Eq)]
158pub enum NodeKind {
159    /// Matches the empty string.
160    Empty,
161    /// One code point.
162    Literal(u32),
163    /// `.`
164    Any,
165    /// Class or shorthand.
166    Class(Class),
167    /// Class with nesting or set operations (Rust).
168    ClassSet(ClassExpr),
169    /// Sequence.
170    Concat(Vec<Node>),
171    /// Alternation, in source order.
172    Alt(Vec<Node>),
173    /// Repetition. `max == None` is unbounded.
174    Repeat {
175        /// Repeated node.
176        node: Box<Node>,
177        /// Minimum count.
178        min: u64,
179        /// Maximum count.
180        max: Option<u64>,
181        /// Greedy (true) or lazy (false); same language, different spans.
182        greedy: bool,
183    },
184    /// Group. Capture status does not affect the language but is kept.
185    Group {
186        /// Capture index when capturing.
187        capture: Option<u32>,
188        /// Group name when named.
189        name: Option<String>,
190        /// Body.
191        node: Box<Node>,
192    },
193    /// Group with scoped flag changes.
194    FlagGroup {
195        /// The changes.
196        delta: FlagDelta,
197        /// Body.
198        node: Box<Node>,
199    },
200    /// Zero-width assertion.
201    Assert(Assertion),
202    /// Lookahead or lookbehind.
203    Look {
204        /// Lookahead (true) or lookbehind (false).
205        ahead: bool,
206        /// Negative form.
207        negate: bool,
208        /// Body.
209        node: Box<Node>,
210    },
211}
212
213/// A node with its source span.
214#[derive(Debug, Clone, PartialEq, Eq)]
215pub struct Node {
216    /// Shape.
217    pub kind: NodeKind,
218    /// Source range.
219    pub span: Span,
220}
221
222impl fmt::Display for Node {
223    /// Compact S-expression form, used by tests and `explain`.
224    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225        match &self.kind {
226            NodeKind::Empty => write!(f, "eps"),
227            NodeKind::Literal(c) => match char::from_u32(*c) {
228                Some(ch) if ch.is_ascii_graphic() => write!(f, "{ch}"),
229                _ => write!(f, "U+{c:04X}"),
230            },
231            NodeKind::Any => write!(f, "any"),
232            NodeKind::Class(c) => {
233                write!(f, "[{}", if c.negated { "^" } else { "" })?;
234                for (i, it) in c.items.iter().enumerate() {
235                    if i > 0 {
236                        write!(f, " ")?;
237                    }
238                    match it {
239                        ClassItem::Range(a, b) if a == b => write!(f, "{a:X}")?,
240                        ClassItem::Range(a, b) => write!(f, "{a:X}-{b:X}")?,
241                        ClassItem::Shorthand(s) => write!(f, "\\{}", s.letter())?,
242                        ClassItem::Property(p) => write!(
243                            f,
244                            "\\{}{{{:?}:{}}}",
245                            if p.negated { "P" } else { "p" },
246                            p.kind,
247                            p.name
248                        )?,
249                    }
250                }
251                write!(f, "]")
252            }
253            NodeKind::ClassSet(e) => write!(f, "[set {e:?}]"),
254            NodeKind::Concat(v) | NodeKind::Alt(v) => {
255                let tag = if matches!(self.kind, NodeKind::Concat(_)) {
256                    "cat"
257                } else {
258                    "alt"
259                };
260                write!(f, "({tag}")?;
261                for n in v {
262                    write!(f, " {n}")?;
263                }
264                write!(f, ")")
265            }
266            NodeKind::Repeat {
267                node,
268                min,
269                max,
270                greedy,
271            } => {
272                let max = max.map_or("inf".to_string(), |m| m.to_string());
273                write!(
274                    f,
275                    "(rep{} {min} {max} {node})",
276                    if *greedy { "" } else { "?" }
277                )
278            }
279            NodeKind::Group {
280                capture,
281                name,
282                node,
283            } => match (capture, name) {
284                (Some(i), Some(n)) => write!(f, "(group#{i}:{n} {node})"),
285                (Some(i), None) => write!(f, "(group#{i} {node})"),
286                _ => write!(f, "(ncgroup {node})"),
287            },
288            NodeKind::FlagGroup { node, .. } => write!(f, "(flags {node})"),
289            NodeKind::Assert(a) => write!(f, "{a:?}"),
290            NodeKind::Look {
291                ahead,
292                negate,
293                node,
294            } => {
295                let d = if *ahead { "ahead" } else { "behind" };
296                write!(f, "(look{}{} {node})", d, if *negate { "!" } else { "" })
297            }
298        }
299    }
300}