Skip to main content

rs_luau/
lib.rs

1#[cfg(feature = "compiler")]
2pub mod compile;
3
4pub mod ffi;
5mod libs;
6mod memory;
7mod threads;
8mod userdata;
9
10use std::{
11    any::Any,
12    cell::Cell,
13    ffi::{c_int, c_uint, c_void, CStr, CString},
14    ptr::{null, null_mut},
15    rc::Rc,
16    slice,
17};
18
19use ffi::{
20    luauconf::{LUAI_MAXCSTACK, LUA_MEMORY_CATEGORIES},
21    prelude::*,
22};
23use memory::{luau_alloc_cb, DefaultLuauAllocator};
24use userdata::{
25    drop_userdata, dtor_rs_luau_userdata_callback, Userdata, UserdataBorrowError, UserdataRef,
26    UserdataRefMut, UD_TAG,
27};
28
29pub use ffi::prelude::LuauStatus;
30pub use libs::LuauLibs;
31pub use memory::LuauAllocator;
32pub use threads::LuauThread;
33
34macro_rules! luau_stack_precondition {
35    ($cond:expr) => {
36        assert!(
37            $cond,
38            "Stack indicies should not exceed the top of the stack or extend below."
39        )
40    };
41}
42
43struct AssociatedData {
44    main_thread_rc: Rc<Cell<bool>>,
45    allocator: Box<dyn LuauAllocator>,
46    app_data: Option<Box<dyn Any>>,
47}
48
49#[cfg(feature = "codegen")]
50/// Returns true if codegen is supported for the given platform
51pub fn codegen_supported() -> bool {
52    unsafe { luau_codegen_supported() == 1 }
53}
54
55/// Main struct implementing luau functionality
56pub struct Luau {
57    owned: bool,
58    state: *mut _LuaState,
59}
60
61impl Luau {
62    unsafe fn new_state(allocator: impl LuauAllocator + 'static) -> *mut _LuaState {
63        let associated_data = Box::new(AssociatedData {
64            main_thread_rc: Rc::new(Cell::new(true)),
65            app_data: None,
66            allocator: Box::new(allocator),
67        });
68
69        let state = lua_newstate(luau_alloc_cb, Box::into_raw(associated_data) as _);
70
71        lua_setuserdatadtor(state, UD_TAG, Some(dtor_rs_luau_userdata_callback));
72
73        (*lua_callbacks(state)).panic = Some(fatal_error_handler);
74
75        state
76    }
77
78    pub fn new(allocator: impl LuauAllocator + 'static) -> Self {
79        let state = unsafe { Self::new_state(allocator) };
80
81        if state.is_null() {
82            panic!("Initialization of Luau failed");
83        }
84
85        Self { owned: true, state }
86    }
87
88    #[cfg(feature = "codegen")]
89    /// Enables codegen for the given state
90    pub fn enable_codegen(&self) {
91        unsafe {
92            luau_codegen_create(self.state);
93        }
94    }
95
96    /// Creates a Luau struct from a raw state pointer
97    ///
98    /// # Safety
99    /// The pointer must be a valid Luau state created by `Luau::new`
100    pub unsafe fn from_ptr(state: *mut _LuaState) -> Self {
101        Self {
102            owned: false,
103            state,
104        }
105    }
106
107    /// Creates a Luau struct from a raw state pointer
108    ///
109    /// # Safety
110    /// The pointer must be a valid Luau state and must not alias a Luau struct
111    pub unsafe fn from_ptr_owned(state: *mut _LuaState) -> Self {
112        Self { owned: true, state }
113    }
114
115    const ASSOCIATED_DATA_ERROR: &str = "Expected associated data structure";
116
117    pub(crate) fn get_associated(&self) -> &AssociatedData {
118        unsafe {
119            let mut ptr: *const AssociatedData = null();
120            lua_getallocf(self.state, &raw mut ptr as _);
121
122            ptr.as_ref().expect(Self::ASSOCIATED_DATA_ERROR)
123        }
124    }
125
126    pub(crate) fn get_associated_mut(&self) -> *mut AssociatedData {
127        unsafe {
128            let mut ptr: *mut AssociatedData = null_mut();
129            lua_getallocf(self.state, &raw mut ptr as _);
130
131            assert!(!ptr.is_null(), "{}", Self::ASSOCIATED_DATA_ERROR);
132
133            ptr
134        }
135    }
136
137    pub fn get_app_data<T: Any>(&self) -> Option<&T> {
138        self.get_associated()
139            .app_data
140            .as_ref()
141            .and_then(|v| v.downcast_ref())
142    }
143
144    /// Sets the associated app data for the Luau state returning the previous value
145    pub fn set_app_data<T: Any>(&self, ud: Option<T>) -> Option<Box<dyn Any>> {
146        let associated = unsafe { &mut *self.get_associated_mut() };
147
148        if let Some(v) = ud {
149            let boxed_data = Box::new(v);
150
151            associated.app_data.replace(boxed_data)
152        } else {
153            associated.app_data.take()
154        }
155    }
156
157    pub fn load_libs(&self, lib: LuauLibs) {
158        macro_rules! load_lib {
159            ($func:ident) => {
160                unsafe {
161                    self.push_raw_function(
162                        $func,
163                        Some(&CString::new(stringify!($func)).unwrap()),
164                        0,
165                        None,
166                    );
167                    self.push_string("");
168                    self.call(1, 0);
169                };
170            };
171
172            ($idnt:expr, $func:ident) => {
173                unsafe {
174                    self.push_raw_function(
175                        $func,
176                        Some(&CString::new(stringify!($func)).unwrap()),
177                        0,
178                        None,
179                    );
180                    self.push_string($idnt);
181                    self.call(1, 0);
182                };
183            };
184        }
185
186        if lib.has(LuauLibs::ALL_LIBS) {
187            unsafe { luaL_openlibs(self.state) };
188
189            return;
190        }
191
192        if lib.has(LuauLibs::LIB_BASE) {
193            load_lib!(luaopen_base);
194        }
195
196        if lib.has(LuauLibs::LIB_COROUTINE) {
197            load_lib!(LUA_COLIBNAME, luaopen_coroutine);
198        }
199
200        if lib.has(LuauLibs::LIB_TABLE) {
201            load_lib!(LUA_TABLIBNAME, luaopen_table);
202        }
203
204        if lib.has(LuauLibs::LIB_OS) {
205            load_lib!(LUA_OSLIBNAME, luaopen_os);
206        }
207
208        if lib.has(LuauLibs::LIB_STRING) {
209            load_lib!(LUA_STRLIBNAME, luaopen_string);
210        }
211
212        if lib.has(LuauLibs::LIB_MATH) {
213            load_lib!(LUA_MATHLIBNAME, luaopen_math);
214        }
215
216        if lib.has(LuauLibs::LIB_DEBUG) {
217            load_lib!(LUA_DBLIBNAME, luaopen_debug);
218        }
219
220        if lib.has(LuauLibs::LIB_UTF8) {
221            load_lib!(LUA_UTF8LIBNAME, luaopen_utf8);
222        }
223
224        if lib.has(LuauLibs::LIB_BIT32) {
225            load_lib!(LUA_BITLIBNAME, luaopen_bit32);
226        }
227
228        if lib.has(LuauLibs::LIB_BUFFER) {
229            load_lib!(LUA_BUFFERLIBNAME, luaopen_buffer);
230        }
231    }
232
233    #[inline]
234    pub fn to_ptr(&self) -> *mut _LuaState {
235        self.state
236    }
237
238    #[inline]
239    pub fn top(&self) -> c_int {
240        unsafe { lua_gettop(self.state) }
241    }
242
243    /// Returns the status of the Luau state
244    pub fn status(&self) -> LuauStatus {
245        unsafe { lua_status(self.state) }
246    }
247
248    /// Yields the luau state with the number of results
249    ///
250    /// Should be used as the end expression or a return from a function as this returns `-1`
251    pub fn yield_luau(&self, nresults: c_int) -> c_int {
252        assert!(
253            self.top() >= nresults,
254            "The number of yield returns must not exceed the stack size"
255        );
256
257        unsafe { lua_yield(self.state, nresults) }
258    }
259
260    /// Breaks the luau state for the purposes of a debug interrupt
261    ///
262    /// Should be used as the end expression or a return from a function as this returns `-1`
263    pub fn break_luau(&self) -> c_int {
264        unsafe { lua_break(self.state) }
265    }
266
267    /// Produces an error with the value on the top of the stack
268    pub fn error(&self) -> c_int {
269        luau_stack_precondition!(self.check_index(-1));
270
271        // SAFETY: a value on the top of the stack exists as verified by the precondition
272        unsafe { lua_error(self.state) }
273    }
274
275    /// Returns the type of a luau value at `idx`
276    pub fn type_of(&self, idx: c_int) -> LuauType {
277        luau_stack_precondition!(self.check_index(idx));
278
279        unsafe { lua_type(self.state, idx) }
280    }
281
282    /// Pops `n` values from the stack
283    pub fn pop(&self, n: c_int) {
284        // assert that the set position is not greater than the top
285        luau_stack_precondition!(self.check_index(-n));
286
287        // SAFETY: -n is validated by the precondition
288        unsafe { lua_settop(self.state, -(n + 1)) }
289    }
290
291    /// Returns an upvalue index for the specified upvalue index
292    pub fn upvalue(&self, uv_idx: c_int) -> c_int {
293        lua_upvalueindex(uv_idx)
294    }
295
296    /// Sets the memory category for all allocations taking place after its set
297    pub fn set_memory_category(&self, cat: c_int) {
298        assert!(
299            cat < LUA_MEMORY_CATEGORIES,
300            "Memory category index must not exceed {LUA_MEMORY_CATEGORIES}"
301        );
302
303        unsafe {
304            lua_setmemcat(self.state, cat);
305        }
306    }
307
308    pub fn check_index(&self, idx: c_int) -> bool {
309        if idx <= LUA_REGISTRYINDEX {
310            return true;
311        }
312
313        if idx == 0 {
314            return false;
315        }
316
317        let top = self.top();
318
319        let idx = if idx < 0 {
320            // "subtract" the top (idx is negative)
321            top.wrapping_add(idx)
322        } else {
323            idx
324        };
325
326        if idx < LUA_GLOBALSINDEX {
327            // upvalue idx
328            return true;
329        }
330
331        // zero is acceptable here
332
333        idx >= 0 && // greater or equal to zero and
334        idx <= top && // lesser than or equal to the top and
335        idx < LUAI_MAXCSTACK // smaller than the maximum c stack
336    }
337
338    pub fn check_stack(&self, sz: c_int) -> bool {
339        unsafe { lua_checkstack(self.state, sz) == 1 }
340    }
341
342    #[inline]
343    pub fn registry(&self) -> c_int {
344        LUA_REGISTRYINDEX
345    }
346
347    #[inline]
348    pub fn globals(&self) -> c_int {
349        LUA_GLOBALSINDEX
350    }
351
352    pub fn check_args(&self, count: c_int, extra_message: Option<&CStr>) {
353        if self.top() >= count {
354            return;
355        }
356
357        unsafe {
358            luaL_argerrorL(
359                self.state,
360                count - self.top(),
361                extra_message.map(CStr::as_ptr).unwrap_or(null()),
362            );
363        }
364    }
365
366    /// Returns true if the value at `idx` is nil
367    pub fn is_nil(&self, idx: c_int) -> bool {
368        self.type_of(idx) == LuauType::LUA_TNIL
369    }
370
371    /// Pushes a nil value to the stack
372    pub fn push_nil(&self) {
373        luau_stack_precondition!(self.check_stack(1));
374
375        // SAFETY: stack size is validated by precondition
376        unsafe {
377            lua_pushnil(self.state);
378        }
379    }
380
381    /// Returns true if the value at `idx` is a bool, false otherwise
382    pub fn is_boolean(&self, idx: c_int) -> bool {
383        self.type_of(idx) == LuauType::LUA_TBOOLEAN
384    }
385
386    /// Returns true if the value at `idx` is not nil or false, otherwise returns false
387    pub fn to_boolean(&self, idx: c_int) -> bool {
388        luau_stack_precondition!(self.check_index(idx));
389
390        // SAFETY: idx is validated by the precondition
391        unsafe { lua_toboolean(self.state, idx) == 1 }
392    }
393
394    /// Pushes a boolean value to the Luau stack
395    pub fn push_boolean(&self, value: bool) {
396        luau_stack_precondition!(self.check_stack(1));
397
398        // SAFETY: stack size is validated by the precondition
399        unsafe {
400            lua_pushboolean(self.state, value as i32);
401        }
402    }
403
404    /// Returns true if the value at idx is a number, false otherwise
405    pub fn is_number(&self, idx: c_int) -> bool {
406        self.type_of(idx) == LuauType::LUA_TNUMBER
407    }
408
409    /// Pushes an integer onto the Luau stack
410    pub fn push_integer(&self, n: c_int) {
411        luau_stack_precondition!(self.check_stack(1));
412
413        // SAFETY: we have adequate stack space as checked by the precondition
414        unsafe {
415            lua_pushinteger(self.state, n);
416        }
417    }
418
419    /// Pushes an unsigned integer onto the Luau stack
420    pub fn push_unsigned_integer(&self, n: c_uint) {
421        luau_stack_precondition!(self.check_stack(1));
422
423        // SAFETY: we have adequate stack space as checked by the precondition
424        unsafe {
425            lua_pushunsigned(self.state, n);
426        }
427    }
428
429    /// Push a double into the Luau stack
430    pub fn push_number(&self, n: f64) {
431        // validate if the pushed index will not exceed the max C stack length
432        luau_stack_precondition!(self.check_stack(1));
433
434        // SAFETY: stack is appropriately sized, as checked by the precondition above
435        unsafe {
436            lua_pushnumber(self.state, n);
437        }
438    }
439
440    /// Gets/converts a Lua value at `idx` to a number.
441    ///
442    /// Will convert a compatible string to a number
443    pub fn to_number(&self, idx: c_int) -> Option<f64> {
444        luau_stack_precondition!(self.check_index(idx));
445
446        let mut is_number = 0;
447        // SAFETY: idx is validated by the precondition and is therefore safe to access
448        let number = unsafe { lua_tonumberx(self.state, idx, &raw mut is_number) };
449
450        if is_number == 1 {
451            Some(number)
452        } else {
453            None
454        }
455    }
456
457    /// Returns true if the value at `idx` is a number, false otherwise
458    pub fn is_string(&self, idx: c_int) -> bool {
459        self.type_of(idx) == LuauType::LUA_TSTRING
460    }
461
462    /// Pushes a string to the top of the Luau stack
463    pub fn push_string(&self, str: impl AsRef<[u8]>) {
464        luau_stack_precondition!(self.check_stack(1));
465
466        let slice = str.as_ref();
467
468        // SAFETY: the stack size is checked by the precondition
469        unsafe {
470            lua_pushlstring(self.state, slice.as_ptr() as _, slice.len());
471        }
472    }
473
474    /// Gets or tries to coerce a Luau value at `idx` into a slice of u8s
475    pub fn to_str_slice(&self, idx: c_int) -> Option<&[u8]> {
476        luau_stack_precondition!(self.check_index(idx));
477
478        // needs to have a lifetime to bind the result on a lifetime to prevent use after frees
479        let mut len = 0;
480        // SAFETY: idx is validated by the precondition
481        let data = unsafe { lua_tolstring(self.state, idx, &mut len) };
482
483        if !data.is_null() {
484            // SAFETY: Luau can be trusted to return the correct len
485            unsafe { Some(std::slice::from_raw_parts(data as _, len)) }
486        } else {
487            None
488        }
489    }
490
491    /// Gets or tries to coerce a Luau value at `idx` into a str reference
492    pub fn to_str(&self, idx: c_int) -> Option<Result<&str, std::str::Utf8Error>> {
493        // preconditions are checked by to_string_slice
494        self.to_str_slice(idx).map(|v| std::str::from_utf8(v))
495    }
496
497    /// Gets or converts a Luau value at `idx` into a string with a reasonable format, will invoke __tostring metamethods.
498    pub fn convert_to_str_slice(&self, idx: c_int) -> &[u8] {
499        luau_stack_precondition!(self.check_index(idx));
500
501        unsafe {
502            let mut len = 0;
503            let data = luaL_tolstring(self.state, idx, &raw mut len);
504
505            if data.is_null() {
506                unreachable!("Luau string conversion returned NULL ptr");
507            } else {
508                std::slice::from_raw_parts(data as _, len)
509            }
510        }
511    }
512
513    /// Returns true if the userdata at `idx` is a userdata and is of type T
514    pub fn is_userdata<T: Any>(&self, idx: c_int) -> bool {
515        luau_stack_precondition!(self.check_index(idx));
516
517        // SAFETY: idx is validated by the precondition and the behavior of userdata is checked
518        unsafe {
519            let userdata_ptr: *mut Userdata<()> =
520                lua_touserdatatagged(self.state, idx, UD_TAG) as _;
521
522            !userdata_ptr.is_null() && (*userdata_ptr).is::<T>()
523        }
524    }
525
526    /// Returns true if the userdata at `idx` is any type of userdata
527    pub fn is_any_userdata<T: Any>(&self, idx: c_int) -> bool {
528        luau_stack_precondition!(self.check_index(idx));
529
530        // SAFETY: idx is validated by the precondition
531        unsafe { lua_isuserdata(self.state, idx) == 1 }
532    }
533
534    /// Pushes a value T as a userdata to Luau
535    pub fn push_userdata<T: Any>(&self, object: T) {
536        luau_stack_precondition!(self.check_stack(1));
537
538        // SAFETY: We allocate a DST as a userdata on a stack with the known proper size with our own tag.
539        // if the userdat allo
540        // if our type T has drop glue then we will set the dtor field which will be invoked
541        // we then construct a struct which has ownership of T
542        // we need the dtor field because the struct is opaque elsewhere
543        unsafe {
544            let userdata_ptr: *mut Userdata<T> =
545                lua_newuserdatatagged(self.state, size_of::<Userdata<T>>(), UD_TAG).cast();
546
547            let dtor = if std::mem::needs_drop::<T>() {
548                let fn_item: unsafe fn(*mut Userdata<T>) = drop_userdata::<T>;
549
550                Some(fn_item)
551            } else {
552                None
553            };
554
555            userdata_ptr.write(Userdata {
556                id: object.type_id(),
557                count_cell: Cell::new(0),
558                dtor,
559                inner: object,
560            });
561        }
562    }
563
564    fn get_userdata_ptr<T: Any>(&self, idx: c_int) -> Option<*mut Userdata<T>> {
565        luau_stack_precondition!(self.check_index(idx));
566
567        // SAFETY: We validate that the userdata at the checked idx is of the proper type T or null
568        unsafe {
569            let userdata_ptr: *mut Userdata<()> =
570                lua_touserdatatagged(self.state, idx, UD_TAG) as _;
571
572            if !userdata_ptr.is_null() && (*userdata_ptr).is::<T>() {
573                Some(userdata_ptr as _)
574            } else {
575                None
576            }
577        }
578    }
579
580    /// Returns a result with a ref to a userdata value of type T or an error if the userdata is already mutably borrowed.
581    ///
582    /// Returns `None` if the value isn't a userdata or the userdata is not of type T.
583    pub fn try_borrow_userdata<T: Any>(
584        &self,
585        idx: c_int,
586    ) -> Option<Result<UserdataRef<T>, UserdataBorrowError>> {
587        // SAFETY: We validate that the userdata at the checked idx is a userdata and a valid T through `get_userdata_ptr`
588        unsafe {
589            let userdata_ptr = self.get_userdata_ptr(idx)?;
590
591            Some(UserdataRef::try_from_ptr(userdata_ptr))
592        }
593    }
594
595    /// Gets a reference to a userdata value of type T, returning None if the value isn't a userdata or the userdata is not of type T.
596    ///
597    /// Will panic if the userdata is already mutably borrowed
598    pub fn borrow_userdata<T: Any>(&self, idx: c_int) -> Option<UserdataRef<T>> {
599        self.try_borrow_userdata(idx).map(Result::unwrap)
600    }
601
602    /// Tries to get a mutable reference to a userdata value of type T. Returns a result with the ref or an error.
603    ///
604    /// Returns `None` if the value is not of the correct type or if the value is already at idx.
605    pub fn try_borrow_userdata_mut<T: Any>(
606        &self,
607        idx: c_int,
608    ) -> Option<Result<UserdataRefMut<T>, UserdataBorrowError>> {
609        // SAFETY: We validate that the userdata at the checked idx is a userdata and a valid T through `get_userdata_ptr`
610        unsafe {
611            let userdata_ptr = self.get_userdata_ptr(idx)?;
612
613            Some(UserdataRefMut::try_from_ptr(userdata_ptr))
614        }
615    }
616
617    /// Retrives a userdata of type T without performing a type check to determine if the inner type is really T
618    ///
619    /// Will return None if the value at idx is not a userdata
620    ///
621    /// # Safety
622    /// You need to know beforehand that the userdata here is of the correct type or has such a layout that the type requested is valid
623    pub unsafe fn get_userdata_unchecked<T: 'static>(&self, idx: c_int) -> Option<&mut T> {
624        luau_stack_precondition!(self.check_index(idx));
625
626        // SAFETY: we don't do any checking other than validating idx
627        unsafe { Some(&mut (*self.get_userdata_ptr(idx)?).inner) }
628    }
629
630    /// Returns true if the value at `idx` is a light userdata, it returns false otherwise.
631    pub fn is_lightuserdata(&self, idx: c_int) -> bool {
632        self.type_of(idx) == LuauType::LUA_TLIGHTUSERDATA
633    }
634
635    /// Returns an option of a raw pointer. Will be Some if the value at `idx` is a lightuserdata, None otherwise.
636    pub fn to_lightuserdata<T>(&self, idx: c_int) -> Option<*mut T> {
637        luau_stack_precondition!(self.check_index(idx));
638
639        // SAFETY: idx is checked by precondition
640        unsafe {
641            let ptr: *mut T = lua_tolightuserdata(self.state, idx).cast();
642
643            if ptr.is_null() {
644                None
645            } else {
646                Some(ptr)
647            }
648        }
649    }
650
651    /// Returns true if the Luau value at `idx` is a buffer, false otherwise
652    pub fn is_buffer(&self, idx: c_int) -> bool {
653        self.type_of(idx) == LuauType::LUA_TBUFFER
654    }
655
656    /// Creates a luau buffer of a provided size and pushes it on the stack
657    ///
658    /// This will issue an error if the allocation cannot be performed
659    pub fn push_buffer(&mut self, size: usize) -> &mut [u8] {
660        luau_stack_precondition!(self.check_stack(1));
661
662        unsafe {
663            let ptr: *mut u8 = lua_newbuffer(self.state, size) as _;
664
665            std::slice::from_raw_parts_mut(ptr, size)
666        }
667    }
668
669    /// Pushes a slice to the Luau stack as a buffer
670    pub fn push_buffer_from_slice(&mut self, slice: impl AsRef<[u8]>) -> &mut [u8] {
671        // precondition is validated by push_buffer
672
673        let slice = slice.as_ref();
674
675        let buffer = self.push_buffer(slice.len());
676        buffer.copy_from_slice(slice);
677
678        buffer
679    }
680
681    /// Gets a Luau value at `idx` as a mutable slice of bytes
682    pub fn to_buffer(&mut self, idx: c_int) -> Option<&mut [u8]> {
683        luau_stack_precondition!(self.check_index(idx));
684
685        let mut len = 0;
686        // SAFETY: idx is validated by the precondition
687        let data: *mut u8 = unsafe { lua_tobuffer(self.state, idx, &mut len) as _ };
688
689        // will be null if the value is not a buffer
690        if !data.is_null() {
691            // SAFETY: Luau will report the right length
692            unsafe { Some(slice::from_raw_parts_mut(data, len)) }
693        } else {
694            None
695        }
696    }
697
698    /// Gets the pointer of a buffer value returning NULL if the value at `idx` is not a buffer
699    pub fn to_buffer_ptr(&self, idx: c_int, len: &mut usize) -> *mut c_void {
700        luau_stack_precondition!(self.check_index(idx));
701
702        // SAFETY: idx is validated by precondition
703        unsafe { lua_tobuffer(self.state, idx, len) }
704    }
705
706    /// Pushes an empty table to the Luau stack
707    pub fn create_table(&self) {
708        unsafe {
709            lua_createtable(self.state, 0, 0);
710        }
711    }
712
713    /// Pushes an empty table to the Luau stack with a preallocated array portion of `narr` and an associative portion of `nrec`
714    pub fn create_table_with_capacity(&self, narr: c_int, nrec: c_int) {
715        unsafe {
716            lua_createtable(self.state, narr, nrec);
717        }
718    }
719
720    pub fn shift(&self, to: c_int) {
721        luau_stack_precondition!(self.check_index(to));
722
723        unsafe {
724            lua_insert(self.state, to);
725        }
726    }
727
728    /// Makes a reference to the value at `idx` which can be retrieved from `get_reference`
729    pub fn reference(&self, idx: c_int) -> RefIndex {
730        luau_stack_precondition!(self.check_index(idx));
731
732        // SAFETY: idx is checked
733        unsafe { lua_ref(self.state, idx) }
734    }
735
736    /// Retrieves a reference from a RefIndex and pushes it to the top of the stack while returning the type's value
737    pub fn get_reference(&self, ref_index: RefIndex) -> LuauType {
738        luau_stack_precondition!(self.check_stack(1));
739
740        // SAFETY: stack size is checked
741        unsafe { lua_getref(self.state, ref_index) }
742    }
743
744    /// Removes a reference
745    pub fn unreference(&self, ref_index: RefIndex) {
746        unsafe {
747            lua_unref(self.state, ref_index);
748        }
749    }
750
751    /// Returns true if the value at `idx` is a table, false otherwise
752    pub fn is_table(&self, idx: c_int) -> bool {
753        self.type_of(idx) == LuauType::LUA_TTABLE
754    }
755
756    /// Sets t\[k\] = v where k is the field string, t is the table at idx and k is the value on the top of the stack
757    ///
758    /// May invoke a __newindex metamethod
759    pub fn set_field(&self, idx: c_int, field: impl AsRef<[u8]>) {
760        luau_stack_precondition!(self.check_stack(1));
761
762        // bad hack
763        let idx = if idx < 0 && !lua_ispseudo(idx) && !(idx == -1 && self.top() == 1) {
764            idx - 1
765        } else {
766            idx
767        };
768
769        self.push_string(field);
770        self.shift(-2);
771
772        self.set_table(idx);
773    }
774
775    /// Sets t\[k\] = v where k is the field string, t is the table at idx and k is the value on the top of the stack
776    ///
777    /// Will not invoke a __newindex metamethod
778    pub fn raw_set_field(&self, idx: c_int, field: &str) {
779        luau_stack_precondition!(self.check_stack(1));
780
781        let idx = if idx < 0 && !lua_ispseudo(idx) && !(idx == -1 && self.top() == 1) {
782            idx - 1
783        } else {
784            idx
785        };
786
787        self.push_string(field);
788        self.shift(-2);
789
790        self.raw_set_table(idx);
791    }
792
793    /// Sets the value of t\[k\] with the value at the top of the stack where t is at the index and k is the value beneath the top of the stack.
794    ///
795    /// May invoke a __newindex metamethod
796    pub fn set_table(&self, idx: c_int) {
797        assert!(
798            self.top() >= 2,
799            "There must be a key and value on the stack to set table"
800        );
801        luau_stack_precondition!(self.check_index(idx));
802
803        // SAFETY: idx is validated by the precondition
804        unsafe {
805            lua_settable(self.state, idx);
806        }
807    }
808
809    /// Sets the value of t\[k\] with the value at the top of the stack where t is at the index and k is the value beneath the top of the stack.
810    ///
811    /// Will not invoke a __newindex metamethod
812    pub fn raw_set_table(&self, idx: c_int) {
813        assert!(
814            self.top() >= 2,
815            "There must be a key and value on the stack to set table"
816        );
817        luau_stack_precondition!(self.check_index(idx));
818
819        // SAFETY: idx is validated by the precondition
820        unsafe {
821            lua_rawset(self.state, idx);
822        }
823    }
824
825    /// Gets t\[k\] where k is the field string where t is the table at idx.
826    ///
827    /// May invoke a __index metamethod
828    pub fn get_field(&self, idx: c_int, field: impl AsRef<[u8]>) {
829        luau_stack_precondition!(self.check_index(idx));
830        luau_stack_precondition!(self.check_stack(1));
831
832        let idx = if idx < 0 && !lua_ispseudo(idx) {
833            idx - 1
834        } else {
835            idx
836        };
837
838        self.push_string(field);
839        self.get_table(idx);
840    }
841
842    /// Gets t\[k\] where k is the field string where t is the table at idx.
843    ///
844    /// Will not invoke a __index metamethod
845    pub fn raw_get_field(&self, idx: c_int, field: impl AsRef<[u8]>) {
846        luau_stack_precondition!(self.check_index(idx));
847        luau_stack_precondition!(self.check_stack(1));
848
849        let idx = if idx < 0 && !lua_ispseudo(idx) {
850            idx - 1
851        } else {
852            idx
853        };
854
855        self.push_string(field);
856        self.raw_get_table(idx);
857    }
858
859    /// Gets the value of t\[k\] where t is the value at the index and k is the value on the top of the stack.
860    ///
861    /// May invoke a __index metamethod
862    pub fn get_table(&self, idx: c_int) {
863        assert!(
864            self.top() >= 1,
865            "There must be a key on the stack to index the table"
866        );
867        luau_stack_precondition!(self.check_index(idx));
868
869        // SAFETY: idx is validated by the precondition
870        unsafe {
871            lua_gettable(self.state, idx);
872        }
873    }
874
875    /// Gets the value of t\[k\] where t is the value at the index and k is the value on the top of the stack.
876    ///
877    /// Will not invoke a __index metamethod
878    pub fn raw_get_table(&self, idx: c_int) {
879        assert!(
880            self.top() >= 1,
881            "There must be a key on the stack to index the table"
882        );
883        luau_stack_precondition!(self.check_index(idx));
884
885        // SAFETY: idx is validated by the precondition
886        unsafe {
887            lua_rawget(self.state, idx);
888        }
889    }
890
891    /// Changes the readonly mode of a table at `idx` to the supplied boolean
892    pub fn set_readonly(&self, idx: c_int, enabled: bool) {
893        assert!(self.is_table(idx));
894
895        // SAFETY: is_table has a precondition to validate idx
896        unsafe {
897            lua_setreadonly(self.state, idx, enabled as c_int);
898        }
899    }
900
901    /// Sets the metatable for the value idx to the table located on the top of the stack.
902    ///
903    /// Sets the metatable for individual tables and userdata or sets the metatable for an entire type.
904    pub fn set_metatable(&self, idx: c_int) {
905        luau_stack_precondition!(self.check_index(idx));
906
907        unsafe {
908            lua_setmetatable(self.state, idx);
909        }
910    }
911
912    /// Returns true if the value at idx is a vector, false otherwise
913    pub fn is_vector(&self, idx: c_int) -> bool {
914        self.type_of(idx) == LuauType::LUA_TVECTOR
915    }
916
917    /// Pushes a vector to the Luau stack
918    pub fn push_vector(&self, x: f32, y: f32, z: f32, #[cfg(feature = "luau_vector4")] w: f32) {
919        luau_stack_precondition!(self.check_stack(1));
920
921        // SAFETY: stack size is validated by precondition
922        unsafe {
923            #[cfg(not(feature = "luau_vector4"))]
924            lua_pushvector(self.state, x, y, z);
925            #[cfg(feature = "luau_vector4")]
926            lua_pushvector(self.state, x, y, z, w);
927        }
928    }
929
930    #[cfg(not(feature = "luau_vector4"))]
931    /// Returns the value of a vector if the value at idx is a vector or will return None
932    pub fn to_vector(&self, idx: c_int) -> Option<(f32, f32, f32)> {
933        luau_stack_precondition!(self.check_index(idx));
934        unsafe {
935            Option::from(lua_tovector(self.state, idx)).map(|ptr| (*ptr, *ptr.add(1), *ptr.add(2)))
936        }
937    }
938
939    #[cfg(feature = "luau_vector4")]
940    /// Returns the value of a vector if the value at idx is a vector or will return None
941    pub fn to_vector(&self, idx: c_int) -> Option<(f32, f32, f32, f32)> {
942        luau_stack_precondition!(self.check_index(idx));
943        unsafe {
944            Option::from(lua_tovector(self.state, idx))
945                .map(|ptr| (*ptr, *ptr.add(1), *ptr.add(2), *ptr.add(3)))
946        }
947    }
948
949    /// Returns true if the value at `idx` is a thread, false otherwise
950    pub fn is_thread(&self, idx: c_int) -> bool {
951        self.type_of(idx) == LuauType::LUA_TTHREAD
952    }
953
954    pub fn new_thread(&self) -> LuauThread {
955        unsafe {
956            let thread_ptr = lua_newthread(self.state);
957            LuauThread::from_ptr(thread_ptr, self.get_associated().main_thread_rc.clone())
958        }
959    }
960
961    pub fn get_thread(&self, idx: c_int) -> Option<LuauThread> {
962        let ptr = unsafe { lua_tothread(self.state, idx) };
963
964        if !ptr.is_null() {
965            unsafe {
966                Some(LuauThread::from_ptr(
967                    ptr,
968                    self.get_associated().main_thread_rc.clone(),
969                ))
970            }
971        } else {
972            None
973        }
974    }
975
976    /// Returns the thread local userdata
977    pub fn get_thread_data<T: Any>(&self) -> Option<&T> {
978        let boxed = unsafe { (lua_getthreaddata(self.state) as *const Box<dyn Any>).as_ref()? };
979
980        boxed.downcast_ref()
981    }
982
983    /// Sets the thread local userdata
984    pub fn set_thread_data<T: Any>(&self, userdata: T) {
985        let b: Box<dyn Any> = Box::new(userdata);
986
987        unsafe {
988            lua_setthreaddata(self.state, Box::into_raw(b) as _);
989        }
990    }
991
992    /// Resumes the given Luau thread with the number of arguments.
993    ///
994    /// Will resume the function on the top of the given Luau thread's execution stack
995    pub fn resume(&self, luau_thread: &LuauThread, nargs: c_int) -> LuauStatus {
996        unsafe { lua_resume(luau_thread.get_state().state, self.state, nargs) }
997    }
998
999    /// Returns true if the value at `idx` is a function, false otherwise
1000    pub fn is_function(&self, idx: c_int) -> bool {
1001        self.type_of(idx) == LuauType::LUA_TFUNCTION
1002    }
1003
1004    /// Pushes a raw rust function to the stack which receives a pointer to the luau state and returns the number of result values
1005    ///
1006    /// Can receive a number of upvalues specified by the `num_upvalues` argument which are accessed through ffi's upvalueindex
1007    ///
1008    /// # Safety
1009    /// You will need to uphold all safety invariants with respect to the Luau VM in the user supplied `func`
1010    pub unsafe fn push_raw_function(
1011        &self,
1012        func: CFunction,
1013        debug_name: Option<&CStr>,
1014        num_upvals: c_int,
1015        continuation: Option<LuaContinuation>,
1016    ) {
1017        luau_stack_precondition!(self.check_stack(1));
1018
1019        assert!(
1020            self.top() >= num_upvals,
1021            "The number of upvalues for a raw function must not exceed the stack length"
1022        );
1023
1024        // SAFETY: upvalue count and stack size are validated as a precondition and assert
1025        unsafe {
1026            lua_pushcclosurek(
1027                self.state,
1028                func,
1029                if let Some(name) = debug_name {
1030                    name.as_ptr()
1031                } else {
1032                    null()
1033                },
1034                num_upvals,
1035                continuation,
1036            );
1037        }
1038    }
1039
1040    /// Pushes a Rust function into Luau with an associated continuation
1041    ///
1042    /// This function wraps a Rust function to allow closures to capture values, to avoid this minor overhead you can use `push_function_raw`
1043    pub fn push_function_continuation<
1044        F: FnMut(&Luau) -> c_int,
1045        Cont: FnMut(&Luau, LuauStatus) -> c_int,
1046    >(
1047        &self,
1048        func: F,
1049        debug_name: Option<&CStr>,
1050        num_upvals: c_int,
1051        cont: Cont,
1052    ) {
1053        assert!(
1054            self.top() >= num_upvals,
1055            "The number of upvalues for a raw function must not exceed the stack length"
1056        );
1057
1058        luau_stack_precondition!(self.check_stack(2));
1059
1060        struct CallState<F, Cont> {
1061            func: F,
1062            cont: Cont,
1063        }
1064
1065        let call_state = Box::new(CallState { func, cont });
1066
1067        unsafe extern "C-unwind" fn invoke_fn<
1068            F: FnMut(&Luau) -> c_int,
1069            Cont: FnMut(&Luau, LuauStatus) -> c_int,
1070        >(
1071            state: *mut _LuaState,
1072        ) -> c_int {
1073            let call_state =
1074                lua_tolightuserdata(state, lua_upvalueindex(1)).cast::<CallState<F, Cont>>();
1075
1076            let luau = Luau::from_ptr(state);
1077
1078            ((*call_state).func)(&luau)
1079        }
1080
1081        unsafe extern "C-unwind" fn invoke_continuation<
1082            F: FnMut(&Luau) -> c_int,
1083            Cont: FnMut(&Luau, LuauStatus) -> c_int,
1084        >(
1085            state: *mut _LuaState,
1086            status: c_int,
1087        ) -> c_int {
1088            let call_state =
1089                lua_tolightuserdata(state, lua_upvalueindex(1)).cast::<CallState<F, Cont>>();
1090
1091            let luau = Luau::from_ptr(state);
1092            ((*call_state).cont)(&luau, std::mem::transmute::<c_int, LuauStatus>(status))
1093        }
1094
1095        unsafe {
1096            lua_pushlightuserdata(self.state, Box::into_raw(call_state) as _);
1097
1098            self.push_raw_function(
1099                invoke_fn::<F, Cont>,
1100                debug_name,
1101                1 + num_upvals,
1102                Some(invoke_continuation::<F, Cont>),
1103            );
1104        }
1105    }
1106
1107    /// Pushes a Rust function into Luau
1108    ///
1109    /// This function wraps a Rust function to allow closures to capture values, to avoid this minor overhead you can use `push_function_raw`
1110    pub fn push_function<F: FnMut(&Luau) -> i32>(
1111        &self,
1112        func: F,
1113        debug_name: Option<&CStr>,
1114        num_upvals: c_int,
1115    ) {
1116        assert!(
1117            self.top() >= num_upvals,
1118            "The number of upvalues for a raw function must not exceed the stack length"
1119        );
1120
1121        luau_stack_precondition!(self.check_stack(2));
1122
1123        let func_box = Box::new(func);
1124
1125        unsafe extern "C-unwind" fn invoke_fn<T: FnMut(&Luau) -> i32>(
1126            state: *mut _LuaState,
1127        ) -> c_int {
1128            let func = lua_tolightuserdata(state, lua_upvalueindex(1)).cast::<T>();
1129
1130            let state = Luau::from_ptr(state);
1131            (*func)(&state)
1132        }
1133
1134        unsafe {
1135            lua_pushlightuserdata(self.state, Box::into_raw(func_box) as _);
1136
1137            self.push_raw_function(invoke_fn::<F>, debug_name, 1 + num_upvals, None);
1138        }
1139    }
1140
1141    /// Calls the Luau function at the top of the stack returning the status of the Luau state when it returns
1142    pub fn call(&self, nargs: c_int, nresults: c_int) -> LuauStatus {
1143        assert!(
1144            self.is_function(-1),
1145            "The value at top of stack must be a function"
1146        );
1147
1148        assert!(
1149            self.top() >= nargs,
1150            "Argument count may not exceed the total stack size"
1151        );
1152
1153        luau_stack_precondition!(self.check_stack(nresults));
1154
1155        unsafe { lua_pcall(self.state, nargs, nresults, 0) }
1156    }
1157
1158    /// Loads bytecode into the VM and pushes a function to the stack
1159    pub fn load(&self, chunk_name: Option<&CStr>, bytecode: &[u8], env: c_int) -> Result<(), &str> {
1160        // specifically allow env 0
1161        luau_stack_precondition!(env == 0 || self.check_index(env));
1162        luau_stack_precondition!(self.check_stack(2));
1163
1164        let success = unsafe {
1165            luau_load(
1166                self.state,
1167                chunk_name.unwrap_or(c"").as_ptr(),
1168                bytecode.as_ptr() as _,
1169                bytecode.len(),
1170                env,
1171            )
1172        };
1173
1174        if success == 0 {
1175            Ok(())
1176        } else {
1177            dbg!(self.top());
1178            // we have an error and know its ascii
1179            Err(self.to_str(-1).unwrap().unwrap())
1180        }
1181    }
1182
1183    #[cfg(feature = "codegen")]
1184    /// Compiles a function with native code generation.
1185    ///
1186    /// This will fail silently if the codegen is not supported and initialized
1187    pub fn codegen(&self, idx: c_int) {
1188        luau_stack_precondition!(self.check_index(idx));
1189        assert!(
1190            self.is_function(idx),
1191            "The value at idx must be a function to be compiled with codegen"
1192        );
1193
1194        unsafe {
1195            luau_codegen_compile(self.state, idx);
1196        }
1197    }
1198}
1199
1200// TODO: do this
1201unsafe extern "C-unwind" fn fatal_runtime_error_handler(state: *mut _LuaState) -> c_int {
1202    let luau = unsafe { Luau::from_ptr(state) };
1203
1204    panic!(
1205        "Uncaught runtime error - \"{}\"",
1206        String::from_utf8_lossy(luau.convert_to_str_slice(-1))
1207    );
1208}
1209
1210/// Final resting place for Luau code, we don't return from this.
1211unsafe extern "C-unwind" fn fatal_error_handler(state: *mut _LuaState, status: LuauStatus) {
1212    match status {
1213        // Unhandled runtime error
1214        LuauStatus::LUA_ERRRUN => fatal_runtime_error_handler(state),
1215        // memory allocation error, just die
1216        LuauStatus::LUA_ERRMEM => std::process::abort(),
1217        // some error handling mechanism errored
1218        LuauStatus::LUA_ERRERR => panic!("Error originating from error handling mechanism"),
1219        // shouldnt be reachable
1220        _ => unreachable!(),
1221    };
1222
1223    panic!("Fatal error in Luau execution");
1224}
1225
1226impl Default for Luau {
1227    fn default() -> Self {
1228        Self::new(DefaultLuauAllocator {})
1229    }
1230}
1231
1232impl Drop for Luau {
1233    fn drop(&mut self) {
1234        if !self.owned {
1235            return;
1236        }
1237
1238        unsafe {
1239            let mut associated: *mut AssociatedData = null_mut();
1240            lua_getallocf(self.state, &raw mut associated as _);
1241
1242            let associated_owned = Box::from_raw(associated);
1243
1244            // mark main thread dead
1245            associated_owned.main_thread_rc.set(false);
1246
1247            lua_close(self.state);
1248
1249            _ = associated_owned
1250        }
1251    }
1252}
1253
1254#[macro_export]
1255macro_rules! try_luau {
1256    ($state:ident, $block:block) => {{
1257        $state.push_function(|$state| $block, Some(c"_try_lua"), 0);
1258        $state.call(0, 0)
1259    }};
1260}
1261
1262#[cfg(test)]
1263#[allow(non_snake_case)]
1264mod tests {
1265    use std::{
1266        ffi::{c_int, c_void},
1267        hint::black_box,
1268        rc::Rc,
1269    };
1270
1271    use crate::{
1272        Luau, LuauAllocator, _LuaState,
1273        compile::Compiler,
1274        lua_error, lua_tonumber, lua_upvalueindex,
1275        userdata::{UserdataBorrowError, UserdataRef},
1276        LuauLibs, LuauStatus, LuauType,
1277    };
1278
1279    #[test]
1280    fn try_test() {
1281        let luau = Luau::default();
1282
1283        let status = try_luau!(luau, {
1284            luau.push_boolean(true);
1285            luau.error()
1286        });
1287
1288        assert!(
1289            matches!(status, LuauStatus::LUA_ERRRUN),
1290            "Expected a runtime error"
1291        );
1292        assert!(luau.to_boolean(-1), "Expected the boolean to be true");
1293    }
1294
1295    #[test]
1296    #[should_panic]
1297    fn stack_checking_no_value() {
1298        let luau = Luau::default();
1299
1300        luau.is_number(1);
1301    }
1302
1303    #[test]
1304    #[should_panic]
1305    fn stack_checking_neg_no_value() {
1306        let luau = Luau::default();
1307
1308        luau.is_number(-1);
1309    }
1310
1311    #[test]
1312    fn stack_checking_has_value() {
1313        let luau = Luau::default();
1314
1315        luau.push_number(0.0);
1316
1317        luau.is_number(-1);
1318        luau.is_number(1);
1319    }
1320
1321    #[cfg(all(feature = "codegen", feature = "compiler"))]
1322    #[test]
1323    fn codegen() {
1324        use crate::compile::Compiler;
1325
1326        let compiler = Compiler::new();
1327        let luau = Luau::default();
1328
1329        let result = compiler.compile("(function() return 123 end)()");
1330
1331        assert!(result.is_ok(), "Compiler result is expected to be OK");
1332
1333        let load_result = luau.load(None, result.bytecode().unwrap(), 0);
1334
1335        assert!(load_result.is_ok(), "Load result should be Ok");
1336
1337        let load_result = luau.load(Some(c"test"), result.bytecode().unwrap(), 0);
1338
1339        assert!(load_result.is_ok(), "Load result should be Ok");
1340
1341        luau.codegen(-1);
1342
1343        luau.call(0, 0);
1344    }
1345
1346    #[test]
1347    fn load_error() {
1348        let luau = Luau::default();
1349
1350        let load_result = luau.load(None, b"\0Error!", 0);
1351
1352        // might change depending on luau updates
1353        assert!(
1354            load_result.is_err_and(|v| v == r#"[string ""]Error!"#),
1355            "Expected load result to be an error and be the correct error message."
1356        );
1357    }
1358
1359    #[test]
1360    fn load_libs() {
1361        let luau = Luau::default();
1362
1363        luau.load_libs(LuauLibs::ALL_LIBS);
1364
1365        luau.get_field(luau.globals(), "table");
1366
1367        assert_eq!(luau.type_of(-1), LuauType::LUA_TTABLE);
1368
1369        luau.get_field(luau.globals(), "print");
1370
1371        assert_eq!(luau.type_of(-1), LuauType::LUA_TFUNCTION);
1372    }
1373
1374    #[test]
1375    fn tables() {
1376        let luau = Luau::default();
1377
1378        luau.create_table();
1379
1380        luau.push_number(123.0);
1381
1382        luau.set_field(-2, "abc");
1383
1384        luau.get_field(-1, "abc");
1385
1386        assert_eq!(luau.to_number(-1), Some(123.0));
1387
1388        luau.pop(1);
1389
1390        // should be valid
1391        luau.set_field(-1, "a");
1392    }
1393
1394    #[test]
1395    fn metatables() {
1396        let luau = Luau::default();
1397
1398        luau.create_table();
1399        luau.create_table();
1400
1401        let mut called: Option<String> = None;
1402        luau.push_function(
1403            |luau| {
1404                called = luau.to_str(-1).map(Result::unwrap).map(str::to_string);
1405                0
1406            },
1407            None,
1408            0,
1409        );
1410        luau.set_field(-2, "__index");
1411        luau.set_metatable(-2);
1412
1413        let index = "Hello!".to_string();
1414        luau.get_field(-1, &index);
1415
1416        assert_eq!(called, Some(index));
1417    }
1418
1419    #[test]
1420    #[should_panic]
1421    fn unhandled_error() {
1422        let luau = Luau::default();
1423
1424        luau.push_string("hello error!");
1425
1426        unsafe {
1427            lua_error(luau.to_ptr());
1428        }
1429    }
1430
1431    #[test]
1432    fn pop() {
1433        let luau = Luau::default();
1434
1435        luau.push_number(0.0);
1436
1437        assert_eq!(luau.top(), 1);
1438
1439        luau.pop(1);
1440
1441        assert_eq!(luau.top(), 0);
1442
1443        luau.push_number(0.0);
1444        luau.push_number(0.0);
1445
1446        assert_eq!(luau.top(), 2);
1447
1448        luau.pop(2);
1449
1450        assert_eq!(luau.top(), 0);
1451    }
1452
1453    #[test]
1454    fn threads() {
1455        let luau = Luau::default();
1456
1457        let thread = luau.new_thread();
1458        let thread_state = thread.get_state();
1459
1460        let mut was_called = false;
1461
1462        thread_state.push_function(
1463            |_| {
1464                was_called = true;
1465                0
1466            },
1467            None,
1468            0,
1469        );
1470
1471        luau.resume(&thread, 0);
1472
1473        assert!(was_called, "Expected thread function to be called");
1474    }
1475
1476    #[test]
1477    fn app_data() {
1478        let luau = Luau::default();
1479
1480        luau.set_app_data(Some(true));
1481
1482        assert_eq!(luau.get_app_data::<bool>().copied(), Some(true))
1483    }
1484
1485    #[test]
1486    fn function_upvalue_test() {
1487        let luau = Luau::default();
1488
1489        luau.push_number(1.0);
1490        luau.push_number(2.0);
1491        luau.push_number(3.0);
1492
1493        luau.push_function(
1494            |luau| {
1495                assert_eq!(luau.to_number(luau.upvalue(1)), Some(1.0));
1496                assert_eq!(luau.to_number(luau.upvalue(2)), Some(2.0));
1497                assert_eq!(luau.to_number(luau.upvalue(3)), Some(3.0));
1498
1499                0
1500            },
1501            Some(c"test"),
1502            3,
1503        );
1504
1505        luau.call(0, 0);
1506    }
1507
1508    #[test]
1509    fn raw_function_upvalue_test() {
1510        let luau = Luau::default();
1511
1512        luau.push_number(1.0);
1513        luau.push_number(2.0);
1514        luau.push_number(3.0);
1515
1516        unsafe extern "C-unwind" fn test_extern_fn(state: *mut _LuaState) -> c_int {
1517            assert_eq!(lua_tonumber(state, lua_upvalueindex(1)), 1.0);
1518            assert_eq!(lua_tonumber(state, lua_upvalueindex(2)), 2.0);
1519            assert_eq!(lua_tonumber(state, lua_upvalueindex(3)), 3.0);
1520
1521            0
1522        }
1523
1524        unsafe {
1525            luau.push_raw_function(test_extern_fn, Some(c"test"), 3, None);
1526        }
1527
1528        luau.call(0, 0);
1529    }
1530
1531    #[test]
1532    fn continuations() {
1533        let luau = Luau::default();
1534        let compiler = Compiler::new();
1535
1536        let bc = compiler.compile("(...)()");
1537
1538        let thread = luau.new_thread();
1539        let thread_state = thread.get_state();
1540
1541        let mut cont = false;
1542
1543        thread_state.push_function_continuation(
1544            |l| l.yield_luau(0),
1545            None,
1546            0,
1547            |_, _| {
1548                cont = true;
1549                0
1550            },
1551        );
1552        thread_state.load(None, bc.bytecode().unwrap(), 0).unwrap();
1553
1554        luau.resume(&thread, 1);
1555        luau.resume(&thread, 0);
1556
1557        assert!(cont, "Expected that the continuation would be called.")
1558    }
1559
1560    #[test]
1561    #[should_panic]
1562    fn luau_panic_unwind() {
1563        struct PanicAllocator;
1564
1565        impl LuauAllocator for PanicAllocator {
1566            fn allocate(&self, _: usize) -> *mut std::ffi::c_void {
1567                panic!()
1568            }
1569
1570            fn reallocate(&self, _: *mut c_void, _: usize, _: usize) -> *mut std::ffi::c_void {
1571                panic!()
1572            }
1573
1574            fn deallocate(&self, _: *mut c_void, _: usize) {
1575                panic!()
1576            }
1577        }
1578
1579        {
1580            black_box(Luau::new(PanicAllocator {}));
1581        };
1582    }
1583
1584    #[test]
1585    fn function_check() {
1586        let luau = Luau::default();
1587
1588        luau.push_function(
1589            |l| {
1590                l.check_args(1, None);
1591
1592                0
1593            },
1594            None,
1595            0,
1596        );
1597
1598        let status = luau.call(0, 0);
1599
1600        assert!(
1601            matches!(status, LuauStatus::LUA_ERRRUN),
1602            "Expected there to be a runtime error."
1603        );
1604
1605        luau.push_function(
1606            |l| {
1607                l.check_args(1, None);
1608
1609                0
1610            },
1611            Some(c"test"),
1612            0,
1613        );
1614
1615        let status = luau.call(0, 0);
1616
1617        assert!(
1618            matches!(status, LuauStatus::LUA_ERRRUN),
1619            "Expected there to be a runtime error."
1620        );
1621
1622    }
1623
1624    #[test]
1625    fn userdata_borrow() {
1626        let luau = Luau::default();
1627
1628        luau.push_userdata(());
1629
1630        {
1631            let borrow = luau.try_borrow_userdata_mut::<()>(-1);
1632
1633            assert!(
1634                borrow.as_ref().is_some_and(Result::is_ok),
1635                "Expected mutable borrow for userdata to be valid"
1636            );
1637
1638            assert!(
1639                matches!(
1640                    luau.try_borrow_userdata::<()>(-1),
1641                    Some(Err(UserdataBorrowError::AlreadyMutable))
1642                ),
1643                "Expected immutable borrow for userdata to be invalid"
1644            );
1645
1646            assert!(
1647                matches!(
1648                    luau.try_borrow_userdata_mut::<()>(-1),
1649                    Some(Err(UserdataBorrowError::AlreadyMutable))
1650                ),
1651                "Expected mutable borrow for userdata to be invalid"
1652            );
1653
1654            drop(borrow);
1655
1656            assert!(
1657                matches!(luau.try_borrow_userdata_mut::<()>(-1), Some(Ok(_))),
1658                "Expected mutable borrow for userdata to be valid"
1659            );
1660        }
1661
1662        {
1663            let borrow = luau.try_borrow_userdata::<()>(-1);
1664
1665            assert!(
1666                matches!(borrow, Some(Ok(_))),
1667                "Expected to be a valid borrow"
1668            );
1669
1670            assert!(
1671                matches!(
1672                    luau.try_borrow_userdata_mut::<()>(-1),
1673                    Some(Err(UserdataBorrowError::AlreadyImmutable))
1674                ),
1675                "Expected borrow to be an AlreadyImmutable error"
1676            );
1677        }
1678    }
1679
1680    #[test]
1681    fn userdata_values() {
1682        let luau = Luau::default();
1683
1684        luau.push_userdata(());
1685
1686        let mut vec = Vec::with_capacity(128);
1687        for i in 0..128 {
1688            vec.push(i);
1689        }
1690
1691        luau.push_userdata(vec);
1692
1693        #[repr(transparent)]
1694        struct DropCheck(Rc<bool>);
1695
1696        let drop_rc = Rc::new(true);
1697        let yes_drop = DropCheck(drop_rc.clone());
1698
1699        luau.push_userdata(yes_drop);
1700
1701        assert!(luau.borrow_userdata(-3).is_some_and(
1702            #[allow(clippy::unit_cmp)]
1703            |v: UserdataRef<()>| *v == ()
1704        ));
1705
1706        assert!(luau
1707            .borrow_userdata::<Vec<i32>>(-2)
1708            .is_some_and(|v| v.is_sorted())); // is larger data preserved correctly
1709
1710        assert!(luau.borrow_userdata::<DropCheck>(-1).is_some());
1711
1712        drop(luau);
1713
1714        // assert!(, "Expected userdata to be dropped with luau state");
1715    }
1716
1717    #[test]
1718    fn string_values() {
1719        let luau = Luau::default();
1720
1721        const TEST_CONST: &[u8] = &[0xCA, 0xFE, 0xBA, 0xBE];
1722        const INVALID_SEQUENCE: &[u8] = &[0xC3, 0x28];
1723
1724        luau.push_string("Hello, world!");
1725        luau.push_string(TEST_CONST);
1726        luau.push_number(12345.0f64);
1727        luau.push_string(INVALID_SEQUENCE);
1728
1729        assert_eq!(luau.to_str_slice(-4), Some(b"Hello, world!" as _));
1730        assert_eq!(luau.to_str(-4), Some(Ok("Hello, world!")));
1731        assert_eq!(luau.to_str_slice(1), Some(b"Hello, world!" as _));
1732        assert_eq!(luau.to_str(1), Some(Ok("Hello, world!")));
1733
1734        assert_eq!(luau.to_str_slice(-3), Some(TEST_CONST));
1735        assert_eq!(luau.to_str_slice(2), Some(TEST_CONST));
1736
1737        assert_eq!(luau.to_str_slice(-2), Some(b"12345" as _));
1738        assert_eq!(luau.to_str(-2), Some(Ok("12345")));
1739        assert_eq!(luau.to_str_slice(3), Some(b"12345" as _));
1740        assert_eq!(luau.to_str(3), Some(Ok("12345")));
1741
1742        assert_eq!(luau.to_str_slice(-1), Some(INVALID_SEQUENCE));
1743        assert!(luau.to_str(-1).is_some_and(|r| r.is_err()));
1744        assert_eq!(luau.to_str_slice(4), Some(INVALID_SEQUENCE));
1745        assert!(luau.to_str(4).is_some_and(|r| r.is_err()));
1746    }
1747
1748    #[test]
1749    fn numeric_values() {
1750        let luau = Luau::default();
1751
1752        luau.push_number(f64::NAN);
1753        luau.push_number(f64::INFINITY);
1754        luau.push_number(f64::EPSILON);
1755        luau.push_string("12345");
1756
1757        // nan is not equal to itself, because that makes sense
1758        assert_eq!(
1759            luau.to_number(-4).map(f64::to_bits),
1760            Some(f64::NAN.to_bits())
1761        );
1762        assert_eq!(
1763            luau.to_number(1).map(f64::to_bits),
1764            Some(f64::NAN.to_bits())
1765        );
1766
1767        assert_eq!(luau.to_number(-3), Some(f64::INFINITY));
1768        assert_eq!(luau.to_number(2), Some(f64::INFINITY));
1769
1770        assert_eq!(luau.to_number(-2), Some(f64::EPSILON));
1771        assert_eq!(luau.to_number(3), Some(f64::EPSILON));
1772
1773        assert_eq!(luau.to_number(-1), Some(12345.0f64));
1774        assert_eq!(luau.to_number(4), Some(12345.0f64));
1775    }
1776}