Skip to main content

mago_syntax_core/ast/
mod.rs

1//! Shared AST primitives used across every syntax crate.
2
3use std::slice::Iter;
4
5use bumpalo::Bump;
6use bumpalo::collections::Vec as BVec;
7use bumpalo::collections::vec::IntoIter;
8use serde::Serialize;
9
10use mago_span::HasPosition;
11use mago_span::HasSpan;
12use mago_span::Span;
13
14/// A sequence of AST nodes allocated in a [`bumpalo::Bump`].
15#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, PartialOrd, Ord)]
16#[repr(transparent)]
17pub struct Sequence<'arena, T> {
18    pub nodes: BVec<'arena, T>,
19}
20
21impl<'arena, T> Sequence<'arena, T> {
22    #[inline]
23    #[must_use]
24    pub const fn new(inner: BVec<'arena, T>) -> Self {
25        Self { nodes: inner }
26    }
27
28    #[inline]
29    pub fn empty(arena: &'arena Bump) -> Self {
30        Self { nodes: BVec::new_in(arena) }
31    }
32
33    #[inline]
34    #[must_use]
35    pub fn len(&self) -> usize {
36        self.nodes.len()
37    }
38
39    #[inline]
40    #[must_use]
41    pub fn is_empty(&self) -> bool {
42        self.nodes.is_empty()
43    }
44
45    #[inline]
46    #[must_use]
47    pub fn get(&self, index: usize) -> Option<&T> {
48        self.nodes.get(index)
49    }
50
51    #[inline]
52    #[must_use]
53    pub fn first(&self) -> Option<&T> {
54        self.nodes.first()
55    }
56
57    #[inline]
58    #[must_use]
59    pub fn last(&self) -> Option<&T> {
60        self.nodes.last()
61    }
62
63    #[inline]
64    pub fn iter(&self) -> Iter<'_, T> {
65        self.nodes.iter()
66    }
67
68    #[inline]
69    #[must_use]
70    pub fn as_slice(&self) -> &[T] {
71        self.nodes.as_slice()
72    }
73}
74
75impl<T: HasSpan> Sequence<'_, T> {
76    #[inline]
77    #[must_use]
78    pub fn first_span(&self) -> Option<Span> {
79        self.nodes.first().map(HasSpan::span)
80    }
81
82    #[inline]
83    #[must_use]
84    pub fn last_span(&self) -> Option<Span> {
85        self.nodes.last().map(HasSpan::span)
86    }
87
88    /// Compute the span covering the sequence, anchored at `from`.
89    ///
90    /// Returns a zero-width span at `from` when the sequence is empty.
91    #[inline]
92    #[must_use]
93    pub fn span(&self, file_id: mago_database::file::FileId, from: mago_span::Position) -> Span {
94        self.last_span().map_or(Span::new(file_id, from, from), |span| Span::new(file_id, from, span.end))
95    }
96}
97
98impl<T> std::ops::Index<usize> for Sequence<'_, T> {
99    type Output = T;
100
101    #[inline]
102    fn index(&self, index: usize) -> &T {
103        &self.nodes[index]
104    }
105}
106
107impl<T, Tok> std::ops::Index<usize> for TokenSeparatedSequence<'_, T, Tok> {
108    type Output = T;
109
110    #[inline]
111    fn index(&self, index: usize) -> &T {
112        &self.nodes[index]
113    }
114}
115
116impl<'arena, T> IntoIterator for Sequence<'arena, T> {
117    type Item = T;
118    type IntoIter = IntoIter<'arena, Self::Item>;
119
120    fn into_iter(self) -> Self::IntoIter {
121        self.nodes.into_iter()
122    }
123}
124
125impl<'seq, T> IntoIterator for &'seq Sequence<'_, T> {
126    type Item = &'seq T;
127    type IntoIter = Iter<'seq, T>;
128
129    fn into_iter(self) -> Self::IntoIter {
130        self.iter()
131    }
132}
133
134/// A sequence of AST nodes separated by infix tokens.
135///
136/// The token type is generic so every syntax crate can plug in its own
137/// token definition. Methods that depend on spatial relationships between
138/// nodes and tokens require [`HasPosition`] on the token; that's the
139/// narrowest bound that still supports `has_trailing_token` style
140/// queries, and every sibling crate's token already carries a [`Position`]
141/// start so the impl is trivial.
142///
143/// [`Position`]: mago_span::Position
144#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, PartialOrd, Ord)]
145pub struct TokenSeparatedSequence<'arena, T, Tok> {
146    pub nodes: BVec<'arena, T>,
147    pub tokens: BVec<'arena, Tok>,
148}
149
150impl<'arena, T, Tok> TokenSeparatedSequence<'arena, T, Tok> {
151    #[inline]
152    #[must_use]
153    pub const fn new(nodes: BVec<'arena, T>, tokens: BVec<'arena, Tok>) -> Self {
154        Self { nodes, tokens }
155    }
156
157    #[inline]
158    pub fn empty(arena: &'arena Bump) -> Self {
159        Self { nodes: BVec::new_in(arena), tokens: BVec::new_in(arena) }
160    }
161
162    #[inline]
163    #[must_use]
164    pub fn len(&self) -> usize {
165        self.nodes.len()
166    }
167
168    #[inline]
169    #[must_use]
170    pub fn is_empty(&self) -> bool {
171        self.nodes.is_empty()
172    }
173
174    #[inline]
175    #[must_use]
176    pub fn get(&self, index: usize) -> Option<&T> {
177        self.nodes.get(index)
178    }
179
180    #[inline]
181    #[must_use]
182    pub fn first(&self) -> Option<&T> {
183        self.nodes.first()
184    }
185
186    #[inline]
187    #[must_use]
188    pub fn last(&self) -> Option<&T> {
189        self.nodes.last()
190    }
191
192    #[inline]
193    pub fn iter(&self) -> Iter<'_, T> {
194        self.nodes.iter()
195    }
196
197    #[inline]
198    #[must_use]
199    pub fn as_slice(&self) -> &[T] {
200        self.nodes.as_slice()
201    }
202
203    /// Iterate yielding `(index, node, optional trailing token)` tuples.
204    ///
205    /// The token is `None` only for the last element if it has no trailing
206    /// separator.
207    #[inline]
208    pub fn iter_with_tokens(&self) -> impl Iterator<Item = (usize, &T, Option<&Tok>)> {
209        self.nodes.iter().enumerate().map(move |(i, item)| (i, item, self.tokens.get(i)))
210    }
211}
212
213impl<T, Tok> TokenSeparatedSequence<'_, T, Tok>
214where
215    T: HasSpan,
216    Tok: HasPosition,
217{
218    /// Whether the sequence ends with a trailing separator token.
219    #[inline]
220    #[must_use]
221    pub fn has_trailing_token(&self) -> bool {
222        self.tokens.last().is_some_and(|t| t.offset() >= self.nodes.last().map_or(0, |n| n.span().end.offset))
223    }
224
225    /// Return the trailing separator token, if any.
226    #[inline]
227    #[must_use]
228    pub fn get_trailing_token(&self) -> Option<&Tok> {
229        self.tokens.last().filter(|t| t.offset() >= self.nodes.last().map_or(0, |n| n.span().end.offset))
230    }
231}
232
233impl<'arena, T, Tok> IntoIterator for TokenSeparatedSequence<'arena, T, Tok> {
234    type Item = T;
235    type IntoIter = IntoIter<'arena, Self::Item>;
236
237    fn into_iter(self) -> Self::IntoIter {
238        self.nodes.into_iter()
239    }
240}
241
242impl<'seq, T, Tok> IntoIterator for &'seq TokenSeparatedSequence<'_, T, Tok> {
243    type Item = &'seq T;
244    type IntoIter = Iter<'seq, T>;
245
246    fn into_iter(self) -> Self::IntoIter {
247        self.iter()
248    }
249}