Skip to main content

span_lang/
pos.rs

1//! Byte positions — the atom every [`Span`](crate::Span) is built from.
2
3use core::fmt;
4
5/// A zero-based byte offset into a single source buffer.
6///
7/// `BytePos` is a `Copy` newtype over a `u32`, so it is eight-times cheaper to
8/// move than a `usize` pair and fits two-to-a-cache-line inside a
9/// [`Span`](crate::Span). The 32-bit width bounds a single source to 4 GiB, which
10/// is the addressing envelope language front-ends use; a larger source belongs in
11/// a multi-file mapping above this crate, not in a wider offset here.
12///
13/// The offset is a *byte* index, not a character index — it may only legally fall
14/// on a UTF-8 character boundary. Resolving an offset that lands inside a
15/// multi-byte sequence is defined (it rounds down) rather than undefined; see
16/// [`LineIndex::line_col`](crate::LineIndex::line_col).
17///
18/// # Examples
19///
20/// ```
21/// use span_lang::BytePos;
22///
23/// let p = BytePos::new(42);
24/// assert_eq!(p.to_u32(), 42);
25/// assert_eq!(p.to_usize(), 42);
26///
27/// // Ordered, so positions sort and compare naturally.
28/// assert!(BytePos::new(1) < BytePos::new(2));
29/// ```
30#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
31#[cfg_attr(feature = "serde", serde(transparent))]
32#[repr(transparent)]
33#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
34pub struct BytePos(u32);
35
36impl BytePos {
37    /// Constructs a position from a raw byte offset.
38    ///
39    /// # Examples
40    ///
41    /// ```
42    /// use span_lang::BytePos;
43    ///
44    /// const START: BytePos = BytePos::new(0);
45    /// assert_eq!(START.to_u32(), 0);
46    /// ```
47    #[inline]
48    #[must_use]
49    pub const fn new(offset: u32) -> Self {
50        Self(offset)
51    }
52
53    /// Returns the raw byte offset.
54    ///
55    /// # Examples
56    ///
57    /// ```
58    /// use span_lang::BytePos;
59    ///
60    /// assert_eq!(BytePos::new(7).to_u32(), 7);
61    /// ```
62    #[inline]
63    #[must_use]
64    pub const fn to_u32(self) -> u32 {
65        self.0
66    }
67
68    /// Returns the offset widened to a `usize`, ready to index a byte slice.
69    ///
70    /// # Examples
71    ///
72    /// ```
73    /// use span_lang::BytePos;
74    ///
75    /// let src = b"hello";
76    /// let at = BytePos::new(1);
77    /// assert_eq!(src[at.to_usize()], b'e');
78    /// ```
79    #[inline]
80    #[must_use]
81    pub const fn to_usize(self) -> usize {
82        self.0 as usize
83    }
84}
85
86impl From<u32> for BytePos {
87    #[inline]
88    fn from(offset: u32) -> Self {
89        Self(offset)
90    }
91}
92
93impl From<BytePos> for u32 {
94    #[inline]
95    fn from(pos: BytePos) -> Self {
96        pos.0
97    }
98}
99
100impl fmt::Display for BytePos {
101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102        fmt::Display::fmt(&self.0, f)
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109
110    #[test]
111    fn test_byte_pos_round_trips_through_u32() {
112        let p = BytePos::new(123);
113        assert_eq!(u32::from(p), 123);
114        assert_eq!(BytePos::from(123u32), p);
115    }
116
117    #[test]
118    fn test_byte_pos_default_is_zero() {
119        assert_eq!(BytePos::default(), BytePos::new(0));
120    }
121
122    #[test]
123    fn test_byte_pos_ordering_matches_offset() {
124        assert!(BytePos::new(3) < BytePos::new(4));
125        assert!(BytePos::new(9) > BytePos::new(8));
126    }
127
128    #[test]
129    fn test_byte_pos_display_is_the_number() {
130        extern crate alloc;
131        use alloc::string::ToString;
132        assert_eq!(BytePos::new(256).to_string(), "256");
133    }
134}