koto_bytecode/
module_loader.rsuse crate::{Chunk, Compiler, CompilerError, CompilerSettings};
use dunce::canonicalize;
use koto_memory::Ptr;
use koto_parser::{format_source_excerpt, KString, Span};
use rustc_hash::FxHasher;
use std::{
collections::HashMap,
error, fmt,
hash::BuildHasherDefault,
io,
ops::Deref,
path::{Path, PathBuf},
};
use thiserror::Error;
#[derive(Error, Debug)]
#[allow(missing_docs)]
pub enum ModuleLoaderErrorKind {
#[error("{0}")]
Compiler(#[from] CompilerError),
#[error(transparent)]
Io(#[from] io::Error),
#[error("Failed to get parent of path ('{0}')")]
FailedToGetPathParent(PathBuf),
#[error("Unable to find module '{0}'")]
UnableToFindModule(String),
}
#[derive(Clone, Debug)]
pub struct ModuleLoaderError {
pub error: Ptr<ModuleLoaderErrorKind>,
pub source: Option<Ptr<LoaderErrorSource>>,
}
#[derive(Debug)]
pub struct LoaderErrorSource {
pub contents: String,
pub span: Span,
pub path: Option<KString>,
}
impl ModuleLoaderError {
pub(crate) fn from_compiler_error(
error: CompilerError,
source: &str,
source_path: Option<KString>,
) -> Self {
let source = LoaderErrorSource {
contents: source.into(),
span: error.span,
path: source_path,
};
Self {
error: ModuleLoaderErrorKind::from(error).into(),
source: Some(source.into()),
}
}
pub fn is_indentation_error(&self) -> bool {
match self.error.deref() {
ModuleLoaderErrorKind::Compiler(e) => e.is_indentation_error(),
_ => false,
}
}
}
impl fmt::Display for ModuleLoaderError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "{}.", self.error)?;
if let Some(source) = &self.source {
write!(
f,
"{}",
format_source_excerpt(&source.contents, &source.span, source.path.as_deref())
)?;
}
Ok(())
}
}
impl error::Error for ModuleLoaderError {}
impl From<io::Error> for ModuleLoaderError {
fn from(error: io::Error) -> Self {
Self::from(ModuleLoaderErrorKind::Io(error))
}
}
impl From<ModuleLoaderErrorKind> for ModuleLoaderError {
fn from(error: ModuleLoaderErrorKind) -> Self {
Self {
error: error.into(),
source: None,
}
}
}
#[derive(Clone, Default)]
pub struct ModuleLoader {
chunks: HashMap<PathBuf, Ptr<Chunk>, BuildHasherDefault<FxHasher>>,
}
impl ModuleLoader {
pub fn compile_script(
&mut self,
script: &str,
script_path: Option<KString>,
settings: CompilerSettings,
) -> Result<Ptr<Chunk>, ModuleLoaderError> {
Compiler::compile(script, script_path.clone(), settings)
.map_err(|e| ModuleLoaderError::from_compiler_error(e, script, script_path))
}
pub fn compile_module(
&mut self,
module_name: &str,
current_script_path: Option<&Path>,
) -> Result<CompileModuleResult, ModuleLoaderError> {
let module_path = find_module(module_name, current_script_path)?;
match self.chunks.get(&module_path) {
Some(chunk) => Ok(CompileModuleResult {
chunk: chunk.clone(),
path: module_path,
loaded_from_cache: true,
}),
None => {
let script = std::fs::read_to_string(&module_path)?;
let chunk = self.compile_script(
&script,
Some(module_path.clone().into()),
CompilerSettings::default(),
)?;
self.chunks.insert(module_path.clone(), chunk.clone());
Ok(CompileModuleResult {
chunk,
path: module_path,
loaded_from_cache: false,
})
}
}
}
pub fn clear_cache(&mut self) {
self.chunks.clear();
}
}
pub struct CompileModuleResult {
pub chunk: Ptr<Chunk>,
pub path: PathBuf,
pub loaded_from_cache: bool,
}
pub fn find_module(
module_name: &str,
current_script_path: Option<&Path>,
) -> Result<PathBuf, ModuleLoaderError> {
let search_folder = match ¤t_script_path {
Some(path) => {
let canonicalized = canonicalize(path)?;
if canonicalized.is_file() {
match canonicalized.parent() {
Some(parent_dir) => parent_dir.to_path_buf(),
None => {
let path = PathBuf::from(path);
return Err(ModuleLoaderErrorKind::FailedToGetPathParent(path).into());
}
}
} else {
canonicalized
}
}
None => std::env::current_dir()?,
};
let extension = "koto";
let result = search_folder.join(module_name).with_extension(extension);
if result.exists() {
Ok(result)
} else {
let result = search_folder
.join(module_name)
.join("main")
.with_extension(extension);
if result.exists() {
let result = canonicalize(result)?;
Ok(result)
} else {
Err(ModuleLoaderErrorKind::UnableToFindModule(module_name.into()).into())
}
}
}