use std::os::raw::c_int;
use wstp::{self, Link};
use crate::{
catch_panic::{call_and_catch_panic, CaughtPanic},
expr::{Expr, Symbol},
sys::{self, MArgument, LIBRARY_NO_ERROR},
NativeFunction, WstpFunction,
};
mod error_code {
use std::os::raw::c_int;
const OFFSET: c_int = 1000;
pub const FAILED_TO_INIT: c_int = OFFSET + 1;
pub const FAILED_WITH_PANIC: c_int = OFFSET + 2;
}
unsafe fn call_wstp_link_wolfram_library_function<
F: FnOnce(&mut Link) + std::panic::UnwindSafe,
>(
libdata: sys::WolframLibraryData,
mut unsafe_link: wstp::sys::WSLINK,
function: F,
) -> c_int {
if crate::initialize(libdata).is_err() {
return error_code::FAILED_TO_INIT;
}
let link = Link::unchecked_ref_cast_mut(&mut unsafe_link);
let result: Result<(), CaughtPanic> =
call_and_catch_panic(std::panic::AssertUnwindSafe(|| {
let _: () = function(link);
}));
match result {
Ok(()) => LIBRARY_NO_ERROR as c_int,
Err(panic) => match write_panic_failure_to_link(link, panic) {
Ok(()) => LIBRARY_NO_ERROR as c_int,
Err(_wstp_err) => {
error_code::FAILED_WITH_PANIC
},
},
}
}
fn write_panic_failure_to_link(
link: &mut Link,
caught_panic: CaughtPanic,
) -> Result<(), wstp::Error> {
link.clear_error();
if link.is_ready() {
link.raw_get_next()?;
let result: Result<(), _> = link.new_packet();
if result.is_err() {
link.clear_error();
}
}
link.put_expr(&caught_panic.to_pretty_expr())
}
pub unsafe fn call_native_wolfram_library_function<'a, F: NativeFunction<'a>>(
lib_data: sys::WolframLibraryData,
args: *mut MArgument,
argc: sys::mint,
res: MArgument,
func: F,
) -> c_int {
use std::panic::AssertUnwindSafe;
if crate::initialize(lib_data).is_err() {
return error_code::FAILED_TO_INIT;
}
let argc = match usize::try_from(argc) {
Ok(argc) => argc,
Err(_) => return sys::LIBRARY_FUNCTION_ERROR as c_int,
};
let args: &[MArgument] = std::slice::from_raw_parts(args, argc);
if call_and_catch_panic(AssertUnwindSafe(move || func.call(args, res))).is_err() {
return error_code::FAILED_WITH_PANIC;
};
sys::LIBRARY_NO_ERROR as c_int
}
pub unsafe fn call_wstp_wolfram_library_function<
F: WstpFunction + std::panic::UnwindSafe,
>(
libdata: sys::WolframLibraryData,
unsafe_link: wstp::sys::WSLINK,
func: F,
) -> c_int {
call_wstp_link_wolfram_library_function(
libdata,
unsafe_link,
move |link: &mut Link| {
let _: () = func.call(link);
},
)
}
pub enum LibraryLinkFunction {
Native {
name: &'static str,
signature: fn() -> Result<(Vec<Expr>, Expr), String>,
},
Wstp {
name: &'static str,
},
}
#[cfg(feature = "automate-function-loading-boilerplate")]
inventory::collect!(LibraryLinkFunction);
#[cfg(feature = "automate-function-loading-boilerplate")]
pub unsafe fn load_library_functions_impl(
lib_data: sys::WolframLibraryData,
raw_link: wstp::sys::WSLINK,
) -> c_int {
call_wstp_link_wolfram_library_function(lib_data, raw_link, |link: &mut Link| {
let arg_count: usize =
link.test_head("List").expect("expected 'List' expression");
if arg_count != 1 {
panic!(
"expected 1 argument: the name of or file path to the dynamic library"
);
}
let path = {
let path = match link.get_string_ref() {
Ok(value) => value,
Err(err) => panic!("expected String argument (error: {})", err),
};
std::path::PathBuf::from(path.as_str())
};
let expr = exported_library_functions_association(Some(path));
link.put_expr(&expr)
.expect("failed to write loader Association");
})
}
#[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 mut fields = Vec::new();
let rule = Symbol::new("System`Rule");
for func in inventory::iter::<LibraryLinkFunction> {
let code = match func.loading_code(&library) {
Ok(code) => code,
Err(_) => continue,
};
fields.push(Expr::normal(&rule, vec![Expr::string(func.name()), code]));
}
Expr::normal(Symbol::new("System`Association"), fields)
}
#[cfg_attr(
not(feature = "automate-function-loading-boilerplate"),
allow(dead_code)
)]
impl LibraryLinkFunction {
fn name(&self) -> &str {
match self {
LibraryLinkFunction::Native { name, .. } => name,
LibraryLinkFunction::Wstp { name } => name,
}
}
fn loading_code(&self, library: &std::path::PathBuf) -> Result<Expr, String> {
fn sys(name: &str) -> Symbol {
Symbol::new(&format!("System`{}", name))
}
let lib_func_load = sys("LibraryFunctionLoad");
let link_object = Expr::from(sys("LinkObject"));
let library = Expr::string(
library
.to_str()
.expect("unable to convert library file path to str"),
);
let code = match self {
LibraryLinkFunction::Native { name, signature } => {
let (args, ret) = signature()?;
Expr::normal(&lib_func_load, vec![
library.clone(),
Expr::string(*name),
Expr::normal(sys("List"), args),
ret,
])
},
LibraryLinkFunction::Wstp { name } => {
let load_call = Expr::normal(&lib_func_load, vec![
library.clone(),
Expr::string(*name),
link_object.clone(),
link_object,
]);
let var = Expr::from(Symbol::new("RustLink`Private`wstpFunc"));
Expr::normal(sys("With"), vec![
Expr::normal(sys("List"), vec![Expr::normal(sys("Set"), vec![
var.clone(),
load_call,
])]),
Expr::normal(sys("Function"), vec![Expr::normal(
sys("Block"),
vec![
Expr::normal(sys("List"), vec![
Expr::normal(sys("Set"), vec![
Expr::from(sys("$Context")),
Expr::string("RustLinkWSTPPrivateContext`"),
]),
Expr::normal(sys("Set"), vec![
Expr::from(sys("$ContextPath")),
Expr::normal(sys("List"), vec![]),
]),
]),
Expr::normal(var, vec![Expr::normal(
sys("SlotSequence"),
vec![Expr::from(1)],
)]),
],
)]),
])
},
};
Ok(code)
}
}
pub unsafe fn init_with_user_function(
lib: sys::WolframLibraryData,
user_init_func: fn(),
) -> c_int {
if let Err(()) = crate::initialize(lib) {
return error_code::FAILED_TO_INIT as c_int;
}
if let Err(_) = call_and_catch_panic(user_init_func) {
error_code::FAILED_WITH_PANIC as c_int
} else {
sys::LIBRARY_NO_ERROR as c_int
}
}