1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382
//! 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<u8>`. 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<Option<T>>` where
/// `T: Into<Self>`.
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<bool>,
{
/// Try to interpret datum as boolean value
fn try_as_bool(&self) -> Result<bool, TypeMismatch>;
}
/// Types that can be a function
pub trait MaybeFunction
where
Self: From<Self::Function>,
{
/// 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<Target = Self::Function> + Borrow<Self::Function>
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<Self::FunctionRef<'_>, TypeMismatch>;
}
/// Types that can be a reference to an opaque datum allowing comparison for equality and hashing
pub trait MaybeOpaque
where
Self: TryInto<Self::Opaque, Error = DatumConversionFailure<Self>>,
{
/// 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<Target = Self::Opaque> + Borrow<Self::Opaque>
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::Opaque, TypeMismatch> {
self.try_into().map_err(Into::into)
}
/// Try to interpret datum as reference to opaque datum
fn try_as_opaque(&self) -> Result<Self::OpaqueRef<'_>, TypeMismatch>;
}
/// Types that can be an UTF-8 text string
pub trait MaybeString<'c>
where
Self: From<String>,
Self: From<&'c str>,
Self: TryInto<String, Error = DatumConversionFailure<Self>>,
{
/// Try to convert datum into Unicode string
fn try_into_string(self) -> Result<String, TypeMismatch> {
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<Vec<u8>>,
Self: From<&'c [u8]>,
Self: TryInto<Vec<u8>, Error = DatumConversionFailure<Self>>,
{
/// Try to convert datum into binary (8 bit) string
fn try_into_binary(self) -> Result<Vec<u8>, 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<f64>,
Self: From<f32>,
{
/// Try to interpret datum as `f64`
fn try_as_f64(&self) -> Result<f64, TypeMismatch>;
/// Try to interpret datum as `f64`
fn try_as_f32(&self) -> Result<f32, TypeMismatch> {
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<usize, Error = TryFromIntError>,
Self: TryFrom<isize, Error = TryFromIntError>,
Self: TryFrom<u64, Error = TryFromIntError>,
Self: TryFrom<i64, Error = TryFromIntError>,
Self: TryFrom<u32, Error = TryFromIntError>,
Self: From<i32>,
Self: From<i16>,
Self: From<u16>,
Self: From<i8>,
Self: From<u8>,
{
/// Try to interpret datum as `i64`
fn try_as_i64(&self) -> Result<i64, TypeMismatch>;
/// Try to interpret datum as `u64`
fn try_as_u64(&self) -> Result<u64, TypeMismatch> {
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<i32, TypeMismatch> {
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<u32, TypeMismatch> {
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<i16, TypeMismatch> {
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<u16, TypeMismatch> {
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<i8, TypeMismatch> {
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<u8, TypeMismatch> {
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<usize, TypeMismatch> {
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<usize, TypeMismatch> {
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<<D as MaybeArray>::Element<'static>, MachineError>;
fn next(&mut self) -> Option<Result<<D as MaybeArray>::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<usize, MachineError>;
/// 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<Self::Element<'static>, 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<ArrayIter<'b, Self>, 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<Vec<Self::Element<'static>>, 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<Self::Value<'static>, 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<Self::Datum<'b, 'static>, MachineError>;
/// Create datum that is an array
fn new_array<'b, 'c, I>(&'b self, elements: I) -> Result<Self::Datum<'b, 'static>, MachineError>
where
I: IntoIterator<Item = Self::Datum<'b, 'c>>,
for<'d> <Self as Machine<'a>>::Datum<'b, 'static>:
MaybeArray<Element<'d> = <Self as Machine<'a>>::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<Self::Datum<'b, 'static>, MachineError>;
/// Create datum that is a string map
fn new_string_map<'b, 'c, 'd, I>(
&'b self,
entries: I,
) -> Result<Self::Datum<'b, 'static>, MachineError>
where
I: IntoIterator<Item = (&'d str, Self::Datum<'b, 'c>)>,
for<'e> <Self as Machine<'a>>::Datum<'b, 'static>:
MaybeStringMap<Value<'e> = <Self as Machine<'a>>::Datum<'b, 'e>>,
{
let datum = self.new_empty_string_map()?;
for (key, value) in entries {
datum.string_map_set(key, value)?;
}
Ok(datum)
}
}