Skip to main content

tsz_common/
span.rs

1//! Span - Source location tracking for AST nodes and diagnostics
2//!
3//! A Span represents a range of source code by byte offsets. It is used for:
4//! - Tracking the location of AST nodes
5//! - Pointing to error locations in diagnostics
6//! - Source map generation
7//!
8//! Spans are small (8 bytes) and cheap to copy.
9
10use serde::{Deserialize, Serialize};
11
12/// A span of source code, represented as a byte range.
13///
14/// Spans use half-open intervals: `[start, end)`.
15/// An empty span has `start == end`.
16#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
17pub struct Span {
18    /// Start byte offset (inclusive)
19    pub start: u32,
20    /// End byte offset (exclusive)
21    pub end: u32,
22}
23
24impl Span {
25    /// Create a new span from start and end offsets.
26    #[inline]
27    #[must_use]
28    pub const fn new(start: u32, end: u32) -> Self {
29        Self { start, end }
30    }
31
32    /// Create an empty span at the given position.
33    #[inline]
34    #[must_use]
35    pub const fn at(pos: u32) -> Self {
36        Self {
37            start: pos,
38            end: pos,
39        }
40    }
41
42    /// Create a span from start position and length.
43    #[inline]
44    #[must_use]
45    pub const fn from_len(start: u32, len: u32) -> Self {
46        Self {
47            start,
48            end: start + len,
49        }
50    }
51
52    /// Create a dummy/invalid span (used for synthetic nodes).
53    #[inline]
54    #[must_use]
55    pub const fn dummy() -> Self {
56        Self {
57            start: u32::MAX,
58            end: u32::MAX,
59        }
60    }
61
62    /// Check if this is a dummy/invalid span.
63    #[inline]
64    #[must_use]
65    pub const fn is_dummy(&self) -> bool {
66        self.start == u32::MAX && self.end == u32::MAX
67    }
68
69    /// Get the length of this span in bytes.
70    #[inline]
71    #[must_use]
72    pub const fn len(&self) -> u32 {
73        self.end.saturating_sub(self.start)
74    }
75
76    /// Check if this span is empty.
77    #[inline]
78    #[must_use]
79    pub const fn is_empty(&self) -> bool {
80        self.start == self.end
81    }
82
83    /// Check if this span contains a byte offset.
84    #[inline]
85    #[must_use]
86    pub const fn contains(&self, offset: u32) -> bool {
87        offset >= self.start && offset < self.end
88    }
89
90    /// Check if this span contains another span.
91    #[inline]
92    #[must_use]
93    pub const fn contains_span(&self, other: Self) -> bool {
94        other.start >= self.start && other.end <= self.end
95    }
96
97    /// Check if this span overlaps with another span.
98    #[inline]
99    #[must_use]
100    pub const fn overlaps(&self, other: Self) -> bool {
101        self.start < other.end && other.start < self.end
102    }
103
104    /// Merge two spans to create a span covering both.
105    #[inline]
106    #[must_use]
107    pub const fn merge(&self, other: Self) -> Self {
108        let start = if self.start < other.start {
109            self.start
110        } else {
111            other.start
112        };
113        let end = if self.end > other.end {
114            self.end
115        } else {
116            other.end
117        };
118        Self { start, end }
119    }
120
121    /// Get the intersection of two spans, if they overlap.
122    #[inline]
123    #[must_use]
124    pub fn intersect(&self, other: Self) -> Option<Self> {
125        let start = self.start.max(other.start);
126        let end = self.end.min(other.end);
127        (start < end).then_some(Self { start, end })
128    }
129
130    /// Shrink this span by removing bytes from the start.
131    #[inline]
132    #[must_use]
133    pub const fn shrink_start(&self, amount: u32) -> Self {
134        let new_start = self.start + amount;
135        Self {
136            start: if new_start > self.end {
137                self.end
138            } else {
139                new_start
140            },
141            end: self.end,
142        }
143    }
144
145    /// Shrink this span by removing bytes from the end.
146    #[inline]
147    #[must_use]
148    pub const fn shrink_end(&self, amount: u32) -> Self {
149        let new_end = self.end.saturating_sub(amount);
150        Self {
151            start: self.start,
152            end: if new_end < self.start {
153                self.start
154            } else {
155                new_end
156            },
157        }
158    }
159
160    /// Create a span for just the first byte.
161    #[inline]
162    #[must_use]
163    pub const fn first_byte(&self) -> Self {
164        Self {
165            start: self.start,
166            end: if self.end > self.start {
167                self.start + 1
168            } else {
169                self.end
170            },
171        }
172    }
173
174    /// Create a span for just the last byte.
175    #[inline]
176    #[must_use]
177    pub const fn last_byte(&self) -> Self {
178        Self {
179            start: if self.end > self.start {
180                self.end - 1
181            } else {
182                self.start
183            },
184            end: self.end,
185        }
186    }
187
188    /// Extract the slice of text covered by this span.
189    #[inline]
190    #[must_use]
191    pub fn slice<'a>(&self, text: &'a str) -> &'a str {
192        let start = self.start as usize;
193        let end = self.end as usize;
194        text.get(start..end).unwrap_or("")
195    }
196
197    /// Extract the slice of text covered by this span, with safety checks.
198    #[inline]
199    #[must_use]
200    pub fn slice_safe<'a>(&self, text: &'a str) -> &'a str {
201        let start = (self.start as usize).min(text.len());
202        let end = (self.end as usize).min(text.len());
203        if start <= end {
204            text.get(start..end).unwrap_or("")
205        } else {
206            ""
207        }
208    }
209}
210
211impl From<(u32, u32)> for Span {
212    fn from((start, end): (u32, u32)) -> Self {
213        Self::new(start, end)
214    }
215}
216
217impl From<Span> for (u32, u32) {
218    fn from(span: Span) -> Self {
219        (span.start, span.end)
220    }
221}
222
223impl std::fmt::Display for Span {
224    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
225        write!(f, "{}..{}", self.start, self.end)
226    }
227}
228
229// =============================================================================
230// Spanned Trait
231// =============================================================================
232
233/// A trait for types that have a source span.
234pub trait Spanned {
235    /// Get the source span of this element.
236    fn span(&self) -> Span;
237
238    /// Get the start byte offset.
239    fn start(&self) -> u32 {
240        self.span().start
241    }
242
243    /// Get the end byte offset.
244    fn end(&self) -> u32 {
245        self.span().end
246    }
247
248    /// Get the length in bytes.
249    fn len(&self) -> u32 {
250        self.span().len()
251    }
252
253    /// Check if the span is empty.
254    fn is_empty(&self) -> bool {
255        self.span().is_empty()
256    }
257}
258
259impl Spanned for Span {
260    fn span(&self) -> Span {
261        *self
262    }
263}
264
265// =============================================================================
266// SpanBuilder - For constructing spans during parsing
267// =============================================================================
268
269/// Helper for building spans during parsing.
270///
271/// Usage:
272/// ```ignore
273/// let builder = SpanBuilder::start(parser.pos());
274/// // ... parse some content ...
275/// let span = builder.end(parser.pos());
276/// ```
277#[derive(Clone, Copy, Debug)]
278pub struct SpanBuilder {
279    start: u32,
280}
281
282impl SpanBuilder {
283    /// Start building a span at the given position.
284    #[inline]
285    #[must_use]
286    pub const fn start(pos: u32) -> Self {
287        Self { start: pos }
288    }
289
290    /// Finish building the span at the given position.
291    #[inline]
292    #[must_use]
293    pub const fn end(&self, pos: u32) -> Span {
294        Span::new(self.start, pos)
295    }
296
297    /// Get the start position.
298    #[inline]
299    #[must_use]
300    pub const fn start_pos(&self) -> u32 {
301        self.start
302    }
303}
304
305// =============================================================================
306// ByteSpan - For working with raw byte slices
307// =============================================================================
308
309/// A span that also carries a reference to the source text.
310///
311/// This is useful when you need both the span and the text it covers.
312#[derive(Clone, Copy, Debug)]
313pub struct ByteSpan<'a> {
314    /// The source text
315    pub text: &'a str,
316    /// The span within the text
317    pub span: Span,
318}
319
320impl<'a> ByteSpan<'a> {
321    /// Create a new `ByteSpan`.
322    #[must_use]
323    pub const fn new(text: &'a str, span: Span) -> Self {
324        ByteSpan { text, span }
325    }
326
327    /// Get the slice of text covered by this span.
328    #[must_use]
329    pub fn as_str(&self) -> &'a str {
330        self.span.slice(self.text)
331    }
332
333    /// Get the length in bytes.
334    #[must_use]
335    pub const fn len(&self) -> u32 {
336        self.span.len()
337    }
338
339    /// Check if empty.
340    #[must_use]
341    pub const fn is_empty(&self) -> bool {
342        self.span.is_empty()
343    }
344}
345
346impl Spanned for ByteSpan<'_> {
347    fn span(&self) -> Span {
348        self.span
349    }
350}
351
352impl std::fmt::Display for ByteSpan<'_> {
353    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
354        write!(f, "{}", self.as_str())
355    }
356}
357
358// =============================================================================
359// Tests
360// =============================================================================
361
362#[cfg(test)]
363#[path = "../tests/span.rs"]
364mod tests;