rustmex 0.6.4

Rustmex: providing convenient Rust bindings to Matlab MEX API's
Documentation
/*!
 * Cell arrays
 *
 */

use std::ptr::NonNull;
use std::ops::DerefMut;
use crate::convert::FromMatlab;

use rustmex_core::shim::{
	rustmex_get_cell as mxGetCell,
	rustmex_set_cell as mxSetCell,
	rustmex_create_cell_array as mxCreateCellArray,
};

use rustmex_core::classid::ClassID;

use rustmex_core::raw::{
	mwIndex,
};
use rustmex_core::convert::{FromMatlabError};
use rustmex_core::{mxArray, pointers::{MxArray, MatlabPtr, MutMatlabPtr},};
use rustmex_core::{
	MatlabClass,
	MutMatlabClass,
	OwnedMatlabClass,
	NewEmpty,
};

use super::index::Index;

/**
 * Wrapper type for a Cell Array.
 */
#[derive(Debug, Clone, Copy)]
#[repr(transparent)]
pub struct CellArray<P: MatlabPtr>(P);

impl<'a> FromMatlab<'a> for CellArray<&'a mxArray> {
	fn from_matlab(mx: &'a mxArray) -> Result<Self, FromMatlabError<&'a mxArray>> {
		Self::from_mx_array(mx)
	}
}

impl<P> std::ops::Deref for CellArray<P> where P: MatlabPtr {
	type Target = mxArray;

	fn deref(&self) -> &Self::Target {
		&self.0
	}
}

impl<P> std::ops::DerefMut for CellArray<P> where P: MutMatlabPtr {
	fn deref_mut(&mut self) -> &mut Self::Target {
		&mut self.0
	}
}

impl<P> MatlabClass<P> for CellArray<P> where P: MatlabPtr {
	fn from_mx_array(mx: P) -> Result<Self, FromMatlabError<P>> {
		if mx.class_id() == Ok(ClassID::Cell) {
			Ok(Self(mx))
		} else {
			Err(FromMatlabError::new_badclass(mx))
		}
	}

	fn into_inner(self) -> P {
		self.0
	}

	fn inner(&self) -> &P {
		&self.0
	}

	type Owned = CellArray<MxArray>;
	fn duplicate(&self) -> Self::Owned {
		CellArray(self.0.duplicate())
	}
}

impl<P> MutMatlabClass<P> for CellArray<P> where P: MutMatlabPtr {
	type AsBorrowed<'a> = CellArray<&'a mxArray> where Self: 'a;
	fn as_borrowed<'a>(&'a self) -> Self::AsBorrowed<'a> {
		CellArray(self.0.deref())
	}

	fn inner_mut(&mut self) -> &mut P {
		&mut self.0
	}
}

impl OwnedMatlabClass for CellArray<MxArray> {
	type AsMutable<'a> = CellArray<&'a mut mxArray> where Self: 'a;
	fn as_mutable<'a>(&'a mut self) -> Self::AsMutable<'a> {
		CellArray(self.0.deref_mut())
	}
}

impl NewEmpty for CellArray<MxArray> {
	fn new_empty() -> Self {
		const EMPTY: [usize; 2] = [0, 0];
		Self::new(&EMPTY)
	}
}

#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
#[non_exhaustive]
pub enum CellError {
	/// The cell which you're trying to access is out of bounds of the array.
	OutOfBounds,
}

impl<'a, P: MatlabPtr + 'a> CellArray<P> {
	/**
	 * Get the inner value of some cell in this array at a given index.
	 *
	 * This value may not yet be initialised, in which case this method returns
	 * `Ok(None)`. If the index is out of bounds, it returns
	 * `Err(CellError::OutOfBounds)`.
	 */
	pub fn get<I: Index>(&self, idx: I) -> Result<Option<&'a mxArray>, CellError> {
		Ok(self.get_core(idx)?.map(|ptr| unsafe { ptr.as_ref() }))
	}

	/// Shared behaviour for the get(_mut)? methods.
	///
	/// If this returns a None, it means that the particular cell was not initialised
	fn get_core<I: Index>(&self, idx: I) -> Result<Option<NonNull<mxArray>>, CellError> {
		let idx = idx.index_into(self).ok_or(CellError::OutOfBounds)?;

		Ok(NonNull::new(unsafe { mxGetCell(self.0.as_ref(), idx as mwIndex) }))
	}

	/**
	 * When one uses the "spread operator" in matlab on a cell array (i.e. `x{:}`) it
	 * produces a "comma separated list". This method is the Rustmex equivalent;
	 * iterating over all the cells in the cell array.
	 */
	pub fn comma_separated_list(&self) -> impl Iterator<Item = Option<&'a mxArray>> + '_ {
		(0..self.numel()).map(|idx| self.get(idx).unwrap())
	}
}

impl<'a, P: MutMatlabPtr + 'a> CellArray<P> {
	/// Get a mutable reference to a child mxArray
	pub fn get_mut<I: Index>(&mut self, idx: I) -> Result<Option<&'a mut mxArray>, CellError> {
		Ok(self.get_core(idx)?.map(|mut ptr| unsafe { ptr.as_mut() }))
	}

	/**
	 * Replace the current value of the cell at the given index, by optionally a new
	 * value. Returns the value that was there previously, if there was any.
	 */
	pub fn replace<I: Index>(&mut self, idx: I, mx: Option<MxArray>) -> Result<Option<MxArray>, CellError> {
		let idx = idx.index_into(self).ok_or(CellError::OutOfBounds)?;

		let old = unsafe { NonNull::new(mxGetCell(self.0.deref_mut(), idx as _)) }
			.map(|mut ptr| unsafe { MxArray::assume_responsibility(ptr.as_mut()) } );

		unsafe { mxSetCell(
				self.0.deref_mut(),
				idx as _,
				if let Some(val) = mx {
					MxArray::transfer_responsibility_ptr(val)
				} else {
					std::ptr::null_mut()
				}
			)
		}

		Ok(old)
	}

	/// Set the value at the given index. Returns the previous, if there was any.
	#[inline]
	pub fn set<I: Index>(&mut self, idx: I, mx: MxArray) -> Result<Option<MxArray>, CellError> {
		self.replace(idx, Some(mx))
	}

	/// Remove the value (if there is any) at the given index and return it, leaving the cell empty.
	#[inline]
	pub fn unset<I: Index>(&mut self, idx: I) -> Result<Option<MxArray>, CellError> {
		self.replace(idx, None)
	}
}

impl CellArray<MxArray> {
	/**
	 * Create a new cell array with the given shape.
	 */
	pub fn new(shape: &[usize]) -> Self {
		rustmex_core::shape_ok!(shape);

		let ptr = unsafe { mxCreateCellArray(
			shape.len(),
			shape.as_ptr(),
		)};

		if ptr.is_null() {
			panic!("OOM")
		}

		Self(unsafe { MxArray::assume_responsibility(&mut *ptr) } )
	}
}