Skip to main content

surrealguard_syntax/
span.rs

1//! Byte-based span primitives.
2
3use serde::{Deserialize, Serialize};
4
5use crate::source::SourceId;
6
7/// Half-open byte range, `[start, end)`, in a source file or virtual source.
8#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
9pub struct ByteRange {
10    start: u32,
11    end: u32,
12}
13
14impl ByteRange {
15    /// Builds a range, rejecting `start > end`.
16    pub fn new(start: u32, end: u32) -> Result<Self, InvalidByteRange> {
17        if start <= end {
18            Ok(Self { start, end })
19        } else {
20            Err(InvalidByteRange { start, end })
21        }
22    }
23
24    /// Inclusive start byte offset.
25    pub fn start(self) -> u32 {
26        self.start
27    }
28
29    /// Exclusive end byte offset.
30    pub fn end(self) -> u32 {
31        self.end
32    }
33
34    /// Number of bytes covered.
35    pub fn len(self) -> u32 {
36        self.end - self.start
37    }
38
39    /// Whether the range covers zero bytes.
40    pub fn is_empty(self) -> bool {
41        self.start == self.end
42    }
43}
44
45/// Error returned when constructing a byte range with `start > end`.
46#[derive(Clone, Copy, Debug, PartialEq, Eq)]
47pub struct InvalidByteRange {
48    start: u32,
49    end: u32,
50}
51
52impl InvalidByteRange {
53    /// The rejected start offset.
54    pub fn start(self) -> u32 {
55        self.start
56    }
57
58    /// The rejected end offset.
59    pub fn end(self) -> u32 {
60        self.end
61    }
62}
63
64/// A byte range tied to the source it belongs to.
65#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
66pub struct SourceSpan {
67    source: SourceId,
68    range: ByteRange,
69}
70
71impl SourceSpan {
72    /// Pairs a byte range with the source it belongs to.
73    pub fn new(source: SourceId, range: ByteRange) -> Self {
74        Self { source, range }
75    }
76
77    /// The source this span points into.
78    pub fn source(&self) -> &SourceId {
79        &self.source
80    }
81
82    /// The byte range within the source.
83    pub fn range(&self) -> ByteRange {
84        self.range
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91    use crate::source::SourceId;
92
93    #[test]
94    fn byte_range_accepts_ordered_bounds() {
95        let range = ByteRange::new(0, 3).expect("ordered range should be valid");
96
97        assert_eq!(range.start(), 0);
98        assert_eq!(range.end(), 3);
99        assert_eq!(range.len(), 3);
100        assert!(!range.is_empty());
101    }
102
103    #[test]
104    fn byte_range_rejects_reversed_bounds() {
105        let error = ByteRange::new(3, 0).expect_err("reversed range should be invalid");
106
107        assert_eq!(error.start(), 3);
108        assert_eq!(error.end(), 0);
109    }
110
111    #[test]
112    fn source_span_preserves_source_identity_and_range() {
113        let source = SourceId::new("query:001");
114        let range = ByteRange::new(4, 9).expect("range should be valid");
115
116        let span = SourceSpan::new(source.clone(), range);
117
118        assert_eq!(span.source(), &source);
119        assert_eq!(span.range(), range);
120    }
121}