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}