synkit_core/traits/
stream.rs

1use super::parse::Parse;
2use super::peek::Peek;
3use crate::Error;
4
5/// A span representing a source location range.
6///
7/// Spans track the byte offsets of tokens and AST nodes within source text.
8/// All implementations must be `Clone` to support parser backtracking.
9pub trait SpanLike: Clone {
10    /// Returns the start byte offset.
11    fn start(&self) -> usize;
12
13    /// Returns the end byte offset (exclusive).
14    fn end(&self) -> usize;
15
16    /// Creates a new span from start and end offsets.
17    fn new(start: usize, end: usize) -> Self;
18
19    /// Returns a synthetic span for generated code.
20    fn call_site() -> Self;
21
22    /// Returns the length of this span.
23    ///
24    /// # Clamping Behavior
25    ///
26    /// Uses saturating subtraction to compute `end - start`. If `end < start`
27    /// (an inverted span), this returns `0` rather than panicking or wrapping.
28    /// This ensures safe handling of malformed or sentinel span values.
29    #[inline]
30    fn len(&self) -> usize {
31        self.end().saturating_sub(self.start())
32    }
33
34    #[inline]
35    fn is_empty(&self) -> bool {
36        self.len() == 0
37    }
38
39    /// Join two spans into one covering both regions.
40    ///
41    /// # Clamping Behavior
42    ///
43    /// Uses `min()` for start and `max()` for end positions. No overflow checking
44    /// is performed since `min`/`max` operations cannot overflow. The result
45    /// spans from the earliest start to the latest end, regardless of whether
46    /// the input spans are inverted or disjoint.
47    #[inline]
48    fn join(&self, other: &Self) -> Self {
49        Self::new(self.start().min(other.start()), self.end().max(other.end()))
50    }
51}
52
53/// A value paired with its source location span.
54///
55/// Wraps any value `T` with span information for error reporting and
56/// source mapping. Implementations must be `Clone` to support backtracking.
57pub trait SpannedLike<T>: Clone {
58    /// The span type used to track source locations.
59    type Span: SpanLike;
60
61    /// Returns a reference to the span.
62    fn span(&self) -> &Self::Span;
63
64    /// Returns a reference to the wrapped value.
65    fn value_ref(&self) -> &T;
66
67    /// Consumes self and returns the wrapped value.
68    fn value(self) -> T;
69
70    /// Creates a new spanned value from offsets and a value.
71    fn new(start: usize, end: usize, value: T) -> Self;
72
73    /// Maps the inner value while preserving the span.
74    #[inline]
75    fn map<U: Clone, F: FnOnce(T) -> U>(self, f: F) -> impl SpannedLike<U, Span = Self::Span>
76    where
77        Self: Sized,
78    {
79        let span = self.span().clone();
80        MappedSpanned {
81            span,
82            value: f(self.value()),
83        }
84    }
85}
86
87#[derive(Clone)]
88struct MappedSpanned<T, S> {
89    span: S,
90    value: T,
91}
92
93impl<T: Clone, S: SpanLike> SpannedLike<T> for MappedSpanned<T, S> {
94    type Span = S;
95
96    #[inline]
97    fn span(&self) -> &Self::Span {
98        &self.span
99    }
100
101    #[inline]
102    fn value_ref(&self) -> &T {
103        &self.value
104    }
105
106    #[inline]
107    fn value(self) -> T {
108        self.value
109    }
110
111    #[inline]
112    fn new(start: usize, end: usize, value: T) -> Self {
113        Self {
114            span: S::new(start, end),
115            value,
116        }
117    }
118}
119
120/// A stream of tokens for parsing.
121///
122/// Provides the core interface for lexer output consumption. Token streams
123/// support peeking, consumption, forking for lookahead, and rewinding.
124pub trait TokenStream: Sized {
125    /// The token type produced by the lexer.
126    type Token: Clone;
127
128    /// The span type for tracking source locations.
129    type Span: SpanLike;
130
131    /// A spanned wrapper type for associating values with spans.
132    type Spanned<T: Clone>: SpannedLike<T, Span = Self::Span>;
133
134    /// Peeks at the next token without consuming (includes whitespace).
135    fn peek_token_raw(&self) -> Option<&Self::Spanned<Self::Token>>;
136
137    /// Consumes and returns the next token (includes whitespace).
138    fn next_raw(&mut self) -> Option<Self::Spanned<Self::Token>>;
139
140    /// Returns the current cursor position.
141    fn cursor(&self) -> usize;
142
143    /// Rewinds to a previous cursor position.
144    fn rewind(&mut self, pos: usize);
145
146    /// Creates a fork for lookahead without consuming tokens.
147    fn fork(&self) -> Self;
148
149    /// Returns the span at the current cursor position.
150    fn cursor_span(&self) -> Option<Self::Span>;
151
152    /// Returns the span of the last consumed token.
153    fn last_span(&self) -> Option<Self::Span>;
154
155    /// Peeks at the next significant token (skips whitespace by default).
156    #[inline]
157    fn peek_token(&self) -> Option<&Self::Spanned<Self::Token>> {
158        self.peek_token_raw()
159    }
160
161    /// Consumes and returns the next significant token.
162    #[inline]
163    fn next(&mut self) -> Option<Self::Spanned<Self::Token>> {
164        self.next_raw()
165    }
166
167    /// Checks if the next token matches type `T` without consuming.
168    #[inline]
169    fn peek<T: Peek<Token = Self::Token>>(&self) -> bool {
170        T::peek(self)
171    }
172
173    /// Parses a value of type `T` from the stream.
174    #[inline]
175    fn parse<T: Parse<Token = Self::Token>>(&mut self) -> Result<T, T::Error> {
176        T::parse(self)
177    }
178
179    /// Parses a value and wraps it with its source span.
180    fn parse_spanned<T: Parse<Token = Self::Token> + Clone>(
181        &mut self,
182    ) -> Result<Self::Spanned<T>, T::Error> {
183        let start = self.cursor_span().unwrap_or_else(Self::Span::call_site);
184        let value = T::parse(self)?;
185        let end = self.last_span().unwrap_or_else(Self::Span::call_site);
186        Ok(Self::Spanned::new(start.start(), end.end(), value))
187    }
188
189    #[inline]
190    fn is_empty(&self) -> bool {
191        self.peek_token().is_none()
192    }
193
194    /// Returns the number of remaining significant tokens (excluding whitespace).
195    ///
196    /// The default implementation counts tokens via `peek_token()` and `next()`,
197    /// which may be inefficient. Implementations may override this for better performance.
198    fn remaining(&self) -> usize {
199        let mut count = 0;
200        let mut fork = self.fork();
201        while fork.next().is_some() {
202            count += 1;
203        }
204        count
205    }
206
207    /// Ensures the stream has been fully consumed.
208    ///
209    /// Returns `Ok(())` if no significant tokens remain (whitespace is ignored).
210    /// Returns `Err(Error::StreamNotConsumed)` if tokens remain.
211    ///
212    /// # Example
213    ///
214    /// ```ignore
215    /// let doc: Document = stream.parse()?;
216    /// stream.ensure_consumed()?; // Error if trailing garbage
217    /// ```
218    #[inline]
219    fn ensure_consumed(&self) -> Result<(), Error> {
220        let remaining = self.remaining();
221        if remaining > 0 {
222            Err(Error::StreamNotConsumed { remaining })
223        } else {
224            Ok(())
225        }
226    }
227
228    /// Create a span covering a range of cursor positions.
229    ///
230    /// This is useful for tracking the span of a parsed AST node that
231    /// spans multiple tokens.
232    ///
233    /// # Example
234    ///
235    /// ```ignore
236    /// let start = stream.cursor();
237    /// // ... parse multiple tokens ...
238    /// let end = stream.cursor();
239    /// let span = stream.span_range(start..end);
240    /// ```
241    #[inline]
242    fn span_range(&self, range: core::ops::Range<usize>) -> Self::Span {
243        Self::Span::new(range.start, range.end)
244    }
245
246    /// Get the span of a token at a specific cursor position.
247    ///
248    /// Returns `None` if the position is out of bounds.
249    fn span_at(&self, pos: usize) -> Option<Self::Span>;
250}