use std::mem::MaybeUninit;
use std::os::raw::c_int;
use std::panic::AssertUnwindSafe;
use wolfram_expr::Expr;
use wolfram_library_link::call_and_catch_panic;
use wolfram_library_link::sys::{self, MArgument};
use wolfram_library_link::{NativeFunction, NumericArray, UninitNumericArray};
use wolfram_serialize::{
from_wxf, ExpressionEnum, HeaderEnum, SliceReader, Writer, WxfReader, WxfWriter,
};
pub use wolfram_serialize::{FromWXF, ToWXF};
pub fn wxf_signature() -> Result<(Vec<Expr>, Expr), String> {
Ok((vec![Expr::string("ByteArray")], Expr::string("ByteArray")))
}
pub fn decode<A: for<'de> FromWXF<'de>>(input: &NumericArray<u8>) -> Result<A, String> {
from_wxf::<A>(input.as_slice()).map_err(|e| format!("{e:?}"))
}
pub fn decode_args<R, F>(
input: &NumericArray<u8>,
n_expected: u64,
read: F,
) -> Result<R, wolfram_serialize::Error>
where
F: for<'a> FnOnce(
&mut WxfReader<SliceReader<'a>>,
) -> Result<R, wolfram_serialize::Error>,
{
wolfram_serialize::read_wxf(input.as_slice(), |r| {
let tok = r.read_expr_token()?;
if tok != ExpressionEnum::Function {
return Err(wolfram_serialize::Error::unexpected_token(
&["Function"],
tok,
));
}
let n = r.read_varint()?;
r.skip()?; if n != n_expected {
return Err(wolfram_serialize::Error::ArgCountMismatch {
expected: n_expected,
got: n,
});
}
read(r)
})
}
const WXF_HEADER: [u8; 2] =
[HeaderEnum::Version as u8, HeaderEnum::Separator as u8];
struct UninitSliceWriter<'a> {
buf: &'a mut [MaybeUninit<u8>],
pos: usize,
}
impl std::io::Write for UninitSliceWriter<'_> {
fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
let dest = self
.buf
.get_mut(self.pos..self.pos + bytes.len())
.ok_or(std::io::ErrorKind::WriteZero)?;
for (d, &b) in dest.iter_mut().zip(bytes) {
d.write(b);
}
self.pos += bytes.len();
Ok(bytes.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
fn wxf_byte_len<R: ToWXF>(value: &R) -> Result<usize, wolfram_serialize::Error> {
struct ByteCounter(usize);
impl std::io::Write for ByteCounter {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.0 += buf.len();
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
let mut counter = ByteCounter(WXF_HEADER.len());
let mut w = WxfWriter::new(&mut counter);
value.to_wxf(&mut w)?;
Ok(counter.0)
}
pub fn try_encode<R: ToWXF>(
value: &R,
) -> Result<NumericArray<u8>, wolfram_serialize::Error> {
let len = wxf_byte_len(value)?;
let mut uninit = UninitNumericArray::<u8>::from_dimensions(&[len]);
let mut sink = UninitSliceWriter {
buf: uninit.as_slice_mut(),
pos: 0,
};
sink.write_bytes(&WXF_HEADER)?;
{
let mut w = WxfWriter::new(&mut sink);
value.to_wxf(&mut w)?;
}
debug_assert_eq!(sink.pos, len);
Ok(unsafe { uninit.assume_init() })
}
pub fn encode<R: ToWXF>(value: &R) -> NumericArray<u8> {
try_encode(value).unwrap_or_else(|e| panic!("WXF serialize failed: {:?}", e))
}
pub fn encode_arg_error(e: wolfram_serialize::Error) -> NumericArray<u8> {
encode(&wolfram_expr::expr!(
System::Failure["ArgumentError", { "Message" -> (format!("{e:?}")) }]
))
}
pub fn encode_result<R: ToWXF>(
value: &R,
) -> Result<NumericArray<u8>, wolfram_serialize::Error> {
try_encode(value)
}
pub fn call_and_encode_panic<F>(func: F) -> NumericArray<u8>
where
F: FnOnce() -> NumericArray<u8>,
{
match call_and_catch_panic(AssertUnwindSafe(func)) {
Ok(result) => result,
Err(failure_expr) => encode(&failure_expr),
}
}
pub trait WxfFunction {}
impl<A: for<'de> FromWXF<'de>, R: ToWXF> WxfFunction for fn(A) -> R {}
pub unsafe fn call_wxf_wolfram_library_function<'a, F: NativeFunction<'a>>(
lib_data: sys::WolframLibraryData,
args: *mut MArgument,
argc: sys::mint,
res: MArgument,
func: F,
) -> c_int {
if wolfram_library_link::initialize(lib_data).is_err() {
return wolfram_library_link::FAILED_TO_INIT;
}
let argc = match usize::try_from(argc) {
Ok(argc) => argc,
Err(_) => return wolfram_library_link::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 wolfram_library_link::FAILED_WITH_PANIC;
}
sys::LIBRARY_NO_ERROR as c_int
}