#![warn(missing_docs)]
#[cfg(feature = "automate-function-loading-boilerplate")]
pub use inventory;
use wolfram_expr::{expr, Association, Expr, ExprKind, RuleEntry};
use wolfram_serialize::{FromWXF, ToWXF};
#[derive(ToWXF, FromWXF, Debug)]
#[allow(missing_docs)]
pub struct FunctionEntry {
pub name: String,
pub kind: String,
pub params: Vec<Expr>,
pub ret: Expr,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum ExportKind {
Native,
Wstp,
Wxf,
}
impl ExportKind {
pub fn from_kind_str(s: &str) -> Option<ExportKind> {
match s {
"Native" => Some(ExportKind::Native),
"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",
}
}
pub 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
]]
]),
}
}
pub 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 => {
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 => expr!(::NativeCaller[load]),
ExportKind::Wstp => expr!(::WSTPCaller[load]),
ExportKind::Wxf => expr!(::WXFCaller[load]),
}
}
pub fn library_function_rule(
kind: ExportKind,
name: &str,
key: &str,
lib: Expr,
native_sig: Option<(Vec<Expr>, Expr)>,
) -> RuleEntry {
RuleEntry::rule(
Expr::from(key),
library_function_load(kind, name, lib, native_sig),
)
}
pub 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])
}
pub fn export_key(namespace: Option<&str>, name: &str) -> String {
match namespace {
Some(ns) => format!("{ns}::{name}"),
None => name.to_owned(),
}
}
pub enum ExportEntry {
Native {
name: &'static str,
signature: fn() -> Result<(Vec<Expr>, Expr), String>,
},
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__(out_len: *mut usize) -> *const u8 {
let assoc: Expr = exported_library_functions_association(None);
let bytes: Vec<u8> =
wolfram_serialize::to_wxf(&assoc, None).expect("manifest WXF serialization");
let len = bytes.len();
let ptr = Box::leak(bytes.into_boxed_slice()).as_ptr();
unsafe {
*out_len = len;
}
ptr
}
#[cfg(feature = "automate-function-loading-boilerplate")]
#[no_mangle]
pub extern "C" fn __wolfram_manifest_data__() -> *const u8 {
let entries: Vec<FunctionEntry> = inventory::iter::<ExportEntry>()
.map(|e| match e {
ExportEntry::Native { name, signature } => {
let (params, ret) =
signature().unwrap_or_else(|_| (vec![], Expr::string("")));
FunctionEntry {
name: (*name).to_owned(),
kind: "Native".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(""),
},
})
.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 lib = Expr::string(library);
let assoc: Association = inventory::iter::<ExportEntry>()
.filter_map(|entry| {
let (kind, native_sig) = match entry {
ExportEntry::Native { signature, .. } => {
(ExportKind::Native, Some(signature().ok()?))
},
ExportEntry::Wstp { .. } => (ExportKind::Wstp, None),
ExportEntry::Wxf { .. } => (ExportKind::Wxf, None),
};
let key = export_key(None, entry.name());
Some(library_function_rule(
kind,
entry.name(),
&key,
lib.clone(),
native_sig,
))
})
.collect();
with_callers(Vec::new(), assoc)
}
#[cfg_attr(
not(feature = "automate-function-loading-boilerplate"),
allow(dead_code)
)]
impl ExportEntry {
fn name(&self) -> &str {
match self {
ExportEntry::Native { name, .. } => name,
ExportEntry::Wstp { name } => name,
ExportEntry::Wxf { name, .. } => name,
}
}
}