/*!
* Conversion to and from matlab with associated errors
*
* The rustmex family of crates define a set of wrapper types, to encapsulate an untyped
* [`MatlabPtr`](crate::pointers::MatlabPtr) with some type information, after which it's
* contents can be accessed safely. This module defines the error types and other
* conversion tooling.
*/
use std::fmt::{self, Display};
/**
* Error converting from matlab. Stores the unconverted data, allowing it to be reused,
* especially for owned data.
*/
pub struct FromMatlabError
{
unconverted_data: P,
the_reason: FromMatlabErrorReason,
}
impl FromMatlabError {
pub fn new(unconverted_data: S, the_reason: FromMatlabErrorReason) -> Self {
Self {
unconverted_data,
the_reason,
}
}
pub fn new_badclass(unconverted_data: S) -> Self {
Self::new(unconverted_data, FromMatlabErrorReason::BadClass)
}
pub fn new_badcomplexity(unconverted_data: S) -> Self {
Self::new(unconverted_data, FromMatlabErrorReason::BadComplexity)
}
pub fn new_badsparsity(unconverted_data: S) -> Self {
Self::new(unconverted_data, FromMatlabErrorReason::BadSparsity)
}
pub fn new_badsize(unconverted_data: S) -> Self {
Self::new(unconverted_data, FromMatlabErrorReason::Size)
}
pub fn original(self) -> S {
self.unconverted_data
}
pub fn reason_ref(&self) -> &FromMatlabErrorReason {
&self.the_reason
}
pub fn reason(&self) -> FromMatlabErrorReason {
self.the_reason
}
}
/**
* Possible errors which can occur when converting an mxArray into a Rust type.
*/
#[derive(Debug, Copy, Clone, PartialEq, Hash)]
#[non_exhaustive]
pub enum FromMatlabErrorReason {
/// Returned when the type of the mxArray does not agree
BadClass,
/// Returned when the complexity of the mxArray does not agree with the type
BadComplexity,
/// Returned when the sparsity does not match the expected sparsity
BadSparsity,
/// Returned when the size of the mxArray does not match
Size,
}
impl Display for FromMatlabError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// TODO: This should probably a bit more specific, but the enum atm does
// not store the source mxArray nor the target type, so we can't (yet).
write!(f, "Error converting mxArray to rust type")
}
}
/**
* Most Vectors are either a column-, or a row vector. This enum can be used to configure
* that direction via [`FromVec`].
*/
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum VecType {
Column,
Row
}
impl VecType {
/**
* Construct the shape matrix.
*
* For a row vector, it will return `[1, length]`, since, for a row vector there
* are `length` columns; vice versa for a row vector.
*/
pub fn shape_matrix(&self, length: usize) -> [usize; 2] {
match self {
VecType::Row => [1, length],
VecType::Column => [length, 1]
}
}
}
/// The default vectype is a column.
impl Default for VecType {
fn default() -> Self {
Self::Column
}
}
/**
* Trait for types describing how an array of vectors should be layed out when passed to
* Matlab. Matlab arrays are, after all, multidimensional; the vector can lay among any
* of them.
*/
pub trait VecLayout {
/**
* The list of numbers describing the vectors layout to Matlab. For flexibility,
* any type which can be referenced to as a usize-slice is allowed.
*/
type Layout: AsRef<[usize]>;
/**
* Given the array length, compute the [`Layout`](Self::Layout) which needs to be
* passed to matlab.
*/
fn layout(&self, length: usize) -> Self::Layout;
}
impl VecLayout for VecType {
/// For a vectype, the layout will always be two usizes.
type Layout = [usize; 2];
fn layout(&self, length: usize) -> Self::Layout {
self.shape_matrix(length)
}
}
/**
* At compile time one might already know along what dimension to lay out a vector. That
* dimension (also for higher dimensionality than rows/columns) can be selected via
* `Cat1`.
*
* As the name alludes to, somewhat confusingly the const generic does not specify the
* index of the dimension as 0-based, but as 1-based --- and therefore also the length of
* the dimensions array. This is due to a limitation of the Rust type system, which, at
* the moment, does not allow (useful) arithmetic with const generics.
*
* However, this implementation does avoid a heap allocation, since it is aware of the
* length needed to describe the layout --- it can therefore be passed purely on the
* stack. Contrast this with [`DynCat`], which has to dynamically allocate a [`Vec`].
*
* For example, a row vector is created by setting the second element to the vector's
* length (it has that many columns), so that would be `Cat1<2>`. Alternatively, for a
* vector in the direction of the planes (the third dimension), `Cat1<3>` can be used.
*/
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct Cat1;
impl Cat1 {
/// 0-based index, computed from the 1-based index, into the dimension array
/// along which the vector will be layed out.
const IDX: usize = IDX1 - 1;
}
impl VecLayout for Cat1 {
type Layout = [usize; IDX1];
fn layout(&self, length: usize) -> Self::Layout {
let mut l = [1; IDX1];
l[Cat1::::IDX] = length;
l
}
}
/**
* In case one does not know ahead of time how a vector is to be layed out --- the
* direction is dynamic --- `DynCat` can be used. Note that DynCat is 0-based.
*
* Since it is not known ahead of time how many dimensions are needed, a small vector
* will be allocated. If you do know ahead of time what direction the vector is to be
* layed out in, consider using [`Cat1`] or [`VecType`]; these operate fully on the stack
* without allocating.
*/
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct DynCat(usize);
/// The default is a column vector
impl Default for DynCat {
fn default() -> Self {
DynCat(0)
}
}
impl VecLayout for DynCat {
type Layout = Vec;
fn layout(&self, length: usize) -> Self::Layout {
let mut v = vec![1; self.0+1];
v[self.0] = length;
v
}
}
/**
* Since a Matlab vector always has at least two dimensions, we need some extra
* information to determine what shape the converted matrix in Matlab will have,
* since it is not stored in a Rust vector. See also [`VecType`].
*/
pub trait FromVec {
/**
* Convert an owned `Vec` to some Matlab class.
*/
fn from_vec(v: Vec, l: L) -> Self where Self: Sized {
Self::from_boxed_slice(v.into_boxed_slice(), l)
}
fn from_slice>, L: VecLayout>(s: S, l: L) -> Self where Self: Sized {
Self::from_boxed_slice(s.into(), l)
}
/**
* Convert a boxed slice into some matlab type.
*/
fn from_boxed_slice(b: Box<[T]>, l: L) -> Self where Self: Sized;
}
/**
* For a lot of types, the shape the data is not stored along with the data itself, so it
* has to be provided separately. There can thus be a mismatch between the
* number of elements in the data and the shape provided. In that case, the data to be
* converted into a Matlab type is converted, along with the cause of the error.
*/
#[derive(Copy, Clone, Debug)]
pub struct DataShapeMismatch {
unconverted_data: S,
the_reason: DataShapeMismatchReason,
}
impl DataShapeMismatch {
pub fn new(unconverted_data: S, the_reason: DataShapeMismatchReason) -> Self {
Self {
unconverted_data,
the_reason
}
}
pub fn because_numel_shape(data: S) -> Self {
Self::new(data, DataShapeMismatchReason::NumelShape)
}
pub fn because_im_re(data: S) -> Self {
Self::new(data, DataShapeMismatchReason::ImRe)
}
/// Return the reason the conversion failed
pub fn reason(&self) -> DataShapeMismatchReason {
*self.reason_ref()
}
/// Return a reference to the reason the conversion failed.
pub fn reason_ref(&self) -> &DataShapeMismatchReason {
&self.the_reason
}
/// Retrieve the original, unconverted data
pub fn original(self) -> S {
self.unconverted_data
}
}
/**
* The reason a conversion failed
*/
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum DataShapeMismatchReason {
/**
* The number of elements in the data does not match the number of elements
* implied by the shape (_i.e._ the number of elements does not match the
* product of the lengths of the dimensions in the shape argument.
*/
NumelShape,
/**
* In case of a separated complex layout, the number of elements in the imaginary
* array does not match the number of elements in the real array.
*/
ImRe,
}
/**
* Convenience [`Result`] type for converting types to Matlab types; returning the
* unconverted original data if the conversion failed. See [`DataShapeMismatch`].
*/
pub type ToMatlabResult = Result>;
impl Display for DataShapeMismatch {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.the_reason {
DataShapeMismatchReason::NumelShape => {
write!(f,
"The specified shape did not match the length of the data")
},
DataShapeMismatchReason::ImRe => {
write!(f, "The lengths of the data for the real and imaginary parts do not match")
}
}
}
}