use std::ffi::{CStr, CString};
use std::iter::FromIterator;
use std::{convert::TryFrom, fmt, os::raw::c_char};
use crate::{
sys::{
self, WSGetArgCount, WSGetInteger16, WSGetInteger32, WSGetInteger64,
WSGetInteger8, WSGetReal32, WSGetReal64, WSGetUTF16String, WSGetUTF32String,
WSGetUTF8String, WSReleaseUTF16String, WSReleaseUTF16Symbol,
WSReleaseUTF32String, WSReleaseUTF32Symbol, WSReleaseUTF8String,
WSReleaseUTF8Symbol,
},
Error, Link, Utf16Str, Utf32Str, Utf8Str,
};
#[allow(missing_docs)]
#[derive(Debug)]
pub enum Token<'link> {
Integer(i64),
Real(f64),
Symbol(LinkStr<'link>),
String(LinkStr<'link>),
Function {
length: usize,
},
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum TokenType {
Integer,
Real,
Symbol,
String,
Function,
}
pub struct LinkStr<'link, T: LinkStrType + ?Sized = str> {
link: &'link Link,
ptr: *const T::Element,
length: usize,
is_symbol: bool,
}
pub unsafe trait LinkStrType: fmt::Debug {
type Element;
unsafe fn from_slice_unchecked<'s>(slice: &'s [Self::Element]) -> &'s Self;
unsafe fn release(
link: &Link,
ptr: *const Self::Element,
len: usize,
is_symbol: bool,
);
}
impl Link {
pub fn get_type(&self) -> Result<TokenType, Error> {
use wstp_sys::{WSTKFUNC, WSTKINT, WSTKREAL, WSTKSTR, WSTKSYM};
let type_: i32 = self.get_raw_type()?;
let token_type = match u8::try_from(type_).unwrap() {
WSTKINT => TokenType::Integer,
WSTKREAL => TokenType::Real,
WSTKSTR => TokenType::String,
WSTKSYM => TokenType::Symbol,
WSTKFUNC => TokenType::Function,
_ => return Err(Error::custom(format!("unknown WSLINK type: {}", type_))),
};
Ok(token_type)
}
pub fn get_token(&mut self) -> Result<Token<'_>, Error> {
let token = match self.get_type()? {
TokenType::Integer => Token::Integer(self.get_i64()?),
TokenType::Real => Token::Real(self.get_f64()?),
TokenType::String => Token::String(self.get_string_ref()?),
TokenType::Symbol => Token::Symbol(self.get_symbol_ref()?),
TokenType::Function => Token::Function {
length: self.get_arg_count()?,
},
};
Ok(token)
}
pub fn get_raw_type(&self) -> Result<i32, Error> {
let type_ = unsafe { sys::WSGetType(self.raw_link) };
if type_ == sys::WSTKERR {
return Err(self.error_or_unknown());
}
Ok(type_)
}
pub fn get_string_ref<'link>(&'link mut self) -> Result<LinkStr<'link, str>, Error> {
let mut c_string: *const u8 = std::ptr::null();
let mut num_bytes: i32 = 0;
let mut num_chars = 0;
if unsafe {
WSGetUTF8String(self.raw_link, &mut c_string, &mut num_bytes, &mut num_chars)
} == 0
{
return Err(self.error_or_unknown());
}
let num_bytes = usize::try_from(num_bytes).unwrap();
Ok(LinkStr {
link: self,
ptr: c_string,
length: num_bytes,
is_symbol: false,
})
}
pub fn get_string(&mut self) -> Result<String, Error> {
Ok(self.get_string_ref()?.get().to_owned())
}
pub fn get_symbol_ref<'link>(&'link mut self) -> Result<LinkStr<'link, str>, Error> {
let mut c_string: *const u8 = std::ptr::null();
let mut num_bytes: i32 = 0;
let mut num_chars = 0;
if unsafe {
sys::WSGetUTF8Symbol(
self.raw_link,
&mut c_string,
&mut num_bytes,
&mut num_chars,
)
} == 0
{
return Err(self.error_or_unknown());
}
let num_bytes = usize::try_from(num_bytes).unwrap();
Ok(LinkStr {
link: self,
ptr: c_string,
length: num_bytes,
is_symbol: true,
})
}
pub fn get_utf8_str<'link>(
&'link mut self,
) -> Result<LinkStr<'link, Utf8Str>, Error> {
let mut c_string: *const u8 = std::ptr::null();
let mut num_bytes: i32 = 0;
let mut num_chars = 0;
if unsafe {
WSGetUTF8String(self.raw_link, &mut c_string, &mut num_bytes, &mut num_chars)
} == 0
{
return Err(self.error_or_unknown());
}
let num_bytes = usize::try_from(num_bytes).unwrap();
Ok(LinkStr {
link: self,
ptr: c_string,
length: num_bytes,
is_symbol: false,
})
}
pub fn get_utf16_str<'link>(
&'link mut self,
) -> Result<LinkStr<'link, Utf16Str>, Error> {
let mut c_string: *const u16 = std::ptr::null();
let mut num_elems: i32 = 0;
let mut num_chars = 0;
if unsafe {
WSGetUTF16String(self.raw_link, &mut c_string, &mut num_elems, &mut num_chars)
} == 0
{
return Err(self.error_or_unknown());
}
let num_elems = usize::try_from(num_elems).unwrap();
Ok(LinkStr {
link: self,
ptr: c_string,
length: num_elems,
is_symbol: false,
})
}
pub fn get_utf32_str<'link>(
&'link mut self,
) -> Result<LinkStr<'link, Utf32Str>, Error> {
let mut c_string: *const u32 = std::ptr::null();
let mut num_elems: i32 = 0;
if unsafe { WSGetUTF32String(self.raw_link, &mut c_string, &mut num_elems) } == 0
{
return Err(self.error_or_unknown());
}
let num_elems = usize::try_from(num_elems).unwrap();
Ok(LinkStr {
link: self,
ptr: c_string,
length: num_elems,
is_symbol: false,
})
}
pub fn test_head(&mut self, symbol: &str) -> Result<usize, Error> {
let c_string = CString::new(symbol).unwrap();
self.test_head_cstr(c_string.as_c_str())
}
pub fn test_head_cstr(&mut self, symbol: &CStr) -> Result<usize, Error> {
let mut len: std::os::raw::c_int = 0;
if unsafe { sys::WSTestHead(self.raw_link, symbol.as_ptr(), &mut len) } == 0 {
return Err(self.error_or_unknown());
}
let len = usize::try_from(len).expect("c_int overflows usize");
Ok(len)
}
pub fn get_arg_count(&mut self) -> Result<usize, Error> {
let mut arg_count = 0;
if unsafe { WSGetArgCount(self.raw_link, &mut arg_count) } == 0 {
return Err(self.error_or_unknown());
}
let arg_count = usize::try_from(arg_count)
.expect("WSTKFUNC argument count could not be converted to usize");
Ok(arg_count)
}
pub fn get_number_as_string(&mut self) -> Result<String, Error> {
let mut c_string: *const c_char = std::ptr::null();
if unsafe { sys::WSGetNumberAsString(self.raw_link, &mut c_string) } == 0 {
return Err(self.error_or_unknown());
}
let owned = unsafe { CStr::from_ptr(c_string).to_string_lossy().into_owned() };
unsafe { sys::WSReleaseString(self.raw_link, c_string) };
Ok(owned)
}
pub fn get_i64(&mut self) -> Result<i64, Error> {
let mut int = 0;
if unsafe { WSGetInteger64(self.raw_link, &mut int) } == 0 {
return Err(self.error_or_unknown());
}
Ok(int)
}
pub fn get_i32(&mut self) -> Result<i32, Error> {
let mut int = 0;
if unsafe { WSGetInteger32(self.raw_link, &mut int) } == 0 {
return Err(self.error_or_unknown());
}
Ok(int)
}
pub fn get_i16(&mut self) -> Result<i16, Error> {
let mut int = 0;
if unsafe { WSGetInteger16(self.raw_link, &mut int) } == 0 {
return Err(self.error_or_unknown());
}
Ok(int)
}
pub fn get_u8(&mut self) -> Result<u8, Error> {
let mut int = 0;
if unsafe { WSGetInteger8(self.raw_link, &mut int) } == 0 {
return Err(self.error_or_unknown());
}
Ok(int)
}
pub fn get_f64(&mut self) -> Result<f64, Error> {
let mut real: f64 = 0.0;
if unsafe { WSGetReal64(self.raw_link, &mut real) } == 0 {
return Err(self.error_or_unknown());
}
Ok(real)
}
pub fn get_f32(&mut self) -> Result<f32, Error> {
let mut real: f32 = 0.0;
if unsafe { WSGetReal32(self.raw_link, &mut real) } == 0 {
return Err(self.error_or_unknown());
}
Ok(real)
}
pub fn get_i64_array(&mut self) -> Result<Array<'_, i64>, Error> {
unsafe { self.get_array(sys::WSGetInteger64Array, sys::WSReleaseInteger64Array) }
}
pub fn get_i32_array(&mut self) -> Result<Array<'_, i32>, Error> {
unsafe { self.get_array(sys::WSGetInteger32Array, sys::WSReleaseInteger32Array) }
}
pub fn get_i16_array(&mut self) -> Result<Array<'_, i16>, Error> {
unsafe { self.get_array(sys::WSGetInteger16Array, sys::WSReleaseInteger16Array) }
}
pub fn get_u8_array(&mut self) -> Result<Array<'_, u8>, Error> {
unsafe { self.get_array(sys::WSGetInteger8Array, sys::WSReleaseInteger8Array) }
}
pub fn get_f64_array(&mut self) -> Result<Array<'_, f64>, Error> {
unsafe { self.get_array(sys::WSGetReal64Array, sys::WSReleaseReal64Array) }
}
pub fn get_f32_array(&mut self) -> Result<Array<'_, f32>, Error> {
unsafe { self.get_array(sys::WSGetReal32Array, sys::WSReleaseReal32Array) }
}
#[allow(non_snake_case)]
unsafe fn get_array<T: 'static>(
&mut self,
WSGetTArray: unsafe extern "C" fn(
sys::WSLINK,
*mut *mut T,
*mut *mut i32,
*mut *mut *mut c_char,
*mut i32,
) -> i32,
WSReleaseTArray: unsafe extern "C" fn(
sys::WSLINK,
*mut T,
*mut i32,
*mut *mut c_char,
i32,
),
) -> Result<Array<'_, T>, Error> {
let Link { raw_link } = *self;
let mut data_ptr: *mut T = std::ptr::null_mut();
let mut dims_ptr: *mut i32 = std::ptr::null_mut();
let mut heads_ptr: *mut *mut c_char = std::ptr::null_mut();
let mut depth: i32 = 0;
let result: i32 = {
WSGetTArray(
raw_link,
&mut data_ptr,
&mut dims_ptr,
&mut heads_ptr,
&mut depth,
)
};
if result == 0 {
return Err(self.error_or_unknown());
}
let depth: usize =
usize::try_from(depth).expect("WSGet*Array depth overflows usize");
let dims: &[i32] = { std::slice::from_raw_parts(dims_ptr, depth) };
let dims: Vec<usize> = Vec::from_iter(dims.iter().map(|&val| {
usize::try_from(val)
.expect("WSGetInteger64Array dimension size overflows usize")
}));
Ok(Array {
link: self,
data_ptr,
release_callback: Box::new(move |link: &Link| {
WSReleaseTArray(
link.raw_link,
data_ptr,
dims_ptr,
heads_ptr,
depth as i32,
);
}),
dimensions: dims,
})
}
}
impl<'link, T: LinkStrType + ?Sized> LinkStr<'link, T> {
pub fn get<'this>(&'this self) -> &'this T {
let LinkStr {
link: _,
ptr,
length,
is_symbol: _,
} = *self;
unsafe {
let slice: &'this [T::Element] = std::slice::from_raw_parts(ptr, length);
T::from_slice_unchecked(slice)
}
}
}
impl<'link> LinkStr<'link, str> {
pub fn as_str<'s>(&'s self) -> &'s str {
self.get()
}
#[deprecated(note = "Use LinkStr::as_str() instead")]
pub fn to_str<'s>(&'s self) -> &'s str {
self.get()
}
}
impl<'link, T: ?Sized + LinkStrType> Drop for LinkStr<'link, T> {
fn drop(&mut self) {
let LinkStr {
link,
ptr,
length,
is_symbol,
} = *self;
let () = unsafe { T::release(link, ptr, length, is_symbol) };
}
}
unsafe impl LinkStrType for str {
type Element = u8;
unsafe fn from_slice_unchecked<'s>(slice: &'s [Self::Element]) -> &'s Self {
let str: &'s str = std::str::from_utf8_unchecked(slice);
str
}
unsafe fn release(
link: &Link,
ptr: *const Self::Element,
len: usize,
is_symbol: bool,
) {
let len = i32::try_from(len).expect("LinkStr usize length overflows i32");
match is_symbol {
true => WSReleaseUTF8Symbol(link.raw_link, ptr, len),
false => WSReleaseUTF8String(link.raw_link, ptr, len),
}
}
}
unsafe impl LinkStrType for Utf8Str {
type Element = u8;
unsafe fn from_slice_unchecked<'s>(slice: &'s [Self::Element]) -> &'s Self {
let str: &'s Utf8Str = Utf8Str::from_utf8_unchecked(slice);
str
}
unsafe fn release(
link: &Link,
ptr: *const Self::Element,
len: usize,
is_symbol: bool,
) {
let len = i32::try_from(len).expect("LinkStr usize length overflows i32");
match is_symbol {
true => WSReleaseUTF8Symbol(link.raw_link, ptr, len),
false => WSReleaseUTF8String(link.raw_link, ptr, len),
}
}
}
unsafe impl LinkStrType for Utf16Str {
type Element = u16;
unsafe fn from_slice_unchecked<'s>(slice: &'s [Self::Element]) -> &'s Self {
let str: &'s Utf16Str = Utf16Str::from_utf16_unchecked(slice);
str
}
unsafe fn release(
link: &Link,
ptr: *const Self::Element,
len: usize,
is_symbol: bool,
) {
let len = i32::try_from(len).expect("LinkStr usize length overflows i32");
match is_symbol {
true => WSReleaseUTF16Symbol(link.raw_link, ptr, len),
false => WSReleaseUTF16String(link.raw_link, ptr, len),
}
}
}
unsafe impl LinkStrType for Utf32Str {
type Element = u32;
unsafe fn from_slice_unchecked<'s>(slice: &'s [Self::Element]) -> &'s Self {
let str: &'s Utf32Str = Utf32Str::from_utf32_unchecked(slice);
str
}
unsafe fn release(
link: &Link,
ptr: *const Self::Element,
len: usize,
is_symbol: bool,
) {
let len = i32::try_from(len).expect("LinkStr usize length overflows i32");
match is_symbol {
true => WSReleaseUTF32Symbol(link.raw_link, ptr, len),
false => WSReleaseUTF32String(link.raw_link, ptr, len),
}
}
}
pub struct Array<'link, T> {
link: &'link Link,
data_ptr: *mut T,
release_callback: Box<dyn FnMut(&Link)>,
dimensions: Vec<usize>,
}
impl<'link, T> Array<'link, T> {
pub fn data<'s>(&'s self) -> &'s [T] {
let data_len: usize = self.dimensions.iter().product();
let data: &'s [T] =
unsafe { std::slice::from_raw_parts(self.data_ptr, data_len) };
data
}
pub fn rank(&self) -> usize {
self.dimensions.len()
}
pub fn dimensions(&self) -> &[usize] {
self.dimensions.as_slice()
}
pub fn length(&self) -> usize {
self.dimensions[0]
}
}
impl<'link, T> Drop for Array<'link, T> {
fn drop(&mut self) {
let Array {
link,
ref mut release_callback,
data_ptr: _,
dimensions: _,
} = *self;
release_callback(link)
}
}
impl<'link, T: LinkStrType + fmt::Debug + ?Sized> fmt::Debug for LinkStr<'link, T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let LinkStr {
link,
ptr,
length,
is_symbol,
} = self;
let value = format!("{:?}", self.get());
f.debug_struct("LinkStr")
.field("<value>", &value)
.field("link", link)
.field("ptr", ptr)
.field("length", length)
.field("is_symbol", is_symbol)
.finish()
}
}
impl<'link, T> fmt::Debug for Array<'link, T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let Array {
link,
data_ptr,
release_callback: _,
dimensions,
} = self;
f.debug_struct("Array")
.field("link", link)
.field("dimensions", dimensions)
.field("data_ptr", data_ptr)
.finish()
}
}