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(
13        Source {
14            text: Arc::from(""),
15            line_starts: Arc::new(vec![])
16        },
17        0,
18        0,
19        None
20    )
21    .unwrap();
22}
23
24// remote="Self" is a serde pattern for post-deserialization code.
25// See https://github.com/serde-rs/serde/issues/1118#issuecomment-1320706758
26#[derive(Debug, Clone, Serialize, Deserialize)]
27#[serde(transparent, remote = "Self")]
28pub struct Source {
29    pub text: Arc<str>,
30    #[serde(skip)]
31    pub line_starts: Arc<Vec<usize>>,
32}
33
34impl serde::Serialize for Source {
35    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
36    where
37        S: serde::Serializer,
38    {
39        Self::serialize(self, serializer)
40    }
41}
42
43impl<'de> serde::Deserialize<'de> for Source {
44    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
45        let mut src = Self::deserialize(deserializer)?;
46        src.line_starts = Self::calc_line_starts(&src.text);
47        Ok(src)
48    }
49}
50
51impl Source {
52    fn calc_line_starts(text: &str) -> Arc<Vec<usize>> {
53        let mut lines_starts = Vec::with_capacity(text.len() / 80);
54        lines_starts.push(0);
55        for (idx, c) in text.char_indices() {
56            if c == '\n' {
57                lines_starts.push(idx + c.len_utf8())
58            }
59        }
60        Arc::new(lines_starts)
61    }
62
63    pub fn new(text: &str) -> Self {
64        Self {
65            text: Arc::from(text),
66            line_starts: Self::calc_line_starts(text),
67        }
68    }
69
70    /// Both lines and columns start at index 0
71    pub fn line_col_zero_index(&self, position: usize) -> LineCol {
72        if position > self.text.len() || self.text.is_empty() {
73            LineCol { line: 0, col: 0 }
74        } else {
75            let (line, line_start) = match self.line_starts.binary_search(&position) {
76                Ok(line) => (line, self.line_starts.get(line)),
77                Err(0) => (0, None),
78                Err(line) => (line - 1, self.line_starts.get(line - 1)),
79            };
80            line_start.map_or(LineCol { line: 0, col: 0 }, |line_start| LineCol {
81                line,
82                col: position - line_start,
83            })
84        }
85    }
86
87    /// Both lines and columns start at index 1
88    pub fn line_col_one_index(&self, position: usize) -> LineCol {
89        let LineCol { line, col } = self.line_col_zero_index(position);
90        LineCol {
91            line: line + 1,
92            col: col + 1,
93        }
94    }
95}
96
97impl From<&str> for Source {
98    fn from(value: &str) -> Self {
99        Self::new(value)
100    }
101}
102
103/// Represents a span of the source code in a specific file.
104#[derive(Clone, Serialize, Deserialize)]
105pub struct Span {
106    // The original source code.
107    src: Source,
108    // The byte position in the string of the start of the span.
109    start: usize,
110    // The byte position in the string of the end of the span.
111    end: usize,
112    // A reference counted pointer to the file from which this span originated.
113    source_id: Option<SourceId>,
114}
115
116impl PartialOrd for Span {
117    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
118        if !Arc::ptr_eq(&self.src.text, &other.src.text) {
119            None
120        } else {
121            match self.start.partial_cmp(&other.start) {
122                Some(core::cmp::Ordering::Equal) => self.end.partial_cmp(&other.end),
123                ord => ord,
124            }
125        }
126    }
127}
128
129impl Hash for Span {
130    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
131        self.start.hash(state);
132        self.end.hash(state);
133        self.source_id.hash(state);
134    }
135}
136
137impl PartialEq for Span {
138    fn eq(&self, other: &Self) -> bool {
139        self.start == other.start && self.end == other.end && self.source_id == other.source_id
140    }
141}
142
143impl Eq for Span {}
144
145impl From<Span> for std::ops::Range<usize> {
146    fn from(value: Span) -> Self {
147        Self {
148            start: value.start,
149            end: value.end,
150        }
151    }
152}
153
154impl Span {
155    pub fn dummy() -> Span {
156        DUMMY_SPAN.clone()
157    }
158
159    pub fn new(src: Source, start: usize, end: usize, source: Option<SourceId>) -> Option<Span> {
160        let _ = src.text.get(start..end)?;
161        Some(Span {
162            src,
163            start,
164            end,
165            source_id: source,
166        })
167    }
168
169    /// Creates an empty [Span], means a span whose [Span::start] and [Span::end] are the same.
170    /// The resulting empty [Span] will point to the start of the provided `span` and
171    /// be in the same file.
172    pub fn empty_at_start(span: &Span) -> Span {
173        Span::new(
174            span.src().clone(),
175            span.start(),
176            span.start(),
177            span.source_id().copied(),
178        )
179        .expect("the existing `span` is a valid `Span`")
180    }
181
182    /// Creates an empty [Span], means a span whose [Span::start] and [Span::end] are the same.
183    /// The resulting empty [Span] will point to the end of the provided `span` and
184    /// be in the same file.
185    pub fn empty_at_end(span: &Span) -> Span {
186        Span::new(
187            span.src().clone(),
188            span.end(),
189            span.end(),
190            span.source_id().copied(),
191        )
192        .expect("the existing `span` is a valid `Span`")
193    }
194
195    pub fn from_string(source: String) -> Span {
196        let len = source.len();
197        Span::new(Source::new(&source), 0, len, None).unwrap()
198    }
199
200    pub fn src(&self) -> &Source {
201        &self.src
202    }
203
204    pub fn source_id(&self) -> Option<&SourceId> {
205        self.source_id.as_ref()
206    }
207
208    pub fn start(&self) -> usize {
209        self.start
210    }
211
212    pub fn end(&self) -> usize {
213        self.end
214    }
215
216    /// Both lines and columns start at index 1
217    pub fn start_line_col_one_index(&self) -> LineCol {
218        self.src.line_col_one_index(self.start)
219    }
220
221    /// Both lines and columns start at index 1
222    pub fn end_line_col_one_index(&self) -> LineCol {
223        self.src.line_col_one_index(self.end)
224    }
225
226    /// Returns an empty [Span] that points to the start of `self`.
227    pub fn start_span(&self) -> Span {
228        Self::empty_at_start(self)
229    }
230
231    /// Returns an empty [Span] that points to the end of `self`.
232    pub fn end_span(&self) -> Span {
233        Self::empty_at_end(self)
234    }
235
236    pub fn str(self) -> String {
237        self.as_str().to_owned()
238    }
239
240    pub fn as_str(&self) -> &str {
241        &self.src.text[self.start..self.end]
242    }
243
244    pub fn input(&self) -> &str {
245        &self.src.text
246    }
247
248    pub fn trim(self) -> Span {
249        let start_delta = self.as_str().len() - self.as_str().trim_start().len();
250        let end_delta = self.as_str().len() - self.as_str().trim_end().len();
251        Span {
252            src: self.src,
253            start: self.start + start_delta,
254            end: self.end - end_delta,
255            source_id: self.source_id,
256        }
257    }
258
259    /// Creates a new span that points to very next char of the current span.
260    ///
261    /// ```ignore
262    /// let
263    ///    ^ <- span returned
264    /// ^^^  <- original span
265    /// ```
266    pub fn next_char_utf8(&self) -> Option<Span> {
267        let char = self.src.text[self.end..].chars().next()?;
268        Some(Span {
269            src: self.src.clone(),
270            source_id: self.source_id,
271            start: self.end,
272            end: self.end + char.len_utf8(),
273        })
274    }
275
276    /// This panics if the spans are not from the same file. This should
277    /// only be used on spans that are actually next to each other.
278    pub fn join(s1: Span, s2: &Span) -> Span {
279        assert!(
280            Arc::ptr_eq(&s1.src.text, &s2.src.text) && s1.source_id == s2.source_id,
281            "Spans from different files cannot be joined.",
282        );
283
284        Span {
285            src: s1.src,
286            start: cmp::min(s1.start, s2.start),
287            end: cmp::max(s1.end, s2.end),
288            source_id: s1.source_id,
289        }
290    }
291
292    pub fn join_all(spans: impl IntoIterator<Item = Span>) -> Span {
293        spans
294            .into_iter()
295            .reduce(|s1: Span, s2: Span| Span::join(s1, &s2))
296            .unwrap_or_else(Span::dummy)
297    }
298
299    /// Returns the line and column start and end using index 1.
300    pub fn line_col_one_index(&self) -> LineColRange {
301        LineColRange {
302            start: self.start_line_col_one_index(),
303            end: self.end_line_col_one_index(),
304        }
305    }
306
307    pub fn is_dummy(&self) -> bool {
308        self.eq(&DUMMY_SPAN)
309    }
310
311    pub fn is_empty(&self) -> bool {
312        self.start == self.end
313    }
314
315    /// Returns true if `self` contains `other`.
316    pub fn contains(&self, other: &Span) -> bool {
317        Arc::ptr_eq(&self.src.text, &other.src.text)
318            && self.source_id == other.source_id
319            && self.start <= other.start
320            && self.end >= other.end
321    }
322
323    /// Returns a subset of this span until the first occurrence of the passed text.
324    pub fn subset_first_of(&self, needle: &str) -> Option<Span> {
325        let text = &self.src().text;
326        let needle_offset = text[self.start..].find(needle)?;
327        Span::new(
328            self.src().clone(),
329            self.start,
330            self.start + needle_offset,
331            self.source_id().cloned(),
332        )
333    }
334}
335
336impl fmt::Debug for Span {
337    #[cfg(not(feature = "no-span-debug"))]
338    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
339        fmt.debug_struct("Span")
340            .field("src (ptr)", &self.src.text.as_ptr())
341            .field("source_id", &self.source_id)
342            .field("start", &self.start)
343            .field("end", &self.end)
344            .field("as_str()", &self.as_str())
345            .finish()
346    }
347    #[cfg(feature = "no-span-debug")]
348    fn fmt(&self, _fmt: &mut fmt::Formatter) -> fmt::Result {
349        Ok(())
350    }
351}
352
353pub trait Spanned {
354    fn span(&self) -> Span;
355}
356
357impl<T: Spanned> Spanned for Box<T> {
358    fn span(&self) -> Span {
359        (**self).span()
360    }
361}
362
363#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, Hash)]
364pub struct LineCol {
365    pub line: usize,
366    pub col: usize,
367}
368
369pub struct LineColRange {
370    pub start: LineCol,
371    pub end: LineCol,
372}
373
374impl Display for LineColRange {
375    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
376        f.write_fmt(format_args!("({}, {})", self.start, self.end))
377    }
378}
379
380impl Display for LineCol {
381    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
382        f.write_fmt(format_args!("line {}:{}", self.line, self.col))
383    }
384}