use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[derive(Clone, Debug)]
pub struct SourceFile {
pub name: Arc<str>,
pub text: Arc<str>,
origins: Arc<[SourceOrigin]>,
}
#[derive(Clone, Debug)]
struct SourceOrigin {
start: usize,
end: usize,
name: Arc<str>,
}
impl SourceFile {
#[must_use]
pub fn new(name: impl Into<Arc<str>>, text: impl Into<Arc<str>>) -> Self {
let name = name.into();
let text = text.into();
let origins = vec![SourceOrigin {
start: 0,
end: text.len(),
name: name.clone(),
}];
Self {
name,
text,
origins: origins.into(),
}
}
#[must_use]
pub fn bundle(name: impl Into<Arc<str>>, sources: &[Self]) -> (Self, Vec<usize>) {
let name = name.into();
let mut text = String::new();
let mut origins = Vec::with_capacity(sources.len());
let mut offsets = Vec::with_capacity(sources.len());
for source in sources {
if !text.is_empty() && !text.ends_with('\n') {
text.push('\n');
}
let start = text.len();
offsets.push(start);
text.push_str(&source.text);
let end = text.len();
origins.push(SourceOrigin {
start,
end,
name: source.name.clone(),
});
}
(
Self {
name,
text: Arc::from(text),
origins: origins.into(),
},
offsets,
)
}
fn origin_at(&self, byte: usize) -> Option<&SourceOrigin> {
self.origins
.iter()
.find(|origin| byte >= origin.start && byte < origin.end)
.or_else(|| self.origins.last().filter(|origin| byte == origin.end))
}
#[must_use]
pub fn name_at(&self, byte: usize) -> &str {
self.origin_at(byte)
.map_or(self.name.as_ref(), |origin| origin.name.as_ref())
}
#[must_use]
pub fn local_span(&self, span: Span) -> Option<Span> {
let origin = self.origin_at(span.start)?;
if span.end > origin.end {
return None;
}
Some(Span::new(
span.start.saturating_sub(origin.start),
span.end.saturating_sub(origin.start),
))
}
#[must_use]
pub fn line_col(&self, byte: usize) -> (usize, usize) {
let mut byte = byte.min(self.text.len());
while !self.text.is_char_boundary(byte) {
byte = byte.saturating_sub(1);
}
let origin_start = self.origin_at(byte).map_or(0, |origin| origin.start);
let prefix = &self.text[origin_start..byte];
let line = prefix.bytes().filter(|byte| *byte == b'\n').count() + 1;
let column = prefix.rsplit_once('\n').map_or_else(
|| prefix.chars().count() + 1,
|(_, tail)| tail.chars().count() + 1,
);
(line, column)
}
#[must_use]
pub fn line_text(&self, line: usize) -> &str {
self.text.lines().nth(line.saturating_sub(1)).unwrap_or("")
}
#[must_use]
pub fn line_text_at(&self, byte: usize) -> &str {
let Some(origin) = self.origin_at(byte) else {
let (line, _) = self.line_col(byte);
return self.line_text(line);
};
let local = byte.saturating_sub(origin.start);
self.text[origin.start..origin.end]
.lines()
.nth(
self.text[origin.start..origin.start + local]
.bytes()
.filter(|byte| *byte == b'\n')
.count(),
)
.unwrap_or("")
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Span {
pub start: usize,
pub end: usize,
}
impl Span {
#[must_use]
pub const fn new(start: usize, end: usize) -> Self {
Self { start, end }
}
#[must_use]
pub const fn merge(self, other: Self) -> Self {
Self {
start: if self.start < other.start {
self.start
} else {
other.start
},
end: if self.end > other.end {
self.end
} else {
other.end
},
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Spanned<T> {
pub value: T,
pub span: Span,
}
impl<T> Spanned<T> {
#[must_use]
pub const fn new(value: T, span: Span) -> Self {
Self { value, span }
}
}