Skip to main content

orrery_parser/
span.rs

1//! Source code span tracking.
2//!
3//! This module provides types for tracking positions within source text,
4//! enabling precise source location information for diagnostics, suggestions,
5//! logging, and other tools that reference source code.
6//!
7//! - [`Span`] - A byte range within source text.
8//! - [`Spanned<T>`] - A value with associated source location.
9//!
10//! # Example
11//!
12//! ```
13//! # use orrery_parser::Span;
14//! let span = Span::new(10..25);
15//! assert_eq!(span.start(), 10);
16//! assert_eq!(span.end(), 25);
17//! assert_eq!(span.len(), 15);
18//! ```
19
20use std::{fmt, ops::Deref};
21
22/// A byte range representing a location in source text.
23///
24/// Spans track the start and end byte offsets of syntax elements,
25/// enabling precise source mapping for diagnostics, suggestions, and tooling.
26///
27/// # Examples
28///
29/// ```
30/// # use orrery_parser::Span;
31/// // Create a span covering bytes 10-25
32/// let span = Span::new(10..25);
33/// assert_eq!(span.start(), 10);
34/// assert_eq!(span.end(), 25);
35/// assert_eq!(span.len(), 15);
36///
37/// // Combine two spans into one that covers both
38/// let other = Span::new(30..40);
39/// let combined = span.union(other);
40/// assert_eq!(combined.start(), 10);
41/// assert_eq!(combined.end(), 40);
42/// ```
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
44pub struct Span {
45    start: usize,
46    end: usize,
47}
48
49impl Span {
50    /// Creates a new [`Span`] from a byte range.
51    ///
52    /// # Arguments
53    ///
54    /// * `range` - The start..end byte offset range.
55    pub fn new(range: std::ops::Range<usize>) -> Self {
56        Self {
57            start: range.start,
58            end: range.end,
59        }
60    }
61
62    /// Creates a zero-length, empty span.
63    pub fn empty() -> Self {
64        Self::new(0..0)
65    }
66
67    /// Returns the start byte offset.
68    pub fn start(&self) -> usize {
69        self.start
70    }
71
72    /// Returns the end byte offset.
73    pub fn end(&self) -> usize {
74        self.end
75    }
76
77    /// Returns the length of the span in bytes.
78    ///
79    /// # Panics
80    ///
81    /// Panics if `end` is less than `start`.
82    pub fn len(&self) -> usize {
83        if self.end >= self.start {
84            self.end - self.start
85        } else {
86            panic!("Invalid span: end offset is less than start offset");
87        }
88    }
89
90    /// Returns `true` if the span has zero length.
91    pub fn is_empty(&self) -> bool {
92        self.start == self.end
93    }
94
95    /// Creates the smallest span encompassing both `self` and `other`.
96    ///
97    /// If either span is empty, the other is returned unchanged.
98    ///
99    /// # Arguments
100    ///
101    /// * `other` - The span to merge with.
102    pub fn union(&self, other: Span) -> Span {
103        if self.is_empty() {
104            return other;
105        }
106        if other.is_empty() {
107            return *self;
108        }
109        Self {
110            start: self.start.min(other.start),
111            end: self.end.max(other.end),
112        }
113    }
114
115    /// Returns a new span with both offsets translated by `base` bytes.
116    ///
117    /// This shifts the span's position in the virtual address space
118    /// without changing its length.
119    ///
120    /// # Arguments
121    ///
122    /// * `base` - Number of bytes to add to both `start` and `end`.
123    pub fn shift(self, base: usize) -> Span {
124        Self {
125            start: self.start + base,
126            end: self.end + base,
127        }
128    }
129}
130
131impl Default for Span {
132    fn default() -> Self {
133        Self::empty()
134    }
135}
136
137impl From<std::ops::Range<usize>> for Span {
138    fn from(range: std::ops::Range<usize>) -> Self {
139        Self::new(range)
140    }
141}
142
143/// A value paired with its source location in the input text.
144///
145/// `Spanned<T>` wraps any type `T` with a [`Span`], carrying precise source
146/// locations through to diagnostics.
147///
148/// Equality comparison ([`PartialEq`]) considers only the inner value;
149/// two `Spanned` values with different spans but equal inner values are equal.
150///
151/// # Examples
152///
153/// ```ignore
154/// let name = Spanned::new("server", Span::new(4..10));
155/// assert_eq!(*name.inner(), "server");
156/// assert_eq!(name.span().start(), 4);
157///
158/// // Deref lets you call methods on the inner value directly
159/// assert!(name.starts_with("ser"));
160/// ```
161#[derive(Debug, Default, Clone, Eq)]
162pub struct Spanned<T> {
163    /// The wrapped value.
164    value: T,
165    /// Source location of this value.
166    span: Span,
167}
168
169impl<T> Spanned<T> {
170    /// Creates a new [`Spanned`] value.
171    ///
172    /// # Arguments
173    ///
174    /// * `value` - The value to wrap.
175    /// * `span` - The source location of `value`.
176    pub fn new(value: T, span: Span) -> Self {
177        Self { value, span }
178    }
179
180    /// Returns the source [`Span`] associated with this value.
181    pub fn span(&self) -> Span {
182        self.span
183    }
184
185    /// Transforms the inner value while preserving the span.
186    ///
187    /// # Arguments
188    ///
189    /// * `f` - A function applied to a reference of the inner value.
190    pub fn map<F, U>(&self, f: F) -> Spanned<U>
191    where
192        F: FnOnce(&T) -> U,
193    {
194        Spanned {
195            value: f(&self.value),
196            span: self.span,
197        }
198    }
199
200    /// Returns a reference to the inner value.
201    pub fn inner(&self) -> &T {
202        &self.value
203    }
204
205    /// Consumes the wrapper and returns the inner value.
206    #[allow(dead_code)]
207    pub fn into_inner(self) -> T {
208        self.value
209    }
210}
211
212// Delegates field/method access to the inner value.
213impl<T> Deref for Spanned<T> {
214    type Target = T;
215
216    fn deref(&self) -> &Self::Target {
217        &self.value
218    }
219}
220
221// Delegates formatting to the inner value.
222impl<T: fmt::Display> fmt::Display for Spanned<T> {
223    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224        self.value.fmt(f)
225    }
226}
227
228// Compares only the inner values, ignoring span information.
229impl<T: PartialEq> PartialEq for Spanned<T> {
230    fn eq(&self, other: &Self) -> bool {
231        self.value.eq(&other.value)
232    }
233}
234
235#[cfg(test)]
236mod tests {
237    use super::*;
238
239    #[test]
240    fn test_span_basic_functionality() {
241        let span = Span::new(5..10);
242        assert_eq!(span.start(), 5);
243        assert_eq!(span.end(), 10);
244        assert_eq!(span.len(), 5);
245        assert!(!span.is_empty());
246    }
247
248    #[test]
249    fn test_span_empty() {
250        let span = Span::new(5..5);
251        assert_eq!(span.len(), 0);
252        assert!(span.is_empty());
253    }
254
255    #[test]
256    fn test_span_union() {
257        let span1 = Span::new(5..10);
258        let span2 = Span::new(15..20);
259        let union = span1.union(span2);
260        assert_eq!(union.start(), 5);
261        assert_eq!(union.end(), 20);
262    }
263
264    #[test]
265    fn test_span_shift() {
266        let span = Span::new(5..10);
267        let shifted = span.shift(100);
268        assert_eq!(shifted.start(), 105);
269        assert_eq!(shifted.end(), 110);
270        assert_eq!(shifted.len(), 5);
271    }
272
273    #[test]
274    fn test_span_shift_zero_is_identity() {
275        let span = Span::new(5..10);
276        assert_eq!(span.shift(0), span);
277    }
278
279    #[test]
280    fn test_spanned_with_new_span() {
281        let span = Span::new(5..10);
282        let spanned = Spanned::new("test", span);
283        assert_eq!(spanned.span().start(), 5);
284        assert_eq!(spanned.span().len(), 5);
285        assert_eq!(*spanned.inner(), "test");
286    }
287
288    #[test]
289    fn test_span_empty_constructor() {
290        let span = Span::empty();
291        assert_eq!(span.start(), 0);
292        assert_eq!(span.end(), 0);
293        assert_eq!(span.len(), 0);
294        assert!(span.is_empty());
295    }
296
297    #[test]
298    fn test_span_default_is_empty() {
299        let span = Span::default();
300        assert_eq!(span, Span::empty());
301        assert!(span.is_empty());
302    }
303
304    #[test]
305    fn test_span_union_with_left_empty() {
306        let empty = Span::empty();
307        let span = Span::new(5..10);
308        let union = empty.union(span);
309        assert_eq!(union, span);
310    }
311
312    #[test]
313    fn test_span_union_with_right_empty() {
314        let span = Span::new(5..10);
315        let empty = Span::empty();
316        let union = span.union(empty);
317        assert_eq!(union, span);
318    }
319
320    #[test]
321    fn test_span_union_both_empty() {
322        let union = Span::empty().union(Span::empty());
323        assert!(union.is_empty());
324    }
325
326    #[test]
327    fn test_spanned_eq_ignores_span() {
328        let a = Spanned::new(42, Span::new(0..5));
329        let b = Spanned::new(42, Span::new(10..20));
330        assert_eq!(a, b);
331    }
332
333    #[test]
334    fn test_spanned_ne_different_values() {
335        let a = Spanned::new(1, Span::new(0..5));
336        let b = Spanned::new(2, Span::new(0..5));
337        assert_ne!(a, b);
338    }
339
340    #[test]
341    fn test_spanned_map() {
342        let spanned = Spanned::new(5, Span::new(0..10));
343        let mapped = spanned.map(|v| v * 2);
344        assert_eq!(*mapped.inner(), 10);
345        assert_eq!(mapped.span(), spanned.span());
346    }
347
348    #[test]
349    fn test_spanned_into_inner() {
350        let spanned = Spanned::new(String::from("hello"), Span::new(0..5));
351        let value = spanned.into_inner();
352        assert_eq!(value, "hello");
353    }
354
355    #[test]
356    fn test_spanned_deref() {
357        let spanned = Spanned::new(String::from("hello"), Span::new(0..5));
358        // Deref lets us call String methods directly
359        assert_eq!(spanned.len(), 5);
360        assert!(spanned.starts_with("he"));
361    }
362
363    #[test]
364    fn test_spanned_display() {
365        let spanned = Spanned::new(42, Span::new(0..5));
366        assert_eq!(format!("{spanned}"), "42");
367    }
368}