use crate::parsing::ast::{LemmaRepository, Span};
use std::collections::HashMap;
use std::fmt;
use std::path::PathBuf;
use std::sync::Arc;
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SourceType {
Path(Arc<PathBuf>),
Volatile,
Registry(Arc<LemmaRepository>),
}
impl fmt::Display for SourceType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SourceType::Path(path) => write!(f, "{}", path.display()),
SourceType::Volatile => write!(f, "volatile"),
SourceType::Registry(repo) => {
if let Some(name) = &repo.name {
write!(f, "{}", name)
} else {
write!(f, "<anonymous registry>")
}
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct Source {
pub source_type: SourceType,
pub span: Span,
}
impl Source {
#[must_use]
pub fn new(source_type: SourceType, span: Span) -> Self {
Self { source_type, span }
}
#[must_use]
pub fn text_from<'a>(
&self,
sources: &'a HashMap<SourceType, String>,
) -> Option<std::borrow::Cow<'a, str>> {
let s = sources.get(&self.source_type)?;
s.get(self.span.start..self.span.end)
.map(std::borrow::Cow::Borrowed)
}
#[must_use]
pub fn extract_text(&self, source: &str) -> Option<String> {
let bytes = source.as_bytes();
if self.span.start < bytes.len() && self.span.end <= bytes.len() {
Some(String::from_utf8_lossy(&bytes[self.span.start..self.span.end]).to_string())
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
#[test]
fn test_extract_text_valid() {
let source = "hello world";
let span = Span {
start: 0,
end: 5,
line: 1,
col: 0,
};
let loc = Source::new(SourceType::Volatile, span);
assert_eq!(loc.extract_text(source), Some("hello".to_string()));
}
#[test]
fn test_extract_text_full_string() {
let source = "hello world";
let span = Span {
start: 0,
end: 11,
line: 1,
col: 0,
};
let loc = Source::new(SourceType::Volatile, span);
assert_eq!(loc.extract_text(source), Some("hello world".to_string()));
}
#[test]
fn test_extract_text_empty() {
let source = "hello world";
let span = Span {
start: 5,
end: 5,
line: 1,
col: 5,
};
let loc = Source::new(SourceType::Volatile, span);
assert_eq!(loc.extract_text(source), Some("".to_string()));
}
#[test]
fn test_extract_text_out_of_bounds_start() {
let source = "hello";
let span = Span {
start: 10,
end: 15,
line: 1,
col: 10,
};
let loc = Source::new(SourceType::Volatile, span);
assert_eq!(loc.extract_text(source), None);
}
#[test]
fn test_extract_text_out_of_bounds_end() {
let source = "hello";
let span = Span {
start: 0,
end: 10,
line: 1,
col: 0,
};
let loc = Source::new(SourceType::Volatile, span);
assert_eq!(loc.extract_text(source), None);
}
#[test]
fn test_extract_text_unicode() {
let source = "hello 世界";
let span = Span {
start: 6,
end: 12,
line: 1,
col: 6,
};
let loc = Source::new(SourceType::Volatile, span);
assert_eq!(loc.extract_text(source), Some("世界".to_string()));
}
#[test]
fn test_text_from() {
let mut sources = HashMap::new();
let st = SourceType::Path(Arc::new(PathBuf::from("test.lemma")));
sources.insert(st.clone(), "hello world".to_string());
let loc = Source::new(
st,
Span {
start: 0,
end: 5,
line: 1,
col: 0,
},
);
assert_eq!(loc.text_from(&sources).as_deref(), Some("hello"));
}
}