rustmex 0.6.4

Rustmex: providing convenient Rust bindings to Matlab MEX API's
Documentation
/*!
 * Create and send Matlab warning and error messages
 *
 * Matlab handles errors in a variety of ways, but all of them ultimately boil down to
 * error/warning id plus a message. This module describes that way of error handling in a
 * more Rusty way.
 */

use std::ffi::CString;

use std::fmt::{self, Display};

use ndarray::{ShapeError, ErrorKind};

use rustmex_core::{
	convert::{
		FromMatlabError,
		FromMatlabErrorReason,
	},
};

use rustmex_core::mxArray;
use rustmex_core::pointers::MxArray;

/**
 * Trait describing a "mex message", i.e. the input to mex's warning and error functions.
 *
 * The methods return &str, because this trait is meant to be used within Rust library
 * code. Having this trait return C string pointers is too much of a burden on the users;
 * conversions to c strings for matlab are done by rustmex
 */
pub trait MexMessage: Display {
	fn id(&self) -> &str;
}

// TODO: Redesign how errors are handled; since MexMessages can sometimes hold references
// in recent versions it's a complete mess.
/**
 * Error type, to be returned from the entrypoint function
 */
pub struct Error {
	error_id: String,
	error_msg: String,
}

impl Error {
	pub fn id(&self) -> &str {
		&self.error_id
	}

	pub fn to_string(self) -> String {
		self.error_msg
	}
}

impl<T> From<T> for Error where T: MexMessage {
	fn from(other: T) -> Self {
		Error { error_id: other.id().to_string(), error_msg: other.to_string()}
	}
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct MissingVariable {
	id: &'static str,
	msg: &'static str,
}

/**
 * One of the first things a MEX file has to do is parse its arguments — the LHS array.
 * Part of that is checking whether it got all the arguments it expected. In MEX files,
 * this is best done via slice's `.get()` method. This method then returns an Option;
 * Some if there is an argument, None if that slot is out of bounds. The value can then
 * be pattern matched out via a `let val = if let Some(_)`, but that requires an `else`
 * arm and is overall just combersome.
 *
 * This trait is the solution for that problem. It elegantly maps an `Option` to a
 * `Result`; an `Ok` if there was some value, an `Err` with a [`MissingVariable`]
 * [`MexMessage`] if not.
 *
 * Note that there is a slight difference in functionality between the two implementers.
 */
pub trait Missing<T> {
	fn error_if_missing(self, id: &'static str , msg: &'static str)
		-> Result<T, MissingVariable> where Self: Sized;
}

/**
 * See [`Missing`] for the main explanation. This implementation also dereferences the
 * indirection of the slice, yielding a direct reference to the
 * [`mxArray`](crate::mxArray).
 *
 * Best used with `get` on a slice.
 */
impl<'a> Missing<&'a mxArray> for Option<&'_ &'a mxArray> {
	fn error_if_missing(self, id: &'static str, msg: &'static str)
		-> Result<&'a mxArray, MissingVariable>
	{
		match self {
			Some(v) => Ok(*v),
			None => Err(MissingVariable {
				id, msg
			})
		}
	}
}

/**
 * See [`Missing`] for the main explanation. Contrast with the implementation for
 * Option<&&mxArray>. This implementation does not dereference the slice's reference,
 * since [`MxArray`] does not implement copy —­The slice remains the owner of the
 * [`MxArray`].
 *
 * Best used with `get_mut` on a slice, and `replace` on the reference yielded to place
 * the new value.
 */
impl<'s> Missing<&'s mut Option<MxArray>> for Option<&'s mut Option<MxArray>> {
	fn error_if_missing(self, id: &'static str, msg: &'static str)
		-> Result<&'s mut Option<MxArray>, MissingVariable>
	{
		match self {
			Some(v) => Ok(v),
			None => Err(MissingVariable {
				id, msg
			})
		}
	}
}

impl MexMessage for MissingVariable {
	fn id(&self) -> &str {
		self.id
	}
}

impl Display for MissingVariable {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		write!(f, "{}", self.msg)
	}
}

impl<S> MexMessage for FromMatlabError<S> {
	fn id(&self) -> &str {
		match self.reason() {
			FromMatlabErrorReason::BadClass => "rustmex:convert:bad_class",
			FromMatlabErrorReason::BadComplexity => "rustmex:convert:bad_complexity",
			FromMatlabErrorReason::BadSparsity => "rustmex:convert:bad_sparsity",
			FromMatlabErrorReason::Size => "rustmex:convert:size_mismatch",
			// TODO: Fix this
			_ => "rustmex:enum_defined_elsewhere"
		}
	}
}

pub struct AdHoc<Id, Msg>(pub Id, pub Msg);

/**
 * Convenience implementation for MexMessage for two str's. Sometimes an ad-hoc
 * error/warning is generated, from two strings instead of a specific error type.
 */
impl<Id, Msg> MexMessage for AdHoc<Id, Msg> where Id: AsRef<str>, Msg: Display {
	fn id(&self) -> &str { self.0.as_ref() }
}

impl<Id, Msg> Display for AdHoc<Id, Msg> where Msg: Display {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.1.fmt(f) }
}

/**
 * Generate a Matlab warning
 */
pub fn warning(w: &dyn MexMessage) -> () {
	let id = CString::new(w.id()).expect("No inner nul bytes");
	let msg = CString::new(w.to_string()).expect("No inner nul bytes");
	unsafe { rustmex_core::shim::rustmex_warn_id_and_txt(id.as_ptr(), msg.as_ptr()); }
}

/**
 * Trigger a Matlab error
 *
 * Note that this macro diverges, as it returns control to the Matlab prompt. This
 * divergent behaviour prevents destructors from running, and therefore calling this
 * method may leak memory. Consider returning from your entrypoint with a
 * [`rustmex::Result::Err`](crate::Result) instead.
 */
// This macro was previously named just `error!`, but is now `trigger_error`. Both
// because the name reflects what it does (actually trigger the error), and because now
// the name can be used for something more ergonomic in the root module (to
// unconditionally return a `rustmex::Result::Err`; something which is more likely to
// occur in "normal" code than triggering a diverging error in the middle of it).
#[macro_export]
macro_rules! trigger_error {
	($e:expr) => {{
		use ::rustmex::{
			message::MexMessage,
			alloc::NonPersistent as NP,
		};

		let e = $e;
		let id = unsafe { NP::from_stringish(e.id(), true) };
		let msg = unsafe { NP::from_stringish(e.to_string(), true) };

		// Run e's destructor now, as it can't run after mexErrMsgIdAndTxt
		// drop(e);

		unsafe { ::rustmex::message::trigger_error_fn(NP::ptr(&id) as *const i8, NP::ptr(&msg) as *const i8); }
	}}
}

/**
 * Function to trigger a Matlab error.
 *
 * Used in [`trigger_error`].
 */
pub unsafe fn trigger_error_fn(id: *const i8, msg: *const i8) -> ! {
	::rustmex_core::shim::rustmex_err_id_and_txt(id, msg);
	unreachable!()
}

impl MexMessage for ShapeError {
	fn id(&self) -> &str {
		use ErrorKind::*;
		match self.kind() {
			IncompatibleShape  => "ndarray:incompatible_shape",
			IncompatibleLayout => "ndarray:incompatible_layout",
			RangeLimited	   => "ndarray:range_limited",
			OutOfBounds        => "ndarray:out_of_bounds",
			Unsupported        => "ndarray:unsupported",
			Overflow           => "ndarray:overflow",
			_                  => "rustmex:ndarray:unrecognised_errorkind",
		}
	}
}

use crate::structs::StructError;

impl MexMessage for StructError {
	fn id(&self) -> &str {
		match self {
			StructError::OutOfBounds => "rustmex:structs:out_of_bounds",
			StructError::NotAField => "rustmex:structs:not_a_field",
		}
	}
}

impl Display for StructError {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		let s = match self {
			StructError::OutOfBounds => "Index is out of bounds",
			StructError::NotAField => "Field does not exist",
		};
		write!(f, "{}", s)
	}
}

// NOTE: Objects are only really supported on platforms other than GNU/Octave
use crate::objects::ObjectError;

impl MexMessage for ObjectError {
	fn id(&self) -> &str {
		match self {
			ObjectError::OutOfBounds => "rustmex:objects:out_of_bounds",
			ObjectError::NotAccessible => "rustmex:objects:not_accessible",
		}
	}
}

impl Display for ObjectError {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		let s = match self {
			ObjectError::OutOfBounds => "Index is out of bounds",
			ObjectError::NotAccessible => "Property is not accessible",
		};
		write!(f, "{}", s)
	}
}