quoth/
span.rs

1//! Home of [`Span`] and related types and traits.
2
3use std::{fmt::Display, ops::Range, path::Path, rc::Rc};
4
5use super::*;
6
7/// Represents a specific range of text within a [`Source`].
8///
9/// Internally [`Span`] is extremely lightweight and is essentially just a reference to a
10/// [`Source`] and a range of bytes within that source, so it can be cheaply cloned and passed
11/// around without issue. The underlying [`Source`] mechanism is stored within an [`Rc`] so
12/// that it can be shared between multiple [`Span`]s without needing to be cloned. This cheap
13/// sharing, combined with the lack of any sort of tokenization in Quoth allows us to provide
14/// direct access to the original, unmodified source text for any given [`Span`].
15///
16/// Spans can be created directly using [`Span::new`], or by using the [`Spanned`] trait to
17/// access the underlying [`Span`] of a type.
18///
19/// ```
20/// use quoth::*;
21/// use std::rc::Rc;
22///
23/// let span = Span::new(Rc::new(Source::from_str("Hello, world!")), 0..5);
24/// ```
25///
26/// Spans can be joined together using the [`Span::join`] method, which will return a new
27/// [`Span`] that encompasses both of the original spans. This can be useful for combining
28/// spans that were generated from different parts of the same source.
29///
30/// ```
31/// use quoth::*;
32/// use std::rc::Rc;
33///
34/// let source = Rc::new(Source::from_str("Hello, world!"));
35/// let span1 = Span::new(source.clone(), 0..5);
36/// let span2 = Span::new(source.clone(), 7..12);
37/// let encompassing_span = span1.join(&span2).unwrap();
38/// assert_eq!(encompassing_span.source_text(), "Hello, world");
39/// ```
40#[derive(Clone, Debug, PartialEq, Eq, Hash)]
41pub struct Span {
42    source: Rc<Source>,
43    byte_range: Range<usize>,
44}
45
46/// Indicates that two [`Span`]s could not be joined because they do not come from the same [`Source`].
47#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
48pub struct SpanJoinError;
49
50impl Display for SpanJoinError {
51    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52        write!(f, "the specified spans do not come from the same source")
53    }
54}
55
56impl Span {
57    /// Returns a blank [`Span`] with no source and a zero-length range.
58    ///
59    /// Note that blank [`Span`]s are special in that they can be joined with a [`Span`] from
60    /// any [`Source`] without error, and will always return the other [`Span`] as the result.
61    pub fn blank() -> Span {
62        Span::new(Rc::new(Source::from_str("")), 0..0)
63    }
64
65    /// Creates a new [`Span`] from a [`Source`] and a byte range.
66    pub fn new(source: Rc<Source>, byte_range: Range<usize>) -> Self {
67        let mut byte_range = byte_range;
68        if source.len() > 0 && byte_range.end > source.len() {
69            byte_range.end = source.len();
70        }
71        Span { source, byte_range }
72    }
73
74    /// Returns the [`Source`] that this [`Span`] is associated with.
75    pub fn source(&self) -> &Source {
76        &self.source
77    }
78
79    /// Returns the text of the [`Source`] that this [`Span`] is associated with.
80    pub fn source_text(&self) -> IndexedSlice {
81        self.source.slice(self.byte_range.clone())
82    }
83
84    /// Returns the path of the [`Source`] that this [`Span`] is associated with, if it has one.
85    pub fn source_path(&self) -> Option<&Path> {
86        self.source.source_path()
87    }
88
89    /// Returns the byte range of this [`Span`], representing the start and end of the span
90    /// within the [`Source`].
91    ///
92    /// Note that because of UTF-8, the start and end of the range may not correspond with the
93    /// start and end of a character in the source text.
94    pub fn byte_range(&self) -> &Range<usize> {
95        &self.byte_range
96    }
97
98    /// Returns the line and column of the start of this [`Span`] within the [`Source`].
99    pub fn start(&self) -> LineCol {
100        let mut line = 0;
101        let mut col = 0;
102        for c in self.source.slice(0..self.byte_range.start).chars() {
103            if *c == '\n' {
104                col = 0;
105                line += 1;
106            } else {
107                col += 1;
108            }
109        }
110        LineCol { line, col }
111    }
112
113    /// Returns the line and column of the end of this [`Span`] within the [`Source`].
114    pub fn end(&self) -> LineCol {
115        let LineCol { mut line, mut col } = self.start();
116        for c in self
117            .source
118            .slice(self.byte_range.start..self.byte_range.end)
119            .chars()
120        {
121            if *c == '\n' {
122                col = 0;
123                line += 1;
124            } else {
125                col += 1;
126            }
127        }
128        LineCol { line, col }
129    }
130
131    /// Returns an iterator over the lines of the [`Source`] that this [`Span`] is associated with,
132    pub fn source_lines(&self) -> impl Iterator<Item = (IndexedSlice, Range<usize>)> + '_ {
133        let start_line_col = self.start();
134        let end_line_col = self.end();
135        let start_col = start_line_col.col;
136        let start_line = start_line_col.line;
137        let end_line = end_line_col.line;
138        let end_col = end_line_col.col;
139        self.source
140            .lines()
141            .enumerate()
142            .filter_map(move |(i, line)| {
143                let len = line.len();
144                if start_line == end_line && end_line == i {
145                    Some((line, start_col..end_col))
146                } else if i == start_line {
147                    Some((line, start_col..len))
148                } else if i > start_line && i < end_line {
149                    Some((line, 0..len))
150                } else if i == end_line {
151                    Some((line, 0..end_col))
152                } else {
153                    None
154                }
155            })
156    }
157
158    /// Joins this [`Span`] with another [`Span`], returning a new [`Span`] that encompasses both.
159    ///
160    /// If the two spans do not come from the same [`Source`], this method will return an error
161    /// unless one or more of the spans is [`Span::blank()`].
162    pub fn join(&self, other: &Span) -> core::result::Result<Span, SpanJoinError> {
163        if self.source.is_empty() {
164            return Ok(other.clone());
165        }
166        if other.source.is_empty() {
167            return Ok(self.clone());
168        }
169        if self.source != other.source {
170            return Err(SpanJoinError);
171        }
172        let start = self.byte_range.start.min(other.byte_range.start);
173        let end = self.byte_range.end.max(other.byte_range.end);
174        Ok(Span {
175            source: self.source.clone(),
176            byte_range: start..end,
177        })
178    }
179
180    /// Returns whether this [`Span`] is blank, i.e. has a zero-length range.
181    pub fn is_blank(&self) -> bool {
182        self.byte_range.start == self.byte_range.end
183    }
184}
185
186/// Represents a line and column within a [`Source`].
187///
188/// Note that both the line and column are zero-indexed, so the first line and column are both 0.
189#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
190pub struct LineCol {
191    /// The line number, starting from 0.
192    pub line: usize,
193    /// The column number, starting from 0.
194    pub col: usize,
195}
196
197/// A trait for types that have a [`Span`].
198pub trait Spanned {
199    /// Returns the underlying [`Span`] of self.
200    ///
201    /// If the type has multiple [`Span`]s, this method should return the primary [`Span`],
202    /// i.e. by joining all of the [`Span`]s together, rather than storing a permanent primary
203    /// [`Span`] on the type directly.
204    fn span(&self) -> Span;
205}
206
207impl Spanned for Span {
208    fn span(&self) -> Span {
209        self.clone()
210    }
211}
212
213/// A trait for types that have multiple [`Span`]s.
214pub trait MultiSpan {
215    /// Converts self into a vector of [`Span`]s.
216    fn into_spans(self) -> Vec<Span>;
217}
218
219impl MultiSpan for Vec<Span> {
220    fn into_spans(self) -> Vec<Span> {
221        self
222    }
223}