Skip to main content

ronin_core/syntax/
mod.rs

1//! The rowan-free CST facade.
2//!
3//! `ronin-core` builds its concrete syntax tree on [`rowan`], but the public API
4//! MUST NOT expose any rowan type (TR-009 / INV-7) so the underlying library
5//! stays swappable. This module wraps rowan's `SyntaxNode`/`SyntaxToken`/
6//! `SyntaxElement` behind `ronin-core` newtypes whose accessors return only
7//! `ronin-core` types ([`SyntaxKind`], [`TextRange`], and these newtypes).
8//!
9//! # AD-001 — Trivia attachment rule (module invariant, T011)
10//!
11//! Exactly one trivia-attachment rule is applied consistently across the whole
12//! parser (risk mitigation: "inconsistent trivia model"):
13//!
14//! * **Leading trivia binds to the following significant token.** All
15//!   whitespace / comments / BOM that precede a significant token are emitted
16//!   into the green tree immediately before that token, inside the same node
17//!   the token belongs to.
18//! * **Trailing trivia at end-of-input binds to the last token** — i.e. trivia
19//!   after the final significant token (including a missing trailing newline,
20//!   trailing whitespace, or trailing comments) is attached to the nearest
21//!   preceding structure (the [`SyntaxKind::Root`] node), since there is no
22//!   following token to bind it to.
23//! * A leading UTF-8 **BOM** is the first leading-trivia token of the document
24//!   (AD-008). CRLF vs LF is preserved verbatim inside [`SyntaxKind::Whitespace`]
25//!   tokens.
26//!
27//! This rule is load-bearing for the round-trip invariant (INV-2): because every
28//! trivia byte is emitted into exactly one token in source order, concatenating
29//! all token texts reproduces the source exactly, regardless of where trivia
30//! sits relative to structure.
31
32pub mod ast;
33pub mod kind;
34
35pub use kind::SyntaxKind;
36
37use kind::RonLang;
38
39/// A half-open byte range `[start, end)` into the original source.
40///
41/// This is `ronin-core`'s own range type so no rowan type leaks across the API
42/// boundary (INV-7). Offsets are absolute byte offsets into the accepted UTF-8
43/// source.
44#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
45pub struct TextRange {
46    start: usize,
47    end: usize,
48}
49
50impl TextRange {
51    /// Construct a range from absolute byte offsets. `start <= end` is required.
52    #[inline]
53    #[must_use]
54    pub fn new(start: usize, end: usize) -> Self {
55        debug_assert!(start <= end, "TextRange start must not exceed end");
56        Self { start, end }
57    }
58
59    /// Inclusive start offset (bytes).
60    #[inline]
61    #[must_use]
62    pub fn start(self) -> usize {
63        self.start
64    }
65
66    /// Exclusive end offset (bytes).
67    #[inline]
68    #[must_use]
69    pub fn end(self) -> usize {
70        self.end
71    }
72
73    /// Byte length of the range.
74    #[inline]
75    #[must_use]
76    pub fn len(self) -> usize {
77        self.end - self.start
78    }
79
80    /// `true` if the range covers zero bytes.
81    #[inline]
82    #[must_use]
83    pub fn is_empty(self) -> bool {
84        self.start == self.end
85    }
86
87    /// `true` if `offset` falls within `[start, end)`.
88    #[inline]
89    #[must_use]
90    pub fn contains(self, offset: usize) -> bool {
91        self.start <= offset && offset < self.end
92    }
93}
94
95impl From<rowan::TextRange> for TextRange {
96    #[inline]
97    fn from(r: rowan::TextRange) -> Self {
98        Self {
99            start: usize::from(r.start()),
100            end: usize::from(r.end()),
101        }
102    }
103}
104
105/// An opaque, navigable interior node of the CST.
106///
107/// Wraps `rowan::SyntaxNode<RonLang>`; the inner rowan type is never exposed.
108#[derive(Clone, PartialEq, Eq, Hash)]
109pub struct SyntaxNode(rowan::SyntaxNode<RonLang>);
110
111/// An opaque leaf token of the CST, carrying verbatim source text (incl. trivia).
112///
113/// Wraps `rowan::SyntaxToken<RonLang>`; the inner rowan type is never exposed.
114#[derive(Clone, PartialEq, Eq, Hash)]
115pub struct SyntaxToken(rowan::SyntaxToken<RonLang>);
116
117/// Either a [`SyntaxNode`] or a [`SyntaxToken`] — a child element in source order.
118#[derive(Clone, PartialEq, Eq, Hash)]
119pub enum SyntaxElement {
120    /// An interior node child.
121    Node(SyntaxNode),
122    /// A leaf token child.
123    Token(SyntaxToken),
124}
125
126impl SyntaxNode {
127    /// Wrap a rowan node. Crate-internal: rowan types never cross the API edge.
128    #[inline]
129    #[allow(dead_code)] // used by later stages (OBJ4 edit primitives)
130    pub(crate) fn from_rowan(node: rowan::SyntaxNode<RonLang>) -> Self {
131        Self(node)
132    }
133
134    /// Build a red-tree root from a green node (used by the parser/printer).
135    #[inline]
136    pub(crate) fn new_root(green: rowan::GreenNode) -> Self {
137        Self(rowan::SyntaxNode::new_root(green))
138    }
139
140    /// Borrow the inner rowan node (crate-internal only).
141    #[inline]
142    #[allow(dead_code)] // used by later stages (OBJ4 edit primitives)
143    pub(crate) fn raw(&self) -> &rowan::SyntaxNode<RonLang> {
144        &self.0
145    }
146
147    /// This node's classification.
148    #[inline]
149    #[must_use]
150    pub fn kind(&self) -> SyntaxKind {
151        self.0.kind()
152    }
153
154    /// Absolute byte range spanned by this node (union of descendant tokens).
155    #[inline]
156    #[must_use]
157    pub fn text_range(&self) -> TextRange {
158        self.0.text_range().into()
159    }
160
161    /// The full source text spanned by this node, including trivia, as a string.
162    #[must_use]
163    pub fn text(&self) -> String {
164        self.0.text().to_string()
165    }
166
167    /// Parent node, or `None` for the root.
168    #[inline]
169    #[must_use]
170    pub fn parent(&self) -> Option<SyntaxNode> {
171        self.0.parent().map(SyntaxNode)
172    }
173
174    /// Direct child nodes (excluding tokens), in source order.
175    pub fn children(&self) -> impl Iterator<Item = SyntaxNode> {
176        self.0.children().map(SyntaxNode)
177    }
178
179    /// Direct children (both nodes and tokens), in source order.
180    pub fn children_with_tokens(&self) -> impl Iterator<Item = SyntaxElement> {
181        self.0.children_with_tokens().map(SyntaxElement::from_rowan)
182    }
183
184    /// Every descendant token (leaves), in source order — the basis for printing.
185    pub fn descendant_tokens(&self) -> impl Iterator<Item = SyntaxToken> {
186        self.0.descendants_with_tokens().filter_map(|el| match el {
187            rowan::NodeOrToken::Token(t) => Some(SyntaxToken(t)),
188            rowan::NodeOrToken::Node(_) => None,
189        })
190    }
191
192    /// First child token whose kind matches `kind`, if any.
193    #[must_use]
194    pub fn first_token_of(&self, kind: SyntaxKind) -> Option<SyntaxToken> {
195        self.0
196            .children_with_tokens()
197            .filter_map(|el| el.into_token())
198            .find(|t| t.kind() == kind)
199            .map(SyntaxToken)
200    }
201}
202
203impl SyntaxToken {
204    /// This token's classification.
205    #[inline]
206    #[must_use]
207    pub fn kind(&self) -> SyntaxKind {
208        self.0.kind()
209    }
210
211    /// Absolute byte range spanned by this token.
212    #[inline]
213    #[must_use]
214    pub fn text_range(&self) -> TextRange {
215        self.0.text_range().into()
216    }
217
218    /// The verbatim source slice for this token (never normalized or re-escaped).
219    #[inline]
220    #[must_use]
221    pub fn text(&self) -> &str {
222        self.0.text()
223    }
224
225    /// Parent node.
226    #[inline]
227    #[must_use]
228    pub fn parent(&self) -> Option<SyntaxNode> {
229        self.0.parent().map(SyntaxNode)
230    }
231
232    /// `true` if this token is trivia (whitespace / comment / BOM) per AD-001.
233    #[inline]
234    #[must_use]
235    pub fn is_trivia(&self) -> bool {
236        self.kind().is_trivia()
237    }
238}
239
240impl SyntaxElement {
241    #[inline]
242    fn from_rowan(el: rowan::SyntaxElement<RonLang>) -> Self {
243        match el {
244            rowan::NodeOrToken::Node(n) => Self::Node(SyntaxNode(n)),
245            rowan::NodeOrToken::Token(t) => Self::Token(SyntaxToken(t)),
246        }
247    }
248
249    /// This element's classification (node or token kind).
250    #[inline]
251    #[must_use]
252    pub fn kind(&self) -> SyntaxKind {
253        match self {
254            Self::Node(n) => n.kind(),
255            Self::Token(t) => t.kind(),
256        }
257    }
258
259    /// Absolute byte range spanned by this element.
260    #[inline]
261    #[must_use]
262    pub fn text_range(&self) -> TextRange {
263        match self {
264            Self::Node(n) => n.text_range(),
265            Self::Token(t) => t.text_range(),
266        }
267    }
268
269    /// Borrow as a node, if this element is a node.
270    #[inline]
271    #[must_use]
272    pub fn as_node(&self) -> Option<&SyntaxNode> {
273        match self {
274            Self::Node(n) => Some(n),
275            Self::Token(_) => None,
276        }
277    }
278
279    /// Borrow as a token, if this element is a token.
280    #[inline]
281    #[must_use]
282    pub fn as_token(&self) -> Option<&SyntaxToken> {
283        match self {
284            Self::Token(t) => Some(t),
285            Self::Node(_) => None,
286        }
287    }
288}
289
290impl std::fmt::Debug for SyntaxNode {
291    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
292        write!(f, "{:?}@{:?}", self.kind(), self.text_range())
293    }
294}
295
296impl std::fmt::Debug for SyntaxToken {
297    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
298        write!(
299            f,
300            "{:?}@{:?} {:?}",
301            self.kind(),
302            self.text_range(),
303            self.text()
304        )
305    }
306}
307
308impl std::fmt::Debug for SyntaxElement {
309    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
310        match self {
311            Self::Node(n) => std::fmt::Debug::fmt(n, f),
312            Self::Token(t) => std::fmt::Debug::fmt(t, f),
313        }
314    }
315}