#![warn(missing_docs)]
mod env;
mod error;
mod link_server;
mod wait;
mod get;
mod put;
mod strx;
pub mod kernel;
#[cfg(doctest)]
#[doc(hidden)]
mod test_readme {
#![doc = include_str!("../README.md")]
}
use std::convert::TryFrom;
use std::ffi::{c_char, CStr, CString};
use std::fmt::{self, Display};
use std::net;
use wolfram_expr::{BigInteger, BigReal, Expr, ExprKind, Symbol};
use wstp_sys::{WSErrorMessage, WSReady, WSReleaseErrorMessage, WSLINK};
#[doc(inline)]
pub use wstp_sys as sys;
pub use crate::{
env::shutdown,
error::Error,
get::{Array, LinkStr, Token, TokenType},
link_server::LinkServer,
strx::{Ucs2Str, Utf16Str, Utf32Str, Utf8Str},
};
pub(crate) use env::with_raw_stdenv;
#[derive(Debug)]
#[derive(ref_cast::RefCastCustom)]
#[repr(transparent)]
pub struct Link {
raw_link: WSLINK,
}
impl Link {
#[ref_cast::ref_cast_custom]
pub unsafe fn unchecked_ref_cast_mut(from: &mut WSLINK) -> &mut Self;
}
unsafe impl Send for Link {}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Protocol {
IntraProcess,
SharedMemory,
TCPIP,
}
#[derive(Debug, Clone, PartialEq)]
pub struct UrgentMessage {
pub code: u32,
pub param: u32,
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum UrgentMessageKind {
Terminate,
Interrupt,
Abort,
}
impl UrgentMessage {
pub const TERMINATE: UrgentMessage = UrgentMessage {
code: sys::WSTerminateMessage as u32,
param: 0,
};
pub const INTERRUPT: UrgentMessage = UrgentMessage {
code: sys::WSInterruptMessage as u32,
param: 0,
};
pub const ABORT: UrgentMessage = UrgentMessage {
code: sys::WSAbortMessage as u32,
param: 0,
};
pub fn new(code: u32) -> Self {
UrgentMessage { code, param: 0 }
}
pub fn new_with_param(code: u32, param: u32) -> Self {
UrgentMessage { code, param }
}
pub fn kind(&self) -> Result<UrgentMessageKind, u32> {
let UrgentMessage { code, param: _ } = *self;
let kind = if code == sys::WSTerminateMessage as u32 {
UrgentMessageKind::Terminate
} else if code == sys::WSInterruptMessage as u32 {
UrgentMessageKind::Interrupt
} else if code == sys::WSAbortMessage as u32 {
UrgentMessageKind::Abort
} else {
return Err(code as u32);
};
Ok(kind)
}
}
impl UrgentMessageKind {
pub fn code(&self) -> u32 {
match self {
UrgentMessageKind::Terminate => sys::WSTerminateMessage as u32,
UrgentMessageKind::Interrupt => sys::WSInterruptMessage as u32,
UrgentMessageKind::Abort => sys::WSAbortMessage as u32,
}
}
}
impl Link {
pub fn new_loopback() -> Result<Self, Error> {
unsafe {
let mut err: std::os::raw::c_int = sys::MLEOK;
let raw_link =
with_raw_stdenv(|raw_stdenv| sys::WSLoopbackOpen(raw_stdenv, &mut err))?;
if raw_link.is_null() || err != sys::MLEOK {
return Err(Error::from_code(err));
}
Ok(Link::unchecked_new(raw_link))
}
}
pub fn listen(protocol: Protocol, name: &str) -> Result<Self, Error> {
let protocol_string = protocol.to_string();
let strings: &[&str] = &[
"-wstp",
"-linkmode",
"listen",
"-linkprotocol",
protocol_string.as_str(),
"-linkname",
name,
"-linkoptions",
"MLDontInteract",
];
Link::open_with_args(strings)
}
pub fn connect(protocol: Protocol, name: &str) -> Result<Self, Error> {
Link::connect_with_options(protocol, name, &[])
}
pub fn tcpip_listen<A: net::ToSocketAddrs>(addr: A) -> Result<Self, Error> {
let addrs = addr.to_socket_addrs().map_err(|err| {
Error::custom(format!("error connecting to TCPIP Link address: {}", err))
})?;
for_each_addr(addrs.collect(), |addr| {
Link::listen(Protocol::TCPIP, &tcpip_link_name(&addr))
})
}
pub fn tcpip_connect<A: net::ToSocketAddrs>(addr: A) -> Result<Self, Error> {
let addrs = addr.to_socket_addrs().map_err(|err| {
Error::custom(format!("error connecting to TCPIP Link address: {}", err))
})?;
for_each_addr(addrs.collect(), |addr| {
Link::connect(Protocol::TCPIP, &tcpip_link_name(&addr))
})
}
pub fn connect_to_link_server<A: net::ToSocketAddrs>(
addrs: A,
) -> Result<Self, Error> {
let addrs = addrs.to_socket_addrs().map_err(|err| {
Error::custom(format!("error connecting to LinkServer address: {}", err))
})?;
for_each_addr(addrs.collect(), |addr| {
let mut link = Link::connect_with_options(
Protocol::TCPIP,
&tcpip_link_name(&addr),
&["MLUseUUIDTCPIPConnection"],
)?;
let () = link.activate()?;
return Ok(link);
})
}
#[allow(missing_docs)]
pub fn connect_with_options(
protocol: Protocol,
name: &str,
options: &[&str],
) -> Result<Self, Error> {
let protocol_string = protocol.to_string();
let mut strings: Vec<&str> = vec![
"-wstp",
"-linkmode",
"connect",
"-linkprotocol",
protocol_string.as_str(),
"-linkname",
name,
];
if !options.is_empty() {
strings.push("-linkoptions");
strings.extend(options);
}
Link::open_with_args(&strings)
}
pub fn open_with_args(args: &[&str]) -> Result<Self, Error> {
let mut c_strings: Vec<*mut c_char> = args
.into_iter()
.map(|&str| {
CString::new(str)
.expect("failed to create CString from WSTP link open argument")
.into_raw()
})
.collect();
let mut err: std::os::raw::c_int = sys::MLEOK;
let raw_link = with_raw_stdenv(|raw_stdenv| unsafe {
sys::WSOpenArgcArgv(
raw_stdenv,
i32::try_from(c_strings.len()).unwrap(),
c_strings.as_mut_ptr(),
&mut err,
)
})?;
for c_string in c_strings {
unsafe {
let _ = CString::from_raw(c_string);
}
}
if raw_link.is_null() || err != sys::MLEOK {
return Err(Error::from_code(err));
}
Ok(Link { raw_link })
}
pub unsafe fn unchecked_new(raw_link: WSLINK) -> Self {
Link { raw_link }
}
pub fn activate(&mut self) -> Result<(), Error> {
if unsafe { sys::WSActivate(self.raw_link) } == 0 {
return Err(self.error_or_unknown());
}
Ok(())
}
pub fn close(self) {
}
}
pub fn channel(protocol: Protocol) -> Result<(Link, Link), Error> {
let mut listener = Link::listen(protocol.clone(), "")?;
let mut connecter = Link::connect(protocol, &listener.link_name())?;
let listener = std::thread::spawn(move || {
let () = listener.activate()?;
Ok(listener)
});
let () = connecter.activate()?;
let listener = listener.join().expect("listener thread panicked")?;
Ok((listener, connecter))
}
impl Link {
pub fn link_name(&self) -> String {
let Link { raw_link } = *self;
unsafe {
let name: *const c_char = self::sys::WSName(raw_link as *mut _);
CStr::from_ptr(name).to_str().unwrap().to_owned()
}
}
pub fn is_ready(&self) -> bool {
let Link { raw_link } = *self;
unsafe { WSReady(raw_link) != 0 }
}
pub fn is_loopback(&self) -> bool {
let Link { raw_link } = *self;
1 == unsafe { sys::WSIsLinkLoopback(raw_link) }
}
pub fn error(&self) -> Option<Error> {
let Link { raw_link } = *self;
let (code, message): (i32, *const c_char) =
unsafe { (sys::WSError(raw_link), WSErrorMessage(raw_link)) };
if code == sys::MLEOK || message.is_null() {
return None;
}
let string: String = unsafe {
let cstr = CStr::from_ptr(message);
let string = cstr.to_str().unwrap().to_owned();
WSReleaseErrorMessage(raw_link, message);
string
};
return Some(Error {
code: Some(code),
message: string,
});
}
pub fn error_message(&self) -> Option<String> {
self.error().map(|Error { message, code: _ }| message)
}
pub(crate) fn error_or_unknown(&self) -> Error {
self.error()
.unwrap_or_else(|| Error::custom("unknown error occurred on WSLINK".into()))
}
pub fn clear_error(&mut self) {
let Link { raw_link } = *self;
unsafe {
sys::WSClearError(raw_link);
}
}
pub unsafe fn raw_link(&self) -> WSLINK {
let Link { raw_link } = *self;
raw_link
}
pub unsafe fn user_data(&self) -> (*mut std::ffi::c_void, sys::WSUserFunction) {
let Link { raw_link } = *self;
let mut user_func: sys::WSUserFunction = None;
let data_obj: *mut std::ffi::c_void = sys::WSUserData(raw_link, &mut user_func);
(data_obj, user_func)
}
pub unsafe fn set_user_data(
&mut self,
data_obj: *mut std::ffi::c_void,
user_func: sys::WSUserFunction,
) {
let Link { raw_link } = *self;
sys::WSSetUserData(raw_link, data_obj, user_func);
}
}
impl Link {
pub fn is_message_ready(&self) -> bool {
let Link { raw_link } = *self;
unsafe { sys::WSMessageReady(raw_link) != 0 }
}
pub fn put_message(&mut self, message: UrgentMessage) -> Result<(), Error> {
let Link { raw_link } = *self;
let UrgentMessage { code, param } = message;
let code = i32::try_from(code).expect("WSTP urgent message code overflows i32");
let param =
i32::try_from(param).expect("WSTP urgent message param overflows i32");
let result = unsafe { sys::WSPutMessageWithArg(raw_link, code, param) };
if result == 0 {
return Err(self.error_or_unknown());
}
Ok(())
}
pub fn get_message(&mut self) -> Option<UrgentMessage> {
let Link { raw_link } = *self;
let mut code: i32 = 0;
let mut param: i32 = 0;
let result = unsafe { sys::WSGetMessage(raw_link, &mut code, &mut param) };
let code =
u32::try_from(code).expect("WSTP urgent message code doesn't fit into u32");
let param =
u32::try_from(param).expect("WSTP urgent message param doesn't fit into u32");
if result != 0 {
Some(UrgentMessage { code, param })
} else {
None
}
}
}
impl Link {
pub fn flush(&mut self) -> Result<(), Error> {
if unsafe { sys::WSFlush(self.raw_link) } == 0 {
return Err(self.error_or_unknown());
}
Ok(())
}
pub fn raw_get_next(&mut self) -> Result<i32, Error> {
let type_ = unsafe { sys::WSGetNext(self.raw_link) };
if type_ == sys::WSTKERR {
return Err(self.error_or_unknown());
}
Ok(type_)
}
pub fn raw_next_packet(&mut self) -> Result<i32, Error> {
let type_ = unsafe { sys::WSNextPacket(self.raw_link) };
if type_ == sys::ILLEGALPKT {
return Err(self.error_or_unknown());
}
Ok(type_)
}
pub fn new_packet(&mut self) -> Result<(), Error> {
if unsafe { sys::WSNewPacket(self.raw_link) } == 0 {
return Err(self.error_or_unknown());
}
Ok(())
}
pub fn get_expr(&mut self) -> Result<Expr, Error> {
self.get_expr_with_resolver(&mut |_| None)
}
#[doc(hidden)]
pub fn get_expr_with_resolver(
&mut self,
mut resolver: &mut dyn FnMut(&str) -> Option<Symbol>,
) -> Result<Expr, Error> {
use crate::get::TokenType;
let token_type = self.get_type()?;
let expr: Expr = match token_type {
TokenType::Integer => {
match self.get_i64() {
Ok(v) => Expr::from(v),
Err(_) => {
self.clear_error();
let digits = self.get_number_as_string()?;
Expr::new(ExprKind::BigInteger(BigInteger(digits)))
},
}
},
TokenType::Real => {
match self.get_f64() {
Ok(v) => {
let real = wolfram_expr::F64::new(v).map_err(|_| {
Error::custom(
"NaN value passed on WSLINK cannot be used to construct an Expr"
.to_owned(),
)
})?;
Expr::from(real.into_inner())
},
Err(_) => {
self.clear_error();
let s = self.get_number_as_string()?;
Expr::new(ExprKind::BigReal(BigReal(s)))
},
}
},
TokenType::String => Expr::string(self.get_string_ref()?.as_str()),
TokenType::Symbol => {
let value = self.get_symbol_ref()?;
let symbol_str: &str = value.as_str();
let symbol = Symbol::try_new(symbol_str).or_else(|| resolver(symbol_str));
let symbol: Symbol = match symbol {
Some(sym) => sym,
None => {
return Err(Error::custom(format!(
"symbol name '{}' has no context",
symbol_str
)))
},
};
Expr::symbol(symbol)
},
TokenType::Function => {
let arg_count = self.get_arg_count()?;
let head = self.get_expr_with_resolver(&mut resolver)?;
let mut contents = Vec::with_capacity(arg_count);
for _ in 0..arg_count {
contents.push(self.get_expr_with_resolver(&mut resolver)?);
}
Expr::normal(head, contents)
},
};
Ok(expr)
}
pub fn put_expr(&mut self, expr: &Expr) -> Result<(), Error> {
match expr.kind() {
ExprKind::Normal(normal) => {
self.put_raw_type(i32::from(sys::WSTKFUNC))?;
self.put_arg_count(normal.elements().len())?;
let _: () = self.put_expr(normal.head())?;
for elem in normal.elements() {
let _: () = self.put_expr(elem)?;
}
},
ExprKind::Symbol(symbol) => {
self.put_symbol(symbol.as_str())?;
},
ExprKind::String(string) => {
self.put_str(string.as_str())?;
},
ExprKind::Integer(int) => {
self.put_i64(*int)?;
},
ExprKind::Real(real) => {
self.put_f64(**real)?;
},
ExprKind::Association(assoc) => {
self.put_raw_type(i32::from(sys::WSTKFUNC))?;
self.put_arg_count(assoc.len())?;
self.put_symbol("System`Association")?;
for entry in assoc {
let head = if entry.delayed {
"System`RuleDelayed"
} else {
"System`Rule"
};
self.put_raw_type(i32::from(sys::WSTKFUNC))?;
self.put_arg_count(2)?;
self.put_symbol(head)?;
self.put_expr(&entry.key)?;
self.put_expr(&entry.value)?;
}
},
ExprKind::ByteArray(ba) => {
let bytes: &[u8] = ba.as_slice();
self.put_raw_type(i32::from(sys::WSTKFUNC))?;
self.put_arg_count(1)?;
self.put_symbol("System`ByteArray")?;
self.put_u8_array(bytes, &[bytes.len()])?;
},
ExprKind::BigInteger(_)
| ExprKind::BigReal(_)
| ExprKind::PackedArray(_)
| ExprKind::NumericArray(_) => {
let wxf = wolfram_serialize::to_wxf(expr, None).map_err(|e| {
Error::custom(format!("put_expr: WXF serialization failed: {e:?}"))
})?;
self.put_raw_type(i32::from(sys::WSTKFUNC))?;
self.put_arg_count(1)?;
self.put_symbol("System`BinaryDeserialize")?;
self.put_raw_type(i32::from(sys::WSTKFUNC))?;
self.put_arg_count(1)?;
self.put_symbol("System`ByteArray")?;
self.put_u8_array(&wxf, &[wxf.len()])?;
},
other => {
return Err(Error::custom(format!(
"put_expr: ExprKind variant {:?} not yet supported",
other
)));
},
}
Ok(())
}
pub fn transfer_expr_to(&mut self, dest: &mut Link) -> Result<(), Error> {
let result = unsafe { sys::WSTransferExpression(dest.raw_link, self.raw_link) };
if result == 0 {
return Err(self.error_or_unknown());
}
Ok(())
}
pub fn transfer_to_end_of_loopback_link(
&mut self,
dest: &mut Link,
) -> Result<(), Error> {
if !self.is_loopback() {
panic!("transfer_to_end_of_loopback_link(): self must be a loopback link");
}
let result =
unsafe { sys::WSTransferToEndOfLoopbackLink(dest.raw_link, self.raw_link) };
if result == 0 {
return if let Some(err) = self.error() {
Err(err)
} else if let Some(err) = dest.error() {
Err(err)
} else {
Err(Error::custom("unknown error occurred on WSLINK".into()))
};
}
Ok(())
}
}
fn for_each_addr<T, F>(addrs: Vec<net::SocketAddr>, mut func: F) -> Result<T, Error>
where
F: FnMut(net::SocketAddr) -> Result<T, Error>,
{
let mut last_error = None;
for addr in addrs {
match func(addr) {
Ok(result) => return Ok(result),
Err(err) => last_error = Some(err),
}
}
Err(last_error
.unwrap_or_else(|| Error::custom(format!("socket address list is empty"))))
}
fn tcpip_link_name(addr: &net::SocketAddr) -> String {
format!("{}@{}", addr.port(), addr.ip())
}
impl Display for Protocol {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let str = match self {
Protocol::IntraProcess => "IntraProcess",
Protocol::SharedMemory => "SharedMemory",
Protocol::TCPIP => "TCPIP",
};
write!(f, "{}", str)
}
}
impl Drop for Link {
fn drop(&mut self) {
let Link { raw_link } = *self;
unsafe {
sys::WSClose(raw_link);
}
}
}