//! Traits to be implemented by [`Machine::Datum`] to support specific types //! //! # Rationale //! //! This module allows conversion from and to dynamic data types of scripting //! languages used in a VM. The Rust type covering all types of the scripting //! language is the associated type [`Machine::Datum`]. //! //! As different VMs with different scripting languages may support different //! kinds of types, a [`Machine::Datum`] may implement all or only some //! `Maybe…` traits from this module, e.g. [`MaybeString`] and [`MaybeInteger`] //! when a VM supports strings and integers. //! //! # Converting from and to VM-specific data types //! //! ## From VM-specific data types //! //! Conversion to scalar data types is done through `try_as_…` methods, which //! attempt to interpret a VM-specific datum as certain type, e.g. //! [`MaybeInteger::try_as_i32`] or [`MaybeString`::`try_as_str`]. //! These methods return a [`TypeMismatch`] if the type doesn't match. //! //! When conversion to Unicode or binary strings is desired, the VM-specific //! datum type must furthermore implement [`TryInto`] (ideally by implementing //! [`TryFrom`]) with [`DatumConversionFailure`] as error type to allow //! conversion into an owned `String` or `Vec`. This conversion is made //! available as [`try_into_string`](MaybeString::try_into_string) and //! [`try_into_binary`](MaybeBinary::try_into_binary) to specify the type //! through the method call (as [`TryInto::try_into`] isn't turbofishable). //! These convenience methods also convert the error type from //! [`DatumConversionFailure`] into [`TypeMismatch`] (in order to have an error //! type that is `Send + Sync + 'static`). //! //! Collections are accessed through various methods in the [`MaybeArray`] and //! [`MaybeStringMap`] traits of which some may fail due to runtime errors in //! the VM and thus can return [`MachineError`]s on failure. //! //! ## To VM-specific data types //! //! Conversion from scalar data types to the VM-specific type is done by //! implementing [`From`], which allows creation directly from standard Rust //! data types (such as `&str`, `String`, or `i32`). See supertraits of the //! various `Maybe…` traits in this module. //! //! Collections are created by trait methods that are implemented by the //! machine, e.g. [`HasArray::new_array`]. use super::*; use std::borrow::Borrow; use std::hash::Hash; use std::num::TryFromIntError; use std::ops::Deref; /// Types that can be null /// /// Implementors should consider also implementing `From>` where /// `T: Into`. pub trait Nullable where Self: Sized, { /// Datum representing a null value fn null() -> Self; /// Can the datum be interpreted as a null value? fn is_null(&self) -> bool; } /// Types that can be boolean pub trait MaybeBoolean where Self: From, { /// Try to interpret datum as boolean value fn try_as_bool(&self) -> Result; } /// Types that can be a function pub trait MaybeFunction where Self: From, { /// Function type (see [`Machine::Function`]) type Function: Function; /// Reference or smart pointer to [`MaybeFunction::Function`] as returned /// by [`try_as_function`](MaybeFunction::try_as_function) type FunctionRef<'a>: Deref + Borrow where Self: 'a; // TODO: Add associated type default `FunctionRef<'a> = &'a Self::Function` // when/if feature `associated_type_defaults` is stable. /// Try to interpret datum as function fn try_as_function(&self) -> Result, TypeMismatch>; } /// Types that can be a reference to an opaque datum allowing comparison for equality and hashing pub trait MaybeOpaque where Self: TryInto>, { /// Type used to represent reference to an opaque datum type Opaque: Clone + Eq + PartialEq + Hash; /// Reference or smart pointer to [`MaybeOpaque::Opaque`] as returned by /// [`try_as_opaque`](MaybeOpaque::try_as_opaque) type OpaqueRef<'a>: Deref + Borrow where Self: 'a; // TODO: Add associated type default `OpaqueRef<'a> = &'a Self::Opaque` // when/if feature `associated_type_defaults` is stable. /// Can the datum be interpreted as an opaque value? fn is_opaque(&self) -> bool { self.try_as_opaque().is_ok() } /// Try to convert datum into opaque datum fn try_into_opaque(self) -> Result { self.try_into().map_err(Into::into) } /// Try to interpret datum as reference to opaque datum fn try_as_opaque(&self) -> Result, TypeMismatch>; } /// Types that can be an UTF-8 text string pub trait MaybeString<'c> where Self: From, Self: From<&'c str>, Self: TryInto>, { /// Try to convert datum into Unicode string fn try_into_string(self) -> Result { self.try_into().map_err(Into::into) } /// Try to interpret datum as Unicode string fn try_as_str(&self) -> Result<&str, TypeMismatch>; } /// Types that can be a binary blob pub trait MaybeBinary<'c> where Self: From>, Self: From<&'c [u8]>, Self: TryInto, Error = DatumConversionFailure>, { /// Try to convert datum into binary (8 bit) string fn try_into_binary(self) -> Result, TypeMismatch> { self.try_into().map_err(Into::into) } /// Try to interpret datum as binary (8 bit) string fn try_as_bin(&self) -> Result<&[u8], TypeMismatch>; } /// Types that can be a float pub trait MaybeFloat where Self: From, Self: From, { /// Try to interpret datum as `f64` fn try_as_f64(&self) -> Result; /// Try to interpret datum as `f64` fn try_as_f32(&self) -> Result { Ok(self.try_as_f64()? as f32) } } /// Types that can be an integer (at least 32 bits signed integers must be supported) pub trait MaybeInteger where Self: Sized, Self: TryFrom, Self: TryFrom, Self: TryFrom, Self: TryFrom, Self: TryFrom, Self: From, Self: From, Self: From, Self: From, Self: From, { /// Try to interpret datum as `i64` fn try_as_i64(&self) -> Result; /// Try to interpret datum as `u64` fn try_as_u64(&self) -> Result { self.try_as_i64().and_then(|x| { x.try_into() .map_err(|_| TypeMismatch::with_static_msg("integer value does not fit into u64")) }) } /// Try to interpret datum as `i32` fn try_as_i32(&self) -> Result { self.try_as_i64().and_then(|x| { x.try_into() .map_err(|_| TypeMismatch::with_static_msg("integer value does not fit into i32")) }) } /// Try to interpret datum as `u32` fn try_as_u32(&self) -> Result { self.try_as_i64().and_then(|x| { x.try_into() .map_err(|_| TypeMismatch::with_static_msg("integer value does not fit into u32")) }) } /// Try to interpret datum as `i16` fn try_as_i16(&self) -> Result { self.try_as_i64().and_then(|x| { x.try_into() .map_err(|_| TypeMismatch::with_static_msg("integer value does not fit into i16")) }) } /// Try to interpret datum as `u16` fn try_as_u16(&self) -> Result { self.try_as_i64().and_then(|x| { x.try_into() .map_err(|_| TypeMismatch::with_static_msg("integer value does not fit into u16")) }) } /// Try to interpret datum as `i8` fn try_as_i8(&self) -> Result { self.try_as_i64().and_then(|x| { x.try_into() .map_err(|_| TypeMismatch::with_static_msg("integer value does not fit into i8")) }) } /// Try to interpret datum as `u8` fn try_as_u8(&self) -> Result { self.try_as_i64().and_then(|x| { x.try_into() .map_err(|_| TypeMismatch::with_static_msg("integer value does not fit into u8")) }) } /// Try to interpret datum as `isize` fn try_as_isize(&self) -> Result { self.try_as_i64().and_then(|x| { x.try_into() .map_err(|_| TypeMismatch::with_static_msg("integer value does not fit into isize")) }) } /// Try to interpret datum as `usize` fn try_as_usize(&self) -> Result { self.try_as_i64().and_then(|x| { x.try_into() .map_err(|_| TypeMismatch::with_static_msg("integer value does not fit into usize")) }) } } /// Iterator over an array-like datum /// /// Returned by [`MaybeArray::array_to_iter`]. pub struct ArrayIter<'a, D: ?Sized> { datum: &'a D, len: usize, index: usize, } impl<'a, D: ?Sized> Iterator for ArrayIter<'a, D> where D: MaybeArray, { type Item = Result<::Element<'static>, MachineError>; fn next(&mut self) -> Option::Element<'static>, MachineError>> { if self.index >= self.len { None } else { match self.datum.array_get(self.index) { Ok(element) => { self.index += 1; Some(Ok(element)) } Err(err) => { self.len = 0; Some(Err(err)) } } } } } /// Types that can be an array pub trait MaybeArray { /// Type of elements type Element<'c>; /// Return error unless datum is an array-like type fn try_array(&self) -> Result<(), TypeMismatch>; /// Array length fn array_len(&self) -> Result; /// Retrieve element at index /// /// NOTE: If index is out of bounds, may either return error or /// [null](Nullable::null) fn array_get(&self, index: usize) -> Result, MachineError>; /// Set element at index /// /// NOTE: If index is out of bounds, may either grow array or return error fn array_set<'c>(&self, index: usize, element: Self::Element<'c>) -> Result<(), MachineError>; /// Push element to array fn array_push<'c>(&self, element: Self::Element<'c>) -> Result<(), MachineError>; /// Truncate array fn array_truncate(&self, len: usize) -> Result<(), MachineError>; /// Create [`Iterator`] over entries of array-like datum fn array_to_iter<'b>(&'b self) -> Result, MachineError> { self.try_array()?; Ok(ArrayIter { datum: self, len: self.array_len()?, index: 0, }) } /// Create [`Vec`] from array-like datum fn array_to_vec(&self, maxlen: usize) -> Result>, MachineError> { self.try_array()?; let len = self.array_len()?; if len > maxlen { Err(MachineError::new() .set_kind(MachineErrorKind::Data) .set_message(format!( "array length {} exceeded maximum length {}", len, maxlen )))?; } ArrayIter { datum: self, len, index: 0, } .collect() } } /// Types that can be a string map (mapping strings to other datums) pub trait MaybeStringMap { /// Type of values type Value<'c>; /// Return error unless datum is a string-map-like type fn try_string_map(&self) -> Result<(), TypeMismatch>; /// Get entry from string map fn string_map_get(&self, key: &str) -> Result, MachineError>; /// Set entry in string map fn string_map_set<'c>(&self, key: &str, value: Self::Value<'c>) -> Result<(), MachineError>; } /// [`Machine`]s, which have array-like types pub trait HasArray<'a>: Machine<'a> { /// Create datum that is an empty array fn new_empty_array<'b>(&'b self) -> Result, MachineError>; /// Create datum that is an array fn new_array<'b, 'c, I>(&'b self, elements: I) -> Result, MachineError> where I: IntoIterator>, for<'d> >::Datum<'b, 'static>: MaybeArray = >::Datum<'b, 'd>>, { let datum = self.new_empty_array()?; for element in elements { datum.array_push(element)?; } Ok(datum) } } /// [`Machine`]s, which have string-map-like types pub trait HasStringMap<'a>: Machine<'a> { /// Create datum that is an empty string map fn new_empty_string_map<'b>(&'b self) -> Result, MachineError>; /// Create datum that is a string map fn new_string_map<'b, 'c, 'd, I>( &'b self, entries: I, ) -> Result, MachineError> where I: IntoIterator)>, for<'e> >::Datum<'b, 'static>: MaybeStringMap = >::Datum<'b, 'e>>, { let datum = self.new_empty_string_map()?; for (key, value) in entries { datum.string_map_set(key, value)?; } Ok(datum) } }