use crate::language::span::Span;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct SourceId(pub usize);
pub const ANONYMOUS_NAME: &str = "<source>";
#[derive(Clone, Debug)]
pub struct SourceFile {
id: SourceId,
name: String,
text: String,
line_starts: Vec<usize>,
}
impl SourceFile {
pub fn new(name: impl Into<String>, text: impl Into<String>) -> Self {
Self::with_id(SourceId(0), name, text)
}
pub fn anonymous(text: impl Into<String>) -> Self {
Self::new(ANONYMOUS_NAME, text)
}
pub fn with_id(id: SourceId, name: impl Into<String>, text: impl Into<String>) -> Self {
let text = text.into();
let line_starts = compute_line_starts(&text);
Self {
id,
name: name.into(),
text,
line_starts,
}
}
pub fn id(&self) -> SourceId {
self.id
}
pub fn name(&self) -> &str {
&self.name
}
pub fn text(&self) -> &str {
&self.text
}
pub fn line_count(&self) -> usize {
self.line_starts.len()
}
pub fn location(&self, offset: usize) -> (usize, usize) {
let offset = offset.min(self.text.len());
let line_idx = match self.line_starts.binary_search(&offset) {
Ok(i) => i,
Err(i) => i.saturating_sub(1),
};
let line_start = self.line_starts[line_idx];
let column = self.text[line_start..offset].chars().count() + 1;
(line_idx + 1, column)
}
pub fn line_range(&self, line: usize) -> Option<(usize, usize)> {
if line == 0 || line > self.line_starts.len() {
return None;
}
let start = self.line_starts[line - 1];
let end = self
.line_starts
.get(line)
.map(|next| next.saturating_sub(1))
.unwrap_or(self.text.len());
let end = if end > start && self.text.as_bytes().get(end - 1) == Some(&b'\r') {
end - 1
} else {
end
};
Some((start, end))
}
pub fn line_text(&self, line: usize) -> Option<&str> {
let (start, end) = self.line_range(line)?;
Some(&self.text[start..end])
}
pub fn snippet(&self, span: Span) -> &str {
let start = span.start.min(self.text.len());
let end = span.end.min(self.text.len()).max(start);
&self.text[start..end]
}
}
fn compute_line_starts(text: &str) -> Vec<usize> {
let mut starts = vec![0];
for (i, b) in text.bytes().enumerate() {
if b == b'\n' {
starts.push(i + 1);
}
}
starts
}
#[derive(Clone, Debug, Default)]
pub struct SourceMap {
files: Vec<SourceFile>,
}
impl SourceMap {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, name: impl Into<String>, text: impl Into<String>) -> SourceId {
let id = SourceId(self.files.len());
self.files.push(SourceFile::with_id(id, name, text));
id
}
pub fn get(&self, id: SourceId) -> Option<&SourceFile> {
self.files.get(id.0)
}
pub fn len(&self) -> usize {
self.files.len()
}
pub fn is_empty(&self) -> bool {
self.files.is_empty()
}
pub fn files(&self) -> impl Iterator<Item = &SourceFile> {
self.files.iter()
}
}