Skip to main content

sage_parser/
span.rs

1//! Source span types for error reporting.
2
3use std::fmt;
4use std::sync::Arc;
5
6/// A span representing a range in source code.
7#[derive(Clone, PartialEq, Eq, Hash)]
8pub struct Span {
9    /// Byte offset of the start of the span.
10    pub start: usize,
11    /// Byte offset of the end of the span (exclusive).
12    pub end: usize,
13    /// The source text this span refers to.
14    pub source: Arc<str>,
15}
16
17impl Span {
18    /// Create a new span.
19    #[must_use]
20    pub fn new(start: usize, end: usize, source: Arc<str>) -> Self {
21        Self { start, end, source }
22    }
23
24    /// Create a dummy span for testing or synthetic nodes.
25    #[must_use]
26    pub fn dummy() -> Self {
27        Self {
28            start: 0,
29            end: 0,
30            source: Arc::from(""),
31        }
32    }
33
34    /// Get the length of this span in bytes.
35    #[must_use]
36    pub fn len(&self) -> usize {
37        self.end.saturating_sub(self.start)
38    }
39
40    /// Check if this span is empty.
41    #[must_use]
42    pub fn is_empty(&self) -> bool {
43        self.len() == 0
44    }
45
46    /// Get the source text covered by this span.
47    #[must_use]
48    pub fn text(&self) -> &str {
49        &self.source[self.start..self.end]
50    }
51
52    /// Merge two spans into one that covers both.
53    #[must_use]
54    pub fn merge(&self, other: &Span) -> Span {
55        Span {
56            start: self.start.min(other.start),
57            end: self.end.max(other.end),
58            source: Arc::clone(&self.source),
59        }
60    }
61
62    /// Calculate line and column (1-indexed) for the start of this span.
63    #[must_use]
64    pub fn line_col(&self) -> (usize, usize) {
65        let mut line = 1;
66        let mut col = 1;
67        for (i, ch) in self.source.char_indices() {
68            if i >= self.start {
69                break;
70            }
71            if ch == '\n' {
72                line += 1;
73                col = 1;
74            } else {
75                col += 1;
76            }
77        }
78        (line, col)
79    }
80}
81
82impl fmt::Debug for Span {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        let (line, col) = self.line_col();
85        let len = self.end - self.start;
86        write!(f, "{line}:{col}..{len}")
87    }
88}
89
90impl fmt::Display for Span {
91    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92        let (line, col) = self.line_col();
93        write!(f, "{line}:{col}")
94    }
95}
96
97/// An identifier with its source span.
98#[derive(Clone, PartialEq, Eq, Hash)]
99pub struct Ident {
100    /// The identifier name.
101    pub name: String,
102    /// The source span.
103    pub span: Span,
104}
105
106impl Ident {
107    /// Create a new identifier.
108    #[must_use]
109    pub fn new(name: impl Into<String>, span: Span) -> Self {
110        Self {
111            name: name.into(),
112            span,
113        }
114    }
115
116    /// Create a dummy identifier for testing.
117    #[must_use]
118    pub fn dummy(name: impl Into<String>) -> Self {
119        Self {
120            name: name.into(),
121            span: Span::dummy(),
122        }
123    }
124}
125
126impl fmt::Debug for Ident {
127    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128        write!(f, "Ident({:?})", self.name)
129    }
130}
131
132impl fmt::Display for Ident {
133    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134        write!(f, "{}", self.name)
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    #[test]
143    fn span_text() {
144        let source: Arc<str> = Arc::from("hello world");
145        let span = Span::new(0, 5, Arc::clone(&source));
146        assert_eq!(span.text(), "hello");
147    }
148
149    #[test]
150    fn span_line_col() {
151        let source: Arc<str> = Arc::from("line1\nline2\nline3");
152
153        // Start of file
154        let span = Span::new(0, 1, Arc::clone(&source));
155        assert_eq!(span.line_col(), (1, 1));
156
157        // Start of line 2
158        let span = Span::new(6, 7, Arc::clone(&source));
159        assert_eq!(span.line_col(), (2, 1));
160
161        // Middle of line 3
162        let span = Span::new(14, 15, Arc::clone(&source));
163        assert_eq!(span.line_col(), (3, 3));
164    }
165
166    #[test]
167    fn span_merge() {
168        let source: Arc<str> = Arc::from("hello world");
169        let span1 = Span::new(0, 5, Arc::clone(&source));
170        let span2 = Span::new(6, 11, Arc::clone(&source));
171        let merged = span1.merge(&span2);
172        assert_eq!(merged.start, 0);
173        assert_eq!(merged.end, 11);
174    }
175
176    #[test]
177    fn ident_display() {
178        let ident = Ident::dummy("foo");
179        assert_eq!(format!("{ident}"), "foo");
180    }
181}