#![warn(missing_docs)]
#[cfg(feature = "automate-function-loading-boilerplate")]
pub use inventory;
use wolfram_expr::{expr, Association, Expr, ExprKind, RuleEntry, Symbol};
use wolfram_serialize::{FromWXF, ToWXF};
#[derive(ToWXF, FromWXF, Debug, Clone)]
pub struct FunctionEntry {
pub name: String,
pub kind: String,
pub params: Vec<Expr>,
pub ret: Expr,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum ExportKind {
Native,
Margs,
Wstp,
Wxf,
}
impl ExportKind {
fn from_kind_str(s: &str) -> Option<ExportKind> {
match s {
"Native" => Some(ExportKind::Native),
"Margs" => Some(ExportKind::Margs),
"Wstp" => Some(ExportKind::Wstp),
"Wxf" => Some(ExportKind::Wxf),
_ => None,
}
}
}
fn caller_name(kind: ExportKind) -> &'static str {
match kind {
ExportKind::Native => "NativeCaller",
ExportKind::Wstp => "WSTPCaller",
ExportKind::Wxf => "WXFCaller",
ExportKind::Margs => unreachable!("margs mode never needs a caller binding"),
}
}
fn caller_binding(kind: ExportKind) -> Expr {
match kind {
ExportKind::Native => expr!(::Set[::NativeCaller, ::Identity]),
ExportKind::Wstp => expr!(::Set[
::WSTPCaller,
::Function[::With[
::List[::Set[::f, ::Slot[1]]],
::Function[::Block[
::List[
::Set[::$Context, "RustLinkWSTPPrivateContext`"],
::Set[::$ContextPath, ::List[]]
],
::f[::SlotSequence[1]]
]]
]]
]),
ExportKind::Wxf => expr!(::Set[
::WXFCaller,
::Function[::Composition[
::BinaryDeserialize,
::Slot[1],
::BinarySerialize,
::List
]]
]),
ExportKind::Margs => unreachable!("margs mode never needs a caller binding"),
}
}
fn library_function_load(
kind: ExportKind,
name: &str,
lib: Expr,
native_sig: Option<(Vec<Expr>, Expr)>,
) -> Expr {
let (args, ret): (Expr, Expr) = match kind {
ExportKind::Native | ExportKind::Margs => {
let (args, ret) =
native_sig.unwrap_or_else(|| (Vec::new(), Expr::string("")));
(Expr::from(args), ret)
},
ExportKind::Wstp => (expr!(::LinkObject), expr!(::LinkObject)),
ExportKind::Wxf => (
expr!(::List[::List[::ByteArray, "Constant"]]),
expr!(::ByteArray),
),
};
let load = expr!(::LibraryFunctionLoad[lib, name, args, ret]);
match kind {
ExportKind::Native | ExportKind::Margs => expr!(::NativeCaller[load]),
ExportKind::Wstp => expr!(::WSTPCaller[load]),
ExportKind::Wxf => expr!(::WXFCaller[load]),
}
}
fn with_callers(extra: Vec<Expr>, assoc: Association) -> Expr {
let used = |kind: ExportKind| {
let name = caller_name(kind);
assoc.iter().any(|entry| {
matches!(
entry.value.normal_head().as_ref().map(Expr::kind),
Some(ExprKind::Symbol(s)) if s.as_str() == name
)
})
};
let mut bindings: Vec<Expr> = [ExportKind::Native, ExportKind::Wstp, ExportKind::Wxf]
.iter()
.copied()
.filter(|&kind| used(kind))
.map(caller_binding)
.collect();
bindings.extend(extra);
expr!(::With[bindings, assoc])
}
fn export_key(namespace: Option<&str>, name: &str) -> String {
match namespace {
Some(ns) => format!("{ns}::{name}"),
None => name.to_owned(),
}
}
pub struct LibraryArtifact {
pub path: Expr,
pub namespace: Option<String>,
pub functions: Vec<FunctionEntry>,
}
pub fn library_functions_loader(libraries: &[LibraryArtifact]) -> Expr {
let mut bindings: Vec<Expr> = Vec::new();
let mut rules: Vec<RuleEntry> = Vec::new();
let mut n = 0;
for library in libraries {
if library.functions.is_empty() {
continue;
}
n += 1;
let libvar = Symbol::new(&format!("lib{n}"));
bindings.push(expr!(::Set[(Expr::from(libvar.clone())), (library.path.clone())]));
for entry in &library.functions {
let Some(kind) = ExportKind::from_kind_str(&entry.kind) else {
continue;
};
let native_sig = match kind {
ExportKind::Native | ExportKind::Margs => {
Some((entry.params.clone(), entry.ret.clone()))
},
_ => None,
};
let key = export_key(library.namespace.as_deref(), &entry.name);
rules.push(RuleEntry::rule(
Expr::from(key.as_str()),
library_function_load(
kind,
&entry.name,
Expr::from(libvar.clone()),
native_sig,
),
));
}
}
with_callers(bindings, rules.into_iter().collect())
}
pub enum ExportEntry {
Native {
name: &'static str,
signature: fn() -> Result<(Vec<Expr>, Expr), String>,
},
Margs {
name: &'static str,
signature: fn() -> (Vec<Expr>, Expr),
},
Wstp {
name: &'static str,
},
Wxf {
name: &'static str,
signature: fn() -> Result<(Vec<Expr>, Expr), String>,
},
}
#[cfg(feature = "automate-function-loading-boilerplate")]
inventory::collect!(ExportEntry);
#[cfg(feature = "automate-function-loading-boilerplate")]
#[no_mangle]
pub extern "C" fn __wolfram_manifest__() -> *const u8 {
let entries: Vec<FunctionEntry> = inventory::iter::<ExportEntry>()
.filter_map(ExportEntry::to_function_entry)
.collect();
let wxf = wolfram_serialize::to_wxf(&entries, None)
.expect("manifest WXF serialization failed");
let mut buf = Vec::with_capacity(8 + wxf.len());
buf.extend_from_slice(&(wxf.len() as u64).to_le_bytes());
buf.extend_from_slice(&wxf);
Box::leak(buf.into_boxed_slice()).as_ptr()
}
#[cfg(feature = "automate-function-loading-boilerplate")]
pub fn exported_library_functions_association(
library: Option<std::path::PathBuf>,
) -> Expr {
let library: std::path::PathBuf = library.unwrap_or_else(|| {
process_path::get_dylib_path()
.expect("unable to automatically determine Rust LibraryLink dynamic library file path. Suggestion: pass the library name or path to exported_library_functions_association(..)")
});
let library = library
.to_str()
.expect("unable to convert library file path to str");
let functions: Vec<FunctionEntry> = inventory::iter::<ExportEntry>()
.filter_map(ExportEntry::to_function_entry)
.collect();
library_functions_loader(&[LibraryArtifact {
path: Expr::string(library),
namespace: None,
functions,
}])
}
#[cfg(feature = "automate-function-loading-boilerplate")]
impl ExportEntry {
fn to_function_entry(&self) -> Option<FunctionEntry> {
Some(match self {
ExportEntry::Native { name, signature } => {
let (params, ret) = signature().ok()?;
FunctionEntry {
name: (*name).to_owned(),
kind: "Native".to_owned(),
params,
ret,
}
},
ExportEntry::Margs { name, signature } => {
let (params, ret) = signature();
FunctionEntry {
name: (*name).to_owned(),
kind: "Margs".to_owned(),
params,
ret,
}
},
ExportEntry::Wstp { name } => FunctionEntry {
name: (*name).to_owned(),
kind: "Wstp".to_owned(),
params: vec![],
ret: Expr::string(""),
},
ExportEntry::Wxf { name, .. } => FunctionEntry {
name: (*name).to_owned(),
kind: "Wxf".to_owned(),
params: vec![],
ret: Expr::string(""),
},
})
}
}