use crate::{LuaResult, LuaValue, lua_vm::LuaState};
pub fn lua_require(l: &mut LuaState) -> LuaResult<usize> {
let modname_val = l
.get_arg(1)
.ok_or_else(|| l.error("bad argument #1 to 'require' (string expected)".to_string()))?;
let Some(modname_str) = modname_val.as_str() else {
return Err(l.error("bad argument #1 to 'require' (string expected)".to_string()));
};
let vm = l.vm_mut();
let loaded_val = vm.registry_get("_LOADED")?.ok_or_else(|| {
vm.main_state()
.error("package.loaded not found".to_string())
})?;
let package_table_value = vm
.registry_get("_PACKAGE")?
.ok_or_else(|| vm.main_state().error("package table not found".to_string()))?;
let Some(loaded_table) = loaded_val.as_table_mut() else {
return Err(l.error("package.loaded must be a table".to_string()));
};
let already_loaded = loaded_table
.raw_get(&modname_val)
.unwrap_or(LuaValue::nil());
let is_loaded = match &already_loaded {
v if v.is_nil() => false,
v if v.as_boolean() == Some(false) => false,
_ => true,
};
if is_loaded {
l.push_value(already_loaded)?;
return Ok(1);
}
let searchers_key = l.create_string("searchers")?;
let Some(package_table) = package_table_value.as_table() else {
return Err(l.error("package must be a table".to_string()));
};
let searchers_val = match package_table.raw_get(&searchers_key) {
Some(v) => v,
None => return Err(l.error("package.searchers not found".to_string())),
};
let Some(searchers_table) = searchers_val.as_table() else {
return Err(l.error("package.searchers must be a table".to_string()));
};
let mut error_messages = Vec::new();
let mut i = 1;
loop {
let searcher = searchers_table
.raw_geti(i as i64)
.unwrap_or(LuaValue::nil());
if searcher.is_nil() {
break;
}
l.push_value(searcher)?;
l.push_value(modname_val)?;
let func_idx = l.get_top() - 2;
let (success, result_count) = l.pcall_stack_based(func_idx, 1)?;
if !success {
let error_msg = l.stack_get(func_idx).unwrap_or_default();
if let Some(err) = error_msg.as_str() {
error_messages.push(err.to_string());
}
l.set_top(func_idx)?;
i += 1;
continue;
}
if result_count == 0 {
l.set_top(func_idx)?;
i += 1;
continue;
}
let first_result = l.stack_get(func_idx).unwrap_or_default();
if first_result.is_nil() || (first_result.as_boolean() == Some(false)) {
l.set_top(func_idx)?;
i += 1;
continue;
}
let is_function = first_result.is_function() || first_result.is_cfunction();
if !is_function {
if let Some(msg) = first_result.as_str() {
error_messages.push(msg.to_string());
}
l.set_top(func_idx)?;
i += 1;
continue;
}
let loader = first_result;
let loader_data = if result_count >= 2 {
l.stack_get(func_idx + 1).unwrap_or_default()
} else {
LuaValue::nil()
};
l.set_top(func_idx)?;
l.push_value(loader)?;
l.push_value(modname_val)?;
if !loader_data.is_nil() {
l.push_value(loader_data)?;
}
let loader_func_idx = l.get_top() - if loader_data.is_nil() { 2 } else { 3 };
let loader_nargs = if loader_data.is_nil() { 1 } else { 2 };
let (loader_success, loader_result_count) =
l.pcall_stack_based(loader_func_idx, loader_nargs)?;
if !loader_success {
let error_val = l.stack_get(loader_func_idx).unwrap_or_default();
let error_msg = if let Some(err) = error_val.as_str() {
err.to_string()
} else {
"error loading module".to_string()
};
return Err(l.error(format!(
"error loading module '{}': {}",
modname_str, error_msg
)));
}
let loader_result = if loader_result_count > 0 {
l.stack_get(loader_func_idx).unwrap_or_default()
} else {
LuaValue::nil()
};
if !loader_result.is_nil() {
l.raw_set(&loaded_val, modname_val, loader_result);
}
let current_loaded = loaded_val
.as_table()
.and_then(|t| t.raw_get(&modname_val))
.unwrap_or(LuaValue::nil());
let final_result = if current_loaded.is_nil() {
let val = LuaValue::boolean(true);
l.raw_set(&loaded_val, modname_val, val);
val
} else {
current_loaded
};
l.set_top(loader_func_idx)?;
l.push_value(final_result)?;
l.push_value(loader_data)?;
return Ok(2);
}
let error_msg = if error_messages.is_empty() {
format!("module '{}' not found", modname_str)
} else {
format!(
"module '{}' not found:{}",
modname_str,
error_messages.join("")
)
};
Err(l.error(error_msg))
}