use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct FileId(u32);
impl FileId {
pub const DUMMY: Self = Self(u32::MAX);
}
#[derive(Debug, Default)]
pub struct FileDb {
files: Vec<SourceFile>,
by_name: HashMap<String, FileId>,
}
#[derive(Debug)]
struct SourceFile {
name: String,
source: String,
line_starts: Vec<u32>,
}
impl SourceFile {
fn new(name: String, source: String) -> Self {
let line_starts = std::iter::once(0)
.chain(source.match_indices('\n').map(|(i, _)| (i + 1) as u32))
.collect();
Self {
name,
source,
line_starts,
}
}
}
impl FileDb {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, name: impl Into<String>, source: impl Into<String>) -> FileId {
let name = name.into();
let source = source.into();
let id = FileId(self.files.len() as u32);
self.by_name.insert(name.clone(), id);
self.files.push(SourceFile::new(name, source));
id
}
pub fn get_source(&self, id: FileId) -> Option<&str> {
self.files.get(id.0 as usize).map(|f| f.source.as_str())
}
pub fn get_name(&self, id: FileId) -> Option<&str> {
self.files.get(id.0 as usize).map(|f| f.name.as_str())
}
pub fn id_of(&self, name: &str) -> Option<FileId> {
self.by_name.get(name).copied()
}
pub fn location(&self, id: FileId, offset: u32) -> Option<(usize, usize)> {
let file = self.files.get(id.0 as usize)?;
let line_idx = file
.line_starts
.partition_point(|&start| start <= offset)
.saturating_sub(1);
let line_start = file.line_starts[line_idx] as usize;
let col = offset as usize - line_start + 1;
Some((line_idx + 1, col))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_file_is_found_by_its_name() {
let mut db = FileDb::new();
let a = db.add("<std/core>", "a");
let b = db.add("b.nova", "b");
assert_eq!(db.id_of("<std/core>"), Some(a));
assert_eq!(db.id_of("b.nova"), Some(b));
assert_eq!(db.id_of("c.nova"), None);
}
}