pub mod async_thread;
pub mod call_info;
mod const_string;
pub mod debug_info;
mod execute;
mod file_layout;
pub mod lua_error;
pub mod lua_limits;
mod lua_ref;
mod lua_rng;
mod lua_state;
pub mod opcode;
mod safe_option;
#[cfg(feature = "sandbox")]
mod sandbox;
#[cfg(feature = "shared-proto")]
mod shared_proto;
mod string_arth;
pub mod table_builder;
use crate::compiler::{LuaLanguageLevel, compile_code, compile_code_with_name};
use crate::gc::GC;
use crate::lua_value::lua_convert::{FromLua, FromLuaMulti, IntoLua, collect_into_lua_values};
use crate::lua_value::{
Chunk, LuaUpvalue, LuaUserdata, LuaValue, LuaValueKind, LuaValuePtr, UpvalueStore,
};
pub use crate::lua_vm::call_info::CallInfo;
use crate::lua_vm::const_string::ConstString;
pub use crate::lua_vm::debug_info::DebugInfo;
use crate::lua_vm::execute::lua_execute;
use crate::lua_vm::file_layout::inspect_file_chunk_layout;
pub use crate::lua_vm::lua_error::LuaError;
use crate::lua_vm::lua_ref::RefManager;
pub use crate::lua_vm::lua_ref::{
LUA_NOREF, LUA_REFNIL, LuaAnyRef, LuaFunctionRef, LuaRefValue, LuaStringRef, LuaTableRef,
RefId, UserDataRef,
};
pub use crate::lua_vm::lua_state::LuaState;
pub use crate::lua_vm::safe_option::SafeOption;
#[cfg(feature = "sandbox")]
pub use crate::lua_vm::sandbox::SandboxConfig;
use crate::platform_time::{PlatformInstant, unix_nanos};
use crate::stdlib::Stdlib;
use crate::{
CreateResult, GcKind, LuaEnum, LuaRegistrable, ObjectAllocator, OpaqueUserData, RustCallback,
TableBuilder, ThreadPtr, UpvaluePtr, lib_registry,
};
pub use execute::TmKind;
pub use execute::{get_metamethod_event, get_metatable};
pub use lua_rng::LuaRng;
pub use opcode::{Instruction, OpCode};
use std::future::Future;
pub use string_arth::*;
pub type LuaResult<T> = Result<T, LuaError>;
pub type CFunction = fn(&mut LuaState) -> LuaResult<usize>;
#[doc(hidden)]
pub trait LuaTypedCallback<Args, R>: 'static {
fn invoke_typed(&self, state: &mut LuaState) -> LuaResult<usize>;
}
#[doc(hidden)]
pub trait LuaTypedAsyncCallback<Args, R>: 'static {
fn invoke_typed_async(&self, state: &mut LuaState) -> LuaResult<async_thread::AsyncFuture>;
}
fn typed_callback_arg<T: FromLua>(state: &mut LuaState, index: usize) -> LuaResult<T> {
let value = state.get_arg(index).unwrap_or_default();
match T::from_lua(value, state) {
Ok(value) => Ok(value),
Err(msg) => Err(state.error(msg)),
}
}
impl<Func, R> LuaTypedCallback<(), R> for Func
where
Func: Fn() -> R + 'static,
R: IntoLua,
{
fn invoke_typed(&self, state: &mut LuaState) -> LuaResult<usize> {
match (self)().into_lua(state) {
Ok(count) => Ok(count),
Err(msg) => Err(state.error(msg)),
}
}
}
macro_rules! impl_lua_typed_callback {
($(($(($ty:ident, $value:ident) => $index:literal),+)),* $(,)?) => {
$(
impl<Func, R, $($ty),+> LuaTypedCallback<($($ty,)+), R> for Func
where
Func: Fn($($ty),+) -> R + 'static,
R: IntoLua,
$($ty: FromLua),+
{
fn invoke_typed(&self, state: &mut LuaState) -> LuaResult<usize> {
$(
let $value = typed_callback_arg::<$ty>(state, $index)?;
)+
match (self)($($value),+).into_lua(state) {
Ok(count) => Ok(count),
Err(msg) => Err(state.error(msg)),
}
}
}
)*
};
}
impl_lua_typed_callback!(
((A, a) => 1),
((A, a) => 1, (B, b) => 2),
((A, a) => 1, (B, b) => 2, (C, c) => 3),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4, (E, e) => 5),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4, (E, e) => 5, (T6, t6) => 6),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4, (E, e) => 5, (T6, t6) => 6, (T7, t7) => 7),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4, (E, e) => 5, (T6, t6) => 6, (T7, t7) => 7, (T8, t8) => 8)
);
impl<Func, Fut, R> LuaTypedAsyncCallback<(), R> for Func
where
Func: Fn() -> Fut + 'static,
Fut: Future<Output = LuaResult<R>> + 'static,
R: async_thread::IntoAsyncLua,
{
fn invoke_typed_async(&self, _state: &mut LuaState) -> LuaResult<async_thread::AsyncFuture> {
let future = (self)();
Ok(Box::pin(async move {
let value = future.await?;
Ok(value.into_async_lua())
}))
}
}
macro_rules! impl_lua_typed_async_callback {
($(($(($ty:ident, $value:ident) => $index:literal),+)),* $(,)?) => {
$(
impl<Func, Fut, R, $($ty),+> LuaTypedAsyncCallback<($($ty,)+), R> for Func
where
Func: Fn($($ty),+) -> Fut + 'static,
Fut: Future<Output = LuaResult<R>> + 'static,
R: async_thread::IntoAsyncLua,
$($ty: FromLua),+
{
fn invoke_typed_async(&self, state: &mut LuaState) -> LuaResult<async_thread::AsyncFuture> {
$(
let $value = typed_callback_arg::<$ty>(state, $index)?;
)+
let future = (self)($($value),+);
Ok(Box::pin(async move {
let value = future.await?;
Ok(value.into_async_lua())
}))
}
}
)*
};
}
impl_lua_typed_async_callback!(
((A, a) => 1),
((A, a) => 1, (B, b) => 2),
((A, a) => 1, (B, b) => 2, (C, c) => 3),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4, (E, e) => 5),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4, (E, e) => 5, (T6, t6) => 6),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4, (E, e) => 5, (T6, t6) => 6, (T7, t7) => 7),
((A, a) => 1, (B, b) => 2, (C, c) => 3, (D, d) => 4, (E, e) => 5, (T6, t6) => 6, (T7, t7) => 7, (T8, t8) => 8)
);
pub const LUA_HOOKCALL: i32 = 0;
pub const LUA_HOOKRET: i32 = 1;
pub const LUA_HOOKLINE: i32 = 2;
pub const LUA_HOOKCOUNT: i32 = 3;
pub const LUA_HOOKTAILCALL: i32 = 4;
pub const LUA_MASKCALL: u8 = 1 << LUA_HOOKCALL as u8;
pub const LUA_MASKRET: u8 = 1 << LUA_HOOKRET as u8;
pub const LUA_MASKLINE: u8 = 1 << LUA_HOOKLINE as u8;
pub const LUA_MASKCOUNT: u8 = 1 << LUA_HOOKCOUNT as u8;
pub struct LuaVM {
pub(crate) global: LuaValue,
pub(crate) registry: LuaValue,
pub(crate) ref_manager: RefManager,
pub(crate) object_allocator: ObjectAllocator,
pub(crate) gc: GC,
pub(crate) main_state: ThreadPtr,
pub(crate) string_mt: Option<LuaValue>,
pub(crate) number_mt: Option<LuaValue>,
pub(crate) bool_mt: Option<LuaValue>,
pub(crate) nil_mt: Option<LuaValue>,
pub(crate) safe_option: SafeOption,
pub(crate) n_ccalls: usize,
pub(crate) version: LuaLanguageLevel,
pub(crate) rng: LuaRng,
pub(crate) start_time: PlatformInstant,
pub const_strings: ConstString,
pub(crate) io_default_output: Option<LuaValue>,
pub(crate) io_default_input: Option<LuaValue>,
}
impl LuaVM {
pub fn new(option: SafeOption) -> Box<Self> {
let mut gc = GC::new(option.clone());
gc.set_temporary_memory_limit(isize::MAX / 2);
let mut object_allocator = ObjectAllocator::new();
let cs = ConstString::new(&mut object_allocator, &mut gc);
let time = unix_nanos();
let mut vm = Box::new(LuaVM {
global: LuaValue::nil(),
registry: LuaValue::nil(),
ref_manager: RefManager::new(),
object_allocator,
gc,
main_state: ThreadPtr::null(), string_mt: None,
number_mt: None,
bool_mt: None,
nil_mt: None,
safe_option: option.clone(),
n_ccalls: 0,
version: LuaLanguageLevel::Lua55,
rng: LuaRng::from_seed_time(time),
start_time: PlatformInstant::now(),
const_strings: cs,
io_default_output: None,
io_default_input: None,
});
let ptr_vm = vm.as_mut() as *mut LuaVM;
let thread_value = vm
.object_allocator
.create_thread(&mut vm.gc, LuaState::new(6, ptr_vm, true, option.clone()))
.unwrap();
vm.main_state = thread_value.as_thread_ptr().unwrap();
let registry = vm.create_table(2, 8).unwrap();
vm.registry = registry;
let globals_value = vm.create_table(0, 20).unwrap();
vm.global = globals_value;
vm.set_global("_G", globals_value).unwrap();
vm.set_global("_ENV", globals_value).unwrap();
vm.registry_seti(1, globals_value);
vm.gc.clear_temporary_memory_limit();
vm
}
pub fn main_state(&mut self) -> &mut LuaState {
&mut self.main_state.as_mut_ref().data
}
pub fn main_state_ref(&self) -> &LuaState {
&self.main_state.as_ref().data
}
pub fn register_preload(
&mut self,
name: &str,
loader: crate::lua_vm::CFunction,
) -> LuaResult<()> {
let preload_val = self.registry_get("_PRELOAD")?;
if let Some(preload) = preload_val
&& preload.is_table()
{
let key = self.create_string(name)?;
self.raw_set(&preload, key, LuaValue::cfunction(loader));
}
Ok(())
}
pub fn registry_seti(&mut self, key: i64, value: LuaValue) {
self.raw_seti(&self.registry.clone(), key, value);
}
pub fn registry_geti(&self, key: i64) -> Option<LuaValue> {
self.raw_geti(&self.registry, key)
}
pub fn registry_set(&mut self, key: &str, value: LuaValue) -> LuaResult<()> {
let key_value = self.create_string(key)?;
let registry = self.registry;
self.raw_set(®istry, key_value, value);
Ok(())
}
pub fn registry_get(&mut self, key: &str) -> LuaResult<Option<LuaValue>> {
let key = self.create_string(key)?;
Ok(self.raw_get(&self.registry, &key))
}
pub fn create_ref(&mut self, value: LuaValue) -> LuaRefValue {
if value.is_nil() {
return LuaRefValue::new_direct(LuaValue::nil());
}
if value.is_collectable() {
let ref_id = self.ref_manager.alloc_ref_id();
self.registry_seti(ref_id as i64, value);
LuaRefValue::new_registry(ref_id)
} else {
LuaRefValue::new_direct(value)
}
}
pub fn get_ref_value(&self, lua_ref: &LuaRefValue) -> LuaValue {
lua_ref.get(self)
}
pub fn release_ref(&mut self, lua_ref: LuaRefValue) {
if let Some(ref_id) = lua_ref.ref_id() {
self.registry_seti(ref_id as i64, LuaValue::nil());
self.ref_manager.free_ref_id(ref_id);
}
}
pub fn release_ref_id(&mut self, ref_id: RefId) {
if ref_id > 0 {
self.registry_seti(ref_id as i64, LuaValue::nil());
self.ref_manager.free_ref_id(ref_id);
}
}
pub fn get_ref_value_by_id(&self, ref_id: RefId) -> LuaValue {
if ref_id == LUA_REFNIL {
return LuaValue::nil();
}
if ref_id <= 0 {
return LuaValue::nil();
}
self.registry_geti(ref_id as i64).unwrap_or_default()
}
pub fn open_stdlib(&mut self, lib: Stdlib) -> LuaResult<()> {
lib_registry::create_standard_registry(lib).load_all(self)?;
Ok(())
}
pub fn open_stdlibs(&mut self, libs: &[Stdlib]) -> LuaResult<()> {
for lib in libs {
self.open_stdlib(*lib)?;
}
Ok(())
}
#[cfg(feature = "serde")]
pub fn serialize_to_json(&self, value: &LuaValue) -> Result<serde_json::Value, String> {
crate::serde::lua_to_json(value)
}
#[cfg(feature = "serde")]
pub fn serialize_to_json_string(
&self,
value: &LuaValue,
pretty: bool,
) -> Result<String, String> {
crate::serde::lua_to_json_string(value, pretty)
}
#[cfg(feature = "serde")]
pub fn deserialize_from_json(&mut self, json: &serde_json::Value) -> Result<LuaValue, String> {
crate::serde::json_to_lua(json, self)
}
#[cfg(feature = "serde")]
pub fn deserialize_from_json_string(&mut self, json_str: &str) -> Result<LuaValue, String> {
crate::serde::json_string_to_lua(json_str, self)
}
pub fn execute_chunk(&mut self, chunk: crate::ProtoPtr) -> LuaResult<Vec<LuaValue>> {
let env_upval = self.create_upvalue_closed(self.global)?;
let func = self.create_function(chunk, UpvalueStore::from_single(env_upval))?;
self.execute_function(func, vec![])
}
#[inline]
pub(crate) fn prepare_loaded_chunk(&mut self, chunk: Chunk) -> LuaResult<crate::ProtoPtr> {
#[cfg(feature = "shared-proto")]
{
let proto = self.create_proto(chunk)?;
crate::gc::share_proto(proto);
Ok(proto)
}
#[cfg(not(feature = "shared-proto"))]
self.create_proto(chunk)
}
#[inline]
pub(crate) fn create_loaded_function(
&mut self,
chunk: Chunk,
upvalues: UpvalueStore,
) -> LuaResult<LuaValue> {
let chunk = self.prepare_loaded_chunk(chunk)?;
self.create_function(chunk, upvalues)
}
#[inline]
pub(crate) fn execute_loaded_chunk(&mut self, chunk: Chunk) -> LuaResult<Vec<LuaValue>> {
let chunk = self.prepare_loaded_chunk(chunk)?;
self.execute_chunk(chunk)
}
#[cfg(feature = "sandbox")]
fn execute_chunk_with_env(
&mut self,
chunk: crate::ProtoPtr,
env: LuaValue,
) -> LuaResult<Vec<LuaValue>> {
let env_upval = self.create_upvalue_closed(env)?;
let func = self.create_function(chunk, UpvalueStore::from_single(env_upval))?;
self.execute_function(func, vec![])
}
pub fn execute(&mut self, source: &str) -> LuaResult<Vec<LuaValue>> {
let chunk = self.compile(source)?;
self.execute_loaded_chunk(chunk)
}
#[cfg(feature = "sandbox")]
pub fn execute_sandboxed(
&mut self,
source: &str,
config: &SandboxConfig,
) -> LuaResult<Vec<LuaValue>> {
let chunk = self.compile(source)?;
let env = self.create_sandbox_env(config)?;
let limits = config.runtime_limits();
self.main_state()
.with_sandbox_runtime_limits(limits, |state| {
let chunk = state.vm_mut().prepare_loaded_chunk(chunk)?;
state.vm_mut().execute_chunk_with_env(chunk, env)
})
}
pub fn load(&mut self, source: &str) -> LuaResult<LuaValue> {
let chunk = self.compile(source)?;
let env_upval = self.create_upvalue_closed(self.global)?;
self.create_loaded_function(chunk, UpvalueStore::from_single(env_upval))
}
#[cfg(feature = "sandbox")]
pub fn load_sandboxed(&mut self, source: &str, config: &SandboxConfig) -> LuaResult<LuaValue> {
let chunk = self.compile(source)?;
let env = self.create_sandbox_env(config)?;
let env_upval = self.create_upvalue_closed(env)?;
self.create_loaded_function(chunk, UpvalueStore::from_single(env_upval))
}
pub fn load_with_name(&mut self, source: &str, chunk_name: &str) -> LuaResult<LuaValue> {
let chunk = self.compile_with_name(source, chunk_name)?;
let env_upval = self.create_upvalue_closed(self.global)?;
self.create_loaded_function(chunk, UpvalueStore::from_single(env_upval))
}
#[cfg(feature = "sandbox")]
pub fn load_with_name_sandboxed(
&mut self,
source: &str,
chunk_name: &str,
config: &SandboxConfig,
) -> LuaResult<LuaValue> {
let chunk = self.compile_with_name(source, chunk_name)?;
let env = self.create_sandbox_env(config)?;
let env_upval = self.create_upvalue_closed(env)?;
self.create_loaded_function(chunk, UpvalueStore::from_single(env_upval))
}
pub fn dofile(&mut self, path: &str) -> LuaResult<Vec<LuaValue>> {
let proto = self.load_proto_from_file(path)?;
self.execute_chunk(proto)
}
pub fn call<A: IntoLua, R: FromLuaMulti>(&mut self, func: LuaValue, args: A) -> LuaResult<R> {
let args =
collect_into_lua_values(self.main_state(), args).map_err(|msg| self.error(msg))?;
let results = self.call_raw(func, args)?;
R::from_lua_multi(results, self.main_state_ref()).map_err(|msg| self.error(msg))
}
pub fn call1<A: IntoLua, R: FromLua>(&mut self, func: LuaValue, args: A) -> LuaResult<R> {
let args =
collect_into_lua_values(self.main_state(), args).map_err(|msg| self.error(msg))?;
let result = self
.call_raw(func, args)?
.into_iter()
.next()
.unwrap_or(LuaValue::nil());
R::from_lua(result, self.main_state_ref()).map_err(|msg| self.error(msg))
}
pub fn call_raw(&mut self, func: LuaValue, args: Vec<LuaValue>) -> LuaResult<Vec<LuaValue>> {
self.execute_function(func, args)
}
pub fn call_global<A: IntoLua, R: FromLuaMulti>(
&mut self,
name: &str,
args: A,
) -> LuaResult<R> {
let func = self
.get_global(name)?
.ok_or_else(|| self.error(format!("global '{}' not found", name)))?;
self.call(func, args)
}
pub fn call1_global<A: IntoLua, R: FromLua>(&mut self, name: &str, args: A) -> LuaResult<R> {
let func = self
.get_global(name)?
.ok_or_else(|| self.error(format!("global '{}' not found", name)))?;
self.call1(func, args)
}
pub fn call_global_raw(&mut self, name: &str, args: Vec<LuaValue>) -> LuaResult<Vec<LuaValue>> {
let func = self
.get_global(name)?
.ok_or_else(|| self.error(format!("global '{}' not found", name)))?;
self.call_raw(func, args)
}
pub fn register_function<F>(&mut self, name: &str, f: F) -> LuaResult<()>
where
F: Fn(&mut LuaState) -> LuaResult<usize> + 'static,
{
let closure_val = self.create_closure(f)?;
self.set_global(name, closure_val)
}
pub fn register_function_typed<F, Args, R>(&mut self, name: &str, f: F) -> LuaResult<()>
where
F: LuaTypedCallback<Args, R>,
{
self.register_function(name, move |state| f.invoke_typed(state))
}
pub fn register_async_typed<F, Args, R>(&mut self, name: &str, f: F) -> LuaResult<()>
where
F: LuaTypedAsyncCallback<Args, R>,
{
let wrapper = move |state: &mut LuaState| {
let future = f.invoke_typed_async(state)?;
state.set_pending_future(future);
state.do_yield(vec![async_thread::async_sentinel_value()])?;
Ok(0)
};
let closure_val = self.create_closure(wrapper)?;
self.set_global(name, closure_val)
}
pub fn register_type_of<T: LuaRegistrable>(&mut self, name: &str) -> LuaResult<()> {
self.main_state().register_type_of::<T>(name)
}
pub fn to_ref(&mut self, value: LuaValue) -> LuaAnyRef {
let ref_id = lua_ref::store_in_registry(self, value);
let vm_ptr = self as *mut LuaVM;
LuaAnyRef::from_raw(ref_id, vm_ptr)
}
pub fn to_table_ref(&mut self, value: LuaValue) -> Option<LuaTableRef> {
if !value.is_table() {
return None;
}
let ref_id = lua_ref::store_in_registry(self, value);
let vm_ptr = self as *mut LuaVM;
Some(LuaTableRef::from_raw(ref_id, vm_ptr))
}
pub fn to_function_ref(&mut self, value: LuaValue) -> Option<LuaFunctionRef> {
if !value.is_function() {
return None;
}
let ref_id = lua_ref::store_in_registry(self, value);
let vm_ptr = self as *mut LuaVM;
Some(LuaFunctionRef::from_raw(ref_id, vm_ptr))
}
pub fn to_string_ref(&mut self, value: LuaValue) -> Option<LuaStringRef> {
if !value.is_string() {
return None;
}
let ref_id = lua_ref::store_in_registry(self, value);
let vm_ptr = self as *mut LuaVM;
Some(LuaStringRef::from_raw(ref_id, vm_ptr))
}
pub fn to_userdata_ref<T: 'static>(&mut self, value: LuaValue) -> Option<UserDataRef<T>> {
let userdata = value.as_userdata_mut()?;
userdata.downcast_ref::<T>()?;
let ref_id = lua_ref::store_in_registry(self, value);
let vm_ptr = self as *mut LuaVM;
Some(UserDataRef::from_raw(ref_id, vm_ptr))
}
pub fn create_table_ref(
&mut self,
array_size: usize,
hash_size: usize,
) -> LuaResult<LuaTableRef> {
let table = self.create_table(array_size, hash_size)?;
Ok(self.to_table_ref(table).unwrap())
}
pub fn build_table_ref(&mut self, builder: TableBuilder) -> LuaResult<LuaTableRef> {
let table = builder.build(self)?;
Ok(self.to_table_ref(table).unwrap())
}
pub fn get_global_table(&mut self, name: &str) -> LuaResult<Option<LuaTableRef>> {
match self.get_global(name)? {
Some(val) if val.is_table() => Ok(self.to_table_ref(val)),
_ => Ok(None),
}
}
pub fn get_global_function(&mut self, name: &str) -> LuaResult<Option<LuaFunctionRef>> {
match self.get_global(name)? {
Some(val) if val.is_function() => Ok(self.to_function_ref(val)),
_ => Ok(None),
}
}
pub fn push_any<T: 'static>(&mut self, value: T) -> LuaResult<LuaValue> {
let ud = LuaUserdata::new(OpaqueUserData::new(value));
self.create_userdata(ud)
}
pub fn push_any_with_metatable<T: 'static>(
&mut self,
value: T,
metatable: LuaValue,
) -> LuaResult<LuaValue> {
let mt_ptr = metatable
.as_table_ptr()
.ok_or_else(|| self.error("metatable must be a table".to_string()))?;
let ud = LuaUserdata::with_metatable(OpaqueUserData::new(value), mt_ptr);
self.create_userdata(ud)
}
pub(crate) fn execute_function(
&mut self,
func: LuaValue,
args: Vec<LuaValue>,
) -> LuaResult<Vec<LuaValue>> {
let main_state = self.main_state();
let initial_depth = main_state.call_depth();
let saved_stack_top = main_state.get_top();
let func_idx = saved_stack_top;
let nargs = args.len();
main_state.push_value(func)?;
for arg in args {
main_state.push_value(arg)?;
}
let base = func_idx + 1;
main_state.push_frame(&func, base, nargs, -1)?;
match self.run() {
Ok(results) => {
self.main_state().set_top(0)?;
Ok(results)
}
Err(e) => {
let error_msg = self.main_state().get_error_msg(e);
let traceback = self.generate_traceback(&error_msg);
if !traceback.is_empty() {
self.main_state().error_msg = traceback;
}
let main_state = self.main_state();
let saved_error_msg = main_state.error_msg.clone();
let err_obj = std::mem::take(&mut main_state.error_object);
let frame_base = if main_state.call_depth() > initial_depth {
main_state.call_stack.get(initial_depth).map(|f| f.base)
} else {
None
};
while main_state.call_depth() > initial_depth {
main_state.pop_frame();
}
if let Some(base) = frame_base {
main_state.close_upvalues(base);
let _ = main_state.close_tbc_with_error(base, err_obj);
}
let _ = main_state.set_top(0);
self.main_state().error_msg = saved_error_msg;
Err(e)
}
}
}
fn run(&mut self) -> LuaResult<Vec<LuaValue>> {
self.main_state().inc_n_ccalls()?;
let exec_result = lua_execute(self.main_state(), 0);
self.main_state().dec_n_ccalls();
exec_result?;
let main_state = self.main_state();
let mut results = Vec::new();
let top = main_state.get_top();
for i in 0..top {
if let Some(val) = main_state.stack_get(i) {
results.push(val);
}
}
main_state.check_gc()?;
Ok(results)
}
pub fn compile(&mut self, source: &str) -> LuaResult<Chunk> {
self.gc.disable_memory_check();
let chunk = match compile_code(source, self) {
Ok(c) => c,
Err(e) => {
self.gc.enable_memory_check();
return Err(self.compile_error(e));
}
};
self.gc.enable_memory_check();
self.gc.check_memory()?;
Ok(chunk)
}
pub fn compile_with_name(&mut self, source: &str, chunk_name: &str) -> LuaResult<Chunk> {
self.gc.disable_memory_check();
let chunk = match compile_code_with_name(source, self, chunk_name) {
Ok(c) => c,
Err(e) => {
self.gc.enable_memory_check();
return Err(self.compile_error(e));
}
};
self.gc.enable_memory_check();
self.gc.check_memory()?;
Ok(chunk)
}
pub(crate) fn load_proto_from_file(&mut self, path: &str) -> LuaResult<crate::ProtoPtr> {
use crate::lua_value::chunk_serializer;
let resolved_path = std::fs::canonicalize(path)
.map_err(|e| self.error(format!("cannot open {}: {}", path, e)))?;
#[cfg(feature = "shared-proto")]
{
use crate::lua_vm::shared_proto::SHARED_FILE_PROTO_CACHE;
let metadata = std::fs::metadata(&resolved_path)
.map_err(|e| self.error(format!("cannot open {}: {}", path, e)))?;
let len = metadata.len();
let modified = metadata.modified().ok();
let version = self.version;
if let Some(proto) = SHARED_FILE_PROTO_CACHE.with(|cache| {
let cache = cache.borrow();
cache.get(&resolved_path).and_then(|entry| {
(entry.len == len && entry.modified == modified && entry.version == version)
.then_some(entry.proto)
})
}) {
return Ok(proto);
}
}
let file_bytes = std::fs::read(&resolved_path)
.map_err(|e| self.error(format!("cannot open {}: {}", path, e)))?;
let layout = inspect_file_chunk_layout(&file_bytes);
let chunk_name = format!("@{}", resolved_path.display());
let chunk = if layout.is_binary {
chunk_serializer::deserialize_chunk_with_strings_vm(
&file_bytes[layout.skip_offset..],
self,
)
.map_err(|e| self.error(format!("binary load error: {}", e)))?
} else {
let code_str = String::from_utf8(file_bytes[layout.text_start..].to_vec())
.map_err(|_| self.error("source file is not valid UTF-8".to_string()))?;
self.compile_with_name(&code_str, &chunk_name)?
};
let proto = self.prepare_loaded_chunk(chunk)?;
#[cfg(feature = "shared-proto")]
{
use crate::lua_vm::shared_proto::SHARED_FILE_PROTO_CACHE;
let metadata = std::fs::metadata(&resolved_path)
.map_err(|e| self.error(format!("cannot open {}: {}", path, e)))?;
SHARED_FILE_PROTO_CACHE.with(|cache| {
use crate::lua_vm::shared_proto::SharedFileProtoEntry;
cache.borrow_mut().insert(
resolved_path,
SharedFileProtoEntry {
proto,
len: metadata.len(),
modified: metadata.modified().ok(),
version: self.version,
},
);
});
}
Ok(proto)
}
pub fn get_global(&mut self, name: &str) -> LuaResult<Option<LuaValue>> {
let key = self.create_string(name)?;
Ok(self.raw_get(&self.global, &key))
}
pub fn set_global(&mut self, name: &str, value: LuaValue) -> LuaResult<()> {
let key = self.create_string(name)?;
let global = self.global;
self.raw_set(&global, key, value);
Ok(())
}
#[cfg(feature = "sandbox")]
pub fn create_sandbox_env(&mut self, config: &SandboxConfig) -> LuaResult<LuaValue> {
use crate::lua_vm::sandbox::SANDBOX_LIB_GLOBALS;
let env = self.create_table(0, 24)?;
let g_key = self.create_string("_G")?;
let env_key = self.create_string("_ENV")?;
self.copy_global_into_table(&env, "_G")?;
self.copy_global_into_table(&env, "_ENV")?;
self.raw_set(&env, g_key, env);
self.raw_set(&env, env_key, env);
if config.basic {
use crate::lua_vm::sandbox::SANDBOX_SAFE_BASIC_GLOBALS;
for &name in SANDBOX_SAFE_BASIC_GLOBALS {
self.copy_global_into_table(&env, name)?;
}
if config.allow_require {
self.copy_global_into_table(&env, "require")?;
}
if config.allow_load {
self.copy_global_into_table(&env, "load")?;
}
if config.allow_loadfile {
self.copy_global_into_table(&env, "loadfile")?;
}
if config.allow_dofile {
self.copy_global_into_table(&env, "dofile")?;
}
if config.allow_collectgarbage {
self.copy_global_into_table(&env, "collectgarbage")?;
}
}
for &(lib, global_name) in SANDBOX_LIB_GLOBALS {
let enabled = match lib {
Stdlib::Math => config.math,
Stdlib::String => config.string,
Stdlib::Table => config.table,
Stdlib::Utf8 => config.utf8,
Stdlib::Coroutine => config.coroutine,
Stdlib::Os => config.os,
Stdlib::Io => config.io,
Stdlib::Package => config.package,
Stdlib::Debug => config.debug,
Stdlib::Basic | Stdlib::All => false,
};
if enabled {
self.copy_global_into_table(&env, global_name)?;
}
}
for (name, value) in &config.injected_globals {
let key = self.create_string(name)?;
self.raw_set(&env, key, *value);
}
Ok(env)
}
#[cfg(feature = "sandbox")]
fn copy_global_into_table(&mut self, table: &LuaValue, name: &str) -> LuaResult<()> {
let key = self.create_string(name)?;
if let Some(value) = self.raw_get(&self.global, &key) {
self.raw_set(table, key, value);
}
Ok(())
}
pub fn get_global_as<T: crate::FromLua>(&mut self, name: &str) -> LuaResult<Option<T>> {
match self.get_global(name)? {
None => Ok(None),
Some(val) => {
let converted =
T::from_lua(val, self.main_state()).map_err(|msg| self.error(msg))?;
Ok(Some(converted))
}
}
}
pub fn set_string_metatable(&mut self, string_lib_table: LuaValue) -> LuaResult<()> {
let mt_value = self.create_table(0, 10)?;
let index_key = self
.const_strings
.get_tm_value(crate::lua_vm::TmKind::Index);
self.raw_set(&mt_value, index_key, string_lib_table);
use crate::lua_vm::TmKind;
let arith_metas: &[(TmKind, fn(&mut LuaState) -> LuaResult<usize>)] = &[
(TmKind::Add, string_arith_add),
(TmKind::Sub, string_arith_sub),
(TmKind::Mul, string_arith_mul),
(TmKind::Mod, string_arith_mod),
(TmKind::Pow, string_arith_pow),
(TmKind::Div, string_arith_div),
(TmKind::IDiv, string_arith_idiv),
(TmKind::Unm, string_arith_unm),
];
for &(tm, func) in arith_metas {
let key = self.const_strings.get_tm_value(tm);
self.raw_set(&mt_value, key, LuaValue::cfunction(func));
}
self.string_mt = Some(mt_value);
Ok(())
}
pub fn create_thread(&mut self, func: LuaValue) -> CreateResult {
let mut thread = LuaState::new(1, self as *mut LuaVM, false, self.safe_option.clone());
thread
.push_value(func)
.expect("Failed to push function onto coroutine stack");
self.object_allocator.create_thread(&mut self.gc, thread)
}
pub fn resume_thread(
&mut self,
thread_val: LuaValue,
args: Vec<LuaValue>,
) -> LuaResult<(bool, Vec<LuaValue>)> {
let Some(l) = thread_val.as_thread_mut() else {
return Err(self.error("invalid thread".to_string()));
};
if l.is_main_thread() {
return Err(self.error("cannot resume main thread".to_string()));
}
l.resume(args)
}
#[inline(always)]
pub fn raw_get(&self, table_value: &LuaValue, key: &LuaValue) -> Option<LuaValue> {
let table = table_value.as_table()?;
table.raw_get(key)
}
pub fn table_pairs(&self, table_value: &LuaValue) -> LuaResult<Vec<(LuaValue, LuaValue)>> {
let table = table_value.as_table().ok_or(LuaError::RuntimeError)?;
Ok(table.iter_all())
}
pub fn table_length(&self, table_value: &LuaValue) -> LuaResult<usize> {
let table = table_value.as_table().ok_or(LuaError::RuntimeError)?;
Ok(table.len())
}
pub fn register_async<F, Fut>(&mut self, name: &str, f: F) -> LuaResult<()>
where
F: Fn(Vec<LuaValue>) -> Fut + 'static,
Fut: Future<Output = LuaResult<Vec<async_thread::AsyncReturnValue>>> + 'static,
{
let wrapper = async_thread::wrap_async_function(f);
let closure_val = self.create_closure(wrapper)?;
self.set_global(name, closure_val)?;
Ok(())
}
pub fn create_async_thread(
&mut self,
chunk: Chunk,
args: Vec<LuaValue>,
) -> LuaResult<async_thread::AsyncThread> {
let env_upval = self.create_upvalue_closed(self.global)?;
let func_val = self.create_loaded_function(chunk, UpvalueStore::from_single(env_upval))?;
let thread_val = self.create_thread(func_val)?;
let vm_ptr = self as *mut LuaVM;
Ok(async_thread::AsyncThread::new(thread_val, vm_ptr, args))
}
pub async fn execute_async(&mut self, source: &str) -> LuaResult<Vec<LuaValue>> {
let chunk = self.compile(source)?;
let async_thread = self.create_async_thread(chunk, vec![])?;
async_thread.await
}
pub async fn call_async(
&mut self,
func: LuaValue,
args: Vec<LuaValue>,
) -> LuaResult<Vec<LuaValue>> {
let thread_val = self.create_thread(func)?;
let vm_ptr = self as *mut LuaVM;
let async_thread = async_thread::AsyncThread::new(thread_val, vm_ptr, args);
async_thread.await
}
pub async fn call_async_global(
&mut self,
name: &str,
args: Vec<LuaValue>,
) -> LuaResult<Vec<LuaValue>> {
let func = self
.get_global(name)?
.ok_or_else(|| self.error(format!("global '{}' not found", name)))?;
self.call_async(func, args).await
}
pub fn create_async_call_handle(
&mut self,
func: LuaValue,
) -> LuaResult<async_thread::AsyncCallHandle> {
let chunk = self.compile(async_thread::ASYNC_CALL_RUNNER)?;
let env_upval = self.create_upvalue_closed(self.global)?;
let runner_func =
self.create_loaded_function(chunk, UpvalueStore::from_single(env_upval))?;
let thread_val = self.create_thread(runner_func)?;
let vm_ptr = self as *mut LuaVM;
async_thread::AsyncCallHandle::new(thread_val, vm_ptr, func)
}
pub fn create_async_call_handle_global(
&mut self,
name: &str,
) -> LuaResult<async_thread::AsyncCallHandle> {
let func = self
.get_global(name)?
.ok_or_else(|| self.error(format!("global '{}' not found", name)))?;
self.create_async_call_handle(func)
}
pub fn register_enum<T: LuaEnum>(&mut self, name: &str) -> LuaResult<()> {
let variants = T::variants();
let table = self.create_table(0, variants.len())?;
for &(vname, value) in variants {
let key = self.create_string(vname)?;
let val = LuaValue::integer(value);
self.raw_set(&table, key, val);
}
self.set_global(name, table)
}
#[inline(always)]
pub fn raw_set(&mut self, table_value: &LuaValue, key: LuaValue, value: LuaValue) -> bool {
let Some(table) = table_value.as_table_mut() else {
return false;
};
let (new_key, delta) = table.raw_set(&key, value);
if delta != 0
&& let Some(table_ptr) = table_value.as_table_ptr()
{
self.gc.track_resize(table_ptr, delta);
}
let need_barrier = (new_key && key.iscollectable()) || value.iscollectable();
if need_barrier && let Some(gc_ptr) = table_value.as_gc_ptr() {
self.gc.barrier_back(gc_ptr);
}
true
}
#[inline(always)]
pub fn raw_geti(&self, table_value: &LuaValue, key: i64) -> Option<LuaValue> {
let table = table_value.as_table()?;
table.raw_geti(key)
}
pub fn raw_seti(&mut self, table_value: &LuaValue, key: i64, value: LuaValue) -> bool {
let Some(table) = table_value.as_table_mut() else {
return false;
};
let delta = table.raw_seti(key, value);
if delta != 0
&& let Some(table_ptr) = table_value.as_table_ptr()
{
self.gc.track_resize(table_ptr, delta);
}
if value.is_collectable()
&& let Some(gc_ptr) = table_value.as_gc_ptr()
{
self.gc.barrier_back(gc_ptr);
}
true
}
#[inline]
pub fn create_string(&mut self, s: &str) -> CreateResult {
self.object_allocator.create_string(&mut self.gc, s)
}
#[inline]
pub fn create_binary(&mut self, data: Vec<u8>) -> CreateResult {
self.object_allocator.create_binary(&mut self.gc, data)
}
#[inline]
pub fn create_bytes(&mut self, bytes: &[u8]) -> CreateResult {
self.object_allocator.create_bytes(&mut self.gc, bytes)
}
#[inline]
pub fn create_string_owned(&mut self, s: String) -> CreateResult {
self.object_allocator.create_string_owned(&mut self.gc, s)
}
#[inline]
pub fn create_substring(
&mut self,
s_value: LuaValue,
start: usize,
end: usize,
) -> CreateResult {
self.object_allocator
.create_substring(&mut self.gc, s_value, start, end)
}
#[inline(always)]
pub fn create_table(&mut self, array_size: usize, hash_size: usize) -> CreateResult {
self.object_allocator
.create_table(&mut self.gc, array_size, hash_size)
}
pub fn create_userdata(&mut self, data: LuaUserdata) -> CreateResult {
self.object_allocator.create_userdata(&mut self.gc, data)
}
#[inline(always)]
pub fn create_proto(&mut self, chunk: Chunk) -> LuaResult<crate::ProtoPtr> {
self.object_allocator.create_proto(&mut self.gc, chunk)
}
#[inline(always)]
pub fn create_function(
&mut self,
chunk: crate::ProtoPtr,
upvalues: UpvalueStore,
) -> CreateResult {
self.object_allocator
.create_function(&mut self.gc, chunk, upvalues)
}
#[inline]
pub fn create_c_closure(&mut self, func: CFunction, upvalues: Vec<LuaValue>) -> CreateResult {
self.object_allocator
.create_c_closure(&mut self.gc, func, upvalues)
}
#[inline]
pub fn create_rclosure(&mut self, func: RustCallback, upvalues: Vec<LuaValue>) -> CreateResult {
self.object_allocator
.create_rclosure(&mut self.gc, func, upvalues)
}
#[inline]
pub fn create_closure<F>(&mut self, func: F) -> CreateResult
where
F: Fn(&mut LuaState) -> LuaResult<usize> + 'static,
{
self.create_rclosure(Box::new(func), Vec::new())
}
#[inline]
pub fn create_closure_with_upvalues<F>(
&mut self,
func: F,
upvalues: Vec<LuaValue>,
) -> CreateResult
where
F: Fn(&mut LuaState) -> LuaResult<usize> + 'static,
{
self.create_rclosure(Box::new(func), upvalues)
}
#[inline(always)]
pub fn create_upvalue_open(
&mut self,
stack_index: usize,
ptr: LuaValuePtr,
) -> LuaResult<UpvaluePtr> {
let upval = LuaUpvalue::new_open(stack_index, ptr);
self.object_allocator.create_upvalue(&mut self.gc, upval)
}
#[inline(always)]
pub fn create_upvalue_closed(&mut self, value: LuaValue) -> LuaResult<UpvaluePtr> {
let upval = LuaUpvalue::new_closed(value);
self.object_allocator.create_upvalue(&mut self.gc, upval)
}
#[inline(always)]
fn check_gc(&mut self, l: &mut LuaState) -> bool {
if self.gc.gc_debt <= 0 {
self.gc.step(l);
return true;
}
false
}
fn full_gc(&mut self, l: &mut LuaState, is_emergency: bool) {
self.gc.gc_emergency = is_emergency;
match self.gc.gc_kind {
GcKind::GenMinor => {
self.full_gen(l);
}
GcKind::Inc => {
self.full_inc(l);
}
GcKind::GenMajor => {
self.gc.gc_kind = GcKind::Inc;
self.full_inc(l);
self.gc.gc_kind = GcKind::GenMajor;
}
}
self.gc.gc_emergency = false;
}
fn full_inc(&mut self, l: &mut LuaState) {
if self.gc.keep_invariant() {
self.gc.enter_sweep(l);
}
self.gc.run_until_state(l, crate::gc::GcState::Pause);
self.gc.run_until_state(l, crate::gc::GcState::CallFin);
self.gc.run_until_state(l, crate::gc::GcState::Pause);
self.gc.set_pause();
}
fn full_gen(&mut self, l: &mut LuaState) {
self.gc.change_to_incremental_mode(l);
self.gc.enter_gen(l);
}
pub fn gc_stats(&self) -> String {
let stats = self.gc.stats();
format!(
"GC Stats:\n\
- Bytes allocated: {}\n\
- Threshold: {}\n\
- Total collections: {}\n\
- Minor collections: {}\n\
- Major collections: {}\n\
- Objects collected: {}\n\
- Young generation size: {}\n\
- Old generation size: {}\n\
- Promoted objects: {}",
stats.bytes_allocated,
stats.threshold,
stats.collection_count,
stats.minor_collections,
stats.major_collections,
stats.objects_collected,
stats.young_gen_size,
stats.old_gen_size,
stats.promoted_objects
)
}
pub fn error(&mut self, message: impl Into<String>) -> LuaError {
self.main_state().error(message.into());
LuaError::RuntimeError
}
#[inline]
pub fn compile_error(&mut self, message: impl Into<String>) -> LuaError {
self.main_state().error(message.into());
LuaError::CompileError
}
#[inline]
pub fn get_error_message(&mut self, e: LuaError) -> String {
self.main_state().get_error_msg(e)
}
#[inline]
pub fn into_full_error(&mut self, e: LuaError) -> lua_error::LuaFullError {
let message = self.get_error_message(e);
lua_error::LuaFullError { kind: e, message }
}
pub fn generate_traceback(&mut self, error_msg: &str) -> String {
let result = (|| -> LuaResult<String> {
let debug_table = match self.get_global("debug")? {
Some(v) if v.is_table() => v,
_ => return Ok(String::new()), };
let traceback_func = {
let state = self.main_state();
let traceback_key = state.create_string("traceback")?;
match state.raw_get(&debug_table, &traceback_key) {
Some(v) if v.is_function() => v,
_ => return Ok(String::new()), }
};
let state = self.main_state();
let msg_val = state.create_string(error_msg)?;
let level_val = LuaValue::integer(1);
let (success, results) =
self.protected_call(traceback_func, vec![msg_val, level_val])?;
if success
&& let Some(result) = results.first()
&& let Some(s) = result.as_str()
{
return Ok(s.to_string());
}
Ok(String::new())
})();
match result {
Ok(s) if !s.is_empty() => s,
_ => self.fallback_traceback(error_msg),
}
}
fn fallback_traceback(&self, error_msg: &str) -> String {
let traceback = self.main_state_ref().generate_traceback();
if !traceback.is_empty() {
format!("{}\nstack traceback:\n{}", error_msg, traceback)
} else {
error_msg.to_string()
}
}
pub fn protected_call(
&mut self,
func: LuaValue,
args: Vec<LuaValue>,
) -> LuaResult<(bool, Vec<LuaValue>)> {
self.main_state().pcall(func, args)
}
#[inline]
pub fn protected_call_stack_based(
&mut self,
func_idx: usize,
arg_count: usize,
) -> LuaResult<(bool, usize)> {
self.main_state().pcall_stack_based(func_idx, arg_count)
}
pub fn protected_call_with_handler(
&mut self,
func: LuaValue,
args: Vec<LuaValue>,
err_handler: LuaValue,
) -> LuaResult<(bool, Vec<LuaValue>)> {
self.main_state().xpcall(func, args, err_handler)
}
pub fn get_main_thread_ptr(&self) -> ThreadPtr {
self.main_state
}
pub fn get_basic_metatable(&self, kind: LuaValueKind) -> Option<LuaValue> {
match kind {
LuaValueKind::String => self.string_mt,
LuaValueKind::Integer | LuaValueKind::Float => self.number_mt,
LuaValueKind::Boolean => self.bool_mt,
LuaValueKind::Nil => self.nil_mt,
_ => None,
}
}
pub fn set_basic_metatable(&mut self, kind: LuaValueKind, mt: Option<LuaValue>) {
match kind {
LuaValueKind::String => self.string_mt = mt,
LuaValueKind::Integer | LuaValueKind::Float => self.number_mt = mt,
LuaValueKind::Boolean => self.bool_mt = mt,
LuaValueKind::Nil => self.nil_mt = mt,
_ => {}
}
}
pub fn get_basic_metatables(&self) -> Vec<LuaValue> {
let mut mts = Vec::new();
if let Some(mt) = &self.string_mt {
mts.push(*mt);
}
if let Some(mt) = &self.number_mt {
mts.push(*mt);
}
if let Some(mt) = &self.bool_mt {
mts.push(*mt);
}
if let Some(mt) = &self.nil_mt {
mts.push(*mt);
}
mts
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_count_hook_preserves_multret_unpack_results() {
let mut vm = LuaVM::new(SafeOption::default());
vm.open_stdlib(Stdlib::All).unwrap();
let results = vm
.execute(
r#"
local count = 0
local function f(...) return #({...}), ... end
local a = {}
for i = 1, 30 do a[i] = i end
debug.sethook(function() count = count + 1 end, '', 1)
local t = {f(table.unpack(a, 1, 30))}
debug.sethook()
return #t, t[#t], count > 0
"#,
)
.unwrap();
assert_eq!(results.len(), 3);
assert_eq!(results[0].as_integer(), Some(31));
assert_eq!(results[1].as_integer(), Some(30));
assert_eq!(results[2].as_bool(), Some(true));
}
#[test]
fn test_lua_ref_mechanism() {
let mut vm = LuaVM::new(SafeOption::default());
let table = vm.create_table(0, 2).unwrap();
let num_key = vm.create_string("num").unwrap();
let str_key = vm.create_string("str").unwrap();
let str_val = vm.create_string("hello").unwrap();
vm.raw_set(&table, num_key, LuaValue::number(42.0));
vm.raw_set(&table, str_key, str_val);
let number = LuaValue::number(123.456);
let nil_val = LuaValue::nil();
let table_ref = vm.create_ref(table);
let number_ref = vm.create_ref(number);
let nil_ref = vm.create_ref(nil_val);
assert!(table_ref.is_registry_ref(), "Table should use registry");
assert!(!number_ref.is_registry_ref(), "Number should be direct");
assert!(!nil_ref.is_registry_ref(), "Nil should be direct");
let retrieved_table = vm.get_ref_value(&table_ref);
assert!(retrieved_table.is_table(), "Should retrieve table");
let retrieved_num = vm.get_ref_value(&number_ref);
assert_eq!(
retrieved_num.as_number(),
Some(123.456),
"Should retrieve number"
);
let retrieved_nil = vm.get_ref_value(&nil_ref);
assert!(retrieved_nil.is_nil(), "Should retrieve nil");
let num_key2 = vm.create_string("num").unwrap();
let val = vm.raw_get(&retrieved_table, &num_key2);
assert_eq!(
val.and_then(|v| v.as_number()),
Some(42.0),
"Table content should be preserved"
);
let table_ref_id = table_ref.ref_id();
assert!(table_ref_id.is_some(), "Table ref should have ID");
assert!(table_ref_id.unwrap() > 0, "Ref ID should be positive");
let number_ref_id = number_ref.ref_id();
assert!(number_ref_id.is_none(), "Number ref should not have ID");
vm.release_ref(table_ref);
vm.release_ref(number_ref);
vm.release_ref(nil_ref);
let after_release = vm.get_ref_value_by_id(table_ref_id.unwrap());
assert!(after_release.is_nil(), "Released ref should return nil");
println!("✓ Lua ref mechanism test passed");
}
#[test]
fn test_ref_id_reuse() {
let mut vm = LuaVM::new(SafeOption::default());
let t1 = vm.create_table(0, 0).unwrap();
let ref1 = vm.create_ref(t1);
let id1 = ref1.ref_id().unwrap();
vm.release_ref(ref1);
let t2 = vm.create_table(0, 0).unwrap();
let ref2 = vm.create_ref(t2);
let id2 = ref2.ref_id().unwrap();
assert_eq!(id1, id2, "Ref IDs should be reused");
vm.release_ref(ref2);
println!("✓ Ref ID reuse test passed");
}
#[test]
fn test_multiple_refs() {
let mut vm = LuaVM::new(SafeOption::default());
let mut refs = Vec::new();
for i in 0..10 {
let table = vm.create_table(0, 1).unwrap();
let key = vm.create_string("value").unwrap();
let num_val = LuaValue::number(i as f64);
vm.raw_set(&table, key, num_val);
refs.push(vm.create_ref(table));
}
for (i, lua_ref) in refs.iter().enumerate() {
let table = vm.get_ref_value(lua_ref);
let key = vm.create_string("value").unwrap();
let val = vm.raw_get(&table, &key);
assert_eq!(
val.and_then(|v| v.as_number()),
Some(i as f64),
"Ref {} should have correct value",
i
);
}
for lua_ref in refs {
vm.release_ref(lua_ref);
}
println!("✓ Multiple refs test passed");
}
#[cfg(feature = "serde")]
#[test]
fn test_json_serialization() {
let mut vm = LuaVM::new(SafeOption::default());
let num = LuaValue::number(42.5);
let json = vm.serialize_to_json(&num).unwrap();
assert_eq!(json, serde_json::json!(42.5));
let bool_val = LuaValue::boolean(true);
let json = vm.serialize_to_json(&bool_val).unwrap();
assert_eq!(json, serde_json::json!(true));
let nil = LuaValue::nil();
let json = vm.serialize_to_json(&nil).unwrap();
assert_eq!(json, serde_json::json!(null));
let str_val = vm.create_string("hello world").unwrap();
let json = vm.serialize_to_json(&str_val).unwrap();
assert_eq!(json, serde_json::json!("hello world"));
let arr = vm.create_table(3, 0).unwrap();
vm.raw_set(&arr, LuaValue::number(1.0), LuaValue::number(10.0));
vm.raw_set(&arr, LuaValue::number(2.0), LuaValue::number(20.0));
vm.raw_set(&arr, LuaValue::number(3.0), LuaValue::number(30.0));
let json = vm.serialize_to_json(&arr).unwrap();
assert_eq!(json, serde_json::json!([10, 20, 30]));
let obj = vm.create_table(0, 2).unwrap();
let key1 = vm.create_string("name").unwrap();
let key2 = vm.create_string("age").unwrap();
let val1 = vm.create_string("Alice").unwrap();
vm.raw_set(&obj, key1, val1);
vm.raw_set(&obj, key2, LuaValue::number(30.0));
let json = vm.serialize_to_json(&obj).unwrap();
let expected = serde_json::json!({"name": "Alice", "age": 30});
assert_eq!(json, expected);
let root = vm.create_table(0, 2).unwrap();
let inner = vm.create_table(2, 0).unwrap();
vm.raw_set(&inner, LuaValue::number(1.0), LuaValue::number(1.0));
vm.raw_set(&inner, LuaValue::number(2.0), LuaValue::number(2.0));
let key = vm.create_string("data").unwrap();
vm.raw_set(&root, key, inner);
let key2 = vm.create_string("count").unwrap();
vm.raw_set(&root, key2, LuaValue::number(100.0));
let json = vm.serialize_to_json(&root).unwrap();
let expected = serde_json::json!({"data": [1, 2], "count": 100});
assert_eq!(json, expected);
println!("✓ JSON serialization test passed");
}
#[cfg(feature = "serde")]
#[test]
fn test_json_deserialization() {
let mut vm = LuaVM::new(SafeOption::default());
let json = serde_json::json!(42);
let lua_val = vm.deserialize_from_json(&json).unwrap();
assert_eq!(lua_val.as_number(), Some(42.0));
let json = serde_json::json!(true);
let lua_val = vm.deserialize_from_json(&json).unwrap();
assert_eq!(lua_val.as_bool(), Some(true));
let json = serde_json::json!(null);
let lua_val = vm.deserialize_from_json(&json).unwrap();
assert!(lua_val.is_nil());
let json = serde_json::json!("hello");
let lua_val = vm.deserialize_from_json(&json).unwrap();
assert_eq!(lua_val.as_str(), Some("hello"));
let json = serde_json::json!([1, 2, 3]);
let lua_val = vm.deserialize_from_json(&json).unwrap();
assert!(lua_val.is_table());
let val1 = vm.raw_get(&lua_val, &LuaValue::number(1.0)).unwrap();
assert_eq!(val1.as_number(), Some(1.0));
let json = serde_json::json!({"name": "Bob", "age": 25});
let lua_val = vm.deserialize_from_json(&json).unwrap();
assert!(lua_val.is_table());
let key = vm.create_string("name").unwrap();
let name = vm.raw_get(&lua_val, &key).unwrap();
assert_eq!(name.as_str(), Some("Bob"));
println!("✓ JSON deserialization test passed");
}
#[cfg(feature = "serde")]
#[test]
fn test_json_roundtrip() {
let mut vm = LuaVM::new(SafeOption::default());
let root = vm.create_table(0, 3).unwrap();
let key1 = vm.create_string("name").unwrap();
let val1 = vm.create_string("Test").unwrap();
vm.raw_set(&root, key1, val1);
let key2 = vm.create_string("count").unwrap();
vm.raw_set(&root, key2, LuaValue::number(42.0));
let key3 = vm.create_string("items").unwrap();
let items = vm.create_table(3, 0).unwrap();
vm.raw_set(&items, LuaValue::number(1.0), LuaValue::number(10.0));
vm.raw_set(&items, LuaValue::number(2.0), LuaValue::number(20.0));
vm.raw_set(&items, LuaValue::number(3.0), LuaValue::number(30.0));
vm.raw_set(&root, key3, items);
let json = vm.serialize_to_json(&root).unwrap();
let reconstructed = vm.deserialize_from_json(&json).unwrap();
assert!(reconstructed.is_table());
let key = vm.create_string("name").unwrap();
let name = vm.raw_get(&reconstructed, &key).unwrap();
assert_eq!(name.as_str(), Some("Test"));
let key = vm.create_string("count").unwrap();
let count = vm.raw_get(&reconstructed, &key).unwrap();
assert_eq!(count.as_number(), Some(42.0));
println!("✓ JSON roundtrip test passed");
}
}