#[cfg(feature = "compiler")]
pub mod compile;
pub mod ffi;
mod libs;
mod memory;
mod threads;
mod userdata;
use core::str;
use std::{
any::Any,
cell::Cell,
ffi::{c_int, c_uint, CString},
future::Future,
os::raw::c_void,
pin::Pin,
ptr::{null, null_mut},
rc::Rc,
slice,
task::{Context, RawWakerVTable, Waker},
};
use ffi::{
luauconf::{LUAI_MAXCSTACK, LUA_MEMORY_CATEGORIES},
prelude::*,
};
use memory::{luau_alloc_cb, DefaultLuauAllocator};
use threads::LuauThread;
use userdata::{
drop_userdata, dtor_rs_luau_userdata_callback, Userdata, UserdataBorrowError, UserdataRef,
UserdataRefMut, UD_TAG,
};
pub use ffi::prelude::LuauStatus;
pub use libs::LuauLibs;
pub use memory::LuauAllocator;
macro_rules! luau_stack_precondition {
($cond:expr) => {
assert!(
$cond,
"Stack indicies should not exceed the top of the stack or extend below."
)
};
}
struct AssociatedData {
main_thread_rc: Rc<Cell<bool>>,
allocator: Box<dyn LuauAllocator>,
app_data: Option<Box<dyn Any>>,
}
#[cfg(feature = "codegen")]
pub fn codegen_supported() -> bool {
unsafe { luau_codegen_supported() == 1 }
}
pub struct Luau {
owned: bool,
state: *mut _LuaState,
}
impl Luau {
unsafe fn new_state(allocator: impl LuauAllocator + 'static) -> *mut _LuaState {
let associated_data = Box::new(AssociatedData {
main_thread_rc: Rc::new(Cell::new(true)),
app_data: None,
allocator: Box::new(allocator),
});
let state = lua_newstate(luau_alloc_cb, Box::into_raw(associated_data) as _);
lua_setuserdatadtor(state, UD_TAG, Some(dtor_rs_luau_userdata_callback));
(*lua_callbacks(state)).panic = Some(fatal_error_handler);
state
}
pub fn new(allocator: impl LuauAllocator + 'static) -> Self {
let state = unsafe { Self::new_state(allocator) };
if state.is_null() {
panic!("Initialization of Luau failed");
}
Self { owned: true, state }
}
#[cfg(feature = "codegen")]
pub fn enable_codegen(&self) {
unsafe {
luau_codegen_create(self.state);
}
}
pub unsafe fn from_ptr(state: *mut _LuaState) -> Self {
Self {
owned: false,
state,
}
}
pub unsafe fn from_ptr_owned(state: *mut _LuaState) -> Self {
Self { owned: true, state }
}
const ASSOCIATED_DATA_ERROR: &str = "Expected associated data structure";
pub(crate) fn get_associated(&self) -> &AssociatedData {
unsafe {
let mut ptr: *const AssociatedData = null();
lua_getallocf(self.state, &raw mut ptr as _);
ptr.as_ref().expect(Self::ASSOCIATED_DATA_ERROR)
}
}
pub(crate) fn get_associated_mut(&self) -> *mut AssociatedData {
unsafe {
let mut ptr: *mut AssociatedData = null_mut();
lua_getallocf(self.state, &raw mut ptr as _);
assert!(!ptr.is_null(), "{}", Self::ASSOCIATED_DATA_ERROR);
ptr
}
}
pub fn get_app_data<T: Any>(&self) -> Option<&T> {
self.get_associated()
.app_data
.as_ref()
.and_then(|v| v.downcast_ref())
}
pub fn set_app_data<T: Any>(&self, ud: Option<T>) -> Option<Box<dyn Any>> {
let associated = unsafe { &mut *self.get_associated_mut() };
if let Some(v) = ud {
let boxed_data = Box::new(v);
associated.app_data.replace(boxed_data)
} else {
associated.app_data.take()
}
}
pub fn load_libs(&self, lib: LuauLibs) {
macro_rules! load_lib {
($func:ident) => {
unsafe {
self.push_raw_function($func, Some(stringify!($func)), 0, None);
self.push_string("");
self.call(1, 0);
};
};
($idnt:expr, $func:ident) => {
unsafe {
self.push_raw_function($func, Some(stringify!($func)), 0, None);
self.push_string($idnt);
self.call(1, 0);
};
};
}
if lib.has(LuauLibs::ALL_LIBS) {
unsafe { luaL_openlibs(self.state) };
return;
}
if lib.has(LuauLibs::LIB_BASE) {
load_lib!(luaopen_base);
}
if lib.has(LuauLibs::LIB_COROUTINE) {
load_lib!(LUA_COLIBNAME, luaopen_coroutine);
}
if lib.has(LuauLibs::LIB_TABLE) {
load_lib!(LUA_TABLIBNAME, luaopen_table);
}
if lib.has(LuauLibs::LIB_OS) {
load_lib!(LUA_OSLIBNAME, luaopen_os);
}
if lib.has(LuauLibs::LIB_STRING) {
load_lib!(LUA_STRLIBNAME, luaopen_string);
}
if lib.has(LuauLibs::LIB_MATH) {
load_lib!(LUA_MATHLIBNAME, luaopen_math);
}
if lib.has(LuauLibs::LIB_DEBUG) {
load_lib!(LUA_DBLIBNAME, luaopen_debug);
}
if lib.has(LuauLibs::LIB_UTF8) {
load_lib!(LUA_UTF8LIBNAME, luaopen_utf8);
}
if lib.has(LuauLibs::LIB_BIT32) {
load_lib!(LUA_BITLIBNAME, luaopen_bit32);
}
if lib.has(LuauLibs::LIB_BUFFER) {
load_lib!(LUA_BUFFERLIBNAME, luaopen_buffer);
}
}
#[inline]
pub fn to_ptr(&self) -> *mut _LuaState {
self.state
}
#[inline]
pub fn top(&self) -> c_int {
unsafe { lua_gettop(self.state) }
}
pub fn status(&self) -> LuauStatus {
unsafe { lua_status(self.state) }
}
pub fn yield_luau(&self, nresults: c_int) -> c_int {
assert!(
self.top() >= nresults,
"The number of yield returns must not exceed the stack size"
);
unsafe { lua_yield(self.state, nresults) }
}
pub fn break_luau(&self) -> c_int {
unsafe { lua_break(self.state) }
}
pub fn type_of(&self, idx: c_int) -> LuauType {
luau_stack_precondition!(self.check_index(idx));
unsafe { lua_type(self.state, idx) }
}
pub fn pop(&self, n: c_int) {
luau_stack_precondition!(self.check_index(-n));
unsafe { lua_settop(self.state, -(n + 1)) }
}
pub fn upvalue(&self, uv_idx: c_int) -> c_int {
lua_upvalueindex(uv_idx)
}
pub fn set_memory_category(&self, cat: c_int) {
assert!(
cat < LUA_MEMORY_CATEGORIES,
"Memory category index must not exceed {LUA_MEMORY_CATEGORIES}"
);
unsafe {
lua_setmemcat(self.state, cat);
}
}
fn absolutize(&self, idx: c_int) -> c_int {
if lua_ispseudo(idx) {
idx
} else if idx < 0 {
self.top() + idx + 1
} else {
idx
}
}
pub fn check_index(&self, idx: c_int) -> bool {
if idx <= LUA_REGISTRYINDEX {
return true;
}
let top = self.top();
let idx = if idx < 0 {
top.wrapping_add(idx)
} else {
idx
};
if idx < LUA_GLOBALSINDEX {
return true;
}
idx >= 0 && idx <= top && idx < LUAI_MAXCSTACK }
pub fn check_stack(&self, sz: c_int) -> bool {
unsafe { lua_checkstack(self.state, sz) == 1 }
}
#[inline]
pub fn registry(&self) -> c_int {
LUA_REGISTRYINDEX
}
#[inline]
pub fn globals(&self) -> c_int {
LUA_GLOBALSINDEX
}
pub fn check_args(&self, count: c_int, extra_message: Option<&str>) {
if self.top() >= count {
return;
}
unsafe {
luaL_argerrorL(
self.state,
count - self.top(),
extra_message
.map(|v| {
let cstr =
CString::new(v).expect("extra_message should not contain a null byte");
cstr.as_ptr()
})
.unwrap_or(null()),
);
}
}
pub fn is_nil(&self, idx: c_int) -> bool {
self.type_of(idx) == LuauType::LUA_TNIL
}
pub fn push_nil(&self) {
luau_stack_precondition!(self.check_stack(1));
unsafe {
lua_pushnil(self.state);
}
}
pub fn is_boolean(&self, idx: c_int) -> bool {
self.type_of(idx) == LuauType::LUA_TBOOLEAN
}
pub fn to_boolean(&self, idx: c_int) -> bool {
luau_stack_precondition!(self.check_index(idx));
unsafe { lua_toboolean(self.state, idx) == 1 }
}
pub fn push_boolean(&self, value: bool) {
luau_stack_precondition!(self.check_stack(1));
unsafe {
lua_pushboolean(self.state, value as i32);
}
}
pub fn is_number(&self, idx: c_int) -> bool {
self.type_of(idx) == LuauType::LUA_TNUMBER
}
pub fn push_integer(&self, n: c_int) {
luau_stack_precondition!(self.check_stack(1));
unsafe {
lua_pushinteger(self.state, n);
}
}
pub fn push_unsigned_integer(&self, n: c_uint) {
luau_stack_precondition!(self.check_stack(1));
unsafe {
lua_pushunsigned(self.state, n);
}
}
pub fn push_number(&self, n: f64) {
luau_stack_precondition!(self.check_stack(1));
unsafe {
lua_pushnumber(self.state, n);
}
}
pub fn to_number(&self, idx: c_int) -> Option<f64> {
luau_stack_precondition!(self.check_index(idx));
let mut is_number = 0;
let number = unsafe { lua_tonumberx(self.state, idx, &raw mut is_number) };
if is_number == 1 {
Some(number)
} else {
None
}
}
pub fn is_string(&self, idx: c_int) -> bool {
self.type_of(idx) == LuauType::LUA_TSTRING
}
pub fn push_string(&self, str: impl AsRef<[u8]>) {
luau_stack_precondition!(self.check_stack(1));
let slice = str.as_ref();
unsafe {
lua_pushlstring(self.state, slice.as_ptr() as _, slice.len());
}
}
pub fn to_str_slice(&self, idx: c_int) -> Option<&[u8]> {
luau_stack_precondition!(self.check_index(idx));
let mut len = 0;
let data = unsafe { lua_tolstring(self.state, idx, &mut len) };
if !data.is_null() {
unsafe { Some(std::slice::from_raw_parts(data as _, len)) }
} else {
None
}
}
pub fn to_str(&self, idx: c_int) -> Option<Result<&str, str::Utf8Error>> {
self.to_str_slice(idx).map(|v| str::from_utf8(v))
}
pub fn convert_to_str_slice(&self, idx: c_int) -> &[u8] {
luau_stack_precondition!(self.check_index(idx));
unsafe {
let mut len = 0;
let data = luaL_tolstring(self.state, idx, &raw mut len);
if data.is_null() {
panic!("Luau string conversion returned NULL ptr");
} else {
std::slice::from_raw_parts(data as _, len)
}
}
}
pub fn is_userdata<T: Any>(&self, idx: c_int) -> bool {
luau_stack_precondition!(self.check_index(idx));
unsafe {
let userdata_ptr: *mut Userdata<()> =
lua_touserdatatagged(self.state, idx, UD_TAG) as _;
!userdata_ptr.is_null() && (*userdata_ptr).is::<T>()
}
}
pub fn is_any_userdata<T: Any>(&self, idx: c_int) -> bool {
luau_stack_precondition!(self.check_index(idx));
unsafe { lua_isuserdata(self.state, idx) == 1 }
}
pub fn push_userdata<T: Any>(&self, object: T) {
luau_stack_precondition!(self.check_stack(1));
unsafe {
let userdata_ptr: *mut Userdata<T> =
lua_newuserdatatagged(self.state, size_of::<Userdata<T>>(), UD_TAG).cast();
let dtor = if std::mem::needs_drop::<T>() {
let fn_item: unsafe fn(*mut Userdata<T>) = drop_userdata::<T>;
Some(fn_item)
} else {
None
};
userdata_ptr.write(Userdata {
id: object.type_id(),
count_cell: Cell::new(0),
dtor,
inner: object,
});
}
}
fn get_userdata_ptr<T: Any>(&self, idx: c_int) -> Option<*mut Userdata<T>> {
luau_stack_precondition!(self.check_index(idx));
unsafe {
let userdata_ptr: *mut Userdata<()> =
lua_touserdatatagged(self.state, idx, UD_TAG) as _;
if !userdata_ptr.is_null() && (*userdata_ptr).is::<T>() {
Some(userdata_ptr as _)
} else {
None
}
}
}
pub fn try_borrow_userdata<T: Any>(
&self,
idx: c_int,
) -> Option<Result<UserdataRef<T>, UserdataBorrowError>> {
unsafe {
let userdata_ptr = self.get_userdata_ptr(idx)?;
Some(UserdataRef::try_from_ptr(userdata_ptr))
}
}
pub fn borrow_userdata<T: Any>(&self, idx: c_int) -> Option<UserdataRef<T>> {
self.try_borrow_userdata(idx).map(Result::unwrap)
}
pub fn try_borrow_userdata_mut<T: Any>(
&self,
idx: c_int,
) -> Option<Result<UserdataRefMut<T>, UserdataBorrowError>> {
unsafe {
let userdata_ptr = self.get_userdata_ptr(idx)?;
Some(UserdataRefMut::try_from_ptr(userdata_ptr))
}
}
pub unsafe fn get_userdata_unchecked<T: 'static>(&self, idx: c_int) -> Option<&mut T> {
luau_stack_precondition!(self.check_index(idx));
unsafe { Some(&mut (*self.get_userdata_ptr(idx)?).inner) }
}
pub fn is_lightuserdata(&self, idx: c_int) -> bool {
self.type_of(idx) == LuauType::LUA_TLIGHTUSERDATA
}
pub fn to_lightuserdata<T>(&self, idx: c_int) -> Option<*mut T> {
luau_stack_precondition!(self.check_index(idx));
unsafe {
let ptr: *mut T = lua_tolightuserdata(self.state, idx).cast();
if ptr.is_null() {
None
} else {
Some(ptr)
}
}
}
pub fn is_buffer(&self, idx: c_int) -> bool {
self.type_of(idx) == LuauType::LUA_TBUFFER
}
pub fn push_buffer(&mut self, size: usize) -> &mut [u8] {
luau_stack_precondition!(self.check_stack(1));
unsafe {
let ptr: *mut u8 = lua_newbuffer(self.state, size) as _;
std::slice::from_raw_parts_mut(ptr, size)
}
}
pub fn push_buffer_from_slice(&mut self, slice: impl AsRef<[u8]>) -> &mut [u8] {
let slice = slice.as_ref();
let buffer = self.push_buffer(slice.len());
buffer.copy_from_slice(slice);
buffer
}
pub fn to_buffer(&mut self, idx: c_int) -> Option<&mut [u8]> {
luau_stack_precondition!(self.check_index(idx));
let mut len = 0;
let data: *mut u8 = unsafe { lua_tobuffer(self.state, idx, &mut len) as _ };
if !data.is_null() {
unsafe { Some(slice::from_raw_parts_mut(data, len)) }
} else {
None
}
}
pub fn to_buffer_ptr(&self, idx: c_int, len: &mut usize) -> *mut c_void {
luau_stack_precondition!(self.check_index(idx));
unsafe { lua_tobuffer(self.state, idx, len) }
}
pub fn create_table(&self) {
unsafe {
lua_createtable(self.state, 0, 0);
}
}
pub fn create_table_with_capacity(&self, narr: c_int, nrec: c_int) {
unsafe {
lua_createtable(self.state, narr, nrec);
}
}
pub fn shift(&self, to: c_int) {
luau_stack_precondition!(self.check_index(to));
unsafe {
lua_insert(self.state, to);
}
}
pub fn reference(&self, idx: c_int) -> RefIndex {
luau_stack_precondition!(self.check_index(idx));
unsafe { lua_ref(self.state, idx) }
}
pub fn get_reference(&self, ref_index: RefIndex) -> LuauType {
luau_stack_precondition!(self.check_stack(1));
unsafe { lua_getref(self.state, ref_index) }
}
pub fn unreference(&self, ref_index: RefIndex) {
unsafe {
lua_unref(self.state, ref_index);
}
}
pub fn is_table(&self, idx: c_int) -> bool {
self.type_of(idx) == LuauType::LUA_TTABLE
}
pub fn set_field(&self, idx: c_int, field: impl AsRef<[u8]>) {
luau_stack_precondition!(self.check_stack(1));
let idx = if idx < 0 && !lua_ispseudo(idx) && !(idx == -1 && self.top() == 1) {
idx - 1
} else {
idx
};
self.push_string(field);
self.shift(-2);
self.set_table(idx);
}
pub fn raw_set_field(&self, idx: c_int, field: &str) {
luau_stack_precondition!(self.check_stack(1));
let idx = if idx < 0 && !lua_ispseudo(idx) && !(idx == -1 && self.top() == 1) {
idx - 1
} else {
idx
};
self.push_string(field);
self.shift(-2);
self.raw_set_table(idx);
}
pub fn set_table(&self, idx: c_int) {
assert!(
self.top() >= 2,
"There must be a key and value on the stack to set table"
);
luau_stack_precondition!(self.check_index(idx));
unsafe {
lua_settable(self.state, idx);
}
}
pub fn raw_set_table(&self, idx: c_int) {
assert!(
self.top() >= 2,
"There must be a key and value on the stack to set table"
);
luau_stack_precondition!(self.check_index(idx));
unsafe {
lua_rawset(self.state, idx);
}
}
pub fn get_field(&self, idx: c_int, field: impl AsRef<[u8]>) {
luau_stack_precondition!(self.check_index(idx));
luau_stack_precondition!(self.check_stack(1));
let idx = if idx < 0 && !lua_ispseudo(idx) {
idx - 1
} else {
idx
};
self.push_string(field);
self.get_table(idx);
}
pub fn raw_get_field(&self, idx: c_int, field: impl AsRef<[u8]>) {
luau_stack_precondition!(self.check_index(idx));
luau_stack_precondition!(self.check_stack(1));
let idx = if idx < 0 && !lua_ispseudo(idx) {
idx - 1
} else {
idx
};
self.push_string(field);
self.raw_get_table(idx);
}
pub fn get_table(&self, idx: c_int) {
assert!(
self.top() >= 1,
"There must be a key on the stack to index the table"
);
luau_stack_precondition!(self.check_index(idx));
unsafe {
lua_gettable(self.state, idx);
}
}
pub fn raw_get_table(&self, idx: c_int) {
assert!(
self.top() >= 1,
"There must be a key on the stack to index the table"
);
luau_stack_precondition!(self.check_index(idx));
unsafe {
lua_rawget(self.state, idx);
}
}
pub fn set_readonly(&self, idx: c_int, enabled: bool) {
assert!(self.is_table(idx));
unsafe {
lua_setreadonly(self.state, idx, enabled as c_int);
}
}
pub fn set_metatable(&self, idx: c_int) {
assert!(
self.is_table(-1),
"Expected the value at the top of the stack to be a table"
);
luau_stack_precondition!(self.check_index(idx));
unsafe {
lua_setmetatable(self.state, idx);
}
}
pub fn is_vector(&self, idx: c_int) -> bool {
self.type_of(idx) == LuauType::LUA_TVECTOR
}
pub fn push_vector(&self, x: f32, y: f32, z: f32, #[cfg(feature = "luau_vector4")] w: f32) {
luau_stack_precondition!(self.check_stack(1));
unsafe {
#[cfg(not(feature = "luau_vector4"))]
lua_pushvector(self.state, x, y, z);
#[cfg(feature = "luau_vector4")]
lua_pushvector(self.state, x, y, z, w);
}
}
#[cfg(not(feature = "luau_vector4"))]
pub fn to_vector(&self, idx: c_int) -> Option<(f32, f32, f32)> {
luau_stack_precondition!(self.check_index(idx));
unsafe {
Option::from(lua_tovector(self.state, idx)).map(|ptr| (*ptr, *ptr.add(1), *ptr.add(2)))
}
}
#[cfg(feature = "luau_vector4")]
pub fn to_vector(&self, idx: c_int) -> Option<(f32, f32, f32, f32)> {
luau_stack_precondition!(self.check_index(idx));
unsafe {
Option::from(lua_tovector(self.state, idx))
.map(|ptr| (*ptr, *ptr.add(1), *ptr.add(2), *ptr.add(3)))
}
}
pub fn is_thread(&self, idx: c_int) -> bool {
self.type_of(idx) == LuauType::LUA_TTHREAD
}
pub fn push_thread(&self) -> LuauThread {
unsafe {
let thread_ptr = lua_newthread(self.state);
LuauThread::from_ptr(thread_ptr, self.get_associated().main_thread_rc.clone())
}
}
pub fn get_thread(&self, idx: c_int) -> Option<LuauThread> {
let ptr = unsafe { lua_tothread(self.state, idx) };
if !ptr.is_null() {
unsafe {
Some(LuauThread::from_ptr(
ptr,
self.get_associated().main_thread_rc.clone(),
))
}
} else {
None
}
}
pub fn get_thread_data<T: Any>(&self) -> Option<&T> {
let boxed = unsafe {
(lua_getthreaddata(self.state) as *const Box<dyn Any>).as_ref()?
};
boxed.downcast_ref()
}
pub fn set_thread_data<T: Any>(&self, userdata: T) {
let b: Box<dyn Any> = Box::new(userdata);
unsafe {
lua_setthreaddata(self.state, Box::into_raw(b) as _);
}
}
pub fn resume(&self, luau_thread: &LuauThread, nargs: c_int) -> LuauStatus {
unsafe { lua_resume(luau_thread.get_state().state, self.state, nargs) }
}
pub fn is_function(&self, idx: c_int) -> bool {
self.type_of(idx) == LuauType::LUA_TFUNCTION
}
pub unsafe fn push_raw_function(
&self,
func: CFunction,
debug_name: Option<&str>,
num_upvals: c_int,
continuation: Option<LuaContinuation>,
) {
luau_stack_precondition!(self.check_stack(1));
assert!(
self.top() >= num_upvals,
"The number of upvalues for a raw function must not exceed the stack length"
);
unsafe {
lua_pushcclosurek(
self.state,
func,
if let Some(name) = debug_name {
let name =
CString::new(name).expect("chunk name should not contain a null byte");
name.as_ptr()
} else {
null()
},
num_upvals,
continuation,
);
}
}
pub fn push_function_continuation<
F: FnMut(&Luau) -> c_int,
Cont: FnMut(&Luau, LuauStatus) -> c_int,
>(
&self,
func: F,
debug_name: Option<&str>,
num_upvals: c_int,
cont: Cont,
) {
assert!(
self.top() >= num_upvals,
"The number of upvalues for a raw function must not exceed the stack length"
);
luau_stack_precondition!(self.check_stack(2));
struct CallState<F, Cont> {
func: F,
cont: Cont,
}
let call_state = Box::new(CallState { func, cont });
unsafe extern "C-unwind" fn invoke_fn<
F: FnMut(&Luau) -> c_int,
Cont: FnMut(&Luau, LuauStatus) -> c_int,
>(
state: *mut _LuaState,
) -> c_int {
let call_state =
lua_tolightuserdata(state, lua_upvalueindex(1)).cast::<CallState<F, Cont>>();
let luau = Luau::from_ptr(state);
((*call_state).func)(&luau)
}
unsafe extern "C-unwind" fn invoke_continuation<
F: FnMut(&Luau) -> c_int,
Cont: FnMut(&Luau, LuauStatus) -> c_int,
>(
state: *mut _LuaState,
status: c_int,
) -> c_int {
let call_state =
lua_tolightuserdata(state, lua_upvalueindex(1)).cast::<CallState<F, Cont>>();
let luau = Luau::from_ptr(state);
((*call_state).cont)(&luau, std::mem::transmute::<c_int, LuauStatus>(status))
}
unsafe {
lua_pushlightuserdata(self.state, Box::into_raw(call_state) as _);
self.push_raw_function(
invoke_fn::<F, Cont>,
debug_name,
1 + num_upvals,
Some(invoke_continuation::<F, Cont>),
);
}
}
pub fn push_function<F: FnMut(&Luau) -> i32>(
&self,
func: F,
debug_name: Option<&str>,
num_upvals: c_int,
) {
assert!(
self.top() >= num_upvals,
"The number of upvalues for a raw function must not exceed the stack length"
);
luau_stack_precondition!(self.check_stack(2));
let func_box = Box::new(func);
unsafe extern "C-unwind" fn invoke_fn<T: FnMut(&Luau) -> i32>(
state: *mut _LuaState,
) -> c_int {
let func = lua_tolightuserdata(state, lua_upvalueindex(1)).cast::<T>();
let state = Luau::from_ptr(state);
(*func)(&state)
}
unsafe {
lua_pushlightuserdata(self.state, Box::into_raw(func_box) as _);
self.push_raw_function(invoke_fn::<F>, debug_name, 1 + num_upvals, None);
}
}
pub fn call(&self, nargs: c_int, nresults: c_int) -> LuauStatus {
assert!(
self.is_function(-1),
"The value at top of stack must be a function"
);
assert!(
self.top() >= nargs,
"Argument count may not exceed the total stack size"
);
luau_stack_precondition!(self.check_stack(nresults));
unsafe { lua_pcall(self.state, nargs, nresults, 0) }
}
pub fn load(&self, chunk_name: Option<&str>, bytecode: &[u8], env: c_int) -> Result<(), &str> {
luau_stack_precondition!(self.check_index(env));
luau_stack_precondition!(self.check_stack(2));
let success = unsafe {
luau_load(
self.state,
chunk_name.or(Some("\0")).map(str::as_ptr).unwrap() as _,
bytecode.as_ptr() as _,
bytecode.len(),
env,
)
};
if success == 0 {
Ok(())
} else {
Err(self.to_str(-1).unwrap().unwrap())
}
}
#[cfg(feature = "codegen")]
pub fn codegen(&self, idx: c_int) {
luau_stack_precondition!(self.check_index(idx));
assert!(
self.is_function(idx),
"The value at idx must be a function to be compiled with codegen"
);
unsafe {
luau_codegen_compile(self.state, idx);
}
}
}
unsafe extern "C-unwind" fn fatal_runtime_error_handler(state: *mut _LuaState) -> c_int {
let luau = unsafe { Luau::from_ptr(state) };
panic!(
"Uncaught runtime error - \"{}\"",
String::from_utf8_lossy(luau.convert_to_str_slice(-1))
);
}
unsafe extern "C-unwind" fn fatal_error_handler(state: *mut _LuaState, status: LuauStatus) {
match status {
LuauStatus::LUA_ERRRUN => fatal_runtime_error_handler(state),
LuauStatus::LUA_ERRMEM => std::process::abort(),
LuauStatus::LUA_ERRERR => panic!("Error originating from error handling mechanism"),
_ => unreachable!(),
};
panic!("Fatal error in Luau execution");
}
impl Default for Luau {
fn default() -> Self {
Self::new(DefaultLuauAllocator {})
}
}
impl Drop for Luau {
fn drop(&mut self) {
if !self.owned {
return;
}
unsafe {
let mut associated: *mut AssociatedData = null_mut();
lua_getallocf(self.state, &raw mut associated as _);
let associated_owned = Box::from_raw(associated);
associated_owned.main_thread_rc.set(false);
lua_close(self.state);
_ = associated_owned
}
}
}
#[cfg(test)]
#[allow(non_snake_case)]
mod tests {
use std::{
ffi::{c_int, c_void},
hint::black_box,
rc::Rc,
};
use crate::{
Luau, LuauAllocator, _LuaState,
compile::Compiler,
lua_error, lua_tonumber, lua_upvalueindex,
userdata::{UserdataBorrowError, UserdataRef},
LuauLibs, LuauStatus, LuauType,
};
#[test]
#[should_panic]
fn stack_checking_no_value() {
let luau = Luau::default();
luau.is_number(1);
}
#[test]
#[should_panic]
fn stack_checking_neg_no_value() {
let luau = Luau::default();
luau.is_number(-1);
}
#[test]
fn stack_checking_has_value() {
let luau = Luau::default();
luau.push_number(0.0);
luau.is_number(-1);
luau.is_number(1);
luau.is_number(0); }
#[cfg(all(feature = "codegen", feature = "compiler"))]
#[test]
fn codegen() {
use crate::compile::Compiler;
let compiler = Compiler::new();
let luau = Luau::default();
let result = compiler.compile("(function() return 123 end)()");
assert!(result.is_ok(), "Compiler result is expected to be OK");
let load_result = luau.load(None, result.bytecode().unwrap(), 0);
assert!(load_result.is_ok(), "Load result should be Ok");
luau.codegen(-1);
luau.call(0, 0);
}
#[test]
fn load_error() {
let luau = Luau::default();
let load_result = luau.load(None, b"\0Error!", 0);
assert!(
load_result.is_err_and(|v| v == r#"[string ""]Error!"#),
"Expected load result to be an error and be the correct error message."
);
}
#[test]
fn load_libs() {
let luau = Luau::default();
luau.load_libs(LuauLibs::ALL_LIBS);
luau.get_field(luau.globals(), "table");
assert_eq!(luau.type_of(-1), LuauType::LUA_TTABLE);
luau.get_field(luau.globals(), "print");
assert_eq!(luau.type_of(-1), LuauType::LUA_TFUNCTION);
}
#[test]
fn tables() {
let luau = Luau::default();
luau.create_table();
luau.push_number(123.0);
luau.set_field(-2, "abc");
luau.get_field(-1, "abc");
assert_eq!(luau.to_number(-1), Some(123.0));
luau.pop(1);
luau.set_field(-1, "a");
}
#[test]
fn metatables() {
let luau = Luau::default();
luau.create_table();
luau.create_table();
let mut called: Option<String> = None;
luau.push_function(
|luau| {
called = luau.to_str(-1).map(Result::unwrap).map(str::to_string);
0
},
None,
0,
);
luau.set_field(-2, "__index");
luau.set_metatable(-2);
let index = "Hello!".to_string();
luau.get_field(-1, &index);
assert_eq!(called, Some(index));
}
#[test]
#[should_panic]
fn unhandled_error() {
let luau = Luau::default();
luau.push_string("hello error!");
unsafe {
lua_error(luau.to_ptr());
}
}
#[test]
fn pop() {
let luau = Luau::default();
luau.push_number(0.0);
assert_eq!(luau.top(), 1);
luau.pop(1);
assert_eq!(luau.top(), 0);
luau.push_number(0.0);
luau.push_number(0.0);
assert_eq!(luau.top(), 2);
luau.pop(2);
assert_eq!(luau.top(), 0);
}
#[test]
fn threads() {
let luau = Luau::default();
let thread = luau.push_thread();
let thread_state = thread.get_state();
let mut was_called = false;
thread_state.push_function(
|_| {
was_called = true;
0
},
None,
0,
);
luau.resume(&thread, 0);
assert!(was_called, "Expected thread function to be called");
}
#[test]
fn app_data() {
let luau = Luau::default();
luau.set_app_data(Some(true));
assert_eq!(luau.get_app_data::<bool>().copied(), Some(true))
}
#[test]
fn function_upvalue_test() {
let luau = Luau::default();
luau.push_number(1.0);
luau.push_number(2.0);
luau.push_number(3.0);
luau.push_function(
|luau| {
assert_eq!(luau.to_number(luau.upvalue(1)), Some(1.0));
assert_eq!(luau.to_number(luau.upvalue(2)), Some(2.0));
assert_eq!(luau.to_number(luau.upvalue(3)), Some(3.0));
0
},
Some("test"),
3,
);
luau.call(0, 0);
}
#[test]
fn raw_function_upvalue_test() {
let luau = Luau::default();
luau.push_number(1.0);
luau.push_number(2.0);
luau.push_number(3.0);
unsafe extern "C-unwind" fn test_extern_fn(state: *mut _LuaState) -> c_int {
assert_eq!(lua_tonumber(state, lua_upvalueindex(1)), 1.0);
assert_eq!(lua_tonumber(state, lua_upvalueindex(2)), 2.0);
assert_eq!(lua_tonumber(state, lua_upvalueindex(3)), 3.0);
0
}
unsafe {
luau.push_raw_function(test_extern_fn, Some("test"), 3, None);
}
luau.call(0, 0);
}
#[test]
fn continuations() {
let luau = Luau::default();
let compiler = Compiler::new();
let bc = compiler.compile("(...)()");
let thread = luau.push_thread();
let thread_state = thread.get_state();
let mut cont = false;
thread_state.push_function_continuation(
|l| l.yield_luau(0),
None,
0,
|_, _| {
cont = true;
0
},
);
thread_state.load(None, bc.bytecode().unwrap(), 0).unwrap();
luau.resume(&thread, 1);
luau.resume(&thread, 0);
assert!(cont, "Expected that the continuation would be called.")
}
#[test]
fn luau_panic_unwind() {
struct PanicAllocator {}
impl LuauAllocator for PanicAllocator {
fn allocate(&self, _: usize) -> *mut std::ffi::c_void {
panic!()
}
fn reallocate(&self, _: *mut c_void, _: usize, _: usize) -> *mut std::ffi::c_void {
panic!()
}
fn deallocate(&self, _: *mut c_void, _: usize) {
panic!()
}
}
assert!(std::panic::catch_unwind(|| {
black_box(Luau::new(PanicAllocator {}));
})
.is_err());
}
#[test]
fn function_check() {
let luau = Luau::default();
luau.push_function(
|l| {
l.check_args(1, None);
0
},
None,
0,
);
let status = luau.call(0, 0);
assert!(
matches!(status, LuauStatus::LUA_ERRRUN),
"Expected there to be a runtime error."
);
}
#[test]
fn userdata_borrow() {
let luau = Luau::default();
luau.push_userdata(());
{
let borrow = luau.try_borrow_userdata_mut::<()>(-1);
assert!(
borrow.as_ref().is_some_and(Result::is_ok),
"Expected mutable borrow for userdata to be valid"
);
assert!(
matches!(
luau.try_borrow_userdata::<()>(-1),
Some(Err(UserdataBorrowError::AlreadyMutable))
),
"Expected immutable borrow for userdata to be invalid"
);
assert!(
matches!(
luau.try_borrow_userdata_mut::<()>(-1),
Some(Err(UserdataBorrowError::AlreadyMutable))
),
"Expected mutable borrow for userdata to be invalid"
);
drop(borrow);
assert!(
matches!(luau.try_borrow_userdata_mut::<()>(-1), Some(Ok(_))),
"Expected mutable borrow for userdata to be valid"
);
}
{
let borrow = luau.try_borrow_userdata::<()>(-1);
assert!(
matches!(borrow, Some(Ok(_))),
"Expected to be a valid borrow"
);
assert!(
matches!(
luau.try_borrow_userdata_mut::<()>(-1),
Some(Err(UserdataBorrowError::AlreadyImmutable))
),
"Expected borrow to be an AlreadyImmutable error"
);
}
}
#[test]
fn userdata_values() {
let luau = Luau::default();
luau.push_userdata(());
let mut vec = Vec::with_capacity(128);
for i in 0..128 {
vec.push(i);
}
luau.push_userdata(vec);
#[repr(transparent)]
struct DropCheck(Rc<bool>);
let drop_rc = Rc::new(true);
let yes_drop = DropCheck(drop_rc.clone());
luau.push_userdata(yes_drop);
assert!(luau.borrow_userdata(-3).is_some_and(
#[allow(clippy::unit_cmp)]
|v: UserdataRef<()>| *v == ()
));
assert!(luau
.borrow_userdata::<Vec<i32>>(-2)
.is_some_and(|v| v.is_sorted()));
assert!(luau.borrow_userdata::<DropCheck>(-1).is_some());
drop(luau);
}
#[test]
fn string_values() {
let luau = Luau::default();
const TEST_CONST: &[u8] = &[0xCA, 0xFE, 0xBA, 0xBE];
const INVALID_SEQUENCE: &[u8] = &[0xC3, 0x28];
luau.push_string("Hello, world!");
luau.push_string(TEST_CONST);
luau.push_number(12345.0f64);
luau.push_string(INVALID_SEQUENCE);
assert_eq!(luau.to_str_slice(-4), Some(b"Hello, world!" as _));
assert_eq!(luau.to_str(-4), Some(Ok("Hello, world!")));
assert_eq!(luau.to_str_slice(1), Some(b"Hello, world!" as _));
assert_eq!(luau.to_str(1), Some(Ok("Hello, world!")));
assert_eq!(luau.to_str_slice(-3), Some(TEST_CONST));
assert_eq!(luau.to_str_slice(2), Some(TEST_CONST));
assert_eq!(luau.to_str_slice(-2), Some(b"12345" as _));
assert_eq!(luau.to_str(-2), Some(Ok("12345")));
assert_eq!(luau.to_str_slice(3), Some(b"12345" as _));
assert_eq!(luau.to_str(3), Some(Ok("12345")));
assert_eq!(luau.to_str_slice(-1), Some(INVALID_SEQUENCE));
assert!(luau.to_str(-1).is_some_and(|r| r.is_err()));
assert_eq!(luau.to_str_slice(4), Some(INVALID_SEQUENCE));
assert!(luau.to_str(4).is_some_and(|r| r.is_err()));
}
#[test]
fn numeric_values() {
let luau = Luau::default();
luau.push_number(f64::NAN);
luau.push_number(f64::INFINITY);
luau.push_number(f64::EPSILON);
luau.push_string("12345");
assert_eq!(
luau.to_number(-4).map(f64::to_bits),
Some(f64::NAN.to_bits())
);
assert_eq!(
luau.to_number(1).map(f64::to_bits),
Some(f64::NAN.to_bits())
);
assert_eq!(luau.to_number(-3), Some(f64::INFINITY));
assert_eq!(luau.to_number(2), Some(f64::INFINITY));
assert_eq!(luau.to_number(-2), Some(f64::EPSILON));
assert_eq!(luau.to_number(3), Some(f64::EPSILON));
assert_eq!(luau.to_number(-1), Some(12345.0f64));
assert_eq!(luau.to_number(4), Some(12345.0f64));
}
}