use alloc::boxed::Box;
use alloc::vec::Vec;
use span_lang::{BytePos, Span};
use crate::{SourceFile, SourceId, SourceMapError};
#[derive(Clone, Debug, Default)]
pub struct SourceMap {
files: Vec<SourceFile>,
next_base: u32,
}
impl SourceMap {
#[inline]
#[must_use]
pub const fn new() -> Self {
Self {
files: Vec::new(),
next_base: 0,
}
}
#[inline]
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
Self {
files: Vec::with_capacity(capacity),
next_base: 0,
}
}
pub fn add(
&mut self,
name: impl Into<Box<str>>,
text: impl Into<Box<str>>,
) -> Result<SourceId, SourceMapError> {
let text = text.into();
let needed = text.len() as u64;
let base = self.next_base;
let available = u64::from(u32::MAX - base);
let index = u32::try_from(self.files.len());
let (len, index) = match (needed <= available, index) {
(true, Ok(index)) => (needed as u32, index),
_ => return Err(SourceMapError::SpaceExhausted { needed, available }),
};
let end = base + len;
let span = Span::new(base, end);
let id = SourceId::from_index(index);
self.files.push(SourceFile::new(name.into(), text, span));
self.next_base = end;
Ok(id)
}
#[must_use]
pub fn locate(&self, pos: BytePos) -> Option<(SourceId, BytePos)> {
let at = pos.to_u32();
let after = self
.files
.partition_point(|f| f.span().start().to_u32() <= at);
let index = after.checked_sub(1)?;
let file = &self.files[index];
if file.span().contains(pos) {
let local = at - file.span().start().to_u32();
Some((SourceId::from_index(index as u32), BytePos::new(local)))
} else {
None
}
}
#[inline]
#[must_use]
pub fn source(&self, id: SourceId) -> Option<&SourceFile> {
self.files.get(id.to_u32() as usize)
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.files.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.files.is_empty()
}
pub fn iter(&self) -> impl ExactSizeIterator<Item = (SourceId, &SourceFile)> + '_ {
self.files
.iter()
.enumerate()
.map(|(i, file)| (SourceId::from_index(i as u32), file))
}
}
#[cfg(test)]
mod tests {
extern crate alloc;
use alloc::format;
use alloc::vec::Vec;
use super::*;
#[test]
fn test_add_assigns_sequential_stable_ids() {
let mut map = SourceMap::new();
let a = map.add("a", "x").expect("fits");
let b = map.add("b", "yy").expect("fits");
let c = map.add("c", "zzz").expect("fits");
assert_eq!((a.to_u32(), b.to_u32(), c.to_u32()), (0, 1, 2));
assert_eq!(map.source(a).unwrap().name(), "a");
assert_eq!(map.source(b).unwrap().name(), "b");
}
#[test]
fn test_layout_is_contiguous_and_non_overlapping() {
let mut map = SourceMap::new();
map.add("a", "abc").expect("fits"); map.add("b", "de").expect("fits"); map.add("c", "fghi").expect("fits"); let spans: Vec<_> = map.iter().map(|(_, f)| f.span()).collect();
assert_eq!(spans[0], Span::new(0, 3));
assert_eq!(spans[1], Span::new(3, 5));
assert_eq!(spans[2], Span::new(5, 9));
}
#[test]
fn test_locate_at_boundaries_zero_one_and_past_end() {
let mut map = SourceMap::new();
let a = map.add("a", "abc").expect("fits"); let b = map.add("b", "de").expect("fits"); assert_eq!(map.locate(BytePos::new(0)), Some((a, BytePos::new(0))));
assert_eq!(map.locate(BytePos::new(2)), Some((a, BytePos::new(2))));
assert_eq!(map.locate(BytePos::new(3)), Some((b, BytePos::new(0))));
assert_eq!(map.locate(BytePos::new(4)), Some((b, BytePos::new(1))));
assert_eq!(map.locate(BytePos::new(5)), None);
assert_eq!(map.locate(BytePos::new(6)), None);
}
#[test]
fn test_locate_on_empty_map_is_none() {
let map = SourceMap::new();
assert_eq!(map.locate(BytePos::new(0)), None);
}
#[test]
fn test_empty_source_does_not_advance_space_and_is_unlocatable() {
let mut map = SourceMap::new();
let a = map.add("a", "ab").expect("fits"); let empty = map.add("empty", "").expect("fits"); let b = map.add("b", "cd").expect("fits");
assert!(map.source(empty).unwrap().span().is_empty());
assert_eq!(map.locate(BytePos::new(2)), Some((b, BytePos::new(0))));
assert_eq!(map.locate(BytePos::new(1)), Some((a, BytePos::new(1))));
}
#[test]
fn test_source_rejects_foreign_id() {
let mut map = SourceMap::new();
let _ = map.add("a", "x").expect("fits");
let mut other = SourceMap::new();
let foreign = other.add("b", "y").expect("fits");
let beyond = other.add("c", "z").expect("fits");
assert!(map.source(beyond).is_none());
assert!(map.source(foreign).is_some());
}
#[test]
fn test_add_at_space_boundary_accepts_exact_fit() {
let mut map = SourceMap::new();
map.next_base = u32::MAX - 4;
let id = map.add("edge", "abcd").expect("exactly fills the space");
assert_eq!(
map.source(id).unwrap().span(),
Span::new(u32::MAX - 4, u32::MAX)
);
assert_eq!(map.next_base, u32::MAX);
}
#[test]
fn test_add_past_space_boundary_is_rejected() {
let mut map = SourceMap::new();
map.next_base = u32::MAX - 4;
let err = map.add("edge", "abcde").expect_err("one byte too many");
assert_eq!(
err,
SourceMapError::SpaceExhausted {
needed: 5,
available: 4,
},
);
assert!(map.is_empty());
assert_eq!(map.next_base, u32::MAX - 4);
}
#[test]
fn test_add_empty_source_at_full_space_still_succeeds() {
let mut map = SourceMap::new();
map.next_base = u32::MAX;
let empty = map.add("nothing", "").expect("zero bytes always fit");
assert!(map.source(empty).unwrap().span().is_empty());
assert!(map.add("one", "x").is_err());
}
#[test]
fn test_iter_reports_exact_len_and_pairs_ids_in_order() {
let mut map = SourceMap::new();
for i in 0..5 {
map.add(format!("f{i}"), "..").expect("fits");
}
let mut iter = map.iter();
assert_eq!(iter.len(), 5);
let collected: Vec<_> = iter.by_ref().map(|(id, _)| id.to_u32()).collect();
assert_eq!(collected, [0, 1, 2, 3, 4]);
}
}