use std::borrow::Cow;
use std::collections::BTreeMap;
use std::fmt;
use std::path::{Component, Path, PathBuf};
use crate::value::Value;
pub trait Loader {
fn std_module(&self, name: &str) -> Option<Cow<'static, str>>;
fn read(&self, root: &ImportRoot, rel: &RelPath) -> Result<String, LoadError>;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LoadError {
NotFound,
NotAFile,
OutsideRoot,
TooLarge,
Other(String),
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ImportRoot {
path: PathBuf,
}
impl ImportRoot {
pub fn new(path: impl Into<PathBuf>) -> Self {
Self { path: path.into() }
}
pub fn path(&self) -> &Path {
&self.path
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RelPath(String);
impl RelPath {
pub fn new(path: impl AsRef<Path>) -> Option<Self> {
let mut parts: Vec<&str> = Vec::new();
for component in path.as_ref().components() {
match component {
Component::Normal(part) => parts.push(part.to_str()?),
Component::CurDir => {}
Component::ParentDir => {
parts.pop()?;
}
Component::RootDir | Component::Prefix(_) => return None,
}
}
Self::from_parts(parts)
}
pub(crate) fn from_parts<'a>(parts: impl IntoIterator<Item = &'a str>) -> Option<Self> {
let parts: Vec<&str> = parts.into_iter().collect();
let valid = |part: &&str| !matches!(*part, "" | "." | "..") && !part.contains('/');
(!parts.is_empty() && parts.iter().all(valid)).then(|| Self(parts.join("/")))
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn to_path(&self) -> PathBuf {
self.0.split('/').collect()
}
pub(crate) fn dir(&self) -> Vec<&str> {
let mut parts: Vec<&str> = self.0.split('/').collect();
parts.pop();
parts
}
}
impl fmt::Display for RelPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Source {
pub name: String,
pub text: String,
}
impl Source {
pub fn new(name: impl Into<String>, text: impl Into<String>) -> Self {
Self {
name: name.into(),
text: text.into(),
}
}
}
#[derive(Debug, Clone)]
pub struct SymbolType {
pub name: String,
pub words: bool,
pub strings: bool,
pub constructor: String,
pub parse: fn(&str) -> Result<String, String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HostModule {
pub path: String,
pub source: String,
}
#[derive(Debug, Clone)]
pub struct ImplicitRefs {
pub target: String,
pub labels: fn(&Value) -> Vec<String>,
}
pub struct Host {
pub prelude: Vec<Source>,
pub implicit_parts: Vec<Source>,
pub symbols: Vec<SymbolType>,
pub host_modules: Vec<HostModule>,
pub implicit_refs: Vec<ImplicitRefs>,
pub loader: Box<dyn Loader>,
}
impl Host {
pub fn new(loader: impl Loader + 'static) -> Self {
Self {
prelude: Vec::new(),
implicit_parts: Vec::new(),
symbols: Vec::new(),
host_modules: Vec::new(),
implicit_refs: Vec::new(),
loader: Box::new(loader),
}
}
}
impl fmt::Debug for Host {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Host")
.field("prelude", &self.prelude)
.field("implicit_parts", &self.implicit_parts)
.field("symbols", &self.symbols)
.field("host_modules", &self.host_modules)
.field("implicit_refs", &self.implicit_refs)
.finish_non_exhaustive()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RootSpec {
pub root_struct: String,
pub entries: Vec<PathBuf>,
pub import_roots: Vec<ImportRoot>,
}
#[derive(Debug, Clone, Default)]
pub struct MemoryLoader {
files: BTreeMap<PathBuf, String>,
std: BTreeMap<String, String>,
}
impl MemoryLoader {
pub fn new() -> Self {
Self::default()
}
pub fn with_file(mut self, path: impl Into<PathBuf>, text: impl Into<String>) -> Self {
self.files.insert(path.into(), text.into());
self
}
pub fn with_std(mut self, name: impl Into<String>, text: impl Into<String>) -> Self {
self.std.insert(name.into(), text.into());
self
}
}
impl Loader for MemoryLoader {
fn std_module(&self, name: &str) -> Option<Cow<'static, str>> {
self.std.get(name).map(|text| Cow::Owned(text.clone()))
}
fn read(&self, root: &ImportRoot, rel: &RelPath) -> Result<String, LoadError> {
self.files
.get(&root.path().join(rel.to_path()))
.cloned()
.ok_or(LoadError::NotFound)
}
}