pub mod compiler;
pub mod conversion;
#[cfg(feature = "dynmod")]
pub mod dynmod;
pub mod error;
pub mod handles;
pub(crate) mod platform;
pub mod stdlib;
pub mod vm;
use std::any::Any;
use std::rc::Rc;
pub use conversion::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti};
pub use error::{LuaError, LuaResult, RuntimeError, runtime_error};
pub use handles::{AnyUserData, Function, Table, Thread};
pub use stdlib::StdLib;
pub use vm::closure::RustFn;
pub use vm::state::ThreadStatus;
pub use vm::value::Val;
use vm::callinfo::LUA_MULTRET;
use vm::closure::{Closure, LuaClosure, RustClosure};
use vm::execute::{CallResult, execute};
use vm::proto::Proto;
use vm::state::{Gc, LuaState};
use std::sync::atomic::{AtomicBool, Ordering};
static INTERRUPTED: AtomicBool = AtomicBool::new(false);
pub fn set_interrupted() {
INTERRUPTED.store(true, Ordering::Relaxed);
}
pub fn clear_interrupted() {
INTERRUPTED.store(false, Ordering::Relaxed);
}
pub(crate) fn check_interrupted() -> bool {
if INTERRUPTED.load(Ordering::Relaxed) {
INTERRUPTED.store(false, Ordering::Relaxed);
return true;
}
false
}
pub struct Lua {
state: LuaState,
}
impl Lua {
pub fn new() -> LuaResult<Self> {
let mut lua = Self {
state: LuaState::new(),
};
stdlib::open_libs(&mut lua.state)?;
Ok(lua)
}
pub fn new_empty() -> Self {
Self {
state: LuaState::new(),
}
}
pub fn new_with(libs: StdLib) -> LuaResult<Self> {
let mut lua = Self {
state: LuaState::new(),
};
stdlib::open_libs_selective(&mut lua.state, libs)?;
Ok(lua)
}
pub fn exec(&mut self, source: &str) -> LuaResult<()> {
self.exec_bytes(source.as_bytes(), "=(string)")
}
pub fn exec_bytes(&mut self, source: &[u8], name: &str) -> LuaResult<()> {
let proto = compile_or_undump(source, name)?;
self.run_proto(proto)
}
pub fn exec_file(&mut self, path: &str) -> LuaResult<()> {
let source = std::fs::read(path).map_err(|e| {
LuaError::Runtime(RuntimeError {
message: format!("cannot open {path}: {e}"),
level: 0,
traceback: vec![],
})
})?;
let name = format!("@{path}");
self.exec_bytes(&source, &name)
}
pub fn load(&mut self, source: &str) -> LuaResult<Function> {
self.load_bytes(source.as_bytes(), "=(string)")
}
pub fn load_bytes(&mut self, source: &[u8], name: &str) -> LuaResult<Function> {
let proto = compile_or_undump(source, name)?;
let mut proto = Rc::try_unwrap(proto).unwrap_or_else(|rc| (*rc).clone());
patch_string_constants(&mut proto, &mut self.state.gc);
let proto = Rc::new(proto);
let num_upvalues = proto.num_upvalues as usize;
let mut lua_cl = LuaClosure::new(proto, self.state.global);
for _ in 0..num_upvalues {
let uv = vm::closure::Upvalue::new_closed(Val::Nil);
let uv_ref = self.state.gc.alloc_upvalue(uv);
lua_cl.upvalues.push(uv_ref);
}
let closure_ref = self.state.gc.alloc_closure(Closure::Lua(lua_cl));
Ok(Function(closure_ref))
}
pub fn global<V: FromLua>(&mut self, name: &str) -> LuaResult<V> {
let val = self.get_global_val(name);
V::from_lua(val, self)
}
pub fn set_global<V: IntoLua>(&mut self, name: &str, value: V) -> LuaResult<()> {
let val = value.into_lua(self)?;
self.set_global_val(name, val)
}
pub fn create_table(&mut self) -> Table {
let r = self.state.gc.alloc_table(vm::table::Table::new());
Table(r)
}
pub fn create_userdata<T: Any>(&mut self, data: T) -> AnyUserData {
let ud = vm::value::Userdata::new(Box::new(data));
let r = self.state.gc.alloc_userdata(ud);
AnyUserData(r)
}
pub fn create_typed_userdata<T: Any>(
&mut self,
data: T,
type_name: &str,
) -> LuaResult<AnyUserData> {
let mt = stdlib::new_metatable(&mut self.state, type_name)?;
let ud = vm::value::Userdata::with_metatable(Box::new(data), mt);
let r = self.state.gc.alloc_userdata(ud);
Ok(AnyUserData(r))
}
pub fn create_userdata_metatable(&mut self, type_name: &str) -> LuaResult<Table> {
let mt = stdlib::new_metatable(&mut self.state, type_name)?;
Ok(Table(mt))
}
pub fn register_function(&mut self, name: &str, func: RustFn) -> LuaResult<()> {
let closure = Closure::Rust(RustClosure::new(func, name));
let closure_ref = self.state.gc.alloc_closure(closure);
self.set_global_val(name, Val::Function(closure_ref))
}
pub fn gc_collect(&mut self) -> LuaResult<()> {
self.state.full_gc()
}
pub fn gc_count(&self) -> usize {
self.state.gc.gc_state.total_bytes
}
pub fn gc_stop(&mut self) {
self.state.gc.gc_state.gc_threshold = usize::MAX;
}
pub fn gc_restart(&mut self) {
self.state.gc.gc_state.gc_threshold = self.state.gc.gc_state.total_bytes;
}
pub fn gc_step(&mut self, step_size: i64) -> LuaResult<bool> {
self.state.gc_step(step_size)
}
pub fn gc_set_pause(&mut self, pause: u32) -> u32 {
let old = self.state.gc.gc_state.gc_pause;
self.state.gc.gc_state.gc_pause = pause;
old
}
pub fn gc_set_step_multiplier(&mut self, stepmul: u32) -> u32 {
let old = self.state.gc.gc_state.gc_stepmul;
self.state.gc.gc_state.gc_stepmul = stepmul;
old
}
pub fn call_function(&mut self, func: &Function, args: &[Val]) -> LuaResult<Vec<Val>> {
let func_idx = self.state.top;
self.state.ensure_stack(func_idx + 1 + args.len());
self.state.stack_set(func_idx, Val::Function(func.0));
self.state.top = func_idx + 1;
for arg in args {
let top = self.state.top;
self.state.stack_set(top, *arg);
self.state.top = top + 1;
}
let save_base = self.state.base;
self.state.base = func_idx + 1;
match self.state.precall(func_idx, LUA_MULTRET)? {
CallResult::Lua => execute(&mut self.state)?,
CallResult::Rust => {}
}
let results: Vec<Val> = (func_idx..self.state.top)
.map(|i| self.state.stack_get(i))
.collect();
self.state.top = func_idx;
self.state.base = save_base;
Ok(results)
}
pub fn call_function_traced(&mut self, func: &Function, args: &[Val]) -> LuaResult<Vec<Val>> {
let func_idx = self.state.top;
self.state.ensure_stack(func_idx + 1 + args.len());
self.state.stack_set(func_idx, Val::Function(func.0));
self.state.top = func_idx + 1;
for arg in args {
let top = self.state.top;
self.state.stack_set(top, *arg);
self.state.top = top + 1;
}
let save_base = self.state.base;
let save_ci = self.state.ci;
self.state.base = func_idx + 1;
let result = match self.state.precall(func_idx, LUA_MULTRET) {
Ok(CallResult::Lua) => execute(&mut self.state),
Ok(CallResult::Rust) => Ok(()),
Err(e) => Err(e),
};
match result {
Ok(()) => {
let results: Vec<Val> = (func_idx..self.state.top)
.map(|i| self.state.stack_get(i))
.collect();
self.state.top = func_idx;
self.state.base = save_base;
Ok(results)
}
Err(e) => {
let msg = e.to_string();
let traceback = stdlib::debug::generate_traceback(&self.state, &msg, 0);
self.state.top = func_idx;
self.state.base = save_base;
self.state.ci = save_ci;
Err(LuaError::Runtime(RuntimeError {
message: traceback,
level: 0,
traceback: vec![],
}))
}
}
}
pub fn create_string(&mut self, s: &[u8]) -> Val {
let str_ref = self.state.gc.intern_string(s);
Val::Str(str_ref)
}
pub fn load_file(&mut self, path: Option<&str>) -> LuaResult<Function> {
let (source, name) = if let Some(p) = path {
let bytes = std::fs::read(p).map_err(|e| {
LuaError::Runtime(RuntimeError {
message: format!("cannot open {p}: {e}"),
level: 0,
traceback: vec![],
})
})?;
let name = format!("@{p}");
(bytes, name)
} else {
use std::io::Read;
let mut bytes = Vec::new();
std::io::stdin().read_to_end(&mut bytes).map_err(|e| {
LuaError::Runtime(RuntimeError {
message: format!("cannot read stdin: {e}"),
level: 0,
traceback: vec![],
})
})?;
(bytes, "=stdin".to_string())
};
self.load_bytes(&source, &name)
}
pub fn table_raw_set(&mut self, table: &Table, key: Val, value: Val) -> LuaResult<()> {
table.raw_set(&mut self.state, key, value)
}
pub fn table_raw_get(&self, table: &Table, key: Val) -> LuaResult<Val> {
table.raw_get(&self.state, key)
}
pub fn table_raw_len(&self, table: &Table) -> i64 {
table.raw_len(&self.state)
}
pub fn table_set_function(&mut self, table: &Table, name: &str, func: RustFn) -> LuaResult<()> {
let key = Val::Str(self.state.gc.intern_string(name.as_bytes()));
let closure = Closure::Rust(RustClosure::new(func, name));
let closure_ref = self.state.gc.alloc_closure(closure);
table.raw_set(&mut self.state, key, Val::Function(closure_ref))
}
pub(crate) fn state(&self) -> &LuaState {
&self.state
}
pub(crate) fn state_mut(&mut self) -> &mut LuaState {
&mut self.state
}
fn run_proto(&mut self, proto: Rc<Proto>) -> LuaResult<()> {
let mut proto = Rc::try_unwrap(proto).unwrap_or_else(|rc| (*rc).clone());
patch_string_constants(&mut proto, &mut self.state.gc);
let proto = Rc::new(proto);
let num_upvalues = proto.num_upvalues as usize;
let mut lua_cl = LuaClosure::new(proto, self.state.global);
for _ in 0..num_upvalues {
let uv = vm::closure::Upvalue::new_closed(Val::Nil);
let uv_ref = self.state.gc.alloc_upvalue(uv);
lua_cl.upvalues.push(uv_ref);
}
let closure_ref = self.state.gc.alloc_closure(Closure::Lua(lua_cl));
self.state.stack_set(0, Val::Function(closure_ref));
self.state.top = 1;
self.state.base = 1;
match self.state.precall(0, LUA_MULTRET)? {
CallResult::Lua => execute(&mut self.state)?,
CallResult::Rust => {}
}
Ok(())
}
fn get_global_val(&mut self, name: &str) -> Val {
let key_ref = self.state.gc.intern_string(name.as_bytes());
let Some(global_table) = self.state.gc.tables.get(self.state.global) else {
return Val::Nil;
};
global_table.get_str(key_ref, &self.state.gc.string_arena)
}
fn set_global_val(&mut self, name: &str, val: Val) -> LuaResult<()> {
let key_ref = self.state.gc.intern_string(name.as_bytes());
let key = Val::Str(key_ref);
let global = self.state.global;
let table = self.state.gc.tables.get_mut(global).ok_or_else(|| {
LuaError::Runtime(RuntimeError {
message: "global table not found".into(),
level: 0,
traceback: vec![],
})
})?;
table.raw_set(key, val, &self.state.gc.string_arena)
}
}
pub fn exec(source: &[u8]) -> LuaResult<()> {
exec_with_name(source, "=(string)")
}
pub fn exec_with_name(source: &[u8], name: &str) -> LuaResult<()> {
let mut lua = Lua::new()?;
lua.exec_bytes(source, name)
}
pub(crate) fn compile_or_undump(source: &[u8], name: &str) -> LuaResult<Rc<Proto>> {
let data = if source.first() == Some(&b'#') {
match source.iter().position(|&b| b == b'\n') {
Some(pos) => &source[pos + 1..],
None => &[], }
} else {
source
};
if data.starts_with(vm::dump::LUA_SIGNATURE) {
vm::undump::undump(data, name)
} else {
compiler::compile(source, name)
}
}
pub(crate) fn patch_string_constants(proto: &mut Proto, gc: &mut Gc) {
for (idx, bytes) in proto.string_pool.drain(..) {
let str_ref = gc.intern_string(&bytes);
proto.constants[idx as usize] = Val::Str(str_ref);
}
for child in &mut proto.protos {
patch_string_constants(Rc::make_mut(child), gc);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lua_new_creates_working_state() {
let lua = Lua::new();
assert!(lua.is_ok());
}
#[test]
fn lua_new_empty_has_no_libs() {
let mut lua = Lua::new_empty();
let val: LuaResult<Val> = lua.global("print");
assert!(val.is_ok());
assert_eq!(val.ok(), Some(Val::Nil));
}
#[test]
fn lua_new_with_selective() {
let lua = Lua::new_with(StdLib::BASE | StdLib::STRING);
assert!(lua.is_ok());
}
#[test]
fn lua_exec_string() {
let mut lua = Lua::new().ok().unwrap_or_else(Lua::new_empty);
let result = lua.exec("local x = 1 + 2");
assert!(result.is_ok());
}
#[test]
fn lua_exec_syntax_error() {
let mut lua = Lua::new().ok().unwrap_or_else(Lua::new_empty);
let result = lua.exec("if then end");
assert!(result.is_err());
}
#[test]
fn lua_set_and_get_global_f64() {
let mut lua = Lua::new_empty();
lua.set_global("x", 42.0f64).ok();
let val: LuaResult<f64> = lua.global("x");
assert_eq!(val.ok(), Some(42.0));
}
#[test]
fn lua_set_and_get_global_string() {
let mut lua = Lua::new_empty();
lua.set_global("name", "hello").ok();
let val: LuaResult<String> = lua.global("name");
assert_eq!(val.ok(), Some("hello".to_string()));
}
#[test]
fn lua_set_and_get_global_bool() {
let mut lua = Lua::new_empty();
lua.set_global("flag", true).ok();
let val: LuaResult<bool> = lua.global("flag");
assert_eq!(val.ok(), Some(true));
}
#[test]
fn lua_set_and_get_global_nil() {
let mut lua = Lua::new_empty();
lua.set_global("x", 42.0f64).ok();
lua.set_global::<Option<f64>>("x", None).ok();
let val: LuaResult<Option<f64>> = lua.global("x");
assert_eq!(val.ok(), Some(None));
}
#[test]
fn lua_create_table() {
let mut lua = Lua::new_empty();
let t = lua.create_table();
t.raw_set(&mut lua.state, Val::Num(1.0), Val::Num(10.0))
.ok();
let v = t.raw_get(&lua.state, Val::Num(1.0));
assert_eq!(v.ok(), Some(Val::Num(10.0)));
}
#[test]
fn lua_register_function() {
let mut lua = Lua::new_empty();
let result = lua.register_function("myfn", |state| {
state.push(Val::Num(99.0));
Ok(1)
});
assert!(result.is_ok());
let val: LuaResult<Val> = lua.global("myfn");
assert!(matches!(val.ok(), Some(Val::Function(_))));
}
#[test]
fn lua_gc_methods() {
let mut lua = Lua::new_empty();
let count = lua.gc_count();
assert!(count > 0);
lua.gc_stop();
assert_eq!(lua.state.gc.gc_state.gc_threshold, usize::MAX);
lua.gc_restart();
assert_eq!(
lua.state.gc.gc_state.gc_threshold,
lua.state.gc.gc_state.total_bytes
);
let old_pause = lua.gc_set_pause(300);
assert_eq!(old_pause, 200); assert_eq!(lua.state.gc.gc_state.gc_pause, 300);
let old_mul = lua.gc_set_step_multiplier(400);
assert_eq!(old_mul, 200); assert_eq!(lua.state.gc.gc_state.gc_stepmul, 400);
}
#[test]
fn lua_gc_collect() {
let mut lua = Lua::new_empty();
let result = lua.gc_collect();
assert!(result.is_ok());
}
#[test]
fn lua_load_and_check() {
let mut lua = Lua::new_empty();
let func = lua.load("return 42");
assert!(func.is_ok());
assert!(matches!(func.ok(), Some(Function(_))));
}
#[test]
fn lua_call_function_returns_results() {
let mut lua = Lua::new().ok().unwrap_or_else(Lua::new_empty);
let func = lua.load("return 1, 2, 3").ok();
assert!(func.is_some());
let func = func.unwrap_or_else(|| unreachable!());
let results = lua.call_function(&func, &[]);
assert!(results.is_ok());
let results = results.unwrap_or_default();
assert_eq!(results.len(), 3);
assert_eq!(results[0], Val::Num(1.0));
assert_eq!(results[1], Val::Num(2.0));
assert_eq!(results[2], Val::Num(3.0));
}
#[test]
fn lua_call_function_with_args() {
let mut lua = Lua::new().ok().unwrap_or_else(Lua::new_empty);
let func = lua.load("return select('#', ...)").ok();
assert!(func.is_some());
let func = func.unwrap_or_else(|| unreachable!());
let results = lua.call_function(&func, &[Val::Num(10.0), Val::Num(20.0)]);
assert!(results.is_ok());
let results = results.unwrap_or_default();
assert_eq!(results.len(), 1);
assert_eq!(results[0], Val::Num(2.0));
}
#[test]
fn lua_call_function_no_results() {
let mut lua = Lua::new().ok().unwrap_or_else(Lua::new_empty);
let func = lua.load("local x = 1").ok();
assert!(func.is_some());
let func = func.unwrap_or_else(|| unreachable!());
let results = lua.call_function(&func, &[]);
assert!(results.is_ok());
let results = results.unwrap_or_default();
assert!(results.is_empty());
}
#[test]
fn lua_create_string() {
let mut lua = Lua::new_empty();
let val = lua.create_string(b"hello");
assert!(matches!(val, Val::Str(_)));
let val2 = lua.create_string(b"hello");
assert_eq!(val, val2);
}
#[test]
fn lua_table_raw_set_via_api() {
let mut lua = Lua::new_empty();
let t = lua.create_table();
let result = lua.table_raw_set(&t, Val::Num(1.0), Val::Num(42.0));
assert!(result.is_ok());
let v = t.raw_get(&lua.state, Val::Num(1.0));
assert_eq!(v.ok(), Some(Val::Num(42.0)));
}
#[test]
fn lua_load_file_nonexistent() {
let mut lua = Lua::new_empty();
let result = lua.load_file(Some("/nonexistent/path/to/file.lua"));
assert!(result.is_err());
}
#[test]
fn lua_create_userdata() {
let mut lua = Lua::new_empty();
let ud = lua.create_userdata(42i64);
let val = ud.borrow::<i64>(&lua.state);
assert_eq!(val, Some(&42i64));
}
#[test]
fn lua_create_userdata_type_mismatch() {
let mut lua = Lua::new_empty();
let ud = lua.create_userdata(42i64);
assert!(ud.borrow::<String>(&lua.state).is_none());
}
#[test]
fn lua_create_typed_userdata_with_metatable() {
let mut lua = Lua::new_empty();
let ud = lua.create_typed_userdata(100u32, "MyType");
assert!(ud.is_ok());
let ud = ud.unwrap_or_else(|_| unreachable!());
let mt = ud.metatable(&lua.state);
assert!(mt.is_some());
}
#[test]
fn lua_userdata_metatable_caching() {
let mut lua = Lua::new_empty();
let mt1 = lua.create_userdata_metatable("CachedType");
assert!(mt1.is_ok());
let mt1 = mt1.unwrap_or_else(|_| unreachable!());
let mt2 = lua.create_userdata_metatable("CachedType");
assert!(mt2.is_ok());
let mt2 = mt2.unwrap_or_else(|_| unreachable!());
assert_eq!(mt1.gc_ref(), mt2.gc_ref());
}
#[test]
fn lua_create_userdata_set_global() {
let mut lua = Lua::new().ok().unwrap_or_else(Lua::new_empty);
let ud = lua.create_userdata(99i64);
let val: Val = ud.into_lua(&mut lua).unwrap_or(Val::Nil);
lua.set_global_val("myud", val).ok();
let got = lua.get_global_val("myud");
assert!(matches!(got, Val::Userdata(_)));
}
#[test]
fn backward_compat_exec() {
let result = exec(b"local x = 1 + 2");
assert!(result.is_ok());
}
#[test]
fn backward_compat_exec_with_name() {
let result = exec_with_name(b"local x = 1 + 2", "=test");
assert!(result.is_ok());
}
#[test]
fn interrupt_flag_set_and_check() {
clear_interrupted();
set_interrupted();
assert!(check_interrupted(), "flag should be true after set");
assert!(!check_interrupted(), "flag should auto-clear after check");
}
#[test]
fn interrupt_flag_clear() {
clear_interrupted();
set_interrupted();
clear_interrupted();
assert!(!check_interrupted(), "flag should be false after clear");
}
}