Skip to main content

graphcal_compiler/syntax/
span.rs

1/// Byte-offset span in source code. Compatible with `miette::SourceSpan`.
2#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
3pub struct Span {
4    offset: usize,
5    len: usize,
6}
7
8impl Span {
9    #[must_use]
10    pub const fn new(offset: usize, len: usize) -> Self {
11        Self { offset, len }
12    }
13
14    /// Returns the byte offset.
15    #[must_use]
16    pub const fn offset(self) -> usize {
17        self.offset
18    }
19
20    /// Returns the byte length.
21    #[must_use]
22    pub const fn len(self) -> usize {
23        self.len
24    }
25
26    /// Returns `true` if the span has zero length.
27    #[must_use]
28    pub const fn is_empty(self) -> bool {
29        self.len == 0
30    }
31
32    /// Merge two spans into one that covers both.
33    #[must_use]
34    pub fn merge(self, other: Self) -> Self {
35        let start = self.offset.min(other.offset);
36        let end = (self.offset + self.len).max(other.offset + other.len);
37        Self::new(start, end - start)
38    }
39}
40
41impl From<Span> for miette::SourceSpan {
42    fn from(s: Span) -> Self {
43        (s.offset, s.len).into()
44    }
45}
46
47/// A value paired with its source span.
48#[derive(Debug, Clone, PartialEq, Eq, Hash)]
49pub struct Spanned<T> {
50    pub value: T,
51    pub span: Span,
52}
53
54impl<T> Spanned<T> {
55    /// Create a new spanned value.
56    pub const fn new(value: T, span: Span) -> Self {
57        Self { value, span }
58    }
59}
60
61impl<T> std::ops::Deref for Spanned<T> {
62    type Target = T;
63
64    fn deref(&self) -> &Self::Target {
65        &self.value
66    }
67}
68
69impl<T: std::fmt::Display> std::fmt::Display for Spanned<T> {
70    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71        self.value.fmt(f)
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    #[test]
80    fn span_merge() {
81        let a = Span::new(0, 5);
82        let b = Span::new(10, 3);
83        let merged = a.merge(b);
84        assert_eq!(merged.offset(), 0);
85        assert_eq!(merged.len(), 13);
86    }
87
88    #[test]
89    fn span_merge_overlapping() {
90        let a = Span::new(2, 5);
91        let b = Span::new(4, 6);
92        let merged = a.merge(b);
93        assert_eq!(merged.offset(), 2);
94        assert_eq!(merged.len(), 8);
95    }
96
97    #[test]
98    fn span_to_miette() {
99        let s = Span::new(10, 5);
100        let ms: miette::SourceSpan = s.into();
101        assert_eq!(ms.offset(), 10);
102        assert_eq!(ms.len(), 5);
103    }
104
105    #[test]
106    fn spanned_eq_considers_span() {
107        use crate::syntax::names::DeclName;
108
109        let a = Spanned::new(DeclName::new("x"), Span::new(0, 1));
110        let b = Spanned::new(DeclName::new("x"), Span::new(10, 11));
111        assert_ne!(a, b);
112    }
113
114    #[test]
115    fn spanned_ne_different_value() {
116        use crate::syntax::names::DeclName;
117
118        let a = Spanned::new(DeclName::new("x"), Span::new(0, 1));
119        let b = Spanned::new(DeclName::new("y"), Span::new(0, 1));
120        assert_ne!(a, b);
121    }
122
123    #[test]
124    fn spanned_hash_considers_span() {
125        use crate::syntax::names::DeclName;
126        use std::hash::{DefaultHasher, Hash, Hasher};
127
128        let a = Spanned::new(DeclName::new("x"), Span::new(0, 1));
129        let b = Spanned::new(DeclName::new("x"), Span::new(10, 11));
130        let mut ha = DefaultHasher::new();
131        a.hash(&mut ha);
132        let mut hb = DefaultHasher::new();
133        b.hash(&mut hb);
134        assert_ne!(ha.finish(), hb.finish());
135    }
136}