use std::{
collections::HashMap,
path::PathBuf,
sync::{Mutex, OnceLock},
};
use libloading::Library;
use tan::{
api::compile_string,
context::Context,
error::Error,
eval::{
eval,
util::{eval_file, resolve_module_path},
},
expr::{annotate, expr_clone, Expr},
util::{
args::{unpack_arg, unpack_map_arg, unpack_stringable_arg, unpack_symbolic_arg},
module_util::require_module,
standard_names::CURRENT_MODULE_PATH,
},
};
static FOREIGN_DYN_LIB_MAP: OnceLock<Mutex<HashMap<String, Library>>> = OnceLock::new();
pub fn ann(args: &[Expr], context: &mut Context) -> Result<Expr, Error> {
if args.len() != 1 {
return Err(Error::invalid_arguments(
"`ann` requires one argument",
None,
));
}
let expr = args.first().unwrap();
let expr = eval(expr, context)?;
if let Some(ann) = expr.annotations() {
Ok(Expr::map(ann.clone()))
} else {
Ok(Expr::None)
}
}
pub fn with_ann(args: &[Expr]) -> Result<Expr, Error> {
let annotations = unpack_map_arg(args, 0, "annotations")?;
let target = unpack_arg(args, 1, "target")?;
Ok(Expr::annotated(expr_clone(target), &annotations))
}
pub fn with_type(args: &[Expr]) -> Result<Expr, Error> {
let type_expr = unpack_arg(args, 0, "type")?;
let target = unpack_arg(args, 1, "target")?;
Ok(annotate(expr_clone(target), "type", type_expr.clone()))
}
pub fn debug_expr(args: &[Expr]) -> Result<Expr, Error> {
let [expr, ..] = args else {
return Err(Error::invalid_arguments("invalid arguments", None));
};
let s = match expr {
Expr::Annotated(expr, ann) => format!("ANN({expr:?}, {ann:?})"),
_ => format!("{expr:?}"),
};
Ok(Expr::string(s))
}
pub fn type_of(args: &[Expr], context: &mut Context) -> Result<Expr, Error> {
let [expr] = args else {
return Err(Error::invalid_arguments("one argument expected", None));
};
Ok(expr.dyn_type(context))
}
pub fn is_a(args: &[Expr], context: &mut Context) -> Result<Expr, Error> {
let typ = unpack_symbolic_arg(args, 0, "typ")?;
let expr = unpack_arg(args, 1, "expr")?;
let dyn_type = expr.dyn_type(context);
let Some(dyn_typ) = dyn_type.as_type() else {
return Err(Error::general("cannot compute dyn type"));
};
Ok(Expr::Bool(typ == dyn_typ))
}
pub fn load_file(args: &[Expr], context: &mut Context) -> Result<Expr, Error> {
let [path] = args else {
return Err(Error::invalid_arguments("requires `path` argument", None));
};
let Some(path) = path.as_stringable() else {
return Err(Error::invalid_arguments(
"`path` argument should be a Stringable",
path.range(),
));
};
let prev_current_module_path = context.scope.get(CURRENT_MODULE_PATH);
context.scope.insert(
CURRENT_MODULE_PATH,
Expr::string(
PathBuf::from(&path)
.parent()
.unwrap()
.to_string_lossy()
.to_string(),
),
);
let result = eval_file(path, context);
if let Some(prev_current_module_path) = prev_current_module_path {
context
.scope
.insert(CURRENT_MODULE_PATH, prev_current_module_path);
}
match result {
Ok(value) => Ok(value),
Err(errors) => {
dbg!(&errors);
Err(Error::failed_use(path, errors))
}
}
}
pub fn eval_string(args: &[Expr], context: &mut Context) -> Result<Expr, Error> {
if let Some(input) = args.first() {
let Some(input_str) = input.as_string() else {
return Err(Error::invalid_arguments(
"expected String argument",
input.range(),
));
};
let result = compile_string(input_str, context);
if let Ok(exprs) = result {
let mut value = Expr::None;
for expr in exprs {
value = eval(&expr, context)?;
}
Ok(value)
} else {
dbg!(&result);
Err(Error::general("cannot read string, eval failed"))
}
} else {
Err(Error::invalid_arguments("expected one argument", None))
}
}
fn curry(params: &[Expr], body: &[Expr]) -> Option<(Vec<Expr>, Vec<Expr>)> {
params.first().map(|param| {
let rest_params = ¶ms[1..];
let curried_body = if let Some((nested_params, nested_body)) = curry(rest_params, body) {
vec![Expr::List(vec![
Expr::Type("Func".to_owned()),
Expr::array(nested_params),
nested_body.first().unwrap().clone(), ])]
} else {
body.to_owned()
};
(vec![param.clone()], curried_body)
})
}
pub fn func_curry(args: &[Expr]) -> Result<Expr, Error> {
let Some(func) = args.first() else {
return Err(Error::invalid_arguments("expected `func` argument", None));
};
let Expr::Func(params, body, func_scope, filename) = func.unpack() else {
return Err(Error::invalid_arguments(
"`func` argument should be a Func",
None,
));
};
let (params, curried_body) = curry(params, body).unwrap_or((vec![], body.clone()));
let curried_func = Expr::Func(params, curried_body, func_scope.clone(), filename.clone());
Ok(curried_func)
}
pub fn link_foreign_dyn_lib(args: &[Expr], context: &mut Context) -> Result<Expr, Error> {
let dyn_lib_path = unpack_stringable_arg(args, 0, "path")?;
let dyn_lib_path = resolve_module_path(dyn_lib_path, context)?;
unsafe {
let foreign_dyn_lib_map = FOREIGN_DYN_LIB_MAP.get_or_init(|| Mutex::new(HashMap::new()));
if foreign_dyn_lib_map
.lock()
.expect("poisoned lock")
.contains_key(&dyn_lib_path)
{
return Ok(Expr::None);
}
let library = match Library::new(&dyn_lib_path) {
Ok(library) => library,
Err(error) => {
return Err(Error::general(&format!(
"cannot open foreign dyn lib `{dyn_lib_path}`: {error}"
)));
}
};
let link_foreign_dyn_lib =
match library.get::<unsafe fn(&mut Context) -> i32>(b"install_foreign_dyn_lib\0") {
Ok(link_foreign_dyn_lib) => link_foreign_dyn_lib,
Err(error) => {
return Err(Error::general(&format!(
"cannot get link_foreign_dyn_lib for `{dyn_lib_path}`: {error}"
)));
}
};
link_foreign_dyn_lib(context);
foreign_dyn_lib_map
.lock()
.expect("poisoned lock")
.insert(dyn_lib_path, library);
}
Ok(Expr::None)
}
pub fn setup_lib_lang(context: &mut Context) {
let module = require_module("prelude", context);
module.insert_invocable("ann", Expr::foreign_func_mut_context(&ann));
module.insert_invocable("with-ann", Expr::foreign_func(&with_ann));
module.insert_invocable("with-type", Expr::foreign_func(&with_type));
module.insert_invocable("cast", Expr::foreign_func(&with_type));
module.insert_invocable("dbg!", Expr::foreign_func(&debug_expr));
module.insert_invocable("type-of", Expr::foreign_func_mut_context(&type_of));
module.insert_invocable("is-a?", Expr::foreign_func_mut_context(&is_a));
module.insert_invocable("eval-string", Expr::foreign_func_mut_context(&eval_string));
module.insert_invocable(
"eval-string$$String",
Expr::foreign_func_mut_context(&eval_string),
);
module.insert_invocable("load-file", Expr::foreign_func_mut_context(&load_file));
module.insert_invocable("curry", Expr::foreign_func(&func_curry));
module.insert_invocable(
"link-foreign-dyn-lib",
Expr::foreign_func_mut_context(&link_foreign_dyn_lib),
);
}