ruwren/
lib.rs

1//! We expose the Wren API in a Rust-y way
2pub extern crate wren_sys;
3
4use foreign_v2::ForeignItem;
5use std::any::{Any, TypeId};
6use std::cell::RefCell;
7use std::collections::HashMap;
8use std::rc::{Rc, Weak};
9use std::sync::mpsc::{channel, Receiver, Sender};
10use wren_sys::{wrenGetUserData, WrenConfiguration, WrenHandle, WrenVM};
11
12mod module_loader;
13pub use module_loader::{BasicFileLoader, NullLoader};
14
15pub mod foreign_v1;
16pub mod foreign_v2;
17
18use std::{any, ffi, marker, mem, os::raw};
19
20mod runtime;
21#[cfg(test)]
22mod tests;
23#[cfg(feature = "derive")]
24pub use ruwren_macros::*;
25
26#[derive(Debug)]
27/// Directly internally to report errors
28pub enum WrenError {
29    Compile(String, i32, String),
30    Runtime(String),
31    StackTrace(String, i32, String),
32}
33
34#[derive(Debug, Clone)]
35/// Possible errors for a Wren script
36pub enum VMError {
37    Compile {
38        module: String,
39        line: i32,
40        error: String,
41    },
42    Runtime {
43        error: String,
44        frames: Vec<VMStackFrameError>,
45    },
46}
47
48#[derive(Debug, Clone)]
49pub struct VMStackFrameError {
50    pub module: String,
51    pub line: i32,
52    pub function: String,
53}
54
55#[cfg(not(target_arch = "wasm32"))]
56pub fn handle_panic<F, O>(func: F) -> Result<O, Box<dyn Any + Send>>
57where
58    F: FnOnce() -> O + std::panic::UnwindSafe,
59{
60    std::panic::catch_unwind(func)
61}
62
63#[cfg(target_arch = "wasm32")]
64pub fn handle_panic<F, O: 'static>(func: F) -> Result<O, Box<dyn Any + Send>>
65where
66    F: FnOnce() -> O + std::panic::UnwindSafe,
67{
68    match std::panic::catch_unwind(func) {
69        Ok(o) => Ok(o),
70        _ => unreachable!("non-unwinding platforms (like WASM) can't catch unwinds, so don't panic unless absolutely necessary"),
71    }
72}
73
74impl std::fmt::Display for VMError {
75    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
76        match self {
77            VMError::Compile {
78                module,
79                line,
80                error,
81            } => write!(fmt, "Compile Error ({}:{}): {}", module, line, error),
82            VMError::Runtime { error, frames } => {
83                writeln!(fmt, "Runtime Error: {}", error)?;
84                for frame in frames {
85                    if frame.function.is_empty() {
86                        writeln!(fmt, "\tin {}:{}: <constructor>", frame.module, frame.line)?;
87                    } else {
88                        writeln!(
89                            fmt,
90                            "\tin {}:{}: {}",
91                            frame.module, frame.line, frame.function
92                        )?;
93                    }
94                }
95                Ok(())
96            }
97        }
98    }
99}
100
101impl std::error::Error for VMError {}
102
103/// A handle to a Wren object
104#[derive(Debug, PartialEq, Eq)]
105pub struct Handle<'a> {
106    handle: *mut WrenHandle,
107    wvm: *mut WrenVM,
108    vm: marker::PhantomData<&'a VM>,
109}
110
111impl<'a> Drop for Handle<'a> {
112    fn drop(&mut self) {
113        unsafe {
114            wren_sys::wrenReleaseHandle(self.wvm, self.handle);
115        }
116    }
117}
118
119/// A handle to a Wren method call
120#[derive(Debug, PartialEq, Eq)]
121pub struct FunctionHandle<'a>(Handle<'a>);
122
123/// Simulates a module structure for foreign functions
124#[derive(Debug, Clone, Default)]
125pub struct ModuleLibrary {
126    modules: HashMap<String, Module>,
127}
128
129impl ModuleLibrary {
130    /// Creates a new library
131    pub fn new() -> ModuleLibrary {
132        ModuleLibrary {
133            modules: HashMap::new(),
134        }
135    }
136
137    /// Adds a [`Module`] with a specified `name`
138    pub fn module<N: Into<String>>(&mut self, name: N, modl: Module) {
139        let module_name = name.into();
140        if let Some(module) = self.modules.get_mut(&module_name) {
141            module.classes.extend(modl.classes);
142        } else {
143            self.modules.insert(module_name, modl);
144        }
145    }
146
147    /// Attempts to find a [`RuntimeClass`] given a `module` name and a `class` name
148    fn get_foreign_class<M: AsRef<str>, C: AsRef<str>>(
149        &self, module: M, class: C,
150    ) -> Option<&RuntimeClass> {
151        self.modules
152            .get(module.as_ref())
153            .and_then(|md| md.classes.get(class.as_ref()))
154    }
155}
156
157#[derive(Debug, Clone)]
158/// Represetnation of classes at runtime
159struct RuntimeClass {
160    construct: extern "C" fn(*mut WrenVM),
161    destruct: extern "C" fn(*mut ffi::c_void),
162    methods: ClassObjectPointers,
163
164    // Use for "loading in" appropriate objects
165    type_id: any::TypeId,
166}
167
168#[derive(Debug, Clone, Default)]
169/// A container for `RuntimeClass` structs
170pub struct Module {
171    classes: HashMap<String, RuntimeClass>,
172}
173
174#[derive(Debug, Clone)]
175/// List of [`MethodPointer`]s that make up the methods of a ['RuntimeClass`]
176pub struct ClassObjectPointers {
177    pub function_pointers: Vec<MethodPointer>,
178}
179
180#[derive(Debug, Clone)]
181pub struct MethodPointer {
182    pub is_static: bool,
183    pub signature: FunctionSignature,
184    pub pointer: unsafe extern "C" fn(*mut WrenVM),
185}
186
187impl Module {
188    /// Create a new module
189    pub fn new() -> Module {
190        Module {
191            classes: HashMap::new(),
192        }
193    }
194
195    /// Add class `C` to this module with a `name`
196    pub fn class<C: 'static + ClassObject, S: Into<String>>(&mut self, name: S) -> &mut Self {
197        let cp = C::generate_pointers();
198        let init = C::initialize_pointer();
199        let deinit = C::finalize_pointer();
200        self.classes.insert(
201            name.into(),
202            RuntimeClass {
203                construct: init,
204                destruct: deinit,
205                methods: cp,
206                type_id: any::TypeId::of::<C>(),
207            },
208        );
209        self
210    }
211}
212
213/// Initialize function for Wren classes
214pub trait Class {
215    fn initialize(_: &VM) -> Self
216    where
217        Self: Sized;
218}
219
220/// Indicates a "real" Wren class, and must be implemented to be added to a [`Module`]
221pub trait ClassObject: Class {
222    fn initialize_pointer() -> extern "C" fn(*mut WrenVM)
223    where
224        Self: Sized;
225    fn finalize_pointer() -> extern "C" fn(*mut ffi::c_void)
226    where
227        Self: Sized;
228    fn generate_pointers() -> ClassObjectPointers
229    where
230        Self: Sized;
231}
232
233#[derive(Debug, Copy, Clone)]
234/// Indicates a "foreign object" to Wren
235pub struct ForeignObject<T> {
236    pub object: *mut T,
237    pub type_id: any::TypeId,
238}
239
240pub fn type_name_of<T>(_: &T) -> &'static str {
241    any::type_name::<T>()
242}
243
244/// Enables one to enable module loading for Wren
245pub trait ModuleScriptLoader {
246    /// Takes a desired module `name`
247    ///
248    /// ### Returns
249    /// - `Some(String)` containing the Wren source if the module exists
250    /// - `None` if not
251    fn load_script(&mut self, name: String) -> Option<String>;
252}
253
254impl<T> ModuleScriptLoader for T
255where
256    T: FnMut(String) -> Option<String>,
257{
258    fn load_script(&mut self, name: String) -> Option<String> {
259        (*self)(name)
260    }
261}
262
263type Evm = Rc<RefCell<VM>>;
264
265/// Sends strings for printing to an output
266pub trait Printer {
267    /// Called whenever a string is to be sent to output
268    fn print(&mut self, s: String);
269}
270
271impl<T> Printer for T
272where
273    T: FnMut(String),
274{
275    fn print(&mut self, s: String) {
276        (*self)(s)
277    }
278}
279
280struct PrintlnPrinter;
281impl Printer for PrintlnPrinter {
282    fn print(&mut self, s: String) {
283        print!("{}", s);
284    }
285}
286
287type ClassMap = RefCell<HashMap<TypeId, Rc<RefCell<Box<dyn Any>>>>>;
288
289#[derive(Debug)]
290pub struct VM {
291    pub vm: *mut WrenVM,
292    classes_v2: ClassMap,
293    error_recv: Receiver<WrenError>,
294}
295
296/// A mostly internal class that is exposed so that some externally generated code can access it.
297pub struct UserData {
298    error_channel: Sender<WrenError>,
299    printer: Box<dyn Printer>,
300    pub vm: Weak<RefCell<VM>>, // is used a *lot* by externally generated code.
301    library: Option<ModuleLibrary>,
302    loader: Box<dyn ModuleScriptLoader>,
303}
304
305/// Represents Wren slot types
306#[derive(Debug, Clone, Copy, PartialEq, Eq)]
307pub enum SlotType {
308    Num,
309    Bool,
310    List,
311    Map,
312    Null,
313    String,
314    Foreign,
315    Unknown,
316}
317
318pub type SlotId = usize;
319
320/// Represents Wren function signatures
321#[derive(Debug, Clone)]
322pub enum FunctionSignature {
323    Function { name: String, arity: usize },
324    Getter(String),
325    Setter(String),
326}
327
328impl FunctionSignature {
329    pub fn new_function<N: Into<String>>(name: N, arity: usize) -> FunctionSignature {
330        FunctionSignature::Function {
331            name: name.into(),
332            arity,
333        }
334    }
335
336    pub fn new_getter<N: Into<String>>(name: N) -> FunctionSignature {
337        FunctionSignature::Getter(name.into())
338    }
339
340    pub fn new_setter<N: Into<String>>(name: N) -> FunctionSignature {
341        FunctionSignature::Setter(name.into())
342    }
343
344    fn as_wren_string(&self) -> String {
345        match self {
346            FunctionSignature::Function { name, arity } => {
347                format!("{}({})", name, vec!["_".to_string(); *arity].join(","))
348            }
349            FunctionSignature::Getter(name) => name.clone(),
350            FunctionSignature::Setter(name) => format!("{}=(_)", name),
351        }
352    }
353
354    /// Get number of arguments this function signature would require
355    pub fn arity(&self) -> usize {
356        match self {
357            FunctionSignature::Function { arity, .. } => *arity,
358            FunctionSignature::Getter(_) => 0,
359            FunctionSignature::Setter(_) => 1,
360        }
361    }
362}
363
364/// High-level wrapper around a Wren VM
365#[derive(Debug, Clone)]
366pub struct VMWrapper(Evm);
367
368impl VMWrapper {
369    /// Calls a given function from its signature
370    pub fn call(&self, signature: FunctionSignature) -> Result<(), VMError> {
371        let handle = self.make_call_handle(signature);
372        self.call_handle(&handle)
373    }
374
375    /// Calls a given function from its handle
376    pub fn call_handle(&self, handle: &FunctionHandle) -> Result<(), VMError> {
377        let vm = self.0.borrow();
378        match unsafe { wren_sys::wrenCall(vm.vm, handle.0.handle) } {
379            wren_sys::WrenInterpretResult_WREN_RESULT_SUCCESS => Ok(()),
380            wren_sys::WrenInterpretResult_WREN_RESULT_COMPILE_ERROR => {
381                unreachable!("wrenCall doesn't compile anything")
382            }
383            wren_sys::WrenInterpretResult_WREN_RESULT_RUNTIME_ERROR => {
384                let mut error = "".to_string();
385                let mut frames = vec![];
386                while let Ok(err) = vm.error_recv.try_recv() {
387                    match err {
388                        WrenError::Runtime(msg) => {
389                            error = msg;
390                        }
391                        WrenError::StackTrace(module, line, msg) => {
392                            frames.push(VMStackFrameError {
393                                module,
394                                line,
395                                function: msg,
396                            });
397                        }
398                        _ => unreachable!(),
399                    }
400                }
401                Err(VMError::Runtime { error, frames })
402            }
403            _ => unreachable!(),
404        }
405    }
406
407    /// Interprets a given string as Wren code
408    pub fn interpret<M: AsRef<str>, C: AsRef<str>>(
409        &self, module: M, code: C,
410    ) -> Result<(), VMError> {
411        let module = ffi::CString::new(module.as_ref()).expect("module name conversion failed");
412        let code = ffi::CString::new(code.as_ref()).expect("code conversion failed");
413        let vm = self.0.borrow();
414        match unsafe { wren_sys::wrenInterpret(vm.vm, module.as_ptr(), code.as_ptr()) } {
415            wren_sys::WrenInterpretResult_WREN_RESULT_SUCCESS => Ok(()),
416            wren_sys::WrenInterpretResult_WREN_RESULT_COMPILE_ERROR => {
417                match vm.error_recv.try_recv() {
418                    Ok(WrenError::Compile(module, line, msg)) => Err(VMError::Compile {
419                        module,
420                        line,
421                        error: msg,
422                    }),
423                    _ => unreachable!(),
424                }
425            }
426            wren_sys::WrenInterpretResult_WREN_RESULT_RUNTIME_ERROR => {
427                let mut error = "".to_string();
428                let mut frames = vec![];
429                while let Ok(err) = vm.error_recv.try_recv() {
430                    match err {
431                        WrenError::Runtime(msg) => {
432                            error = msg;
433                        }
434                        WrenError::StackTrace(module, line, msg) => {
435                            frames.push(VMStackFrameError {
436                                module,
437                                line,
438                                function: msg,
439                            });
440                        }
441                        _ => unreachable!(),
442                    }
443                }
444                Err(VMError::Runtime { error, frames })
445            }
446            _ => unreachable!(),
447        }
448    }
449
450    /// Allows access to the internal VM wrapper object
451    pub fn execute<T, F>(&self, f: F) -> T
452    where
453        F: FnOnce(&VM) -> T,
454    {
455        f(&self.0.borrow())
456    }
457
458    /// Gets a handle to a value in a certain slot
459    pub fn get_slot_handle(&self, slot: SlotId) -> Rc<Handle> {
460        Rc::new(Handle {
461            handle: unsafe { wren_sys::wrenGetSlotHandle(self.0.borrow().vm, slot as raw::c_int) },
462            wvm: self.0.borrow().vm,
463            vm: marker::PhantomData,
464        })
465    }
466
467    /// Sets the value in a certain slot to the value of a handle
468    pub fn set_slot_handle(&self, slot: SlotId, handle: &Handle) {
469        unsafe {
470            wren_sys::wrenSetSlotHandle(self.0.borrow().vm, slot as raw::c_int, handle.handle)
471        }
472    }
473
474    /// Create a callable handle, that can be used with [`call_handle`](VMWrapper::call_handle)
475    pub fn make_call_handle(&self, signature: FunctionSignature) -> Rc<FunctionHandle> {
476        VM::make_call_handle(self.0.borrow().vm, signature)
477    }
478
479    /// Instruct Wren to start a garbage collection cycle
480    pub fn collect_garbage(&self) {
481        unsafe { wren_sys::wrenCollectGarbage(self.0.borrow().vm) }
482    }
483}
484
485/// Allows for the customization of a Wren VM
486pub struct VMConfig {
487    printer: Box<dyn Printer>,
488    script_loader: Box<dyn ModuleScriptLoader>,
489    library: Option<ModuleLibrary>,
490    initial_heap_size: usize,
491    min_heap_size: usize,
492    heap_growth_percent: usize,
493
494    /// Enables @module syntax to mean `module` loaded relative to current module
495    enable_relative_import: bool,
496}
497
498impl Default for VMConfig {
499    fn default() -> Self {
500        Self::new()
501    }
502}
503
504impl VMConfig {
505    pub fn new() -> VMConfig {
506        VMConfig {
507            printer: Box::new(PrintlnPrinter),
508            script_loader: Box::new(NullLoader),
509            library: None,
510            initial_heap_size: 1024 * 1024 * 10,
511            min_heap_size: 1024 * 1024,
512            heap_growth_percent: 50,
513            enable_relative_import: false,
514        }
515    }
516
517    pub fn printer<P: 'static + Printer>(mut self, p: P) -> Self {
518        self.printer = Box::new(p);
519        self
520    }
521
522    pub fn script_loader<L: 'static + ModuleScriptLoader>(mut self, l: L) -> Self {
523        self.script_loader = Box::new(l);
524        self
525    }
526
527    pub fn library(mut self, l: &ModuleLibrary) -> Self {
528        self.library = Some(l.clone());
529        self
530    }
531
532    pub fn no_library(mut self) -> Self {
533        self.library = None;
534        self
535    }
536
537    pub fn initial_heap_size(mut self, ihs: usize) -> Self {
538        self.initial_heap_size = ihs;
539        self
540    }
541
542    pub fn min_heap_size(mut self, mhs: usize) -> Self {
543        self.min_heap_size = mhs;
544        self
545    }
546
547    pub fn heap_growth_percent(mut self, hgp: usize) -> Self {
548        self.heap_growth_percent = hgp;
549        self
550    }
551
552    pub fn enable_relative_import(mut self, eri: bool) -> Self {
553        self.enable_relative_import = eri;
554        self
555    }
556
557    pub fn build(self) -> VMWrapper {
558        let (etx, erx) = channel();
559
560        // Have an uninitialized VM...
561        let wvm = Rc::new(RefCell::new(VM {
562            vm: std::ptr::null_mut(),
563            classes_v2: RefCell::new(HashMap::new()),
564            error_recv: erx,
565        }));
566
567        let vm_config = Box::into_raw(Box::new(UserData {
568            error_channel: etx,
569            printer: self.printer,
570            vm: Rc::downgrade(&wvm),
571            loader: self.script_loader,
572            library: self.library,
573        }));
574
575        // Configure the Wren side of things
576        let mut config = unsafe {
577            let mut uconfig = mem::MaybeUninit::<WrenConfiguration>::zeroed();
578            wren_sys::wrenInitConfiguration(uconfig.as_mut_ptr());
579            let mut config = uconfig.assume_init();
580            config.errorFn = Some(runtime::wren_error);
581            config.writeFn = Some(runtime::wren_print);
582            config.reallocateFn = Some(runtime::wren_realloc);
583            config.bindForeignMethodFn = Some(runtime::wren_bind_foreign_method);
584            config.bindForeignClassFn = Some(runtime::wren_bind_foreign_class);
585            config.loadModuleFn = Some(runtime::wren_load_module);
586            config.resolveModuleFn = if self.enable_relative_import {
587                Some(runtime::wren_canonicalize)
588            } else {
589                None
590            };
591            config.initialHeapSize = self.initial_heap_size;
592            config.minHeapSize = self.min_heap_size;
593            config.heapGrowthPercent = self.heap_growth_percent as raw::c_int;
594            config.userData = vm_config as *mut ffi::c_void;
595            config
596        };
597
598        let vm = unsafe { wren_sys::wrenNewVM(&mut config) };
599        wvm.borrow_mut().vm = vm;
600        VMWrapper(wvm)
601    }
602}
603
604#[derive(Debug, Clone, Copy, PartialEq, Eq)]
605/// Errors that can happen when sending a foreign object to Wren
606pub enum ForeignSendError {
607    /// No ['RuntimeClass'] exists in the specfied module with the given name
608    NoForeignClass,
609    /// No Wrne declaration of the foreign class was made
610    NoWrenClass,
611    /// Ran out of memory to allocate the class
612    NoMemory,
613    /// The type of the ['RuntimeClass`] [`ClassObject`] differes from the given object
614    ClassMismatch,
615}
616
617impl std::fmt::Display for ForeignSendError {
618    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
619        match self {
620            ForeignSendError::NoForeignClass => write!(fmt, "no foreign class"),
621            ForeignSendError::NoWrenClass => write!(fmt, "no Wren class"),
622            ForeignSendError::NoMemory => write!(fmt, "unable to allocate memory"),
623            ForeignSendError::ClassMismatch => write!(fmt, "class mismatch"),
624        }
625    }
626}
627
628impl std::error::Error for ForeignSendError {}
629
630impl VM {
631    // Slot and Handle API
632    pub fn ensure_slots(&self, count: usize) {
633        unsafe { wren_sys::wrenEnsureSlots(self.vm, count as raw::c_int) }
634    }
635
636    pub fn get_slot_count(&self) -> usize {
637        unsafe { wren_sys::wrenGetSlotCount(self.vm) as usize }
638    }
639
640    pub fn set_slot_bool(&self, slot: SlotId, val: bool) {
641        self.ensure_slots(slot + 1);
642        unsafe { wren_sys::wrenSetSlotBool(self.vm, slot as raw::c_int, val) }
643    }
644
645    pub fn set_slot_double(&self, slot: SlotId, val: f64) {
646        self.ensure_slots(slot + 1);
647        unsafe { wren_sys::wrenSetSlotDouble(self.vm, slot as raw::c_int, val) }
648    }
649
650    pub fn set_slot_null(&self, slot: SlotId) {
651        self.ensure_slots(slot + 1);
652        unsafe { wren_sys::wrenSetSlotNull(self.vm, slot as raw::c_int) }
653    }
654
655    pub fn set_slot_bytes(&self, slot: SlotId, bytes: &[u8]) {
656        self.ensure_slots(slot + 1);
657        unsafe {
658            wren_sys::wrenSetSlotBytes(
659                self.vm,
660                slot as raw::c_int,
661                bytes as *const _ as *const raw::c_char,
662                bytes.len(),
663            );
664        }
665    }
666
667    pub fn set_slot_string<S: AsRef<str>>(&self, slot: SlotId, string: S) {
668        self.ensure_slots(slot + 1);
669        let string = string.as_ref();
670        unsafe {
671            wren_sys::wrenSetSlotBytes(
672                self.vm,
673                slot as raw::c_int,
674                string.as_ptr() as *const _,
675                string.len(),
676            );
677        }
678    }
679
680    pub fn get_slot_bool(&self, slot: SlotId) -> Option<bool> {
681        self.ensure_slots(slot + 1);
682        if self.get_slot_type(slot) != SlotType::Bool {
683            None
684        } else {
685            unsafe { Some(wren_sys::wrenGetSlotBool(self.vm, slot as raw::c_int)) }
686        }
687    }
688
689    pub fn get_slot_double(&self, slot: SlotId) -> Option<f64> {
690        self.ensure_slots(slot + 1);
691        if self.get_slot_type(slot) != SlotType::Num {
692            None
693        } else {
694            unsafe { Some(wren_sys::wrenGetSlotDouble(self.vm, slot as raw::c_int)) }
695        }
696    }
697
698    pub fn get_slot_bytes(&self, slot: SlotId) -> Option<Vec<u8>> {
699        self.ensure_slots(slot + 1);
700        if self.get_slot_type(slot) != SlotType::String {
701            None
702        } else {
703            let mut length = 0 as raw::c_int;
704            let ptr = unsafe {
705                wren_sys::wrenGetSlotBytes(self.vm, slot as raw::c_int, &mut length as *mut _)
706            };
707            let mut bytes = vec![];
708
709            // Do some pointer maths to get the vector. Hurrah!
710            for offset in 0..length {
711                unsafe { bytes.push(*ptr.offset(offset as isize) as u8) }
712            }
713
714            Some(bytes)
715        }
716    }
717
718    pub fn get_slot_string(&self, slot: SlotId) -> Option<String> {
719        self.ensure_slots(slot + 1);
720        if self.get_slot_type(slot) != SlotType::String {
721            None
722        } else {
723            let ptr = unsafe { wren_sys::wrenGetSlotString(self.vm, slot as raw::c_int) };
724
725            let cstr = unsafe { ffi::CStr::from_ptr(ptr) };
726
727            Some(cstr.to_string_lossy().to_string())
728        }
729    }
730
731    pub fn get_slot_type(&self, slot: SlotId) -> SlotType {
732        self.ensure_slots(slot + 1);
733        match unsafe { wren_sys::wrenGetSlotType(self.vm, slot as raw::c_int) } {
734            wren_sys::WrenType_WREN_TYPE_NUM => SlotType::Num,
735            wren_sys::WrenType_WREN_TYPE_BOOL => SlotType::Bool,
736            wren_sys::WrenType_WREN_TYPE_LIST => SlotType::List,
737            wren_sys::WrenType_WREN_TYPE_MAP => SlotType::Map,
738            wren_sys::WrenType_WREN_TYPE_NULL => SlotType::Null,
739            wren_sys::WrenType_WREN_TYPE_STRING => SlotType::String,
740            wren_sys::WrenType_WREN_TYPE_FOREIGN => SlotType::Foreign,
741            wren_sys::WrenType_WREN_TYPE_UNKNOWN => SlotType::Unknown,
742            _ => unreachable!(),
743        }
744    }
745
746    /// Returns Some(()) if the variable was found and stored in the given slot
747    ///
748    /// Returns None if the variable does not exist
749    pub fn get_variable<M: AsRef<str>, N: AsRef<str>>(
750        &self, module: M, name: N, slot: SlotId,
751    ) -> bool {
752        self.ensure_slots(slot + 1);
753        if !self.has_variable(&module, &name) {
754            return false;
755        }
756        let module = ffi::CString::new(module.as_ref()).expect("module name conversion failed");
757        let name = ffi::CString::new(name.as_ref()).expect("variable name conversion failed");
758        unsafe {
759            wren_sys::wrenGetVariable(self.vm, module.as_ptr(), name.as_ptr(), slot as raw::c_int)
760        }
761        true
762    }
763
764    pub fn has_variable<M: AsRef<str>, N: AsRef<str>>(&self, module: M, name: N) -> bool {
765        if !self.has_module(&module) {
766            return false;
767        }
768        let module = ffi::CString::new(module.as_ref()).expect("module name conversion failed");
769        let name = ffi::CString::new(name.as_ref()).expect("variable name conversion failed");
770        unsafe { wren_sys::wrenHasVariable(self.vm, module.as_ptr(), name.as_ptr()) }
771    }
772
773    pub fn has_module<M: AsRef<str>>(&self, module: M) -> bool {
774        let module = ffi::CString::new(module.as_ref()).expect("module name conversion failed");
775        unsafe { wren_sys::wrenHasModule(self.vm, module.as_ptr()) }
776    }
777
778    pub fn set_slot_new_list(&self, slot: SlotId) {
779        self.ensure_slots(slot + 1);
780        unsafe { wren_sys::wrenSetSlotNewList(self.vm, slot as raw::c_int) }
781    }
782
783    pub fn get_list_count(&self, slot: SlotId) -> Option<usize> {
784        self.ensure_slots(slot + 1);
785        if self.get_slot_type(slot) == SlotType::List {
786            Some(unsafe { wren_sys::wrenGetListCount(self.vm, slot as raw::c_int) as usize })
787        } else {
788            None
789        }
790    }
791
792    pub fn insert_in_list(&self, list_slot: SlotId, index: i32, element_slot: SlotId) {
793        self.ensure_slots(element_slot + 1);
794        self.ensure_slots(list_slot + 1);
795        unsafe {
796            wren_sys::wrenInsertInList(
797                self.vm,
798                list_slot as raw::c_int,
799                index as raw::c_int,
800                element_slot as raw::c_int,
801            )
802        }
803    }
804
805    pub fn get_list_element(&self, list_slot: SlotId, index: i32, element_slot: SlotId) {
806        self.ensure_slots(element_slot + 1);
807        self.ensure_slots(list_slot + 1);
808        unsafe {
809            wren_sys::wrenGetListElement(
810                self.vm,
811                list_slot as raw::c_int,
812                index as raw::c_int,
813                element_slot as raw::c_int,
814            )
815        }
816    }
817
818    pub fn set_list_element(&self, list_slot: SlotId, index: i32, element_slot: SlotId) {
819        self.ensure_slots(element_slot + 1);
820        self.ensure_slots(list_slot + 1);
821        unsafe {
822            wren_sys::wrenSetListElement(
823                self.vm,
824                list_slot as raw::c_int,
825                index as raw::c_int,
826                element_slot as raw::c_int,
827            )
828        }
829    }
830
831    pub fn set_slot_new_map(&self, slot: SlotId) {
832        self.ensure_slots(slot + 1);
833        unsafe { wren_sys::wrenSetSlotNewMap(self.vm, slot as raw::c_int) }
834    }
835
836    pub fn get_map_count(&self, slot: SlotId) -> Option<usize> {
837        self.ensure_slots(slot + 1);
838        if self.get_slot_type(slot) == SlotType::Map {
839            Some(unsafe { wren_sys::wrenGetMapCount(self.vm, slot as raw::c_int) as usize })
840        } else {
841            None
842        }
843    }
844
845    pub fn get_map_contains_key(&self, map_slot: SlotId, key_slot: SlotId) -> Option<bool> {
846        self.ensure_slots(map_slot + 1);
847        self.ensure_slots(key_slot + 1);
848        if self.get_slot_type(map_slot) == SlotType::Map {
849            Some(unsafe {
850                wren_sys::wrenGetMapContainsKey(
851                    self.vm,
852                    map_slot as raw::c_int,
853                    key_slot as raw::c_int,
854                )
855            })
856        } else {
857            None
858        }
859    }
860
861    pub fn get_map_value(&self, map_slot: SlotId, key_slot: SlotId, value_slot: SlotId) {
862        self.ensure_slots(map_slot + 1);
863        self.ensure_slots(key_slot + 1);
864        self.ensure_slots(value_slot + 1);
865        unsafe {
866            wren_sys::wrenGetMapValue(
867                self.vm,
868                map_slot as raw::c_int,
869                key_slot as raw::c_int,
870                value_slot as raw::c_int,
871            )
872        }
873    }
874
875    pub fn set_map_value(&self, map_slot: SlotId, key_slot: SlotId, value_slot: SlotId) {
876        self.ensure_slots(map_slot + 1);
877        self.ensure_slots(key_slot + 1);
878        self.ensure_slots(value_slot + 1);
879        unsafe {
880            wren_sys::wrenSetMapValue(
881                self.vm,
882                map_slot as raw::c_int,
883                key_slot as raw::c_int,
884                value_slot as raw::c_int,
885            )
886        }
887    }
888
889    pub fn remove_map_value(&self, map_slot: SlotId, key_slot: SlotId, removed_value_slot: SlotId) {
890        self.ensure_slots(map_slot + 1);
891        self.ensure_slots(key_slot + 1);
892        self.ensure_slots(removed_value_slot + 1);
893        unsafe {
894            wren_sys::wrenRemoveMapValue(
895                self.vm,
896                map_slot as raw::c_int,
897                key_slot as raw::c_int,
898                removed_value_slot as raw::c_int,
899            )
900        }
901    }
902
903    pub fn get_slot_foreign<T: 'static + ClassObject>(&self, slot: SlotId) -> Option<&T> {
904        self.ensure_slots(slot + 1);
905        self.get_slot_foreign_mut(slot).map(|mr| &*mr)
906    }
907
908    pub fn get_slot_foreign_mut<T: 'static + ClassObject>(&self, slot: SlotId) -> Option<&mut T> {
909        self.ensure_slots(slot + 1);
910        if self.get_slot_type(slot) != SlotType::Foreign {
911            return None;
912        }
913        unsafe {
914            let ptr = wren_sys::wrenGetSlotForeign(self.vm, slot as raw::c_int);
915            if !ptr.is_null() {
916                let fo = std::ptr::read_unaligned(ptr as *mut ForeignObject<T>);
917                let ret = if fo.type_id == any::TypeId::of::<T>() {
918                    // Safe to downcast
919                    fo.object.as_mut()
920                } else {
921                    // Incorrect type, unsafe to downcast
922                    None
923                };
924                std::ptr::write_unaligned(ptr as *mut ForeignObject<T>, fo);
925                ret
926            } else {
927                None
928            }
929        }
930    }
931
932    /// Accesses the Foreign V2 class immutably for a given type, if it exists (initialize it if it doesn't)
933    pub fn use_class<T: ForeignItem + 'static, F, O>(&self, f: F) -> O
934    where
935        F: FnOnce(&VM, Option<&T::Class>) -> O,
936    {
937        let (update, class) = match self.classes_v2.borrow_mut().get_mut(&TypeId::of::<T>()) {
938            Some(cls) => (false, cls.clone()),
939            None => {
940                use crate::foreign_v2::V2ClassAllocator;
941
942                // Initialize the class (should be done in case the type is *not* constructable)
943                let class = Rc::new(RefCell::new(Box::new(T::Class::allocate()) as Box<dyn Any>));
944                (true, class)
945            }
946        };
947
948        let ret = f(self, class.borrow().downcast_ref());
949
950        if update {
951            self.classes_v2
952                .borrow_mut()
953                .insert(TypeId::of::<T>(), class);
954        }
955
956        ret
957    }
958
959    /// Accesses the Foreign V2 class for a given type, if it exists (initialize it if it doesn't)
960    pub fn use_class_mut<T: ForeignItem + 'static, F, O>(&self, f: F) -> O
961    where
962        F: FnOnce(&VM, Option<&mut T::Class>) -> O,
963    {
964        let (update, class) = match self.classes_v2.borrow_mut().get_mut(&TypeId::of::<T>()) {
965            Some(cls) => (false, cls.clone()),
966            None => {
967                use crate::foreign_v2::V2ClassAllocator;
968
969                // Initialize the class (should be done in case the type is *not* constructable)
970                let class = Rc::new(RefCell::new(Box::new(T::Class::allocate()) as Box<dyn Any>));
971                (true, class)
972            }
973        };
974
975        let ret = f(self, class.borrow_mut().downcast_mut());
976
977        if update {
978            self.classes_v2
979                .borrow_mut()
980                .insert(TypeId::of::<T>(), class);
981        }
982
983        ret
984    }
985
986    /// Looks up the specified module for the given class
987    /// If it's type matches with type T, will create a new instance in the given slot
988    ///
989    /// WARNING: This *will* overwrite slot 0, so be careful.
990    pub fn set_slot_new_foreign<M: AsRef<str>, C: AsRef<str>, T: 'static + ClassObject>(
991        &self, module: M, class: C, object: T, slot: SlotId,
992    ) -> Result<&mut T, ForeignSendError> {
993        self.set_slot_new_foreign_scratch(module, class, object, slot, 0)
994    }
995
996    /// Looks up the specified module for the given class
997    /// If it's type matches with type T, will create a new instance in the given slot
998    ///
999    /// WARNING: This *will* overwrite slot `scratch`, so be careful.
1000    pub fn set_slot_new_foreign_scratch<M: AsRef<str>, C: AsRef<str>, T: 'static + ClassObject>(
1001        &self, module: M, class: C, object: T, slot: SlotId, scratch: SlotId,
1002    ) -> Result<&mut T, ForeignSendError> {
1003        self.ensure_slots(slot.max(scratch) + 1);
1004        let conf = unsafe {
1005            std::ptr::read_unaligned(wren_sys::wrenGetUserData(self.vm) as *mut UserData)
1006        };
1007
1008        // Why did I put this here? (well the equivalent in the original method...)
1009        self.ensure_slots(slot.max(scratch) + 1);
1010        // Even if slot == 0, we can just load the class into slot 0, then use wrenSetSlotNewForeign to "create" a new object
1011        let ret = match conf
1012            .library
1013            .as_ref()
1014            .and_then(|lib| lib.get_foreign_class(module.as_ref(), class.as_ref()))
1015        {
1016            None => Err(ForeignSendError::NoForeignClass), // Couldn't find the corresponding class
1017            Some(runtime_class) => {
1018                if runtime_class.type_id == any::TypeId::of::<T>() {
1019                    // The Wren foreign class corresponds with this real object.
1020                    // We can coerce it and treat this object as that class, even if not instantiated by Wren.
1021
1022                    // Create the new ForeignObject
1023                    let new_obj = ForeignObject {
1024                        object: Box::into_raw(Box::new(object)),
1025                        type_id: any::TypeId::of::<T>(),
1026                    };
1027
1028                    // Load the Wren class object into scratch slot.
1029                    self.get_variable(module, class, scratch);
1030
1031                    // Make sure the class isn't null (undeclared in Wren code)
1032                    match self.get_slot_type(scratch) {
1033                        SlotType::Null => Err(ForeignSendError::NoWrenClass), // You haven't declared the foreign class to Wren
1034                        SlotType::Unknown => unsafe {
1035                            // A Wren class
1036                            // Create the Wren foreign pointer
1037                            let wptr = wren_sys::wrenSetSlotNewForeign(
1038                                self.vm,
1039                                slot as raw::c_int,
1040                                scratch as raw::c_int,
1041                                mem::size_of::<ForeignObject<T>>(),
1042                            );
1043
1044                            if !wptr.is_null() {
1045                                // Move the ForeignObject into the pointer
1046                                std::ptr::write_unaligned(wptr as *mut _, new_obj);
1047                            }
1048
1049                            // Reinterpret the pointer as an object if we were successful
1050                            match (wptr as *mut ForeignObject<T>).as_mut() {
1051                                Some(ptr) => Ok(ptr.object.as_mut().unwrap()),
1052                                None => Err(ForeignSendError::NoMemory),
1053                            }
1054                        },
1055                        _ => Err(ForeignSendError::NoWrenClass),
1056                    }
1057                } else {
1058                    // The classes do not match. Avoid.
1059                    Err(ForeignSendError::ClassMismatch)
1060                }
1061            }
1062        };
1063
1064        unsafe {
1065            std::ptr::write_unaligned(wrenGetUserData(self.vm) as *mut UserData, conf);
1066        }
1067        ret
1068    }
1069
1070    fn make_call_handle<'b>(
1071        vm: *mut WrenVM, signature: FunctionSignature,
1072    ) -> Rc<FunctionHandle<'b>> {
1073        let signature =
1074            ffi::CString::new(signature.as_wren_string()).expect("signature conversion failed");
1075        Rc::new(FunctionHandle(Handle {
1076            handle: unsafe { wren_sys::wrenMakeCallHandle(vm, signature.as_ptr()) },
1077            wvm: vm,
1078            vm: marker::PhantomData,
1079        }))
1080    }
1081
1082    pub fn abort_fiber(&self, slot: SlotId) {
1083        unsafe { wren_sys::wrenAbortFiber(self.vm, slot as raw::c_int) }
1084    }
1085
1086    pub fn get_version_number(&self) -> i32 {
1087        unsafe { wren_sys::wrenGetVersionNumber() }
1088    }
1089}
1090
1091impl Drop for VM {
1092    fn drop(&mut self) {
1093        unsafe {
1094            let conf = wren_sys::wrenGetUserData(self.vm);
1095            let _: Box<UserData> = Box::from_raw(conf as *mut _); // Drop the userdata
1096            wren_sys::wrenFreeVM(self.vm);
1097        }
1098    }
1099}