use crate::{
Dict,
Environment,
FALSE_SYMBOL,
MODULE_NAME_ENV,
MODULE_PATH_ENV,
TRUE_SYMBOL,
builtin::{DEFAULT_KSL_PATH, FILE_EXT, KSL_PATH_ENV},
eval::{apply::eval_apply, eval},
expand_tilde,
find_define_environment,
init_environment,
is_invalid_symbol,
read_from_environment,
value::Value,
};
pub(crate) fn builtin(args: &[Value], env: Environment) -> Result<Value, std::sync::Arc<str>> {
let (target_expr, path_expr, is_force) = match args {
[t, p] => (t, p, None),
[t, p, f] => match f {
op @ Value::Atom(_) if *op == *TRUE_SYMBOL => (t, p, Some(true)),
op @ Value::Atom(_) if *op == *FALSE_SYMBOL => (t, p, Some(true)),
e => {
return Err(std::sync::Arc::from(format!(
"Error[ksl::builtin::Load]: Expected a boolean atom, but got {}.",
e
)));
}
},
_ => {
return Err(std::sync::Arc::from(format!(
"Error[ksl::builtin::Load]: Expected 2 or 3 parameters, got {}.",
args.len()
)));
}
};
let path_val = eval_apply(path_expr, env.clone())?;
let path_str = match path_val {
Value::String(s) => s,
_ => {
return Err(std::sync::Arc::from(format!(
"Error[ksl::builtin::Load]: Expected path string, got `{}`.",
path_val
)));
}
};
let real_path = resolve_path(path_str.as_ref())?;
let real_path_arc: std::sync::Arc<str> = std::sync::Arc::from(real_path.as_str());
let (name, sym_dict) = {
let scope = find_define_environment(&env, &MODULE_PATH_ENV).ok_or(std::sync::Arc::from(
"Error[ksl::builtin::Load]: Uninitialized environment.",
))?;
let lock = scope.store.upgradable_read();
let paths_val = lock
.get(MODULE_PATH_ENV.as_ref())
.ok_or(std::sync::Arc::from(
"Error[ksl::builtin::Load]: Corrupted environment.",
))?
.clone();
let cached_result = if is_force.is_some_and(|p| p) {
None
} else if let Value::Object(_, d) = paths_val.as_ref() {
d.get(&real_path_arc).cloned()
} else {
return Err(std::sync::Arc::from(
"Error[ksl::builtin::Load]: Corrupted environment.",
));
};
match cached_result {
Some(cached) => match cached.as_ref() {
Value::Module(n, s) => (n.clone(), s.clone()),
_ => {
return Err(std::sync::Arc::from(format!(
"Error[ksl::builtin::Load]: Circular reference to module: `{}`.",
real_path_arc
)));
}
},
None => {
let mut writer = parking_lot::RwLockUpgradableReadGuard::upgrade(lock);
let mut paths_dict = match writer.get(MODULE_PATH_ENV.as_ref()).map(|v| v.as_ref()) {
Some(Value::Object(_, d)) => (**d).clone(),
_ => unreachable!(),
};
paths_dict.insert(real_path_arc.clone(), std::sync::Arc::new(Value::Unit));
let paths_obj = std::sync::Arc::new(Value::Object(MODULE_PATH_ENV.clone(), Box::new(paths_dict)));
writer.insert(MODULE_PATH_ENV.clone(), paths_obj);
drop(writer);
let run_result = run_module(&real_path, &env);
match run_result {
Ok((n, s)) => {
let loaded_module = std::sync::Arc::new(Value::Module(n.clone(), s.clone()));
let mut writer = scope.store.write();
let mut paths_dict = match writer.get(MODULE_PATH_ENV.as_ref()).map(|v| v.as_ref()) {
Some(Value::Object(_, d)) => (**d).clone(),
_ => unreachable!(),
};
paths_dict.insert(real_path_arc.clone(), loaded_module);
let paths_obj = std::sync::Arc::new(Value::Object(MODULE_PATH_ENV.clone(), Box::new(paths_dict)));
writer.insert(MODULE_PATH_ENV.clone(), paths_obj);
(n, s)
}
Err(err) => {
let mut writer = scope.store.write();
let mut paths_dict = match writer.get(MODULE_PATH_ENV.as_ref()).map(|v| v.as_ref()) {
Some(Value::Object(_, d)) => (**d).clone(),
_ => unreachable!(),
};
paths_dict.remove(&real_path_arc);
let paths_obj = std::sync::Arc::new(Value::Object(MODULE_PATH_ENV.clone(), Box::new(paths_dict)));
writer.insert(MODULE_PATH_ENV.clone(), paths_obj);
return Err(err);
}
}
}
}
};
match target_expr {
Value::Symbol(mod_sym) => {
if is_invalid_symbol(mod_sym) {
Err(std::sync::Arc::from(format!(
"Error[ksl::builtin::Load]: Rebinding of symbol `{}` is not permitted.",
mod_sym
)))
} else {
let _ = env.store.write().insert(
mod_sym.clone(),
std::sync::Arc::new(Value::Module(name, sym_dict)),
);
Ok(Value::Unit)
}
}
Value::List(symbols) => {
let mut writer = env.store.write();
for item in symbols.as_ref() {
let s = match item {
Value::Symbol(s) => s,
e => {
return Err(std::sync::Arc::from(format!(
"Error[ksl::builtin::Load]: Expected a symbol, but got: `{}`.",
e,
)));
}
};
let val = sym_dict.get(s).ok_or(std::sync::Arc::from(format!(
"Error[ksl::builtin::Load]: Symbol `{}` not found in `{}`.",
s, real_path
)))?;
let _ = writer.insert(s.clone(), val.clone());
}
Ok(Value::Unit)
}
e => Err(std::sync::Arc::from(format!(
concat!(
"Error[ksl::builtin::Load]: ",
"Expected a symbol or a list of symbols, but got: `{}`."
),
e,
))),
}
}
fn resolve_path(path_str: &str) -> Result<std::string::String, std::sync::Arc<str>> {
let file_with_ext = format!("{}.{}", path_str, FILE_EXT);
let path_with_ext = std::path::Path::new(&file_with_ext);
if path_with_ext.exists() {
return Ok(file_with_ext);
}
let arc_path = std::sync::Arc::from(file_with_ext.as_str());
if let Ok(expanded) = expand_tilde(arc_path)
&& expanded.exists()
{
return Ok(expanded.to_string_lossy().into_owned());
}
let ksl_path_env = std::env::var(KSL_PATH_ENV).map_or_else(|_| DEFAULT_KSL_PATH.clone(), std::sync::Arc::from);
Ok(expand_tilde(ksl_path_env)?
.join(file_with_ext)
.to_string_lossy()
.into_owned())
}
fn run_module(
real_path: &str,
parent_env: &Environment,
) -> Result<(std::sync::Arc<str>, std::sync::Arc<Dict>), std::sync::Arc<str>> {
let local_env = init_environment(None);
if let Some(paths) = read_from_environment(parent_env, &MODULE_PATH_ENV) {
let _ = local_env
.store
.write()
.insert(MODULE_PATH_ENV.clone(), paths);
}
let file_content = std::fs::read_to_string(real_path).map_err(|_| {
std::sync::Arc::from(format!(
"Error[ksl::builtin::Load::run_module]: Unable to read `{}`.",
real_path
))
})?;
let eval_result = eval(&file_content, local_env.clone());
if let Some(updated_paths) = local_env
.store
.read()
.get(MODULE_PATH_ENV.as_ref())
.cloned()
&& let Some(scope) = find_define_environment(parent_env, &MODULE_PATH_ENV)
{
scope
.store
.write()
.insert(MODULE_PATH_ENV.clone(), updated_paths);
}
let _ = eval_result?;
let reader = local_env.store.read();
let mod_name = reader
.get(MODULE_NAME_ENV.as_ref())
.and_then(|v| match v.as_ref() {
Value::String(s) => Some(s.clone()),
_ => None,
})
.ok_or_else(|| {
std::sync::Arc::from(format!(
"Error[ksl::builtin::Load::run_module]: `{}` is not a module.",
real_path
))
})?;
let sym_dict = reader
.iter()
.filter(|(k, _)| !k.starts_with('_') && !is_invalid_symbol(k))
.map(|(k, v)| (k.clone(), v.clone()))
.collect::<Dict>();
Ok((mod_name, std::sync::Arc::new(sym_dict)))
}