#![doc = include_str!("../README.md")]
#![warn(
clippy::pedantic,
clippy::allow_attributes_without_reason,
missing_docs
)]
#![feature(round_char_boundary)]
mod buffer;
mod line_col;
mod loc;
use {
::core::{cmp::Ordering, fmt::Debug},
::std::sync::Arc,
};
pub use {buffer::*, line_col::*, loc::*};
#[derive(Debug)]
pub struct Spanner<Src: BufferSource = String> {
buffers: Vec<Arc<Buffer<Src>>>,
end_linear_index: usize,
}
impl<Src: BufferSource> Spanner<Src> {
#[must_use]
pub fn new() -> Self {
Self {
buffers: vec![],
end_linear_index: 0,
}
}
#[must_use]
#[allow(clippy::cast_possible_truncation, reason = "documented")]
pub fn add(&mut self, src: impl FnOnce(Loc) -> Src) -> Arc<Buffer<Src>> {
let bufn = self.buffers.len() as u16;
let src = src(Loc { pos: 0, buf: bufn });
let src_code = src.source();
let line_beginnings = line_col::calc_line_beginnings(src_code);
let linear_index_range = self.end_linear_index..self.end_linear_index + src_code.len();
let buf = Arc::new(Buffer {
index: bufn,
linear_span: linear_index_range.clone(),
src,
line_beginnings,
});
self.buffers.push(Arc::clone(&buf));
self.end_linear_index = linear_index_range.end + 1;
buf
}
#[must_use]
pub fn lookup_buf(&self, loc: Loc) -> &Arc<Buffer<Src>> {
&self.buffers[loc.buf as usize]
}
#[must_use]
pub fn lookup_span(&self, span: Span) -> SrcSpan<Src> {
SrcSpan {
start: span.start,
end: span.end,
buf: self.lookup_buf(span.start()).clone(),
}
}
#[must_use]
pub fn lookup_src(&self, span: Span) -> &str {
self.buffers[span.buf as usize].src_slice(span)
}
#[must_use]
pub fn lookup_linear_index(&self, loc: Loc) -> usize {
self.lookup_buf(loc).linear_span.start + loc.pos as usize
}
#[must_use]
#[allow(clippy::cast_possible_truncation, reason = "documented")]
pub fn lookup_loc(&self, linear_index: usize) -> Loc {
assert!(
linear_index < self.end_linear_index,
"linear index out of range"
);
let buf = self
.buffers
.binary_search_by(|buf| {
if linear_index > buf.linear_span.end + 1 {
Ordering::Less
} else if linear_index < buf.linear_span.start {
Ordering::Greater
} else {
Ordering::Equal
}
})
.unwrap();
Loc {
pos: (linear_index - self.buffers[buf].linear_span.start) as u32,
buf: buf as u16,
}
}
}
impl<Src: BufferSource> Default for Spanner<Src> {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "miette")]
mod miette_impls {
use {
super::*,
::miette::{MietteError, SourceCode, SourceSpan, SpanContents},
::tracing::{error, info, instrument, trace, warn},
};
impl<Src: BufferSource + Send + Sync> SourceCode for Spanner<Src> {
#[instrument(skip(self))]
fn read_span<'a>(
&'a self,
miette_span: &SourceSpan,
context_lines_before: usize,
context_lines_after: usize,
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
if miette_span.offset() == 0 {
return Err(MietteError::OutOfBounds);
}
let (mut start, mut end) = (
self.lookup_loc(miette_span.offset()),
self.lookup_loc(miette_span.offset() + miette_span.len()),
);
if !end.same_buf_as(&start) {
error!(?start, ?end, "span crosses buffers");
return Err(MietteError::OutOfBounds);
}
let buf = self.lookup_buf(start);
if start == buf.end() {
info!("labeling end of buffer");
start = buf.end() - 2;
end = start + 2;
}
let src_span = self.lookup_span(Span::new(start, end));
let source = buf.src.source();
let new_miette_span = {
let nms_start = source.ceil_char_boundary(src_span.start as usize);
let nms_end = source.floor_char_boundary(src_span.end as usize);
SourceSpan::new(nms_start.into(), nms_end - nms_start)
};
trace!(?start, ?end, ?new_miette_span, %src_span);
let contents =
source.read_span(&new_miette_span, context_lines_before, context_lines_after)?;
struct ContentsOverride<'a>(
Box<dyn SpanContents<'a> + 'a>,
SourceSpan,
Option<&'a str>,
);
impl<'a> SpanContents<'a> for ContentsOverride<'a> {
fn data(&self) -> &'a [u8] {
self.0.data()
}
fn span(&self) -> &SourceSpan {
&self.1
}
fn name(&self) -> Option<&str> {
self.2.or(self.0.name())
}
fn line(&self) -> usize {
self.0.line()
}
fn column(&self) -> usize {
self.0.column()
}
fn line_count(&self) -> usize {
self.0.line_count()
}
fn language(&self) -> Option<&str> {
None
}
}
let out_span = *contents.span();
let contents = Box::new(ContentsOverride(
contents,
SourceSpan::new(
(out_span.offset() + buf.linear_span.start).into(),
out_span.len(),
),
buf.src.name(),
));
trace!(contents = ?DebugSpanContents(&*contents));
Ok(contents)
}
}
struct DebugSpanContents<'a, 'b>(&'a dyn SpanContents<'b>);
impl ::core::fmt::Debug for DebugSpanContents<'_, '_> {
fn fmt(&self, fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
fmt.debug_struct("SpanContents")
.field("data", &::core::str::from_utf8(self.0.data()).unwrap())
.field("span", &self.0.span())
.field("name", &self.0.name())
.field("line", &self.0.line())
.field("column", &self.0.column())
.field("line_count", &self.0.line_count())
.field("language", &self.0.language())
.finish()
}
}
}