Skip to main content

svelte_syntax/
primitives.rs

1use std::fmt;
2
3use serde::{Deserialize, Serialize};
4
5/// A byte offset into source text, stored as a `u32`.
6#[derive(
7    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default,
8)]
9#[repr(transparent)]
10pub struct BytePos(u32);
11
12impl BytePos {
13    /// The zero position.
14    pub const ZERO: Self = Self(0);
15
16    /// Create a byte position from a raw `u32` value.
17    pub const fn new(raw: u32) -> Self {
18        Self(raw)
19    }
20
21    /// Return the position as a `u32`.
22    pub const fn as_u32(self) -> u32 {
23        self.0
24    }
25
26    /// Return the position as a `usize`.
27    pub const fn as_usize(self) -> usize {
28        self.0 as usize
29    }
30}
31
32impl TryFrom<usize> for BytePos {
33    type Error = &'static str;
34
35    fn try_from(value: usize) -> Result<Self, Self::Error> {
36        u32::try_from(value)
37            .map(Self)
38            .map_err(|_| "byte position exceeds u32 range")
39    }
40}
41
42impl From<u32> for BytePos {
43    fn from(value: u32) -> Self {
44        Self(value)
45    }
46}
47
48impl fmt::Display for BytePos {
49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50        write!(f, "{}", self.0)
51    }
52}
53
54/// A half-open byte range (`start..end`) in source text.
55#[derive(
56    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default,
57)]
58pub struct Span {
59    /// Inclusive start position.
60    pub start: BytePos,
61    /// Exclusive end position.
62    pub end: BytePos,
63}
64
65impl Span {
66    /// A zero-length span at position 0.
67    pub const EMPTY: Self = Self {
68        start: BytePos::ZERO,
69        end: BytePos::ZERO,
70    };
71
72    /// Create a span from two byte positions.
73    pub const fn new(start: BytePos, end: BytePos) -> Self {
74        Self { start, end }
75    }
76
77    /// Create a span from `usize` offsets, returning `None` if either
78    /// offset exceeds the `u32` range.
79    pub fn from_offsets(start: usize, end: usize) -> Option<Self> {
80        Some(Self {
81            start: BytePos::try_from(start).ok()?,
82            end: BytePos::try_from(end).ok()?,
83        })
84    }
85
86    /// Return the length in bytes.
87    pub const fn len(self) -> u32 {
88        self.end.as_u32().saturating_sub(self.start.as_u32())
89    }
90
91    /// Return `true` if the span covers zero bytes.
92    pub const fn is_empty(self) -> bool {
93        self.start.as_u32() >= self.end.as_u32()
94    }
95
96    /// Return `true` if `pos` falls within this span (inclusive on both ends).
97    pub fn contains(self, pos: BytePos) -> bool {
98        pos >= self.start && pos <= self.end
99    }
100
101    /// Return the smallest span that covers both `self` and `other`.
102    pub fn join(self, other: Span) -> Span {
103        Span {
104            start: if self.start <= other.start {
105                self.start
106            } else {
107                other.start
108            },
109            end: if self.end >= other.end {
110                self.end
111            } else {
112                other.end
113            },
114        }
115    }
116}
117
118impl fmt::Display for Span {
119    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120        write!(f, "{}..{}", self.start, self.end)
121    }
122}
123
124/// An opaque identifier for a source file, used to distinguish multiple
125/// inputs during batch processing.
126#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
127#[repr(transparent)]
128pub struct SourceId(u32);
129
130impl SourceId {
131    /// Create a source identifier from a raw `u32` value.
132    pub const fn new(raw: u32) -> Self {
133        Self(raw)
134    }
135
136    /// Return the raw `u32` value.
137    pub const fn as_u32(self) -> u32 {
138        self.0
139    }
140}
141
142impl fmt::Display for SourceId {
143    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144        write!(f, "{}", self.0)
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::{BytePos, Span};
151
152    #[test]
153    fn span_join_and_len() {
154        let a = Span::new(BytePos::new(2), BytePos::new(5));
155        let b = Span::new(BytePos::new(4), BytePos::new(10));
156        let joined = a.join(b);
157
158        assert_eq!(joined.start.as_u32(), 2);
159        assert_eq!(joined.end.as_u32(), 10);
160        assert_eq!(joined.len(), 8);
161    }
162}