1#![doc = include_str!("../README.md")]
2#![warn(
3 clippy::pedantic,
4 clippy::allow_attributes_without_reason,
5 missing_docs
6)]
7#![feature(round_char_boundary)]
8
9mod buffer;
10mod line_col;
11mod loc;
12
13use {
14 ::core::{cmp::Ordering, fmt::Debug},
15 ::std::sync::Arc,
16};
17pub use {buffer::*, line_col::*, loc::*};
18
19#[derive(Debug)]
47pub struct Spanner<Src: BufferSource = String> {
48 buffers: Vec<Arc<Buffer<Src>>>,
49 end_linear_index: usize,
50}
51
52impl<Src: BufferSource> Spanner<Src> {
53 #[must_use]
55 pub fn new() -> Self {
56 Self {
57 buffers: vec![],
58 end_linear_index: 0,
59 }
60 }
61
62 #[must_use]
66 #[allow(clippy::cast_possible_truncation, reason = "documented")]
67 pub fn add(&mut self, src: impl FnOnce(Loc) -> Src) -> Arc<Buffer<Src>> {
68 let bufn = self.buffers.len() as u16;
69 let src = src(Loc { pos: 0, buf: bufn });
70 let src_code = src.source();
71 let line_beginnings = line_col::calc_line_beginnings(src_code);
72
73 let linear_index_range = self.end_linear_index..self.end_linear_index + src_code.len();
74
75 let buf = Arc::new(Buffer {
76 index: bufn,
77 linear_span: linear_index_range.clone(),
78 src,
79 line_beginnings,
80 });
81
82 self.buffers.push(Arc::clone(&buf));
83 self.end_linear_index = linear_index_range.end + 1; buf
86 }
87
88 #[must_use]
90 pub fn lookup_buf(&self, loc: Loc) -> &Arc<Buffer<Src>> {
91 &self.buffers[loc.buf as usize]
92 }
93
94 #[must_use]
96 pub fn lookup_span(&self, span: Span) -> SrcSpan<Src> {
97 SrcSpan {
98 start: span.start,
99 end: span.end,
100 buf: self.lookup_buf(span.start()).clone(),
101 }
102 }
103
104 #[must_use]
106 pub fn lookup_src(&self, span: Span) -> &str {
107 self.buffers[span.buf as usize].src_slice(span)
108 }
109
110 #[must_use]
114 pub fn lookup_linear_index(&self, loc: Loc) -> usize {
115 self.lookup_buf(loc).linear_span.start + loc.pos as usize
116 }
117
118 #[must_use]
126 #[allow(clippy::cast_possible_truncation, reason = "documented")]
127 pub fn lookup_loc(&self, linear_index: usize) -> Loc {
128 assert!(
129 linear_index < self.end_linear_index,
130 "linear index out of range"
131 );
132 let buf = self
133 .buffers
134 .binary_search_by(|buf| {
135 if linear_index > buf.linear_span.end + 1 {
136 Ordering::Less
137 } else if linear_index < buf.linear_span.start {
138 Ordering::Greater
139 } else {
140 Ordering::Equal
141 }
142 })
143 .unwrap();
144 Loc {
145 pos: (linear_index - self.buffers[buf].linear_span.start) as u32,
146 buf: buf as u16,
147 }
148 }
149}
150
151impl<Src: BufferSource> Default for Spanner<Src> {
152 fn default() -> Self {
153 Self::new()
154 }
155}
156
157#[cfg(feature = "miette")]
158mod miette_impls {
159 use {
160 super::*,
161 ::miette::{MietteError, SourceCode, SourceSpan, SpanContents},
162 ::tracing::{error, info, instrument, trace, warn},
163 };
164
165 impl<Src: BufferSource + Send + Sync> SourceCode for Spanner<Src> {
166 #[instrument(skip(self))]
167 fn read_span<'a>(
168 &'a self,
169 miette_span: &SourceSpan,
170 context_lines_before: usize,
171 context_lines_after: usize,
172 ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
173 if miette_span.offset() == 0 {
174 return Err(MietteError::OutOfBounds);
175 }
176
177 let (mut start, mut end) = (
178 self.lookup_loc(miette_span.offset()),
179 self.lookup_loc(miette_span.offset() + miette_span.len()),
180 );
181
182 if !end.same_buf_as(&start) {
183 error!(?start, ?end, "span crosses buffers");
184 return Err(MietteError::OutOfBounds);
185 }
186
187 let buf = self.lookup_buf(start);
188 if start == buf.end() {
189 info!("labeling end of buffer");
190 start = buf.end() - 2;
191 end = start + 2;
192 }
193
194 let src_span = self.lookup_span(Span::new(start, end));
195 let source = buf.src.source();
196 let new_miette_span = {
197 let nms_start = source.ceil_char_boundary(src_span.start as usize);
198 let nms_end = source.floor_char_boundary(src_span.end as usize);
199 SourceSpan::new(nms_start.into(), nms_end - nms_start)
200 };
201 trace!(?start, ?end, ?new_miette_span, %src_span);
202
203 let contents =
204 source.read_span(&new_miette_span, context_lines_before, context_lines_after)?;
205
206 struct ContentsOverride<'a>(
207 Box<dyn SpanContents<'a> + 'a>,
208 SourceSpan,
209 Option<&'a str>,
210 );
211
212 impl<'a> SpanContents<'a> for ContentsOverride<'a> {
213 fn data(&self) -> &'a [u8] {
214 self.0.data()
215 }
216
217 fn span(&self) -> &SourceSpan {
218 &self.1
219 }
220
221 fn name(&self) -> Option<&str> {
222 self.2.or(self.0.name())
223 }
224
225 fn line(&self) -> usize {
226 self.0.line()
227 }
228
229 fn column(&self) -> usize {
230 self.0.column()
231 }
232
233 fn line_count(&self) -> usize {
234 self.0.line_count()
235 }
236
237 fn language(&self) -> Option<&str> {
238 None
239 }
240 }
241
242 let out_span = *contents.span();
243 let contents = Box::new(ContentsOverride(
244 contents,
245 SourceSpan::new(
246 (out_span.offset() + buf.linear_span.start).into(),
247 out_span.len(),
248 ),
249 buf.src.name(),
250 ));
251
252 trace!(contents = ?DebugSpanContents(&*contents));
253
254 Ok(contents)
255 }
256 }
257
258 struct DebugSpanContents<'a, 'b>(&'a dyn SpanContents<'b>);
259
260 impl ::core::fmt::Debug for DebugSpanContents<'_, '_> {
261 fn fmt(&self, fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
262 fmt.debug_struct("SpanContents")
263 .field("data", &::core::str::from_utf8(self.0.data()).unwrap())
264 .field("span", &self.0.span())
265 .field("name", &self.0.name())
266 .field("line", &self.0.line())
267 .field("column", &self.0.column())
268 .field("line_count", &self.0.line_count())
269 .field("language", &self.0.language())
270 .finish()
271 }
272 }
273}