spore_vm/val/
mod.rs

1//! Contains representation of values within the Spore machine. Each type has differing lifetime and
2//! safety guarantees.
3mod bytecode;
4pub(crate) mod custom;
5mod formatter;
6mod id;
7mod native_function;
8mod protected_val;
9mod struct_val;
10mod symbol;
11mod unsafe_val;
12
13use std::marker::PhantomData;
14
15pub use bytecode::{ByteCode, Instruction};
16pub use custom::{CustomType, CustomVal, CustomValError, CustomValMut, CustomValRef};
17pub use formatter::ValFormatter;
18pub use id::ValId;
19pub use native_function::{NativeFunction, NativeFunctionContext, ValBuilder};
20pub use protected_val::ProtectedVal;
21pub use struct_val::StructVal;
22pub use symbol::Symbol;
23pub use unsafe_val::UnsafeVal;
24
25use crate::Vm;
26
27/// A container for a list.
28pub type ListVal = Vec<UnsafeVal>;
29
30/// Contains a [Val] from the [Vm].
31#[repr(transparent)]
32#[derive(Copy, Clone, Debug, Default)]
33pub struct Val<'a> {
34    inner: UnsafeVal,
35    _lifetime: PhantomData<&'a ()>,
36}
37
38impl Val<'static> {
39    /// Create a new `void` value.
40    pub fn new_void() -> Val<'static> {
41        // Unsafe OK: Void does not have a lifetime.
42        unsafe { Self::from_unsafe_val(().into()) }
43    }
44
45    /// Create a new `bool` value.
46    pub fn new_bool(x: bool) -> Val<'static> {
47        // Unsafe OK: Void does not have a lifetime.
48        unsafe { Self::from_unsafe_val(x.into()) }
49    }
50
51    /// Create a new `int` value.
52    pub fn new_int(x: i64) -> Val<'static> {
53        // Unsafe OK: Void does not have a lifetime.
54        unsafe { Self::from_unsafe_val(x.into()) }
55    }
56
57    /// Create a new `float` value.
58    pub fn new_float(x: f64) -> Val<'static> {
59        // Unsafe OK: Void does not have a lifetime.
60        unsafe { Self::from_unsafe_val(x.into()) }
61    }
62}
63
64impl<'a> Val<'a> {
65    /// Returns `true` if `self` is a void value.
66    pub fn is_void(self) -> bool {
67        matches!(self.inner, UnsafeVal::Void)
68    }
69
70    /// Returns `true` if `self` is *not* `false` or `void`. Only `false` and `void` will return
71    /// `false.`
72    pub fn is_truthy(self) -> bool {
73        self.inner.is_truthy()
74    }
75
76    /// Get the underlying `bool` value or `Err(self)` if `self` is not a bool.
77    pub fn try_bool(self) -> Result<bool, Self> {
78        match self.inner {
79            UnsafeVal::Bool(x) => Ok(x),
80            _ => Err(self),
81        }
82    }
83
84    /// Get the underlying `int` value or `Err(self)` if `self` is not an int.
85    pub fn try_int(self) -> Result<i64, Self> {
86        match self.inner {
87            UnsafeVal::Int(x) => Ok(x),
88            _ => Err(self),
89        }
90    }
91
92    /// Get the underlying `float` value or `Err(self)` if `self` is not a float.
93    pub fn try_float(self) -> Result<f64, Self> {
94        match self.inner {
95            UnsafeVal::Float(x) => Ok(x),
96            _ => Err(self),
97        }
98    }
99
100    /// Get the underlying Symbol or `Err<Val>` if `self` is not a string.
101    pub fn try_symbol(self) -> Result<Symbol, Self> {
102        match self.inner {
103            UnsafeVal::Symbol(sym) => Ok(sym),
104            _ => Err(self),
105        }
106    }
107
108    /// Get the underlying [&str] or `Err<Val>` if `self` is not a string.
109    pub fn try_str(self, vm: &Vm) -> Result<&str, Self> {
110        match self.inner {
111            UnsafeVal::String(id) => Ok(vm.objects.get_str(id)),
112            _ => Err(self),
113        }
114    }
115
116    /// Get the underlying list or `Err<Val>` if `self` is not a list.
117    pub fn try_list(self, vm: &Vm) -> Result<&[Val], Val<'a>> {
118        match self.inner {
119            UnsafeVal::List(id) => {
120                let list = vm.objects.get_list(id);
121                // The VM is borrowed so it is ensured to not garbage collect.
122                Ok(unsafe { Val::from_unsafe_val_slice(list.as_slice()) })
123            }
124            _ => Err(self),
125        }
126    }
127
128    /// Returns `true` if `self` is a struct.
129    pub fn is_struct(self) -> bool {
130        matches!(self.inner, UnsafeVal::Struct(_))
131    }
132
133    /// Get the underlying struct or `Err<Val>` if `self` is not a struct.
134    pub fn try_struct(self, vm: &Vm) -> Result<&StructVal, Val<'a>> {
135        match self.inner {
136            UnsafeVal::Struct(id) => {
137                let strct = vm.objects.get_struct(id);
138                // The VM is borrowed so it is ensured to not garbage collect.
139                Ok(strct)
140            }
141            _ => Err(self),
142        }
143    }
144
145    /// Get the underlying struct or `Err<Val>` if `self` is not a struct.
146    pub fn try_struct_get(self, vm: &'a Vm, name: &str) -> Result<Option<Val<'a>>, Val<'a>> {
147        match self.inner {
148            UnsafeVal::Struct(id) => {
149                let strct = vm.objects.get_struct(id);
150                let sym = vm.get_symbol(name).ok_or(self)?;
151                let maybe_v = strct.get(sym).map(|v| unsafe { Val::from_unsafe_val(v) });
152                Ok(maybe_v)
153            }
154            _ => Err(self),
155        }
156    }
157
158    /// Get the underlying struct or `Err<Val>` if `self` is not a struct.
159    ///
160    /// # Safety
161    /// Provides raw access to the struct val.
162    pub unsafe fn try_unsafe_struct_mut(self, vm: &mut Vm) -> Result<&mut StructVal, Val<'a>> {
163        match self.inner {
164            UnsafeVal::Struct(id) => {
165                let strct = vm.objects.get_struct_mut(id);
166                Ok(strct)
167            }
168            _ => Err(self),
169        }
170    }
171
172    /// Get the [Val] that the mutable box is pointing to or `Err<Val>` if `self` is not a mutable
173    /// box.
174    pub fn try_mutable_box_ref(self, vm: &Vm) -> Result<Val, Val<'a>> {
175        match self.inner {
176            UnsafeVal::MutableBox(id) => {
177                let mutable_box = vm.objects.get_mutable_box(id);
178                // The VM is borrowed so it is ensured to not garbage collect.
179                Ok(unsafe { Val::from_unsafe_val(*mutable_box) })
180            }
181            _ => Err(self),
182        }
183    }
184
185    /// Get the display name of the type held by `self`.
186    pub fn type_name(self) -> &'static str {
187        self.inner.type_name()
188    }
189
190    /// Get a formatter for the underlying type.
191    pub fn formatted(self, vm: &Vm) -> impl '_ + std::fmt::Display {
192        self.inner.formatted(vm)
193    }
194
195    /// Get a formatter for the underlying type.
196    ///
197    /// Compared to [Self::formatted], `strings` are displayed with quotes around them.
198    pub fn format_quoted(self, vm: &Vm) -> impl '_ + std::fmt::Display {
199        self.inner.format_quoted(vm)
200    }
201
202    /// Returns `true` if a custom value is held.
203    pub fn is_custom(&self) -> bool {
204        matches!(self.inner, UnsafeVal::Custom(_))
205    }
206
207    /// Returns the value as a custom type of `T` or [None] if [Self] is not of the given custom
208    /// value.
209    pub fn try_custom<T: CustomType>(&self, vm: &'a Vm) -> Result<CustomValRef<T>, CustomValError> {
210        match self.inner {
211            UnsafeVal::Custom(id) => vm.objects.get_custom(id).get(),
212            _ => Err(CustomValError::WrongType {
213                expected: std::any::type_name::<T>(),
214                actual: self.type_name(),
215            }),
216        }
217    }
218
219    /// Returns the value as a custom type of `T` or [None] if [Self] is not of the given custom
220    /// value.
221    pub fn try_custom_mut<T: CustomType>(
222        &self,
223        vm: &'a Vm,
224    ) -> Result<CustomValMut<T>, CustomValError> {
225        match self.inner {
226            UnsafeVal::Custom(id) => vm.objects.get_custom(id).get_mut(),
227            _ => Err(CustomValError::WrongType {
228                expected: std::any::type_name::<T>(),
229                actual: self.type_name(),
230            }),
231        }
232    }
233}
234
235impl<'a> Val<'a> {
236    /// Create a new [Val] from an [UnsafeVal].
237    ///
238    /// # Safety
239    /// [UnsafeVal] must be a valid value. It is possible to [UnsafeVal] to become invalidated
240    /// through VM garbage collection.
241    pub unsafe fn from_unsafe_val(v: UnsafeVal) -> Val<'a> {
242        Val {
243            inner: v,
244            _lifetime: PhantomData,
245        }
246    }
247
248    /// Return the underlying [UnsafeVal] representation.
249    ///
250    /// # Safety
251    /// This is unsafe as it removes the lifetime offered by [Val].
252    pub unsafe fn as_unsafe_val(self) -> UnsafeVal {
253        self.inner
254    }
255
256    /// Extend the lifetime of [Val] to `'static`.
257    ///
258    /// # Safety
259    /// This is unsafe as the caller must be certain that [Val] will not be garbage collected.
260    pub unsafe fn as_static(self) -> Val<'static> {
261        Val {
262            inner: self.inner,
263            _lifetime: PhantomData,
264        }
265    }
266
267    /// Convert a slice of [UnsafeVal] to a slice of [Val].
268    ///
269    /// # Safety
270    /// All [UnsafeVal] within `slice` must be valid. It is possible for [UnsafeVal] to become
271    /// invalidated through VM garbage collection.
272    pub unsafe fn from_unsafe_val_slice(v: &'a [UnsafeVal]) -> &'a [Val<'a>] {
273        // This is always true as Val is repr(transparent) with an [UnsafeVal] under the hood.
274        debug_assert_eq!(
275            std::mem::size_of::<UnsafeVal>(),
276            std::mem::size_of::<Val<'a>>()
277        );
278        std::mem::transmute(v)
279    }
280
281    /// Convert a slice of [Val] to a slice of [UnsafeVal].
282    ///
283    /// # Safety
284    /// This is unsafe as the caller must be certain that [Val] will not be garbage collected.
285    pub unsafe fn as_unsafe_val_slice<'b>(slice: &'b [Val<'a>]) -> &'b [UnsafeVal] {
286        // This is always true as Val is repr(transparent) with an [UnsafeVal] under the hood.
287        debug_assert_eq!(
288            std::mem::size_of::<UnsafeVal>(),
289            std::mem::size_of::<Val<'a>>()
290        );
291        std::mem::transmute(slice)
292    }
293}
294
295macro_rules! to_val_impl {
296    ($rust_type:ty) => {
297        /// Convert from a simple static Rust value into a [Val].
298        impl From<$rust_type> for Val<'static> {
299            fn from(v: $rust_type) -> Val<'static> {
300                unsafe { Self::from_unsafe_val(v.into()) }
301            }
302        }
303    };
304}
305
306to_val_impl!(());
307to_val_impl!(bool);
308to_val_impl!(i64);
309to_val_impl!(f64);
310
311#[cfg(test)]
312mod tests {
313    use super::*;
314
315    #[test]
316    fn static_values_can_be_created_outside_of_vm() {
317        assert_eq!(
318            Val::new_void().formatted(&Vm::default()).to_string(),
319            "<void>"
320        );
321        assert_eq!(
322            Val::new_bool(true).formatted(&Vm::default()).to_string(),
323            "true"
324        );
325        assert_eq!(Val::new_int(1).formatted(&Vm::default()).to_string(), "1");
326        assert_eq!(
327            Val::new_float(2.5).formatted(&Vm::default()).to_string(),
328            "2.5"
329        );
330    }
331}