use crate::MAX_USEMODE_RECURSION;
use crate::SyntaxErrorKind;
use crate::utils::canon;
use crate::utils::expand_home;
use crate::{Environment, Expression, ModuleInfo, RuntimeError, SyntaxError, use_script};
use std::borrow::Cow;
use std::fs::read_to_string;
use std::path::{Path, PathBuf};
pub fn use_module<'a>(
alias: &'a Option<String>,
module_path: &'a str,
env: &mut Environment,
) -> Result<(Cow<'a, str>, Expression), RuntimeError> {
let base = match env.get("SCRIPT") {
Some(Expression::String(s)) => s,
_ => String::from("."),
};
let cwd = Path::new(&base);
use_module_wrap(alias, module_path, cwd, env, 0)
}
pub fn use_module_wrap<'a>(
alias: &'a Option<String>,
module_path: &'a str,
base: &Path,
env: &mut Environment,
use_depth: usize,
) -> Result<(Cow<'a, str>, Expression), RuntimeError> {
let (module_info, parent_file) = load_module(module_path, base, env)?;
let mut map = module_info.functions;
if !module_info.use_statements.is_empty() {
let cwd = parent_file.parent().unwrap_or(base);
for (ua, up) in module_info.use_statements.iter() {
if MAX_USEMODE_RECURSION.with_borrow(|v| &use_depth < v) {
let (na, np) = use_module_wrap(ua, up, cwd, env, use_depth + 1)?;
map.insert(na.into(), np);
}
}
}
let module_name = get_module_name_from_path(alias, module_path)?;
Ok((module_name, Expression::from(map)))
}
fn load_module(
file_path: &str,
cwd: &Path,
env: &mut Environment,
) -> Result<(ModuleInfo, PathBuf), RuntimeError> {
let mod_file = find_module_file(file_path, cwd, env)?;
Ok((read_module_file(&mod_file, env)?, mod_file))
}
fn find_module_file(
file_path: &str,
cwd: &Path,
env: &mut Environment,
) -> Result<PathBuf, RuntimeError> {
let file = Path::new(expand_home(file_path).as_ref()).with_extension("lm");
let modname = file
.file_prefix()
.and_then(|x| x.to_str())
.unwrap_or("uknown");
let lib = match env.get("LUME_MODULES_PATH") {
Some(Expression::String(mo)) => Path::new(&mo).to_path_buf(),
_ => dirs::data_local_dir()
.unwrap_or(PathBuf::from("~/.local/share"))
.join("lumesh/mods"),
};
let candidate_paths = vec![
cwd.join("mods").join(&file),
cwd.join("mods").join(modname).join("main.lm"),
cwd.join(&file),
cwd.join(modname).join("main.lm"),
lib.join(&file),
lib.join(modname).join("main.lm"),
];
let mod_file = candidate_paths
.iter()
.find_map(|path| {
let path_str = path.to_str().unwrap_or_default();
canon(path_str, env).ok()
})
.ok_or_else(|| {
RuntimeError::common(
format!("module `{file_path}` not found in:\n{:?}", candidate_paths).into(),
Expression::String(file.to_string_lossy().into()),
0,
)
})?;
Ok(mod_file)
}
fn read_module_file(
mod_file: &PathBuf,
_env: &mut Environment,
) -> Result<ModuleInfo, RuntimeError> {
let module_content = match read_to_string(mod_file) {
Ok(content) => content,
Err(e) => {
return Err(RuntimeError::from_io_error(
e,
"loading module".into(),
Expression::None,
0,
));
}
};
match use_script(&module_content) {
Ok(result) => Ok(result),
Err(nom::Err::Error(e)) | Err(nom::Err::Failure(e)) => {
let err = SyntaxError {
source: format!("{module_content} ").into(),
kind: e,
};
Err(RuntimeError::common(
err.to_string().into(),
Expression::None,
0,
))
}
Err(nom::Err::Incomplete(_)) => {
let err = SyntaxError {
source: module_content.into(),
kind: SyntaxErrorKind::InternalError("incompleted".to_string()),
};
Err(RuntimeError::common(
err.to_string().into(),
Expression::None,
0,
))
}
}
}
fn get_module_name_from_path<'a>(
alias: &'a Option<String>,
module_path: &'a str,
) -> Result<Cow<'a, str>, RuntimeError> {
match alias {
Some(n) => Ok(n.into()),
_ => {
let path = Path::new(module_path);
match path.file_name() {
Some(name) => {
let fname = name.to_string_lossy();
Ok(match fname.split_once('.') {
Some((n, _)) => n.to_string().into(),
_ => fname.to_string().into(),
})
}
None => Err(RuntimeError::common(
"get filename failed".into(),
Expression::Use(alias.clone(), module_path.to_string()),
0,
)),
}
}
}
}