atlas_core/utils/
span.rs

1use core::fmt;
2
3use serde::{Deserialize, Serialize};
4
5/// Represents a position in bytes within a source file.
6#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Default, Serialize, Deserialize)]
7pub struct BytePos(usize);
8
9impl BytePos {
10    /// Shifts the byte position by the length of a character.
11    #[inline(always)]
12    pub fn shift(self, ch: char) -> Self {
13        BytePos(self.0 + ch.len_utf8())
14    }
15
16    /// Shifts the byte position by a specified number of bytes.
17    #[inline(always)]
18    pub fn shift_by(self, n: usize) -> Self {
19        BytePos(self.0 + n)
20    }
21}
22
23impl fmt::Display for BytePos {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        write!(f, "{}", self.0)
26    }
27}
28
29impl From<usize> for BytePos {
30    fn from(value: usize) -> Self {
31        BytePos(value)
32    }
33}
34
35impl From<BytePos> for usize {
36    fn from(value: BytePos) -> Self {
37        value.0
38    }
39}
40
41/// Represents a span in a source file, defined by a start and end byte position.
42#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Default, Serialize, Deserialize)]
43pub struct Span {
44    /// The position of character at the start of the span
45    pub start: BytePos,
46    /// The position of character at the end of the span
47    pub end: BytePos,
48}
49
50impl Span {
51    /// Creates a new `Span` without bounds checking.
52    /// # Safety
53    /// It's the caller's responsibility to ensure that `start` and `end` are valid
54    pub unsafe fn new_unchecked(start: usize, end: usize) -> Self {
55        Span {
56            start: BytePos(start),
57            end: BytePos(end),
58        }
59    }
60
61    /// Creates an empty `Span` with both start and end positions at zero.
62    #[inline]
63    pub const fn empty() -> Self {
64        Span {
65            start: BytePos(0),
66            end: BytePos(0),
67        }
68    }
69
70    /// Combines two spans to create a new span that encompasses both.
71    pub fn union_span(self, other: Self) -> Self {
72        use std::cmp;
73        Span {
74            start: cmp::min(self.start, other.start),
75            end: cmp::max(self.end, other.end),
76        }
77    }
78}
79
80impl fmt::Display for Span {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        write!(f, "[{}:{}]", self.start, self.end)
83    }
84}
85
86/// A trait representing an object that has an associated span in the source code.
87///
88/// This trait is used to obtain the span (range of positions) for a token or other
89/// syntactic element. It provides methods to get the start and end positions
90/// of the span.
91///
92/// # Required Methods
93///
94/// - `span(&self) -> Span`: Returns the `Span` representing the start and end positions of the object.
95///
96/// # Provided Methods
97///
98/// - `start(&self) -> usize`: Returns the starting position of the span.
99///   This method is implemented using the `span` method and returns the start position.
100/// - `end(&self) -> usize`: Returns the ending position of the span.
101///   This method is implemented using the `span` method and returns the end position.
102///
103/// # Example
104///
105/// ```
106/// use atlas_core::utils::span::{Spanned, Span, BytePos};
107///
108/// struct Token {
109///     span: Span,
110/// }
111///
112/// # impl Spanned for Token {
113/// #     fn span(&self) -> Span {
114/// #         self.span
115/// #     }
116/// # }
117/// # unsafe {
118/// //NB: "new_unchecked(usize, usize, char)" is an unsafe function, it's only used here as an example
119/// let token = Token { span: Span::new_unchecked(5, 10)};
120/// assert_eq!(token.start(), 5);
121/// assert_eq!(token.end(), 10);
122/// # }
123/// ```
124pub trait Spanned {
125    /// Returns the `Span` representing the start and end positions of the object.
126    fn span(&self) -> Span;
127    /// Returns the starting position of the span.
128    fn start(&self) -> usize {
129        self.span().start.0
130    }
131    /// Returns the end position of the span.
132    fn end(&self) -> usize {
133        self.span().end.0
134    }
135}
136
137impl Spanned for Span {
138    #[inline(always)]
139    fn span(&self) -> Span {
140        *self
141    }
142}