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}