Skip to main content

ronin_core/syntax/
ast.rs

1//! Typed accessors over the CST (TR-010, OBJ4).
2//!
3//! The CST exposed by [`crate::syntax`] is *untyped*: every interior node is a
4//! [`SyntaxNode`] tagged with a [`SyntaxKind`]. This module layers a thin,
5//! zero-copy *typed* view on top of it so callers can navigate RON constructs by
6//! name — `Struct::fields()`, `Map::entries()`, `MapEntry::key()`/`value()`,
7//! `List::items()`, `Tuple::items()`, `EnumVariant::name()`, `Document::value()`
8//! — without matching on `SyntaxKind` themselves.
9//!
10//! # Design
11//!
12//! Each typed wrapper is a newtype around a [`SyntaxNode`] of the matching kind.
13//! Construction goes through `cast`, which returns `None` for a node of the
14//! wrong kind, so a typed handle always refers to a node of its declared kind
15//! (defensive: an `Error`-recovered tree never produces a mis-typed accessor).
16//! Accessors return only `ronin-core` types — other typed wrappers, [`Value`],
17//! [`SyntaxNode`], [`SyntaxToken`], or `&str` — so **no rowan type ever leaks**
18//! (INV-7 / TR-009). The wrappers borrow the tree; they own nothing and copy no
19//! source text.
20//!
21//! Trivia is transparent here: child *nodes* skip trivia tokens automatically
22//! (trivia are leaf tokens, never nodes), so navigation is unaffected by
23//! whitespace/comments while the underlying tree stays byte-lossless.
24
25use crate::syntax::{SyntaxKind, SyntaxNode, SyntaxToken};
26
27/// A typed RON value: the classified wrapper for any value-position node.
28///
29/// Returned by the value accessors ([`Document::value`], [`StructField::value`],
30/// [`MapEntry::key`]/[`MapEntry::value`], and the `items()` iterators). The
31/// `Error` / unknown arm keeps navigation total over error-recovered trees
32/// (INV-3): a malformed value is still reachable as [`Value::Error`] rather than
33/// silently dropped.
34#[derive(Debug, Clone, PartialEq, Eq, Hash)]
35pub enum Value {
36    /// A named or anonymous struct `Name( field: v, .. )` / `( field: v, .. )`.
37    Struct(Struct),
38    /// A positional tuple / tuple-struct `( a, b, c )`.
39    Tuple(Tuple),
40    /// A list / sequence `[ a, b, c ]`.
41    List(List),
42    /// A map `{ k: v, .. }` (keys may be non-string values).
43    Map(Map),
44    /// An enum variant: bare `Ident`, or `Ident(..)` / `Ident{..}` payload.
45    EnumVariant(EnumVariant),
46    /// The unit value `()`.
47    Unit(Unit),
48    /// A scalar literal (int, float, string, raw string, char, bool).
49    Literal(Literal),
50    /// An unparseable / recovered value node (`Error` kind). Kept reachable so
51    /// navigation is total over error-recovered trees (INV-3).
52    Error(SyntaxNode),
53}
54
55impl Value {
56    /// Classify a value-position [`SyntaxNode`] into a typed [`Value`].
57    ///
58    /// Returns `None` only for a node whose kind is not a value (e.g. `Root`,
59    /// `StructField`, `MapEntry`, `ExtensionAttr`) — callers that already hold a
60    /// value-position node always get `Some`.
61    #[must_use]
62    pub fn cast(node: SyntaxNode) -> Option<Self> {
63        Some(match node.kind() {
64            SyntaxKind::Struct => Self::Struct(Struct(node)),
65            SyntaxKind::Tuple => Self::Tuple(Tuple(node)),
66            SyntaxKind::List => Self::List(List(node)),
67            SyntaxKind::Map => Self::Map(Map(node)),
68            SyntaxKind::EnumVariant => Self::EnumVariant(EnumVariant(node)),
69            SyntaxKind::Unit => Self::Unit(Unit(node)),
70            SyntaxKind::Literal => Self::Literal(Literal(node)),
71            SyntaxKind::Error => Self::Error(node),
72            _ => return None,
73        })
74    }
75
76    /// The underlying [`SyntaxNode`], regardless of variant.
77    #[must_use]
78    pub fn syntax(&self) -> &SyntaxNode {
79        match self {
80            Self::Struct(n) => n.syntax(),
81            Self::Tuple(n) => n.syntax(),
82            Self::List(n) => n.syntax(),
83            Self::Map(n) => n.syntax(),
84            Self::EnumVariant(n) => n.syntax(),
85            Self::Unit(n) => n.syntax(),
86            Self::Literal(n) => n.syntax(),
87            Self::Error(n) => n,
88        }
89    }
90}
91
92/// Cast the first value-position child node of `parent` into a [`Value`].
93///
94/// Used by accessors that contain exactly one value (struct field, map-entry
95/// key/value, document root). Skips trivia and non-value nodes (e.g. a struct
96/// field's name is a token, not a node).
97fn first_value_child(parent: &SyntaxNode) -> Option<Value> {
98    parent.children().find_map(Value::cast)
99}
100
101/// The typed root of a parsed document.
102///
103/// Wraps the [`SyntaxKind::Root`] node and exposes the single top-level value
104/// plus any leading extension attributes.
105#[derive(Debug, Clone, PartialEq, Eq, Hash)]
106pub struct Document(SyntaxNode);
107
108impl Document {
109    /// Wrap a [`SyntaxKind::Root`] node, or `None` for any other kind.
110    #[must_use]
111    pub fn cast(node: SyntaxNode) -> Option<Self> {
112        (node.kind() == SyntaxKind::Root).then_some(Self(node))
113    }
114
115    /// The underlying root [`SyntaxNode`].
116    #[must_use]
117    pub fn syntax(&self) -> &SyntaxNode {
118        &self.0
119    }
120
121    /// The single top-level [`Value`], if the document has one (absent for an
122    /// empty / trivia-only file).
123    #[must_use]
124    pub fn value(&self) -> Option<Value> {
125        first_value_child(&self.0)
126    }
127
128    /// The leading extension attributes `#![enable(..)]`, in source order.
129    pub fn extension_attrs(&self) -> impl Iterator<Item = ExtensionAttr> + '_ {
130        self.0.children().filter_map(ExtensionAttr::cast)
131    }
132}
133
134/// A named or anonymous struct: `Name( field: v, .. )` or `( field: v, .. )`.
135#[derive(Debug, Clone, PartialEq, Eq, Hash)]
136pub struct Struct(SyntaxNode);
137
138impl Struct {
139    /// Wrap a [`SyntaxKind::Struct`] node, or `None` for any other kind.
140    #[must_use]
141    pub fn cast(node: SyntaxNode) -> Option<Self> {
142        (node.kind() == SyntaxKind::Struct).then_some(Self(node))
143    }
144
145    /// The underlying [`SyntaxNode`].
146    #[must_use]
147    pub fn syntax(&self) -> &SyntaxNode {
148        &self.0
149    }
150
151    /// The struct's name token (`Ident`) for a named struct, or `None` for an
152    /// anonymous `( field: v, .. )` struct.
153    #[must_use]
154    pub fn name(&self) -> Option<SyntaxToken> {
155        self.0.first_token_of(SyntaxKind::Ident)
156    }
157
158    /// The struct's name as a string slice, if named.
159    #[must_use]
160    pub fn name_text(&self) -> Option<String> {
161        self.name().map(|t| t.text().to_string())
162    }
163
164    /// The `field: value` entries, in source order.
165    pub fn fields(&self) -> impl Iterator<Item = StructField> + '_ {
166        self.0.children().filter_map(StructField::cast)
167    }
168}
169
170/// A single `field: value` entry inside a [`Struct`].
171#[derive(Debug, Clone, PartialEq, Eq, Hash)]
172pub struct StructField(SyntaxNode);
173
174impl StructField {
175    /// Wrap a [`SyntaxKind::StructField`] node, or `None` for any other kind.
176    #[must_use]
177    pub fn cast(node: SyntaxNode) -> Option<Self> {
178        (node.kind() == SyntaxKind::StructField).then_some(Self(node))
179    }
180
181    /// The underlying [`SyntaxNode`].
182    #[must_use]
183    pub fn syntax(&self) -> &SyntaxNode {
184        &self.0
185    }
186
187    /// The field-name token (`Ident`), if present.
188    #[must_use]
189    pub fn name(&self) -> Option<SyntaxToken> {
190        self.0.first_token_of(SyntaxKind::Ident)
191    }
192
193    /// The field name as a string, if present.
194    #[must_use]
195    pub fn name_text(&self) -> Option<String> {
196        self.name().map(|t| t.text().to_string())
197    }
198
199    /// The field's [`Value`], if present (absent in a recovered partial field).
200    #[must_use]
201    pub fn value(&self) -> Option<Value> {
202        first_value_child(&self.0)
203    }
204}
205
206/// A positional tuple / tuple-struct `( a, b, c )`.
207#[derive(Debug, Clone, PartialEq, Eq, Hash)]
208pub struct Tuple(SyntaxNode);
209
210impl Tuple {
211    /// Wrap a [`SyntaxKind::Tuple`] node, or `None` for any other kind.
212    #[must_use]
213    pub fn cast(node: SyntaxNode) -> Option<Self> {
214        (node.kind() == SyntaxKind::Tuple).then_some(Self(node))
215    }
216
217    /// The underlying [`SyntaxNode`].
218    #[must_use]
219    pub fn syntax(&self) -> &SyntaxNode {
220        &self.0
221    }
222
223    /// The positional element [`Value`]s, in source order.
224    pub fn items(&self) -> impl Iterator<Item = Value> + '_ {
225        self.0.children().filter_map(Value::cast)
226    }
227}
228
229/// A list / sequence `[ a, b, c ]`.
230#[derive(Debug, Clone, PartialEq, Eq, Hash)]
231pub struct List(SyntaxNode);
232
233impl List {
234    /// Wrap a [`SyntaxKind::List`] node, or `None` for any other kind.
235    #[must_use]
236    pub fn cast(node: SyntaxNode) -> Option<Self> {
237        (node.kind() == SyntaxKind::List).then_some(Self(node))
238    }
239
240    /// The underlying [`SyntaxNode`].
241    #[must_use]
242    pub fn syntax(&self) -> &SyntaxNode {
243        &self.0
244    }
245
246    /// The element [`Value`]s, in source order.
247    pub fn items(&self) -> impl Iterator<Item = Value> + '_ {
248        self.0.children().filter_map(Value::cast)
249    }
250}
251
252/// A map `{ k: v, .. }` (keys may be non-string values).
253#[derive(Debug, Clone, PartialEq, Eq, Hash)]
254pub struct Map(SyntaxNode);
255
256impl Map {
257    /// Wrap a [`SyntaxKind::Map`] node, or `None` for any other kind.
258    #[must_use]
259    pub fn cast(node: SyntaxNode) -> Option<Self> {
260        (node.kind() == SyntaxKind::Map).then_some(Self(node))
261    }
262
263    /// The underlying [`SyntaxNode`].
264    #[must_use]
265    pub fn syntax(&self) -> &SyntaxNode {
266        &self.0
267    }
268
269    /// The `key: value` entries, in source order.
270    pub fn entries(&self) -> impl Iterator<Item = MapEntry> + '_ {
271        self.0.children().filter_map(MapEntry::cast)
272    }
273}
274
275/// A single `key: value` entry inside a [`Map`].
276#[derive(Debug, Clone, PartialEq, Eq, Hash)]
277pub struct MapEntry(SyntaxNode);
278
279impl MapEntry {
280    /// Wrap a [`SyntaxKind::MapEntry`] node, or `None` for any other kind.
281    #[must_use]
282    pub fn cast(node: SyntaxNode) -> Option<Self> {
283        (node.kind() == SyntaxKind::MapEntry).then_some(Self(node))
284    }
285
286    /// The underlying [`SyntaxNode`].
287    #[must_use]
288    pub fn syntax(&self) -> &SyntaxNode {
289        &self.0
290    }
291
292    /// The key [`Value`] (the first value child — any RON value, including
293    /// non-string keys), if present.
294    #[must_use]
295    pub fn key(&self) -> Option<Value> {
296        self.0.children().filter_map(Value::cast).next()
297    }
298
299    /// The value [`Value`] (the second value child), if present.
300    #[must_use]
301    pub fn value(&self) -> Option<Value> {
302        self.0.children().filter_map(Value::cast).nth(1)
303    }
304}
305
306/// An enum variant: a bare `Ident`, or `Ident(..)` / `Ident{..}` payload.
307#[derive(Debug, Clone, PartialEq, Eq, Hash)]
308pub struct EnumVariant(SyntaxNode);
309
310impl EnumVariant {
311    /// Wrap a [`SyntaxKind::EnumVariant`] node, or `None` for any other kind.
312    #[must_use]
313    pub fn cast(node: SyntaxNode) -> Option<Self> {
314        (node.kind() == SyntaxKind::EnumVariant).then_some(Self(node))
315    }
316
317    /// The underlying [`SyntaxNode`].
318    #[must_use]
319    pub fn syntax(&self) -> &SyntaxNode {
320        &self.0
321    }
322
323    /// The variant name token (`Ident`), if present.
324    #[must_use]
325    pub fn name(&self) -> Option<SyntaxToken> {
326        self.0.first_token_of(SyntaxKind::Ident)
327    }
328
329    /// The variant name as a string, if present.
330    #[must_use]
331    pub fn name_text(&self) -> Option<String> {
332        self.name().map(|t| t.text().to_string())
333    }
334
335    /// The struct-like payload entries for `Variant { .. }`, in source order
336    /// (empty for a bare or tuple-style variant).
337    pub fn entries(&self) -> impl Iterator<Item = MapEntry> + '_ {
338        self.0.children().filter_map(MapEntry::cast)
339    }
340}
341
342/// The unit value `()`.
343#[derive(Debug, Clone, PartialEq, Eq, Hash)]
344pub struct Unit(SyntaxNode);
345
346impl Unit {
347    /// Wrap a [`SyntaxKind::Unit`] node, or `None` for any other kind.
348    #[must_use]
349    pub fn cast(node: SyntaxNode) -> Option<Self> {
350        (node.kind() == SyntaxKind::Unit).then_some(Self(node))
351    }
352
353    /// The underlying [`SyntaxNode`].
354    #[must_use]
355    pub fn syntax(&self) -> &SyntaxNode {
356        &self.0
357    }
358}
359
360/// A scalar literal node (int, float, string, raw string, char, bool keyword).
361#[derive(Debug, Clone, PartialEq, Eq, Hash)]
362pub struct Literal(SyntaxNode);
363
364impl Literal {
365    /// Wrap a [`SyntaxKind::Literal`] node, or `None` for any other kind.
366    #[must_use]
367    pub fn cast(node: SyntaxNode) -> Option<Self> {
368        (node.kind() == SyntaxKind::Literal).then_some(Self(node))
369    }
370
371    /// The underlying [`SyntaxNode`].
372    #[must_use]
373    pub fn syntax(&self) -> &SyntaxNode {
374        &self.0
375    }
376
377    /// The single scalar token this literal wraps (the first non-trivia token).
378    #[must_use]
379    pub fn token(&self) -> Option<SyntaxToken> {
380        self.0
381            .children_with_tokens()
382            .filter_map(|el| el.as_token().cloned())
383            .find(|t| !t.is_trivia())
384    }
385
386    /// The [`SyntaxKind`] of the underlying scalar token (e.g. `Integer`,
387    /// `String`, `Char`, `TrueKw`), if present.
388    #[must_use]
389    pub fn token_kind(&self) -> Option<SyntaxKind> {
390        self.token().map(|t| t.kind())
391    }
392
393    /// The verbatim source text of the scalar token (never normalized), if
394    /// present.
395    #[must_use]
396    pub fn text(&self) -> Option<String> {
397        self.token().map(|t| t.text().to_string())
398    }
399}
400
401/// An extension attribute `#![enable(ext, ..)]`.
402#[derive(Debug, Clone, PartialEq, Eq, Hash)]
403pub struct ExtensionAttr(SyntaxNode);
404
405impl ExtensionAttr {
406    /// Wrap a [`SyntaxKind::ExtensionAttr`] node, or `None` for any other kind.
407    #[must_use]
408    pub fn cast(node: SyntaxNode) -> Option<Self> {
409        (node.kind() == SyntaxKind::ExtensionAttr).then_some(Self(node))
410    }
411
412    /// The underlying [`SyntaxNode`].
413    #[must_use]
414    pub fn syntax(&self) -> &SyntaxNode {
415        &self.0
416    }
417
418    /// The enabled-extension identifier tokens (e.g. `implicit_some`), in source
419    /// order. Unknown extensions are still preserved verbatim as `Ident` tokens.
420    pub fn extensions(&self) -> impl Iterator<Item = SyntaxToken> + '_ {
421        self.0
422            .children_with_tokens()
423            .filter_map(|el| el.as_token().cloned())
424            .filter(|t| t.kind() == SyntaxKind::Ident)
425    }
426}
427
428#[cfg(test)]
429mod tests {
430    use super::*;
431    use crate::parser::parse;
432
433    /// Build the typed [`Document`] for `src`.
434    fn doc_of(src: &str) -> Document {
435        Document::cast(parse(src).root()).expect("root is always a Document")
436    }
437
438    #[test]
439    fn struct_fields_and_name() {
440        let d = doc_of("Point(x: 1, y: -2.0)");
441        let Some(Value::Struct(s)) = d.value() else {
442            panic!("expected a struct");
443        };
444        assert_eq!(s.name_text().as_deref(), Some("Point"));
445        let fields: Vec<_> = s.fields().collect();
446        assert_eq!(fields.len(), 2);
447        assert_eq!(fields[0].name_text().as_deref(), Some("x"));
448        assert_eq!(fields[1].name_text().as_deref(), Some("y"));
449        // Field values are reachable and typed.
450        let Some(Value::Literal(lit)) = fields[0].value() else {
451            panic!("x should be a literal");
452        };
453        assert_eq!(lit.text().as_deref(), Some("1"));
454        assert_eq!(lit.token_kind(), Some(SyntaxKind::Integer));
455    }
456
457    #[test]
458    fn anonymous_struct_has_no_name() {
459        let d = doc_of("(a: 1, b: 2)");
460        let Some(Value::Struct(s)) = d.value() else {
461            panic!("expected a struct");
462        };
463        assert_eq!(s.name_text(), None);
464        assert_eq!(s.fields().count(), 2);
465    }
466
467    #[test]
468    fn list_items() {
469        let d = doc_of("[1, 2, 3,]");
470        let Some(Value::List(list)) = d.value() else {
471            panic!("expected a list");
472        };
473        let items: Vec<_> = list.items().collect();
474        assert_eq!(items.len(), 3);
475        for it in &items {
476            assert!(matches!(it, Value::Literal(_)));
477        }
478    }
479
480    #[test]
481    fn tuple_items() {
482        let d = doc_of("(1, \"two\", 'c')");
483        let Some(Value::Tuple(t)) = d.value() else {
484            panic!("expected a tuple");
485        };
486        assert_eq!(t.items().count(), 3);
487    }
488
489    #[test]
490    fn map_entries_with_non_string_keys() {
491        let d = doc_of("{ 1: \"one\", 'c': true }");
492        let Some(Value::Map(m)) = d.value() else {
493            panic!("expected a map");
494        };
495        let entries: Vec<_> = m.entries().collect();
496        assert_eq!(entries.len(), 2);
497        // First key is an integer literal (a non-string key).
498        let Some(Value::Literal(k0)) = entries[0].key() else {
499            panic!("key 0 should be a literal");
500        };
501        assert_eq!(k0.token_kind(), Some(SyntaxKind::Integer));
502        let Some(Value::Literal(v0)) = entries[0].value() else {
503            panic!("value 0 should be a literal");
504        };
505        assert_eq!(v0.token_kind(), Some(SyntaxKind::String));
506    }
507
508    #[test]
509    fn enum_variant_struct_like() {
510        let d = doc_of("Variant { field: 1 }");
511        let Some(Value::EnumVariant(v)) = d.value() else {
512            panic!("expected an enum variant");
513        };
514        assert_eq!(v.name_text().as_deref(), Some("Variant"));
515        assert_eq!(v.entries().count(), 1);
516    }
517
518    #[test]
519    fn bare_enum_variant() {
520        let d = doc_of("Unit");
521        let Some(Value::EnumVariant(v)) = d.value() else {
522            panic!("expected a bare variant");
523        };
524        assert_eq!(v.name_text().as_deref(), Some("Unit"));
525        assert_eq!(v.entries().count(), 0);
526    }
527
528    #[test]
529    fn unit_value() {
530        let d = doc_of("()");
531        assert!(matches!(d.value(), Some(Value::Unit(_))));
532    }
533
534    #[test]
535    fn literal_text_is_verbatim() {
536        let d = doc_of("r#\"raw \"q\" str\"#");
537        let Some(Value::Literal(lit)) = d.value() else {
538            panic!("expected a literal");
539        };
540        assert_eq!(lit.token_kind(), Some(SyntaxKind::RawString));
541        assert_eq!(lit.text().as_deref(), Some("r#\"raw \"q\" str\"#"));
542    }
543
544    #[test]
545    fn extension_attrs_and_value() {
546        let d = doc_of("#![enable(implicit_some)]\nSome(5)");
547        let attrs: Vec<_> = d.extension_attrs().collect();
548        assert_eq!(attrs.len(), 1);
549        let exts: Vec<_> = attrs[0]
550            .extensions()
551            .map(|t| t.text().to_string())
552            .collect();
553        // `enable` is a keyword token, so only the extension idents surface here.
554        assert!(exts.contains(&"implicit_some".to_string()));
555        // The top-level value is still reachable past the attributes.
556        assert!(d.value().is_some());
557    }
558
559    #[test]
560    fn error_value_is_reachable() {
561        // A stray top-level token recovers into an Error node; navigation stays
562        // total — the value is reachable as Value::Error rather than dropped.
563        let d = doc_of("@");
564        assert!(matches!(d.value(), Some(Value::Error(_))));
565    }
566
567    #[test]
568    fn value_syntax_round_trips_text() {
569        let src = "Foo(x: [1, 2], y: { 'a': 'b' })";
570        let d = doc_of(src);
571        let v = d.value().expect("has a value");
572        // The typed value's underlying node text equals the source span.
573        assert_eq!(v.syntax().text(), src);
574    }
575}