sway_types/
span.rs

1use crate::SourceId;
2use lazy_static::lazy_static;
3use serde::{Deserialize, Serialize};
4use std::{
5    cmp,
6    fmt::{self, Display},
7    hash::Hash,
8    sync::Arc,
9};
10
11lazy_static! {
12    static ref DUMMY_SPAN: Span = Span::new(Arc::from(""), 0, 0, None).unwrap();
13}
14
15pub struct Position<'a> {
16    input: &'a str,
17    pos: usize,
18}
19
20impl<'a> Position<'a> {
21    pub fn new(input: &'a str, pos: usize) -> Option<Position<'a>> {
22        input.get(pos..).map(|_| Position { input, pos })
23    }
24
25    pub fn line_col(&self) -> LineCol {
26        assert!(self.pos <= self.input.len(), "position out of bounds");
27
28        // This is performance critical, so we use bytecount instead of a naive implementation.
29        let newlines_up_to_pos = bytecount::count(&self.input.as_bytes()[..self.pos], b'\n');
30        let line = newlines_up_to_pos + 1;
31
32        // Find the last newline character before the position
33        let last_newline_pos = match self.input[..self.pos].rfind('\n') {
34            Some(pos) => pos + 1, // Start after the newline
35            None => 0,            // If no newline, start is at the beginning
36        };
37
38        // Column number should start from 1, not 0
39        let col = self.pos - last_newline_pos + 1;
40        LineCol { line, col }
41    }
42}
43
44/// Represents a span of the source code in a specific file.
45#[derive(Clone, Ord, PartialOrd, Serialize, Deserialize)]
46pub struct Span {
47    // The original source code.
48    src: Arc<str>,
49    // The byte position in the string of the start of the span.
50    start: usize,
51    // The byte position in the string of the end of the span.
52    end: usize,
53    // A reference counted pointer to the file from which this span originated.
54    source_id: Option<SourceId>,
55}
56
57impl Hash for Span {
58    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
59        self.start.hash(state);
60        self.end.hash(state);
61        self.source_id.hash(state);
62    }
63}
64
65impl PartialEq for Span {
66    fn eq(&self, other: &Self) -> bool {
67        self.start == other.start && self.end == other.end && self.source_id == other.source_id
68    }
69}
70
71impl Eq for Span {}
72
73impl From<Span> for std::ops::Range<usize> {
74    fn from(value: Span) -> Self {
75        Self {
76            start: value.start,
77            end: value.end,
78        }
79    }
80}
81
82impl Span {
83    pub fn dummy() -> Span {
84        DUMMY_SPAN.clone()
85    }
86
87    pub fn new(src: Arc<str>, start: usize, end: usize, source: Option<SourceId>) -> Option<Span> {
88        let _ = src.get(start..end)?;
89        Some(Span {
90            src,
91            start,
92            end,
93            source_id: source,
94        })
95    }
96
97    /// Creates an empty [Span], means a span whose [Span::start] and [Span::end] are the same.
98    /// The resulting empty [Span] will point to the start of the provided `span` and
99    /// be in the same file.
100    pub fn empty_at_start(span: &Span) -> Span {
101        Span::new(
102            span.src().clone(),
103            span.start(),
104            span.start(),
105            span.source_id().copied(),
106        )
107        .expect("the existing `span` is a valid `Span`")
108    }
109
110    /// Creates an empty [Span], means a span whose [Span::start] and [Span::end] are the same.
111    /// The resulting empty [Span] will point to the end of the provided `span` and
112    /// be in the same file.
113    pub fn empty_at_end(span: &Span) -> Span {
114        Span::new(
115            span.src().clone(),
116            span.end(),
117            span.end(),
118            span.source_id().copied(),
119        )
120        .expect("the existing `span` is a valid `Span`")
121    }
122
123    pub fn from_string(source: String) -> Span {
124        let len = source.len();
125        Span::new(Arc::from(source), 0, len, None).unwrap()
126    }
127
128    pub fn src(&self) -> &Arc<str> {
129        &self.src
130    }
131
132    pub fn start(&self) -> usize {
133        self.start
134    }
135
136    pub fn end(&self) -> usize {
137        self.end
138    }
139
140    pub fn source_id(&self) -> Option<&SourceId> {
141        self.source_id.as_ref()
142    }
143
144    pub fn start_pos(&self) -> Position {
145        Position::new(&self.src, self.start).unwrap()
146    }
147
148    pub fn end_pos(&self) -> Position {
149        Position::new(&self.src, self.end).unwrap()
150    }
151
152    pub fn split(&self) -> (Position, Position) {
153        let start = self.start_pos();
154        let end = self.end_pos();
155        (start, end)
156    }
157
158    pub fn str(self) -> String {
159        self.as_str().to_owned()
160    }
161
162    pub fn as_str(&self) -> &str {
163        &self.src[self.start..self.end]
164    }
165
166    pub fn input(&self) -> &str {
167        &self.src
168    }
169
170    pub fn trim(self) -> Span {
171        let start_delta = self.as_str().len() - self.as_str().trim_start().len();
172        let end_delta = self.as_str().len() - self.as_str().trim_end().len();
173        Span {
174            src: self.src,
175            start: self.start + start_delta,
176            end: self.end - end_delta,
177            source_id: self.source_id,
178        }
179    }
180
181    /// Creates a new span that points to very next char of the current span.
182    ///
183    /// ```ignore
184    /// let
185    ///    ^ <- span returned
186    /// ^^^  <- original span
187    /// ```
188    pub fn next_char_utf8(&self) -> Option<Span> {
189        let char = self.src[self.end..].chars().next()?;
190        Some(Span {
191            src: self.src.clone(),
192            source_id: self.source_id,
193            start: self.end,
194            end: self.end + char.len_utf8(),
195        })
196    }
197
198    /// This panics if the spans are not from the same file. This should
199    /// only be used on spans that are actually next to each other.
200    pub fn join(s1: Span, s2: &Span) -> Span {
201        assert!(
202            Arc::ptr_eq(&s1.src, &s2.src) && s1.source_id == s2.source_id,
203            "Spans from different files cannot be joined.",
204        );
205
206        Span {
207            src: s1.src,
208            start: cmp::min(s1.start, s2.start),
209            end: cmp::max(s1.end, s2.end),
210            source_id: s1.source_id,
211        }
212    }
213
214    pub fn join_all(spans: impl IntoIterator<Item = Span>) -> Span {
215        spans
216            .into_iter()
217            .reduce(|s1: Span, s2: Span| Span::join(s1, &s2))
218            .unwrap_or_else(Span::dummy)
219    }
220
221    /// Returns the line and column start and end.
222    pub fn line_col(&self) -> LineColRange {
223        LineColRange {
224            start: self.start_pos().line_col(),
225            end: self.end_pos().line_col(),
226        }
227    }
228
229    pub fn is_dummy(&self) -> bool {
230        self.eq(&DUMMY_SPAN)
231    }
232
233    pub fn is_empty(&self) -> bool {
234        self.start == self.end
235    }
236
237    /// Returns true if `self` contains `other`.
238    pub fn contains(&self, other: &Span) -> bool {
239        Arc::ptr_eq(&self.src, &other.src)
240            && self.source_id == other.source_id
241            && self.start <= other.start
242            && self.end >= other.end
243    }
244}
245
246impl fmt::Debug for Span {
247    #[cfg(not(feature = "no-span-debug"))]
248    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
249        fmt.debug_struct("Span")
250            .field("src (ptr)", &self.src.as_ptr())
251            .field("source_id", &self.source_id)
252            .field("start", &self.start)
253            .field("end", &self.end)
254            .field("as_str()", &self.as_str())
255            .finish()
256    }
257    #[cfg(feature = "no-span-debug")]
258    fn fmt(&self, _fmt: &mut fmt::Formatter) -> fmt::Result {
259        Ok(())
260    }
261}
262
263pub trait Spanned {
264    fn span(&self) -> Span;
265}
266
267impl<T: Spanned> Spanned for Box<T> {
268    fn span(&self) -> Span {
269        (**self).span()
270    }
271}
272
273#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
274pub struct LineCol {
275    pub line: usize,
276    pub col: usize,
277}
278
279pub struct LineColRange {
280    pub start: LineCol,
281    pub end: LineCol,
282}
283
284impl Display for LineColRange {
285    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
286        f.write_fmt(format_args!("({}, {})", self.start, self.end))
287    }
288}
289
290impl Display for LineCol {
291    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
292        f.write_fmt(format_args!("line {}:{}", self.line, self.col))
293    }
294}