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
//! 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 [`DatumViewError`] 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 [`DatumConversionError`] 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).
//!
//! 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, DatumViewError>;
}

/// 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<'_>, DatumViewError>;
}

/// 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 = DatumConversionError<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, DatumConversionError<Self>> {
        self.try_into()
    }
    /// Try to interpret datum as reference to opaque datum
    fn try_as_opaque(&self) -> Result<Self::OpaqueRef<'_>, DatumViewError>;
}

/// 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 = DatumConversionError<Self>>,
{
    /// Try to convert datum into Unicode string
    fn try_into_string(self) -> Result<String, DatumConversionError<Self>> {
        self.try_into()
    }
    /// Try to interpret datum as Unicode string
    fn try_as_str(&self) -> Result<&str, DatumViewError>;
}

/// 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 = DatumConversionError<Self>>,
{
    /// Try to convert datum into binary (8 bit) string
    fn try_into_binary(self) -> Result<Vec<u8>, DatumConversionError<Self>> {
        self.try_into()
    }
    /// Try to interpret datum as binary (8 bit) string
    fn try_as_bin(&self) -> Result<&[u8], DatumViewError>;
}

/// 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, DatumViewError>;
    /// Try to interpret datum as `f64`
    fn try_as_f32(&self) -> Result<f32, DatumViewError> {
        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, DatumViewError>;
    /// Try to interpret datum as `u64`
    fn try_as_u64(&self) -> Result<u64, DatumViewError> {
        self.try_as_i64().and_then(|x| {
            x.try_into()
                .map_err(|_| DatumViewError::new("integer value does not fit into u64"))
        })
    }
    /// Try to interpret datum as `i32`
    fn try_as_i32(&self) -> Result<i32, DatumViewError> {
        self.try_as_i64().and_then(|x| {
            x.try_into()
                .map_err(|_| DatumViewError::new("integer value does not fit into i32"))
        })
    }
    /// Try to interpret datum as `u32`
    fn try_as_u32(&self) -> Result<u32, DatumViewError> {
        self.try_as_i64().and_then(|x| {
            x.try_into()
                .map_err(|_| DatumViewError::new("integer value does not fit into u32"))
        })
    }
    /// Try to interpret datum as `i16`
    fn try_as_i16(&self) -> Result<i16, DatumViewError> {
        self.try_as_i64().and_then(|x| {
            x.try_into()
                .map_err(|_| DatumViewError::new("integer value does not fit into i16"))
        })
    }
    /// Try to interpret datum as `u16`
    fn try_as_u16(&self) -> Result<u16, DatumViewError> {
        self.try_as_i64().and_then(|x| {
            x.try_into()
                .map_err(|_| DatumViewError::new("integer value does not fit into u16"))
        })
    }
    /// Try to interpret datum as `i8`
    fn try_as_i8(&self) -> Result<i8, DatumViewError> {
        self.try_as_i64().and_then(|x| {
            x.try_into()
                .map_err(|_| DatumViewError::new("integer value does not fit into i8"))
        })
    }
    /// Try to interpret datum as `u8`
    fn try_as_u8(&self) -> Result<u8, DatumViewError> {
        self.try_as_i64().and_then(|x| {
            x.try_into()
                .map_err(|_| DatumViewError::new("integer value does not fit into u8"))
        })
    }
    /// Try to interpret datum as `isize`
    fn try_as_isize(&self) -> Result<usize, DatumViewError> {
        self.try_as_i64().and_then(|x| {
            x.try_into()
                .map_err(|_| DatumViewError::new("integer value does not fit into isize"))
        })
    }
    /// Try to interpret datum as `usize`
    fn try_as_usize(&self) -> Result<usize, DatumViewError> {
        self.try_as_i64().and_then(|x| {
            x.try_into()
                .map_err(|_| DatumViewError::new("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<(), DatumViewError>;
    /// 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 {
                kind: MachineErrorKind::Data,
                message: Some(format!(
                    "array length {} exceeded maximum length {}",
                    len, maxlen
                )),
                ..Default::default()
            })?;
        }
        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<(), DatumViewError>;
    /// 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)
    }
}