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}