use crate::error::{LuaError, LuaResult, RuntimeError};
use crate::vm::closure::{Closure, RustClosure};
use crate::vm::gc::arena::GcRef;
use crate::vm::state::LuaState;
use crate::vm::table::Table;
use crate::vm::value::Val;
const LUA_DIRSEP: &str = "/";
const LUA_PATHSEP: char = ';';
const LUA_PATH_MARK: &str = "?";
const LUA_EXECDIR: &str = "!";
const LUA_IGMARK: &str = "-";
const LUA_PATH_DEFAULT: &str = "./?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;/usr/local/lib/lua/5.1/?.lua;/usr/local/lib/lua/5.1/?/init.lua";
const LUA_CPATH_DEFAULT: &str =
"./?.so;/usr/local/lib/lua/5.1/?.so;/usr/local/lib/lua/5.1/loadall.so";
const SENTINEL: usize = 0xDEAD_CAFE;
const LOADED_KEY: &str = "_LOADED";
#[inline]
fn nargs(state: &LuaState) -> usize {
state.top.saturating_sub(state.base)
}
#[inline]
fn arg(state: &LuaState, n: usize) -> Val {
let idx = state.base + n;
if idx < state.top {
state.stack_get(idx)
} else {
Val::Nil
}
}
fn bad_argument(name: &str, n: usize, msg: &str) -> LuaError {
LuaError::Runtime(RuntimeError {
message: format!("bad argument #{n} to '{name}' ({msg})"),
level: 0,
traceback: vec![],
})
}
fn simple_error(msg: String) -> LuaError {
LuaError::Runtime(RuntimeError {
message: msg,
level: 0,
traceback: vec![],
})
}
fn check_string(state: &LuaState, name: &str, n: usize) -> LuaResult<String> {
let val = arg(state, n);
match val {
Val::Str(r) => Ok(state
.gc
.string_arena
.get(r)
.map(|s| String::from_utf8_lossy(s.data()).to_string())
.unwrap_or_default()),
_ => Err(bad_argument(name, n + 1, "string expected")),
}
}
fn get_package_table(state: &LuaState) -> LuaResult<GcRef<Table>> {
let ci = &state.call_stack[state.ci];
let func_val = state.stack_get(ci.func);
let Val::Function(closure_ref) = func_val else {
return Err(simple_error(
"package: cannot get package table from non-function".into(),
));
};
let cl = state
.gc
.closures
.get(closure_ref)
.ok_or_else(|| simple_error("package: closure not found".into()))?;
match cl {
Closure::Rust(rc) => {
if let Some(Val::Table(pkg)) = rc.upvalues.first() {
Ok(*pkg)
} else {
Err(simple_error("package: upvalue[0] is not a table".into()))
}
}
Closure::Lua(_) => Err(simple_error("package: expected Rust closure".into())),
}
}
fn get_field(state: &mut LuaState, table_ref: GcRef<Table>, key: &str) -> Val {
let key_ref = state.gc.intern_string(key.as_bytes());
let Some(table) = state.gc.tables.get(table_ref) else {
return Val::Nil;
};
table.get_str(key_ref, &state.gc.string_arena)
}
fn set_field(state: &mut LuaState, table_ref: GcRef<Table>, key: &str, val: Val) -> LuaResult<()> {
let key_ref = state.gc.intern_string(key.as_bytes());
let table = state
.gc
.tables
.get_mut(table_ref)
.ok_or_else(|| simple_error("package: table not found".into()))?;
table.raw_set(Val::Str(key_ref), val, &state.gc.string_arena)?;
Ok(())
}
fn find_or_create_table(
state: &mut LuaState,
root: GcRef<Table>,
path: &str,
) -> LuaResult<GcRef<Table>> {
let mut current = root;
for part in path.split('.') {
let key_ref = state.gc.intern_string(part.as_bytes());
let key = Val::Str(key_ref);
let val = {
let table = state.gc.tables.get(current).ok_or_else(|| {
simple_error("package: table not found in find_or_create_table".into())
})?;
table.get(key, &state.gc.string_arena)
};
match val {
Val::Table(t) => {
current = t;
}
Val::Nil => {
let new_table = state.gc.alloc_table(Table::new());
let table = state
.gc
.tables
.get_mut(current)
.ok_or_else(|| simple_error("package: table not found".into()))?;
table.raw_set(key, Val::Table(new_table), &state.gc.string_arena)?;
current = new_table;
}
_ => {
return Err(simple_error(format!("name conflict for module '{path}'")));
}
}
}
Ok(current)
}
fn set_path(
state: &mut LuaState,
pkg_table: GcRef<Table>,
field: &str,
env_var: &str,
default: &str,
) -> LuaResult<()> {
let path = match std::env::var(env_var) {
Ok(val) => {
let aux = val.replace(";;", &format!(";{default};"));
let aux = if val.starts_with(";;") {
format!("{default};{}", &aux[1..])
} else {
aux
};
if val.ends_with(";;") {
format!("{};{default}", &aux[..aux.len() - 1])
} else {
aux
}
}
Err(_) => default.to_string(),
};
let path_str = state.gc.intern_string(path.as_bytes());
set_field(state, pkg_table, field, Val::Str(path_str))
}
fn search_path(path: &str, name: &str) -> (Option<String>, String) {
let name = name.replace('.', LUA_DIRSEP);
let mut errors = String::new();
if name.starts_with(LUA_DIRSEP) && std::fs::metadata(&name).is_ok() {
return (Some(name), errors);
}
for template in path.split(LUA_PATHSEP) {
let template = template.trim();
if template.is_empty() {
continue;
}
let filename = template.replace(LUA_PATH_MARK, &name);
if std::fs::metadata(&filename).is_ok() {
return (Some(filename), errors);
}
errors.push_str("\n\tno file '");
errors.push_str(&filename);
errors.push('\'');
}
(None, errors)
}
fn loader_preload(state: &mut LuaState) -> LuaResult<u32> {
let name = check_string(state, "loader_preload", 0)?;
let pkg = get_package_table(state)?;
let preload_val = get_field(state, pkg, "preload");
let Val::Table(preload) = preload_val else {
return Err(simple_error(
"'package.preload' must be a table".to_string(),
));
};
let key_ref = state.gc.intern_string(name.as_bytes());
let val = {
let table = state
.gc
.tables
.get(preload)
.ok_or_else(|| simple_error("package.preload table not found".into()))?;
table.get_str(key_ref, &state.gc.string_arena)
};
if val.is_nil() {
let msg = state
.gc
.intern_string(format!("\n\tno field package.preload['{name}']").as_bytes());
state.push(Val::Str(msg));
} else {
state.push(val);
}
Ok(1)
}
fn loader_lua(state: &mut LuaState) -> LuaResult<u32> {
let name = check_string(state, "loader_lua", 0)?;
let pkg = get_package_table(state)?;
let path_val = get_field(state, pkg, "path");
let path = match path_val {
Val::Str(r) => state
.gc
.string_arena
.get(r)
.map(|s| String::from_utf8_lossy(s.data()).to_string())
.unwrap_or_default(),
_ => {
return Err(simple_error("'package.path' must be a string".to_string()));
}
};
let (found, errors) = search_path(&path, &name);
let Some(filename) = found else {
let msg = state.gc.intern_string(errors.as_bytes());
state.push(Val::Str(msg));
return Ok(1);
};
let source = match std::fs::read(&filename) {
Ok(s) => s,
Err(e) => {
return Err(simple_error(format!(
"error loading module '{name}' from file '{filename}':\n\t{e}"
)));
}
};
let chunk_name = format!("@{filename}");
match crate::compile_or_undump(&source, &chunk_name) {
Ok(proto) => {
let mut proto = std::rc::Rc::try_unwrap(proto).unwrap_or_else(|rc| (*rc).clone());
crate::patch_string_constants(&mut proto, &mut state.gc);
let proto = std::rc::Rc::new(proto);
let lua_cl = crate::vm::closure::LuaClosure::new(proto, state.global);
let closure_ref = state.gc.alloc_closure(Closure::Lua(lua_cl));
state.push(Val::Function(closure_ref));
Ok(1)
}
Err(e) => Err(simple_error(format!(
"error loading module '{name}' from file '{filename}':\n\t{e}"
))),
}
}
#[cfg(not(feature = "dynmod"))]
fn loader_c(state: &mut LuaState) -> LuaResult<u32> {
let name = check_string(state, "loader_c", 0)?;
let pkg = get_package_table(state)?;
let cpath_val = get_field(state, pkg, "cpath");
let cpath = match cpath_val {
Val::Str(r) => state
.gc
.string_arena
.get(r)
.map(|s| String::from_utf8_lossy(s.data()).to_string())
.unwrap_or_default(),
_ => {
return Err(simple_error("'package.cpath' must be a string".to_string()));
}
};
let (found, errors) = search_path(&cpath, &name);
if found.is_some() {
let msg = format!("\n\tnative modules not enabled (cannot load '{name}')");
let msg_ref = state.gc.intern_string(msg.as_bytes());
state.push(Val::Str(msg_ref));
} else {
let msg = state.gc.intern_string(errors.as_bytes());
state.push(Val::Str(msg));
}
Ok(1)
}
#[cfg(feature = "dynmod")]
fn loader_c(state: &mut LuaState) -> LuaResult<u32> {
let name = check_string(state, "loader_c", 0)?;
let pkg = get_package_table(state)?;
let cpath_val = get_field(state, pkg, "cpath");
let cpath = match cpath_val {
Val::Str(r) => state
.gc
.string_arena
.get(r)
.map(|s| String::from_utf8_lossy(s.data()).to_string())
.unwrap_or_default(),
_ => {
return Err(simple_error("'package.cpath' must be a string".to_string()));
}
};
let (found, errors) = search_path(&cpath, &name);
let Some(filename) = found else {
let msg = state.gc.intern_string(errors.as_bytes());
state.push(Val::Str(msg));
return Ok(1);
};
let sym_name = mkfuncname(&name);
ll_loadfunc(state, &filename, &sym_name)
}
#[cfg(not(feature = "dynmod"))]
fn loader_croot(state: &mut LuaState) -> LuaResult<u32> {
let name = check_string(state, "loader_croot", 0)?;
let Some(dot_pos) = name.find('.') else {
return Ok(0);
};
let root = &name[..dot_pos];
let pkg = get_package_table(state)?;
let cpath_val = get_field(state, pkg, "cpath");
let cpath = match cpath_val {
Val::Str(r) => state
.gc
.string_arena
.get(r)
.map(|s| String::from_utf8_lossy(s.data()).to_string())
.unwrap_or_default(),
_ => {
return Err(simple_error("'package.cpath' must be a string".to_string()));
}
};
let (found, errors) = search_path(&cpath, root);
if found.is_some() {
let msg = format!("\n\tno module '{name}' in native root file");
let msg_ref = state.gc.intern_string(msg.as_bytes());
state.push(Val::Str(msg_ref));
} else {
let msg = state.gc.intern_string(errors.as_bytes());
state.push(Val::Str(msg));
}
Ok(1)
}
#[cfg(feature = "dynmod")]
fn loader_croot(state: &mut LuaState) -> LuaResult<u32> {
let name = check_string(state, "loader_croot", 0)?;
let Some(dot_pos) = name.find('.') else {
return Ok(0);
};
let root = &name[..dot_pos];
let pkg = get_package_table(state)?;
let cpath_val = get_field(state, pkg, "cpath");
let cpath = match cpath_val {
Val::Str(r) => state
.gc
.string_arena
.get(r)
.map(|s| String::from_utf8_lossy(s.data()).to_string())
.unwrap_or_default(),
_ => {
return Err(simple_error("'package.cpath' must be a string".to_string()));
}
};
let (found, errors) = search_path(&cpath, root);
let Some(filename) = found else {
let msg = state.gc.intern_string(errors.as_bytes());
state.push(Val::Str(msg));
return Ok(1);
};
let sym_name = mkfuncname(&name);
ll_loadfunc(state, &filename, &sym_name)
}
pub fn ll_require(state: &mut LuaState) -> LuaResult<u32> {
let name = check_string(state, "require", 0)?;
let pkg = get_package_table(state)?;
let loaded_val = get_field(state, pkg, "loaded");
let Val::Table(loaded) = loaded_val else {
return Err(simple_error("'package.loaded' must be a table".to_string()));
};
let name_key = state.gc.intern_string(name.as_bytes());
let cached = {
let loaded_t = state
.gc
.tables
.get(loaded)
.ok_or_else(|| simple_error("package.loaded table not found".into()))?;
loaded_t.get_str(name_key, &state.gc.string_arena)
};
if cached.is_truthy() {
if cached == Val::LightUserdata(SENTINEL) {
return Err(simple_error(format!(
"loop or previous error loading module '{name}'"
)));
}
state.push(cached);
return Ok(1);
}
let loaders_val = get_field(state, pkg, "loaders");
let Val::Table(loaders) = loaders_val else {
return Err(simple_error(
"'package.loaders' must be a table".to_string(),
));
};
{
let name_key = state.gc.intern_string(name.as_bytes());
let loaded_t = state
.gc
.tables
.get_mut(loaded)
.ok_or_else(|| simple_error("package.loaded table not found".into()))?;
loaded_t.raw_set(
Val::Str(name_key),
Val::LightUserdata(SENTINEL),
&state.gc.string_arena,
)?;
}
let mut error_msg = String::new();
let found_loader;
let mut i = 1;
loop {
let loader_val = {
let loaders_t = state
.gc
.tables
.get(loaders)
.ok_or_else(|| simple_error("package.loaders table not found".into()))?;
loaders_t.get(Val::Num(f64::from(i)), &state.gc.string_arena)
};
if loader_val.is_nil() {
let name_key = state.gc.intern_string(name.as_bytes());
let loaded_t = state
.gc
.tables
.get_mut(loaded)
.ok_or_else(|| simple_error("package.loaded table not found".into()))?;
loaded_t.raw_set(Val::Str(name_key), Val::Nil, &state.gc.string_arena)?;
return Err(simple_error(format!(
"module '{name}' not found:{error_msg}"
)));
}
let call_base = state.top;
state.ensure_stack(call_base + 3);
state.stack_set(call_base, loader_val);
let name_str = state.gc.intern_string(name.as_bytes());
state.stack_set(call_base + 1, Val::Str(name_str));
state.top = call_base + 2;
state.call_function(call_base, 1)?;
let result = state.stack_get(call_base);
state.top = call_base;
if let Val::Function(_) = result {
found_loader = result;
break;
} else if let Val::Str(r) = result {
if let Some(s) = state.gc.string_arena.get(r) {
error_msg.push_str(&String::from_utf8_lossy(s.data()));
}
}
i += 1;
}
let call_base = state.top;
state.ensure_stack(call_base + 3);
state.stack_set(call_base, found_loader);
let name_str = state.gc.intern_string(name.as_bytes());
state.stack_set(call_base + 1, Val::Str(name_str));
state.top = call_base + 2;
state.call_function(call_base, 1)?;
let module_result = state.stack_get(call_base);
state.top = call_base;
if !module_result.is_nil() {
let name_key = state.gc.intern_string(name.as_bytes());
let loaded_t = state
.gc
.tables
.get_mut(loaded)
.ok_or_else(|| simple_error("package.loaded table not found".into()))?;
loaded_t.raw_set(Val::Str(name_key), module_result, &state.gc.string_arena)?;
}
let name_key = state.gc.intern_string(name.as_bytes());
let final_val = {
let loaded_t = state
.gc
.tables
.get(loaded)
.ok_or_else(|| simple_error("package.loaded table not found".into()))?;
loaded_t.get_str(name_key, &state.gc.string_arena)
};
if final_val == Val::LightUserdata(SENTINEL) {
let name_key = state.gc.intern_string(name.as_bytes());
let loaded_t = state
.gc
.tables
.get_mut(loaded)
.ok_or_else(|| simple_error("package.loaded table not found".into()))?;
loaded_t.raw_set(Val::Str(name_key), Val::Bool(true), &state.gc.string_arena)?;
state.push(Val::Bool(true));
} else {
state.push(final_val);
}
Ok(1)
}
pub fn ll_module(state: &mut LuaState) -> LuaResult<u32> {
if nargs(state) < 1 {
return Err(bad_argument("module", 1, "string expected"));
}
let name = check_string(state, "module", 0)?;
let pkg = get_package_table(state)?;
let loaded_val = get_field(state, pkg, "loaded");
let Val::Table(loaded) = loaded_val else {
return Err(simple_error("'package.loaded' must be a table".to_string()));
};
let name_key = state.gc.intern_string(name.as_bytes());
let existing = {
let loaded_t = state
.gc
.tables
.get(loaded)
.ok_or_else(|| simple_error("package.loaded table not found".into()))?;
loaded_t.get_str(name_key, &state.gc.string_arena)
};
let module_table = if let Val::Table(t) = existing {
t
} else {
let t = find_or_create_table(state, state.global, &name)?;
let name_key = state.gc.intern_string(name.as_bytes());
let loaded_t = state
.gc
.tables
.get_mut(loaded)
.ok_or_else(|| simple_error("package.loaded table not found".into()))?;
loaded_t.raw_set(Val::Str(name_key), Val::Table(t), &state.gc.string_arena)?;
t
};
let has_name = {
let name_key = state.gc.intern_string(b"_NAME");
let mt = state
.gc
.tables
.get(module_table)
.ok_or_else(|| simple_error("module table not found".into()))?;
!mt.get_str(name_key, &state.gc.string_arena).is_nil()
};
if !has_name {
set_field(state, module_table, "_M", Val::Table(module_table))?;
let name_str = state.gc.intern_string(name.as_bytes());
set_field(state, module_table, "_NAME", Val::Str(name_str))?;
let package_name = if let Some(dot_pos) = name.rfind('.') {
&name[..=dot_pos]
} else {
""
};
let pkg_str = state.gc.intern_string(package_name.as_bytes());
set_field(state, module_table, "_PACKAGE", Val::Str(pkg_str))?;
}
if state.ci > 0 {
let caller_ci = state.ci - 1;
let func_idx = state.call_stack[caller_ci].func;
let func_val = state.stack_get(func_idx);
if let Val::Function(closure_ref) = func_val
&& let Some(Closure::Lua(lua_cl)) = state.gc.closures.get_mut(closure_ref)
{
lua_cl.env = module_table;
}
}
let n = nargs(state);
for i in 1..n {
let opt_fn = arg(state, i);
if let Val::Function(_) = opt_fn {
let call_base = state.top;
state.ensure_stack(call_base + 3);
state.stack_set(call_base, opt_fn);
state.stack_set(call_base + 1, Val::Table(module_table));
state.top = call_base + 2;
state.call_function(call_base, 0)?;
state.top = call_base;
}
}
Ok(0)
}
pub fn ll_seeall(state: &mut LuaState) -> LuaResult<u32> {
if nargs(state) < 1 {
return Err(bad_argument("package.seeall", 1, "table expected"));
}
let table_val = arg(state, 0);
let Val::Table(table_ref) = table_val else {
return Err(bad_argument("package.seeall", 1, "table expected"));
};
let mt = {
let t = state
.gc
.tables
.get(table_ref)
.ok_or_else(|| simple_error("package.seeall: table not found".into()))?;
t.metatable()
};
let mt_ref = if let Some(mt) = mt {
mt
} else {
let mt = state.gc.alloc_table(Table::new());
let t = state
.gc
.tables
.get_mut(table_ref)
.ok_or_else(|| simple_error("package.seeall: table not found".into()))?;
t.set_metatable(Some(mt));
mt
};
let index_key = state.gc.intern_string(b"__index");
let global = state.global;
let mt_table = state
.gc
.tables
.get_mut(mt_ref)
.ok_or_else(|| simple_error("package.seeall: metatable not found".into()))?;
mt_table.raw_set(
Val::Str(index_key),
Val::Table(global),
&state.gc.string_arena,
)?;
Ok(0)
}
#[cfg(not(feature = "dynmod"))]
pub fn ll_loadlib(state: &mut LuaState) -> LuaResult<u32> {
state.push(Val::Nil);
let msg = state
.gc
.intern_string(b"dynamic libraries not enabled; check your Lua installation");
state.push(Val::Str(msg));
let absent = state.gc.intern_string(b"absent");
state.push(Val::Str(absent));
Ok(3)
}
#[cfg(feature = "dynmod")]
pub fn ll_loadlib(state: &mut LuaState) -> LuaResult<u32> {
let path = check_string(state, "loadlib", 0)?;
let funcname = check_string(state, "loadlib", 1)?;
if funcname == "*" {
match crate::platform::dynlib::DynLib::open(&path) {
Ok(lib) => {
store_lib_handle(state, lib)?;
state.push(Val::Bool(true));
return Ok(1);
}
Err(msg) => {
return push_loaderror(state, &msg, "open");
}
}
}
ll_loadfunc(state, &path, &funcname)
}
#[cfg(feature = "dynmod")]
use crate::platform::dynlib::DynLib;
#[cfg(feature = "dynmod")]
const LOADLIB_MT_KEY: &str = "_LOADLIB";
#[cfg(feature = "dynmod")]
fn mkfuncname(modname: &str) -> String {
let name = if let Some(pos) = modname.find(LUA_IGMARK) {
&modname[pos + LUA_IGMARK.len()..]
} else {
modname
};
format!("rilua_open_{}", name.replace('.', "_"))
}
#[cfg(feature = "dynmod")]
#[allow(clippy::unnecessary_wraps)]
fn push_loaderror(state: &mut LuaState, msg: &str, errtype: &str) -> LuaResult<u32> {
state.push(Val::Nil);
let msg_ref = state.gc.intern_string(msg.as_bytes());
state.push(Val::Str(msg_ref));
let err_ref = state.gc.intern_string(errtype.as_bytes());
state.push(Val::Str(err_ref));
Ok(3)
}
#[cfg(feature = "dynmod")]
fn store_lib_handle(state: &mut LuaState, lib: DynLib) -> LuaResult<()> {
let reg_key = format!("LOADLIB: {}", lib.path());
let key_ref = state.gc.intern_string(reg_key.as_bytes());
let registry = state.registry;
let existing = {
let reg = state
.gc
.tables
.get(registry)
.ok_or_else(|| simple_error("registry not found".into()))?;
reg.get_str(key_ref, &state.gc.string_arena)
};
if !existing.is_nil() {
return Ok(());
}
let mt = super::new_metatable(state, LOADLIB_MT_KEY)?;
let ud = crate::vm::value::Userdata::with_metatable(Box::new(lib), mt);
let ud_ref = state.gc.alloc_userdata(ud);
let reg = state
.gc
.tables
.get_mut(registry)
.ok_or_else(|| simple_error("registry not found".into()))?;
reg.raw_set(
Val::Str(key_ref),
Val::Userdata(ud_ref),
&state.gc.string_arena,
)?;
Ok(())
}
#[cfg(feature = "dynmod")]
fn ll_loadfunc(state: &mut LuaState, path: &str, sym_name: &str) -> LuaResult<u32> {
let lib = match DynLib::open(path) {
Ok(lib) => lib,
Err(msg) => return push_loaderror(state, &msg, "open"),
};
if let Err(msg) = crate::dynmod::validate_module_info(&lib) {
return push_loaderror(state, &msg, "open");
}
let entry = match crate::dynmod::load_entry_point(&lib, sym_name) {
Ok(entry) => entry,
Err(msg) => {
store_lib_handle(state, lib)?;
return push_loaderror(state, &msg, "init");
}
};
store_lib_handle(state, lib)?;
make_module_wrapper(state, entry)
}
#[cfg(feature = "dynmod")]
#[allow(unsafe_code, clippy::unnecessary_wraps)]
fn make_module_wrapper(
state: &mut LuaState,
entry: crate::dynmod::RiluaModuleEntry,
) -> LuaResult<u32> {
let ptr_val = Val::LightUserdata(entry as usize);
let wrapper: crate::vm::closure::RustFn = |state: &mut LuaState| {
let ci = &state.call_stack[state.ci];
let func_val = state.stack_get(ci.func);
let Val::Function(closure_ref) = func_val else {
return Err(simple_error("dynmod wrapper: not a function".into()));
};
let cl = state
.gc
.closures
.get(closure_ref)
.ok_or_else(|| simple_error("dynmod wrapper: closure not found".into()))?;
let uv = match cl {
Closure::Rust(rc) => rc.upvalues.first().copied().unwrap_or(Val::Nil),
Closure::Lua(_) => {
return Err(simple_error("dynmod wrapper: expected Rust closure".into()));
}
};
let Val::LightUserdata(ptr) = uv else {
return Err(simple_error("dynmod wrapper: bad upvalue".into()));
};
let entry: crate::dynmod::RiluaModuleEntry = unsafe { std::mem::transmute(ptr) };
match crate::dynmod::call_entry_point(entry, state) {
Ok(n) => Ok(n as u32),
Err(msg) => Err(simple_error(msg)),
}
};
let closure = Closure::Rust(RustClosure {
func: wrapper,
upvalues: vec![ptr_val],
name: "dynmod_entry".to_string(),
env: None,
});
let closure_ref = state.gc.alloc_closure(closure);
state.push(Val::Function(closure_ref));
Ok(1)
}
#[cfg(feature = "dynmod")]
#[allow(clippy::unnecessary_wraps)]
fn loadlib_gc(_state: &mut LuaState) -> LuaResult<u32> {
Ok(0)
}
#[cfg(feature = "dynmod")]
fn create_loadlib_metatable(state: &mut LuaState) -> LuaResult<()> {
let mt = super::new_metatable(state, LOADLIB_MT_KEY)?;
super::register_table_fn(state, mt, "__gc", loadlib_gc)?;
Ok(())
}
fn register_table_fn_with_upvalues(
state: &mut LuaState,
table_ref: GcRef<Table>,
name: &str,
func: crate::vm::closure::RustFn,
upvalues: Vec<Val>,
) -> LuaResult<()> {
let closure = Closure::Rust(RustClosure {
func,
upvalues,
name: name.to_string(),
env: None,
});
let closure_ref = state.gc.alloc_closure(closure);
let key_ref = state.gc.intern_string(name.as_bytes());
let key = Val::Str(key_ref);
let val = Val::Function(closure_ref);
let table = state
.gc
.tables
.get_mut(table_ref)
.ok_or_else(|| simple_error("table not found for function registration".into()))?;
table.raw_set(key, val, &state.gc.string_arena)?;
Ok(())
}
fn register_global_fn_with_upvalues(
state: &mut LuaState,
name: &str,
func: crate::vm::closure::RustFn,
upvalues: Vec<Val>,
) -> LuaResult<()> {
let global = state.global;
register_table_fn_with_upvalues(state, global, name, func, upvalues)
}
pub fn open_package_lib(state: &mut LuaState) -> LuaResult<()> {
let pkg_table = state.gc.alloc_table(Table::new());
let pkg_val = Val::Table(pkg_table);
#[cfg(feature = "dynmod")]
create_loadlib_metatable(state)?;
super::register_table_fn(state, pkg_table, "loadlib", ll_loadlib)?;
super::register_table_fn(state, pkg_table, "seeall", ll_seeall)?;
let loaders_table = state.gc.alloc_table(Table::new());
let loader_fns: &[(&str, crate::vm::closure::RustFn)] = &[
("loader_preload", loader_preload),
("loader_lua", loader_lua),
("loader_c", loader_c),
("loader_croot", loader_croot),
];
for (i, &(name, func)) in loader_fns.iter().enumerate() {
let closure = Closure::Rust(RustClosure {
func,
upvalues: vec![pkg_val],
name: name.to_string(),
env: None,
});
let closure_ref = state.gc.alloc_closure(closure);
let loaders_t = state
.gc
.tables
.get_mut(loaders_table)
.ok_or_else(|| simple_error("loaders table not found".into()))?;
#[allow(clippy::cast_precision_loss)]
loaders_t.raw_set(
Val::Num((i + 1) as f64),
Val::Function(closure_ref),
&state.gc.string_arena,
)?;
}
set_field(state, pkg_table, "loaders", Val::Table(loaders_table))?;
set_path(state, pkg_table, "path", "LUA_PATH", LUA_PATH_DEFAULT)?;
set_path(state, pkg_table, "cpath", "LUA_CPATH", LUA_CPATH_DEFAULT)?;
let config_str =
format!("{LUA_DIRSEP}\n{LUA_PATHSEP}\n{LUA_PATH_MARK}\n{LUA_EXECDIR}\n{LUA_IGMARK}\n");
let config_ref = state.gc.intern_string(config_str.as_bytes());
set_field(state, pkg_table, "config", Val::Str(config_ref))?;
let loaded_table = state.gc.alloc_table(Table::new());
set_field(state, pkg_table, "loaded", Val::Table(loaded_table))?;
{
let key_ref = state.gc.intern_string(LOADED_KEY.as_bytes());
let registry = state.registry;
let reg_t = state
.gc
.tables
.get_mut(registry)
.ok_or_else(|| simple_error("registry not found".into()))?;
reg_t.raw_set(
Val::Str(key_ref),
Val::Table(loaded_table),
&state.gc.string_arena,
)?;
}
let preload_table = state.gc.alloc_table(Table::new());
set_field(state, pkg_table, "preload", Val::Table(preload_table))?;
register_global_fn_with_upvalues(state, "require", ll_require, vec![pkg_val])?;
register_global_fn_with_upvalues(state, "module", ll_module, vec![pkg_val])?;
super::register_global_val(state, "package", pkg_val)?;
let lib_names = [
"string",
"table",
"math",
"os",
"io",
"coroutine",
"debug",
"package",
];
for lib_name in &lib_names {
let key_ref = state.gc.intern_string(lib_name.as_bytes());
let global_t = state
.gc
.tables
.get(state.global)
.ok_or_else(|| simple_error("global table not found".into()))?;
let val = global_t.get_str(key_ref, &state.gc.string_arena);
if !val.is_nil() {
let loaded_t = state
.gc
.tables
.get_mut(loaded_table)
.ok_or_else(|| simple_error("loaded table not found".into()))?;
loaded_t.raw_set(Val::Str(key_ref), val, &state.gc.string_arena)?;
}
}
Ok(())
}