plotnik_lib/parser/
ast.rs

1//! Typed AST wrappers over CST nodes.
2//!
3//! Each struct wraps a `SyntaxNode` and provides typed accessors.
4//! Cast is infallible for correct `SyntaxKind` - validation happens elsewhere.
5//!
6//! ## String Lifetime Limitation
7//!
8//! `SyntaxToken::text()` returns `&str` tied to the token's lifetime, not to the
9//! source `&'src str`. This is a rowan design: tokens store interned strings, not
10//! spans into the original source.
11//!
12//! When building data structures that need source-lifetime strings (e.g.,
13//! `SymbolTable<'src>`), use [`token_src`] instead of `token.text()`.
14
15use super::cst::{SyntaxKind, SyntaxNode, SyntaxToken};
16use rowan::TextRange;
17
18/// Extracts token text with source lifetime.
19///
20/// Use this instead of `token.text()` when you need `&'src str`.
21pub fn token_src<'src>(token: &SyntaxToken, source: &'src str) -> &'src str {
22    let range = token.text_range();
23    &source[range.start().into()..range.end().into()]
24}
25
26macro_rules! ast_node {
27    ($name:ident, $kind:ident) => {
28        #[derive(Debug, Clone, PartialEq, Eq, Hash)]
29        pub struct $name(SyntaxNode);
30
31        impl $name {
32            pub fn cast(node: SyntaxNode) -> Option<Self> {
33                (node.kind() == SyntaxKind::$kind).then(|| Self(node))
34            }
35
36            pub fn as_cst(&self) -> &SyntaxNode {
37                &self.0
38            }
39
40            pub fn text_range(&self) -> TextRange {
41                self.0.text_range()
42            }
43        }
44    };
45}
46
47macro_rules! define_expr {
48    ($($variant:ident),+ $(,)?) => {
49        /// Expression: any pattern that can appear in the tree.
50        #[derive(Debug, Clone, PartialEq, Eq, Hash)]
51        pub enum Expr {
52            $($variant($variant)),+
53        }
54
55        impl Expr {
56            pub fn cast(node: SyntaxNode) -> Option<Self> {
57                $(if let Some(n) = $variant::cast(node.clone()) { return Some(Expr::$variant(n)); })+
58                None
59            }
60
61            pub fn as_cst(&self) -> &SyntaxNode {
62                match self { $(Expr::$variant(n) => n.as_cst()),+ }
63            }
64
65            pub fn text_range(&self) -> TextRange {
66                match self { $(Expr::$variant(n) => n.text_range()),+ }
67            }
68        }
69    };
70}
71
72impl Expr {
73    /// Returns direct child expressions.
74    pub fn children(&self) -> Vec<Expr> {
75        match self {
76            Expr::NamedNode(n) => n.children().collect(),
77            Expr::SeqExpr(s) => s.children().collect(),
78            Expr::CapturedExpr(c) => c.inner().into_iter().collect(),
79            Expr::QuantifiedExpr(q) => q.inner().into_iter().collect(),
80            Expr::FieldExpr(f) => f.value().into_iter().collect(),
81            Expr::AltExpr(a) => a.branches().filter_map(|b| b.body()).collect(),
82            Expr::Ref(_) | Expr::AnonymousNode(_) => vec![],
83        }
84    }
85}
86
87ast_node!(Root, Root);
88ast_node!(Def, Def);
89ast_node!(NamedNode, Tree);
90ast_node!(Ref, Ref);
91ast_node!(AltExpr, Alt);
92ast_node!(Branch, Branch);
93ast_node!(SeqExpr, Seq);
94ast_node!(CapturedExpr, Capture);
95ast_node!(Type, Type);
96ast_node!(QuantifiedExpr, Quantifier);
97ast_node!(FieldExpr, Field);
98ast_node!(NegatedField, NegatedField);
99ast_node!(Anchor, Anchor);
100
101/// Either an expression or an anchor in a sequence.
102#[derive(Debug, Clone, PartialEq, Eq, Hash)]
103pub enum SeqItem {
104    Expr(Expr),
105    Anchor(Anchor),
106}
107
108impl SeqItem {
109    pub fn cast(node: SyntaxNode) -> Option<Self> {
110        if let Some(expr) = Expr::cast(node.clone()) {
111            return Some(SeqItem::Expr(expr));
112        }
113        if let Some(anchor) = Anchor::cast(node) {
114            return Some(SeqItem::Anchor(anchor));
115        }
116        None
117    }
118
119    pub fn as_anchor(&self) -> Option<&Anchor> {
120        match self {
121            SeqItem::Anchor(a) => Some(a),
122            _ => None,
123        }
124    }
125
126    pub fn as_expr(&self) -> Option<&Expr> {
127        match self {
128            SeqItem::Expr(e) => Some(e),
129            _ => None,
130        }
131    }
132}
133
134/// Anonymous node: string literal (`"+"`) or wildcard (`_`).
135/// Maps from CST `Str` or `Wildcard`.
136#[derive(Debug, Clone, PartialEq, Eq, Hash)]
137pub struct AnonymousNode(SyntaxNode);
138
139impl AnonymousNode {
140    pub fn cast(node: SyntaxNode) -> Option<Self> {
141        matches!(node.kind(), SyntaxKind::Str | SyntaxKind::Wildcard).then(|| Self(node))
142    }
143
144    pub fn as_cst(&self) -> &SyntaxNode {
145        &self.0
146    }
147
148    pub fn text_range(&self) -> TextRange {
149        self.0.text_range()
150    }
151
152    /// Returns the string value if this is a literal, `None` if wildcard.
153    pub fn value(&self) -> Option<SyntaxToken> {
154        if self.0.kind() == SyntaxKind::Wildcard {
155            return None;
156        }
157        self.0
158            .children_with_tokens()
159            .filter_map(|it| it.into_token())
160            .find(|t| t.kind() == SyntaxKind::StrVal)
161    }
162
163    /// Returns true if this is the "any" wildcard (`_`).
164    pub fn is_any(&self) -> bool {
165        self.0.kind() == SyntaxKind::Wildcard
166    }
167}
168
169/// Whether an alternation uses tagged or untagged branches.
170#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
171pub enum AltKind {
172    /// All branches have labels: `[A: expr1 B: expr2]`
173    Tagged,
174    /// No branches have labels: `[expr1 expr2]`
175    Untagged,
176    /// Mixed tagged and untagged branches (invalid)
177    Mixed,
178}
179
180define_expr!(
181    NamedNode,
182    Ref,
183    AnonymousNode,
184    AltExpr,
185    SeqExpr,
186    CapturedExpr,
187    QuantifiedExpr,
188    FieldExpr,
189);
190
191impl Root {
192    pub fn defs(&self) -> impl Iterator<Item = Def> + '_ {
193        self.0.children().filter_map(Def::cast)
194    }
195
196    pub fn exprs(&self) -> impl Iterator<Item = Expr> + '_ {
197        self.0.children().filter_map(Expr::cast)
198    }
199}
200
201impl Def {
202    pub fn name(&self) -> Option<SyntaxToken> {
203        self.0
204            .children_with_tokens()
205            .filter_map(|it| it.into_token())
206            .find(|t| t.kind() == SyntaxKind::Id)
207    }
208
209    pub fn body(&self) -> Option<Expr> {
210        self.0.children().find_map(Expr::cast)
211    }
212}
213
214impl NamedNode {
215    pub fn node_type(&self) -> Option<SyntaxToken> {
216        self.0
217            .children_with_tokens()
218            .filter_map(|it| it.into_token())
219            .find(|t| {
220                matches!(
221                    t.kind(),
222                    SyntaxKind::Id
223                        | SyntaxKind::Underscore
224                        | SyntaxKind::KwError
225                        | SyntaxKind::KwMissing
226                )
227            })
228    }
229
230    /// Returns true if the node type is wildcard (`_`), matching any named node.
231    pub fn is_any(&self) -> bool {
232        self.node_type()
233            .map(|t| t.kind() == SyntaxKind::Underscore)
234            .unwrap_or(false)
235    }
236
237    pub fn children(&self) -> impl Iterator<Item = Expr> + '_ {
238        self.0.children().filter_map(Expr::cast)
239    }
240
241    /// Returns all anchors in this node.
242    pub fn anchors(&self) -> impl Iterator<Item = Anchor> + '_ {
243        self.0.children().filter_map(Anchor::cast)
244    }
245
246    /// Returns children interleaved with anchors, preserving order.
247    pub fn items(&self) -> impl Iterator<Item = SeqItem> + '_ {
248        self.0.children().filter_map(SeqItem::cast)
249    }
250}
251
252impl Ref {
253    pub fn name(&self) -> Option<SyntaxToken> {
254        self.0
255            .children_with_tokens()
256            .filter_map(|it| it.into_token())
257            .find(|t| t.kind() == SyntaxKind::Id)
258    }
259}
260
261impl AltExpr {
262    pub fn kind(&self) -> AltKind {
263        let mut tagged = false;
264        let mut untagged = false;
265
266        for child in self.0.children().filter(|c| c.kind() == SyntaxKind::Branch) {
267            let has_label = child
268                .children_with_tokens()
269                .filter_map(|it| it.into_token())
270                .any(|t| t.kind() == SyntaxKind::Id);
271
272            if has_label {
273                tagged = true;
274            } else {
275                untagged = true;
276            }
277        }
278
279        match (tagged, untagged) {
280            (true, true) => AltKind::Mixed,
281            (true, false) => AltKind::Tagged,
282            _ => AltKind::Untagged,
283        }
284    }
285
286    pub fn branches(&self) -> impl Iterator<Item = Branch> + '_ {
287        self.0.children().filter_map(Branch::cast)
288    }
289
290    pub fn exprs(&self) -> impl Iterator<Item = Expr> + '_ {
291        self.0.children().filter_map(Expr::cast)
292    }
293}
294
295impl Branch {
296    pub fn label(&self) -> Option<SyntaxToken> {
297        self.0
298            .children_with_tokens()
299            .filter_map(|it| it.into_token())
300            .find(|t| t.kind() == SyntaxKind::Id)
301    }
302
303    pub fn body(&self) -> Option<Expr> {
304        self.0.children().find_map(Expr::cast)
305    }
306}
307
308impl SeqExpr {
309    pub fn children(&self) -> impl Iterator<Item = Expr> + '_ {
310        self.0.children().filter_map(Expr::cast)
311    }
312
313    /// Returns all anchors in this sequence.
314    pub fn anchors(&self) -> impl Iterator<Item = Anchor> + '_ {
315        self.0.children().filter_map(Anchor::cast)
316    }
317
318    /// Returns children interleaved with anchors, preserving order.
319    pub fn items(&self) -> impl Iterator<Item = SeqItem> + '_ {
320        self.0.children().filter_map(SeqItem::cast)
321    }
322}
323
324impl CapturedExpr {
325    pub fn name(&self) -> Option<SyntaxToken> {
326        self.0
327            .children_with_tokens()
328            .filter_map(|it| it.into_token())
329            .find(|t| t.kind() == SyntaxKind::Id)
330    }
331
332    pub fn inner(&self) -> Option<Expr> {
333        self.0.children().find_map(Expr::cast)
334    }
335
336    pub fn type_annotation(&self) -> Option<Type> {
337        self.0.children().find_map(Type::cast)
338    }
339}
340
341impl Type {
342    pub fn name(&self) -> Option<SyntaxToken> {
343        self.0
344            .children_with_tokens()
345            .filter_map(|it| it.into_token())
346            .find(|t| t.kind() == SyntaxKind::Id)
347    }
348}
349
350impl QuantifiedExpr {
351    pub fn inner(&self) -> Option<Expr> {
352        self.0.children().find_map(Expr::cast)
353    }
354
355    pub fn operator(&self) -> Option<SyntaxToken> {
356        self.0
357            .children_with_tokens()
358            .filter_map(|it| it.into_token())
359            .find(|t| {
360                matches!(
361                    t.kind(),
362                    SyntaxKind::Star
363                        | SyntaxKind::Plus
364                        | SyntaxKind::Question
365                        | SyntaxKind::StarQuestion
366                        | SyntaxKind::PlusQuestion
367                        | SyntaxKind::QuestionQuestion
368                )
369            })
370    }
371
372    /// Returns true if quantifier allows zero matches (?, *, ??, *?).
373    pub fn is_optional(&self) -> bool {
374        self.operator()
375            .map(|op| {
376                matches!(
377                    op.kind(),
378                    SyntaxKind::Question
379                        | SyntaxKind::Star
380                        | SyntaxKind::QuestionQuestion
381                        | SyntaxKind::StarQuestion
382                )
383            })
384            .unwrap_or(false)
385    }
386}
387
388impl FieldExpr {
389    pub fn name(&self) -> Option<SyntaxToken> {
390        self.0
391            .children_with_tokens()
392            .filter_map(|it| it.into_token())
393            .find(|t| t.kind() == SyntaxKind::Id)
394    }
395
396    pub fn value(&self) -> Option<Expr> {
397        self.0.children().find_map(Expr::cast)
398    }
399}
400
401impl NegatedField {
402    pub fn name(&self) -> Option<SyntaxToken> {
403        self.0
404            .children_with_tokens()
405            .filter_map(|it| it.into_token())
406            .find(|t| t.kind() == SyntaxKind::Id)
407    }
408}