pub mod parse_number;
mod require;
use crate::gc::{code_param, decode_param};
use crate::lib_registry::LibraryModule;
use crate::lua_value::{LuaValue, LuaValueKind, UpvalueStore};
use crate::lua_vm::{LuaError, LuaResult, LuaState, get_metatable};
use crate::stdlib::basic::parse_number::parse_lua_number;
use crate::{GcKind, GcState, MAJORMINOR, MINORMAJOR, MINORMUL, PAUSE, STEPMUL, STEPSIZE};
use require::lua_require;
pub fn create_basic_lib() -> LibraryModule {
crate::lib_module!("_G", {
"print" => lua_print,
"type" => lua_type,
"assert" => lua_assert,
"error" => lua_error,
"tonumber" => lua_tonumber,
"tostring" => lua_tostring,
"select" => lua_select,
"ipairs" => lua_ipairs,
"pairs" => lua_pairs,
"next" => lua_next,
"pcall" => lua_pcall,
"xpcall" => lua_xpcall,
"getmetatable" => lua_getmetatable,
"setmetatable" => lua_setmetatable,
"rawget" => lua_rawget,
"rawset" => lua_rawset,
"rawlen" => lua_rawlen,
"rawequal" => lua_rawequal,
"collectgarbage" => lua_collectgarbage,
"require" => lua_require,
"load" => lua_load,
"loadfile" => lua_loadfile,
"dofile" => lua_dofile,
"warn" => lua_warn,
})
.with_value("_VERSION", |vm| {
vm.create_string_owned(format!("{}", vm.version))
})
}
fn lua_print(l: &mut LuaState) -> LuaResult<usize> {
let args = l.get_args();
let mut output = String::new();
for (index, arg) in args.iter().enumerate() {
let s = l.to_string(arg)?;
output.push_str(&s);
if index < args.len() - 1 {
output.push('\t');
}
}
println!("{}", output);
Ok(0)
}
fn lua_type(l: &mut LuaState) -> LuaResult<usize> {
let value = match l.get_arg(1) {
Some(v) => v,
None => {
return Err(l.error("bad argument #1 to 'type' (value expected)".to_string()));
}
};
let cs = &l.vm_mut().const_strings;
let result = match value.kind() {
LuaValueKind::Nil => cs.str_nil,
LuaValueKind::Boolean => cs.str_boolean,
LuaValueKind::Integer | LuaValueKind::Float => cs.str_number,
LuaValueKind::String => cs.str_string,
LuaValueKind::Table => cs.str_table,
LuaValueKind::Function
| LuaValueKind::CFunction
| LuaValueKind::CClosure
| LuaValueKind::RClosure => cs.str_function,
LuaValueKind::Userdata => cs.str_userdata,
LuaValueKind::Thread => cs.str_thread,
};
l.push_value(result)?;
Ok(1)
}
fn lua_assert(l: &mut LuaState) -> LuaResult<usize> {
let arg_count = l.arg_count();
if arg_count == 0 {
return Err(l.error("bad argument #1 to 'assert' (value expected)".to_string()));
}
let condition = l.get_arg(1).unwrap_or_default();
if !condition.is_truthy() {
let msg_arg = l.get_arg(2);
if let Some(msg) = msg_arg {
if msg.is_string() {
let message = l.to_string(&msg)?;
let where_prefix = lua_where(l, 1);
let formatted = format!("{}{}", where_prefix, message);
let err_str = l.create_string(&formatted)?;
l.error_object = err_str;
l.error_msg = formatted;
return Err(LuaError::RuntimeError);
} else {
let message = l.to_string(&msg)?;
return Err(l.error_with_object(message, msg));
}
}
let where_prefix = lua_where(l, 1);
let formatted = format!("{}assertion failed!", where_prefix);
let err_str = l.create_string(&formatted)?;
l.error_object = err_str;
l.error_msg = formatted;
return Err(LuaError::RuntimeError);
}
Ok(arg_count)
}
fn lua_where(l: &LuaState, level: usize) -> String {
let depth = l.call_depth();
let mut lvl = level;
if depth >= 2 {
let mut i = depth - 2;
loop {
lvl -= 1;
if lvl == 0 {
let ci = l.get_call_info(i);
if ci.is_lua() && !ci.chunk_ptr.is_null() {
let chunk = unsafe { &*ci.chunk_ptr };
let source = chunk.source_name.as_deref().unwrap_or("[string]");
let line = if ci.pc > 0 && (ci.pc as usize - 1) < chunk.line_info.len() {
chunk.line_info[ci.pc as usize - 1] as usize
} else if !chunk.line_info.is_empty() {
chunk.line_info[0] as usize
} else {
0
};
return if line > 0 {
format!("{}:{}: ", source, line)
} else {
format!("{}: ", source)
};
}
break;
}
if i == 0 {
break;
}
i -= 1;
}
}
String::new()
}
fn lua_error(l: &mut LuaState) -> LuaResult<usize> {
let arg = l.get_arg(1).unwrap_or_default();
if arg.is_nil() {
return Err(l.error_with_object("<no error object>".to_string(), LuaValue::nil()));
}
let level = l.get_arg(2).and_then(|v| v.as_integer()).unwrap_or(1);
if arg.is_string() && level > 0 {
let message = l.to_string(&arg)?;
let where_prefix = lua_where(l, level as usize);
let formatted_msg = format!("{}{}", where_prefix, message);
let err_str = l.create_string(&formatted_msg)?;
l.error_object = err_str;
l.error_msg = formatted_msg;
Err(LuaError::RuntimeError)
} else {
let message = l.to_string(&arg)?;
Err(l.error_with_object(message, arg))
}
}
fn lua_tonumber(l: &mut LuaState) -> LuaResult<usize> {
let value = l
.get_arg(1)
.ok_or_else(|| l.error("tonumber() requires argument 1".to_string()))?;
let has_base = l.get_arg(2).is_some();
let base = l.get_arg(2).and_then(|v| v.as_integer()).unwrap_or(10);
if has_base && !(2..=36).contains(&base) {
return Err(l.error("bad argument #2 to 'tonumber' (base out of range)".to_string()));
}
let result = match value.kind() {
LuaValueKind::Integer if !has_base => value,
LuaValueKind::Float if !has_base => value,
LuaValueKind::String => {
if let Some(s) = value.as_str() {
let s_str = s.trim();
if has_base {
let (neg, digits) = if let Some(rest) = s_str.strip_prefix('-') {
(true, rest.trim_start())
} else if let Some(rest) = s_str.strip_prefix('+') {
(false, rest.trim_start())
} else {
(false, s_str)
};
if digits.is_empty() || digits.contains('\0') {
LuaValue::nil()
} else {
let mut result: u64 = 0;
let mut valid = true;
let mut has_any = false;
for ch in digits.chars() {
if let Some(d) = ch.to_digit(base as u32) {
result = result.wrapping_mul(base as u64).wrapping_add(d as u64);
has_any = true;
} else {
valid = false;
break;
}
}
if valid && has_any {
let i = result as i64;
LuaValue::integer(if neg { i.wrapping_neg() } else { i })
} else {
LuaValue::nil()
}
}
} else {
parse_lua_number(s_str)
}
} else {
LuaValue::nil()
}
}
_ => {
if has_base {
return Err(l.error(
"bad argument #1 to 'tonumber' (string expected, got number)".to_string(),
));
}
LuaValue::nil()
}
};
l.push_value(result)?;
Ok(1)
}
pub(crate) fn lua_float_to_string(n: f64) -> String {
if n.is_nan() {
return if n.is_sign_negative() { "-nan" } else { "-nan" }.to_string();
}
if n.is_infinite() {
return if n > 0.0 { "inf" } else { "-inf" }.to_string();
}
let s = format_g(n, 15);
let mut result = if s.parse::<f64>().ok() == Some(n) {
s
} else {
format_g(n, 17)
};
if !result.contains('.')
&& !result.contains('e')
&& !result.contains('E')
&& !result.contains('n')
&& !result.contains('i')
{
result.push_str(".0");
}
result
}
fn format_g(n: f64, prec: usize) -> String {
if n == 0.0 {
return if n.is_sign_negative() { "-0" } else { "0" }.to_string();
}
let abs_n = n.abs();
let exp = abs_n.log10().floor() as i32;
let formatted = if exp >= -4 && exp < prec as i32 {
let decimal_places = (prec as i32 - exp - 1).max(0) as usize;
format!("{:.prec$}", n, prec = decimal_places)
} else {
format!("{:.prec$e}", n, prec = prec - 1)
};
strip_trailing_zeros(&formatted)
}
fn strip_trailing_zeros(s: &str) -> String {
if let Some(e_pos) = s.find('e').or_else(|| s.find('E')) {
let (mantissa, exponent) = s.split_at(e_pos);
let stripped = strip_decimal_zeros(mantissa);
format!("{}{}", stripped, exponent)
} else if s.contains('.') {
strip_decimal_zeros(s)
} else {
s.to_string()
}
}
fn strip_decimal_zeros(s: &str) -> String {
let trimmed = s.trim_end_matches('0');
trimmed.strip_suffix('.').unwrap_or(trimmed).to_string()
}
fn lua_tostring(l: &mut LuaState) -> LuaResult<usize> {
let value = l
.get_arg(1)
.ok_or_else(|| l.error("tostring() requires argument 1".to_string()))?;
if value.is_string() {
l.push_value(value)?;
return Ok(1);
}
if value.is_integer() {
let n = value.as_integer_strict().unwrap();
let mut buf = itoa::Buffer::new();
let s = buf.format(n);
let result_value = l.create_string(s)?;
l.push_value(result_value)?;
return Ok(1);
}
if value.is_float() {
let n = value.as_number().unwrap();
let s = lua_float_to_string(n);
let result_value = l.create_string(&s)?;
l.push_value(result_value)?;
return Ok(1);
}
if value.is_nil() {
let result_value = l.vm_mut().const_strings.str_nil;
l.push_value(result_value)?;
return Ok(1);
}
if let Some(b) = value.as_boolean() {
let cs = &l.vm_mut().const_strings;
let result_value = if b { cs.str_true } else { cs.str_false };
l.push_value(result_value)?;
return Ok(1);
}
let result = l.to_string(&value)?;
let result_value = l.create_string_owned(result)?;
l.push_value(result_value)?;
Ok(1)
}
fn lua_select(l: &mut LuaState) -> LuaResult<usize> {
let index_arg = l
.get_arg(1)
.ok_or_else(|| l.error("bad argument #1 to 'select' (value expected)".to_string()))?;
let total_args = l.arg_count();
let vararg_count = if total_args > 0 { total_args - 1 } else { 0 };
if let Some(s) = index_arg.as_str() {
if s == "#" {
let result = LuaValue::integer(vararg_count as i64);
l.push_value(result)?;
return Ok(1);
}
return Err(l.error("bad argument #1 to 'select' (number expected)".to_string()));
}
let index = index_arg
.as_integer()
.ok_or_else(|| l.error("bad argument #1 to 'select' (number expected)".to_string()))?;
if index == 0 {
return Err(l.error("bad argument #1 to 'select' (index out of range)".to_string()));
}
let start_idx = if index > 0 {
(index - 1) as usize
} else {
let abs_idx = (-index) as usize;
if abs_idx > vararg_count {
return Err(l.error("bad argument #1 to 'select' (index out of range)".to_string()));
}
vararg_count - abs_idx
};
if start_idx >= vararg_count {
return Ok(0);
}
let result_count = vararg_count - start_idx;
l.ensure_stack_capacity(result_count)?;
let frame = &l.call_stack[l.call_depth() - 1];
let base = frame.base;
let top = frame.top as usize;
let stack_start = base + 1 + start_idx;
for i in 0..result_count {
let stack_idx = stack_start + i;
let val = if stack_idx < top && stack_idx < l.stack.len() {
l.stack[stack_idx]
} else {
LuaValue::nil()
};
unsafe {
l.push_value_unchecked(val);
}
}
Ok(result_count)
}
fn lua_ipairs(l: &mut LuaState) -> LuaResult<usize> {
let _table_val = l
.get_arg(1)
.ok_or_else(|| l.error("bad argument #1 to 'ipairs' (value expected)".to_string()))?;
let iter_func = LuaValue::cfunction(ipairs_next);
l.push_value(iter_func)?;
l.push_value(_table_val)?;
l.push_value(LuaValue::integer(0))?;
Ok(3)
}
#[inline]
fn ipairs_next(l: &mut LuaState) -> LuaResult<usize> {
let table_val = unsafe { l.get_arg_unchecked(1) };
let index_val = unsafe { l.get_arg_unchecked(2) };
let index = match index_val.as_integer() {
Some(i) => i,
None => return Err(l.error("ipairs iterator: invalid index".to_string())),
};
let next_index = index.wrapping_add(1);
let value = if let Some(table) = table_val.as_table_mut() {
if !table.has_metatable() {
table.raw_geti(next_index).unwrap_or(LuaValue::nil())
} else {
l.table_geti(&table_val, next_index)?
}
} else {
l.table_geti(&table_val, next_index)?
};
if !value.is_nil() {
unsafe {
l.push_value_unchecked(LuaValue::integer(next_index));
l.push_value_unchecked(value);
}
Ok(2)
} else {
unsafe {
l.push_value_unchecked(LuaValue::nil());
}
Ok(1)
}
}
fn lua_pairs(l: &mut LuaState) -> LuaResult<usize> {
let val = l.get_arg(1).ok_or_else(|| {
l.error("bad argument #1 to 'pairs' (table or userdata expected)".to_string())
})?;
if val.is_table() || val.is_userdata() {
let pairs_key = l.vm_mut().const_strings.tm_pairs;
if let Some(mt) = get_metatable(l, &val)
&& let Some(mt_table) = mt.as_table()
&& let Some(mm) = mt_table.raw_get(&pairs_key)
{
let call_base = l.current_frame().unwrap().base;
l.stack_set(call_base, mm)?;
l.stack_set(call_base + 1, val)?;
l.set_top(call_base + 2)?;
let num_results = l.call_stack_based(call_base, 1)?;
if num_results < 4 {
for _ in num_results..4 {
l.push_value(LuaValue::nil())?;
}
} else if num_results > 4 {
l.set_top(call_base + 4)?;
}
return Ok(4);
}
}
if val.is_table() {
let next_func = LuaValue::cfunction(lua_next);
l.push_value(next_func)?;
l.push_value(val)?;
l.push_value(LuaValue::nil())?;
l.push_value(LuaValue::nil())?;
Ok(4)
} else if val.is_userdata() {
let next_func = LuaValue::cfunction(lua_userdata_next);
l.push_value(next_func)?;
l.push_value(val)?;
l.push_value(LuaValue::nil())?;
l.push_value(LuaValue::nil())?;
Ok(4)
} else {
Err(l.error("bad argument #1 to 'pairs' (table or userdata expected)".to_string()))
}
}
fn lua_userdata_next(l: &mut LuaState) -> LuaResult<usize> {
let ud_val = unsafe { l.get_arg_unchecked(1) };
let key_val = l.get_arg(2).unwrap_or_default();
let ud = ud_val.as_userdata_mut().ok_or_else(|| {
l.error("bad argument #1 to userdata iterator (userdata expected)".to_string())
})?;
let control = crate::lua_value::userdata_trait::lua_value_to_udvalue(&key_val);
match ud.get_trait().lua_next(&control) {
Some((next_control, value)) => {
let k = crate::lua_value::userdata_trait::udvalue_to_lua_value(l, next_control)?;
let v = crate::lua_value::userdata_trait::udvalue_to_lua_value(l, value)?;
l.push_value(k)?;
l.push_value(v)?;
Ok(2)
}
None => {
unsafe {
l.push_value_unchecked(LuaValue::nil());
}
Ok(1)
}
}
}
fn lua_next(l: &mut LuaState) -> LuaResult<usize> {
let table_val = unsafe { l.get_arg_unchecked(1) };
let index_val = l.get_arg(2).unwrap_or_default();
let result = {
let table = table_val
.as_table()
.ok_or_else(|| l.error("bad argument #1 to 'next' (table expected)".to_string()))?;
table
.next(&index_val)
.map_err(|_| l.error("invalid key to 'next'".to_string()))?
};
if let Some((k, v)) = result {
unsafe {
l.push_value_unchecked(k);
l.push_value_unchecked(v);
}
Ok(2)
} else {
unsafe {
l.push_value_unchecked(LuaValue::nil());
}
Ok(1)
}
}
fn lua_pcall(l: &mut LuaState) -> LuaResult<usize> {
let arg_count = l.arg_count();
if arg_count < 1 {
return Err(l.error("bad argument #1 to 'pcall' (value expected)".to_string()));
}
let base = l
.current_frame()
.map(|f| f.base)
.ok_or(LuaError::RuntimeError)?;
let func_idx = base;
let call_arg_count = arg_count - 1;
let (success, result_count) = l.pcall_stack_based(func_idx, call_arg_count)?;
l.push_value(LuaValue::nil())?;
{
let stack = l.stack_mut();
for i in (0..result_count).rev() {
stack[func_idx + 1 + i] = stack[func_idx + i];
}
stack[func_idx] = LuaValue::boolean(success);
}
Ok(result_count + 1)
}
fn lua_xpcall(l: &mut LuaState) -> LuaResult<usize> {
let arg_count = l.arg_count();
if arg_count < 2 {
return Err(l.error("bad argument #2 to 'xpcall' (value expected)".to_string()));
}
let base = l
.current_frame()
.map(|f| f.base)
.ok_or(LuaError::RuntimeError)?;
let xpcall_func_pos = l
.current_frame()
.map(|f| f.base - f.func_offset as usize)
.ok_or(LuaError::RuntimeError)?;
let msgh = l.stack_get(base + 1).unwrap_or_default();
let f = l.stack_get(base).unwrap_or_default();
l.stack_set(xpcall_func_pos, msgh)?;
l.stack_set(xpcall_func_pos + 1, f)?;
let call_arg_count = arg_count - 2;
for i in 0..call_arg_count {
let val = l.stack_get(base + 2 + i).unwrap_or_default();
l.stack_set(xpcall_func_pos + 2 + i, val)?;
}
let func_idx = xpcall_func_pos + 1;
let handler_idx = xpcall_func_pos;
l.set_top(func_idx + 1 + call_arg_count)?;
{
use crate::lua_vm::call_info::call_status::CIST_XPCALL;
let frame_idx = l.call_depth() - 1;
let ci = l.get_call_info_mut(frame_idx);
ci.call_status |= CIST_XPCALL;
}
let (success, result_count) = l.xpcall_stack_based(func_idx, call_arg_count, handler_idx)?;
if success {
l.push_value(LuaValue::nil())?; {
let stack = l.stack_mut();
for i in (0..result_count).rev() {
stack[func_idx + 1 + i] = stack[func_idx + i];
}
stack[func_idx] = LuaValue::boolean(true);
}
Ok(result_count + 1)
} else {
let transformed_error = l.stack_get(func_idx).unwrap_or_default();
l.push_value(LuaValue::boolean(false))?;
l.push_value(transformed_error)?;
Ok(2)
}
}
fn lua_getmetatable(l: &mut LuaState) -> LuaResult<usize> {
let value = l
.get_arg(1)
.ok_or_else(|| l.error("bad argument #1 to 'getmetatable' (value expected)".to_string()))?;
let mt = get_metatable(l, &value);
match mt {
Some(mt_val) => {
if let Some(table) = mt_val.as_table() {
let key = l.create_string("__metatable")?;
if let Some(mm) = table.raw_get(&key)
&& !mm.is_nil()
{
l.push_value(mm)?;
return Ok(1);
}
}
l.push_value(mt_val)?;
}
None => {
l.push_value(LuaValue::nil())?;
}
}
Ok(1)
}
fn lua_setmetatable(l: &mut LuaState) -> LuaResult<usize> {
let table = l
.get_arg(1)
.ok_or_else(|| l.error("bad argument #1 to 'setmetatable' (value expected)".to_string()))?;
let metatable = l
.get_arg(2)
.ok_or_else(|| l.error("bad argument #2 to 'setmetatable' (value expected)".to_string()))?;
if let Some(table_ref) = table.as_table_mut() {
if let Some(existing_mt) = table_ref.get_metatable()
&& let Some(mt_table) = existing_mt.as_table()
{
let key = l.create_string("__metatable")?;
let has_protection = mt_table.raw_get(&key).is_some_and(|v| !v.is_nil());
if has_protection {
return Err(l.error("cannot change a protected metatable".to_string()));
}
}
match metatable.kind() {
LuaValueKind::Nil => {
table_ref.set_metatable(None);
}
LuaValueKind::Table => {
table_ref.set_metatable(Some(metatable));
}
_ => {
return Err(
l.error("setmetatable() second argument must be a table or nil".to_string())
);
}
}
if let Some(gc_ptr) = table.as_gc_ptr() {
l.gc_barrier_back(gc_ptr);
}
}
l.vm_mut().gc.check_finalizer(&table);
l.push_value(table)?;
Ok(1)
}
fn lua_rawget(l: &mut LuaState) -> LuaResult<usize> {
let table = l
.get_arg(1)
.ok_or_else(|| l.error("bad argument #1 to 'rawget' (value expected)".to_string()))?;
let key = l
.get_arg(2)
.ok_or_else(|| l.error("bad argument #2 to 'rawget' (value expected)".to_string()))?;
let value = table
.as_table()
.map(|table_ref| table_ref.raw_get(&key).unwrap_or(LuaValue::nil()));
if let Some(v) = value {
l.push_value(v)?;
return Ok(1);
}
Err(l.error("bad argument #1 to 'rawget' (table expected)".to_string()))
}
fn lua_rawset(l: &mut LuaState) -> LuaResult<usize> {
let table = l
.get_arg(1)
.ok_or_else(|| l.error("bad argument #1 to 'rawset' (value expected)".to_string()))?;
let key = l
.get_arg(2)
.ok_or_else(|| l.error("bad argument #2 to 'rawset' (value expected)".to_string()))?;
let value = l
.get_arg(3)
.ok_or_else(|| l.error("bad argument #3 to 'rawset' (value expected)".to_string()))?;
if table.is_table() {
if key.is_float()
&& let Some(f) = key.as_number()
&& f.is_nan()
{
return Err(l.error("table index is NaN".to_string()));
}
l.raw_set(&table, key, value);
l.push_value(table)?;
return Ok(1);
}
Err(l.error("bad argument #1 to 'rawset' (table expected)".to_string()))
}
fn lua_rawlen(l: &mut LuaState) -> LuaResult<usize> {
let value = l
.get_arg(1)
.ok_or_else(|| l.error("bad argument #1 to 'rawlen' (value expected)".to_string()))?;
let len = match value.kind() {
LuaValueKind::Table => {
if let Some(table) = value.as_table() {
table.len() as i64
} else {
return Err(
l.error("bad argument #1 to 'rawlen' (table or string expected)".to_string())
);
}
}
LuaValueKind::String => {
if let Some(s) = value.as_str() {
s.len() as i64
} else {
return Err(
l.error("bad argument #1 to 'rawlen' (table or string expected)".to_string())
);
}
}
_ => {
return Err(
l.error("bad argument #1 to 'rawlen' (table or string expected)".to_string())
);
}
};
l.push_value(LuaValue::integer(len))?;
Ok(1)
}
fn lua_rawequal(l: &mut LuaState) -> LuaResult<usize> {
let v1 = l.get_arg(1).unwrap_or_default();
let v2 = l.get_arg(2).unwrap_or_default();
let result = v1 == v2;
l.push_value(LuaValue::boolean(result))?;
Ok(1)
}
fn lua_collectgarbage(l: &mut LuaState) -> LuaResult<usize> {
if l.vm_mut().gc.gc_stopem {
l.push_value(LuaValue::nil())?;
return Ok(1);
}
let arg1 = l.get_arg(1);
let opt = match &arg1 {
Some(v) if v.is_nil() => "collect".to_string(),
Some(v) => match v.as_str() {
Some(s) => s.to_string(),
None => return Err(crate::stdlib::debug::arg_typeerror(l, 1, "string", v)),
},
None => "collect".to_string(),
};
match opt.as_str() {
"collect" => {
l.collect_garbage()?;
l.push_value(LuaValue::integer(0))?;
Ok(1)
}
"count" => {
let gc = &l.vm_mut().gc;
let real_bytes = gc.total_bytes - gc.gc_debt; let kb = real_bytes.max(0) as f64 / 1024.0;
l.push_value(LuaValue::number(kb))?;
Ok(1)
}
"stop" => {
l.vm_mut().gc.gc_stopped = true;
l.push_value(LuaValue::integer(0))?;
Ok(1)
}
"restart" => {
l.vm_mut().gc.gc_stopped = false;
l.vm_mut().gc.set_debt(0);
l.push_value(LuaValue::integer(0))?;
Ok(1)
}
"step" => {
let arg2 = l.get_arg(2);
let n_arg = arg2.and_then(|v| v.as_integer()).unwrap_or(0);
let old_stopped = l.vm_mut().gc.gc_stopped;
let gc = &l.vm_mut().gc;
let n = if n_arg <= 0 {
gc.gc_debt
} else {
n_arg as isize
};
let mut work = false;
l.vm_mut().gc.gc_stopped = false;
let old_debt = l.vm_mut().gc.gc_debt;
l.vm_mut().gc.set_debt(old_debt.saturating_sub(n));
if l.check_gc()? {
work = true;
}
l.vm_mut().gc.gc_stopped = old_stopped;
let completed = work && matches!(l.vm_mut().gc.gc_state, GcState::Pause);
l.push_value(LuaValue::boolean(completed))?;
Ok(1)
}
"isrunning" => {
let is_running = !l.vm_mut().gc.gc_stopped;
l.push_value(LuaValue::boolean(is_running))?;
Ok(1)
}
"generational" => {
let old_mode = match l.vm_mut().gc.gc_kind {
GcKind::Inc => "incremental",
GcKind::GenMinor => "generational",
GcKind::GenMajor => "generational",
};
let vm_ptr = l.vm_ptr();
let vm = unsafe { &mut *vm_ptr };
vm.gc.change_mode(l, GcKind::GenMinor);
let mode_value = l.create_string(old_mode)?;
l.push_value(mode_value)?;
Ok(1)
}
"incremental" => {
let old_mode = match l.vm_mut().gc.gc_kind {
GcKind::Inc => "incremental",
GcKind::GenMinor => "generational",
GcKind::GenMajor => "generational",
};
let vm_ptr = l.vm_ptr();
let vm = unsafe { &mut *vm_ptr };
vm.gc.change_mode(l, GcKind::Inc);
let mode_value = l.create_string(old_mode)?;
l.push_value(mode_value)?;
Ok(1)
}
"param" => {
let arg2 = l.get_arg(2);
let arg3 = l.get_arg(3);
let param_name = if let Some(v) = arg2 {
v.as_str().map(|s| s.to_string())
} else {
None
};
if param_name.is_none() {
return Err(l.error("collectgarbage 'param': parameter name expected".to_string()));
}
let param_name = param_name.unwrap();
let param_idx = match param_name.as_str() {
"minormul" => Some(MINORMUL), "majorminor" => Some(MAJORMINOR), "minormajor" => Some(MINORMAJOR), "pause" => Some(PAUSE), "stepmul" => Some(STEPMUL), "stepsize" => Some(STEPSIZE), _ => None,
};
if param_idx.is_none() {
return Err(l.error(format!(
"collectgarbage 'param': invalid parameter name '{}'",
param_name
)));
}
let param_idx = param_idx.unwrap();
let old_value = {
let vm = l.vm_mut();
let old = decode_param(vm.gc.gc_params[param_idx]);
if let Some(new_val) = arg3
&& let Some(new_int) = new_val.as_integer()
{
vm.gc.gc_params[param_idx] = code_param(new_int as u32);
}
old
};
l.push_value(LuaValue::integer(old_value as i64))?;
Ok(1)
}
_ => Err(l.error(format!("collectgarbage: invalid option '{}'", opt))),
}
}
fn lua_load(l: &mut LuaState) -> LuaResult<usize> {
use crate::lua_value::chunk_serializer;
let chunk_val = l
.get_arg(1)
.ok_or_else(|| l.error("bad argument #1 to 'load' (value expected)".to_string()))?;
let chunkname_arg = l.get_arg(2).and_then(|v| v.as_str().map(|s| s.to_string()));
let mode_arg = l.get_arg(3).and_then(|v| v.as_str().map(|s| s.to_string()));
let env_arg = l.get_arg(4);
let is_reader = chunk_val.is_function() || chunk_val.is_table();
let (code_bytes, is_binary) = if is_reader {
let mut accumulated = Vec::new();
let mut is_binary = false;
let mut first_chunk = true;
loop {
l.push_value(chunk_val)?;
let func_idx = l.get_top() - 1;
let call_result = l.pcall_stack_based(func_idx, 0);
let result = match call_result {
Ok((true, result_count)) => {
if result_count > 0 {
l.stack_get(func_idx).unwrap_or_default()
} else {
LuaValue::nil()
}
}
Ok((false, _)) => {
let error_val = l.stack_get(func_idx).unwrap_or_default();
l.set_top(func_idx)?;
l.push_value(LuaValue::nil())?;
let err_msg =
l.create_string(&format!("error in reader function: {}", error_val))?;
l.push_value(err_msg)?;
return Ok(2);
}
Err(e) => {
return Err(e);
}
};
if result.is_nil() {
l.set_top(func_idx)?;
break;
}
let bytes_opt = result.as_bytes();
if let Some(bytes) = bytes_opt {
if bytes.is_empty() {
l.set_top(func_idx)?;
break;
}
if first_chunk && !bytes.is_empty() && bytes[0] == 0x1B {
is_binary = true;
}
accumulated.extend_from_slice(bytes);
first_chunk = false;
l.set_top(func_idx)?;
} else {
l.set_top(func_idx)?;
l.push_value(LuaValue::nil())?;
let err_msg = l.create_string("reader function must return a string")?;
l.push_value(err_msg)?;
return Ok(2);
}
}
(accumulated, is_binary)
} else if let Some(bytes) = chunk_val.as_bytes() {
let is_binary = bytes.first() == Some(&0x1B);
(bytes.to_vec(), is_binary)
} else {
return Err(l.error("bad argument #1 to 'load' (function or string expected)".to_string()));
};
let chunkname = if let Some(name) = chunkname_arg {
name
} else if !is_binary {
match String::from_utf8(code_bytes.clone()) {
Ok(s) => s,
Err(_) => "=(load)".to_string(),
}
} else {
"=(load)".to_string()
};
let mode = mode_arg.unwrap_or_else(|| "bt".to_string());
if mode.is_empty() || mode.chars().any(|c| c != 'b' && c != 't') {
return Err(crate::stdlib::debug::argerror(l, 3, "invalid mode"));
}
if is_binary {
if !mode.contains('b') {
l.push_value(LuaValue::nil())?;
let err_msg = l.create_string("attempt to load a binary chunk (mode is 'text')")?;
l.push_value(err_msg)?;
return Ok(2);
}
} else {
if !mode.contains('t') {
l.push_value(LuaValue::nil())?;
let err_msg = l.create_string("attempt to load a text chunk (mode is 'binary')")?;
l.push_value(err_msg)?;
return Ok(2);
}
}
let env = env_arg;
let chunk_result = if is_binary {
let vm = l.vm_mut();
match chunk_serializer::deserialize_chunk_with_strings_vm(&code_bytes, vm) {
Ok(chunk) => Ok(chunk),
Err(e) => Err(format!("binary load error: {}", e)),
}
} else {
let code_str = match String::from_utf8(code_bytes.clone()) {
Ok(s) => s,
Err(_) => return Err(l.error("source is not valid UTF-8".to_string())),
};
let vm = l.vm_mut();
vm.compile_with_name(&code_str, &chunkname).map_err(|e| {
vm.get_error_message(e)
})
};
match chunk_result {
Ok(chunk) => {
let upvalue_count = chunk.upvalue_count;
let mut upvalues = Vec::with_capacity(upvalue_count);
for i in 0..upvalue_count {
if i == 0 {
let env_upvalue_id = if let Some(env) = env {
l.create_upvalue_closed(env)?
} else {
let global = l.vm_mut().global;
l.create_upvalue_closed(global)?
};
upvalues.push(env_upvalue_id);
} else {
let nil_upvalue = l.create_upvalue_closed(LuaValue::nil())?;
upvalues.push(nil_upvalue);
}
}
let func = l
.vm_mut()
.create_loaded_function(chunk, UpvalueStore::from_vec(upvalues))?;
l.push_value(func)?;
Ok(1)
}
Err(e) => {
let err_msg = l.create_string(&e)?;
l.push_value(LuaValue::nil())?;
l.push_value(err_msg)?;
Ok(2)
}
}
}
fn lua_loadfile(l: &mut LuaState) -> LuaResult<usize> {
let filename = l
.get_arg(1)
.ok_or_else(|| l.error("bad argument #1 to 'loadfile' (value expected)".to_string()))?;
let filename_str = if let Some(s) = filename.as_str() {
s.to_string()
} else {
return Err(l.error("bad argument #1 to 'loadfile' (string expected)".to_string()));
};
let mode = l
.get_arg(2)
.and_then(|v| v.as_str().map(|s| s.to_string()))
.unwrap_or_else(|| "bt".to_string());
let env_arg = l.get_arg(3);
let file_bytes = match std::fs::read(&filename_str) {
Ok(b) => b,
Err(e) => {
let err_msg = l.create_string(&format!("cannot open {}: {}", filename_str, e))?;
l.push_value(LuaValue::nil())?;
l.push_value(err_msg)?;
return Ok(2);
}
};
let mut skip_offset = 0;
if file_bytes.first() == Some(&b'#') {
if let Some(pos) = file_bytes.iter().position(|&b| b == b'\n') {
skip_offset = pos + 1;
} else {
skip_offset = file_bytes.len();
}
}
if file_bytes[skip_offset..].starts_with(&[0xEF, 0xBB, 0xBF]) {
skip_offset += 3;
}
let is_binary = file_bytes.get(skip_offset) == Some(&0x1B);
if !mode.is_empty() && mode.chars().all(|c| c == 'b' || c == 't') {
if is_binary && !mode.contains('b') {
let err_msg = l.create_string("attempt to load a binary chunk (mode is 'text')")?;
l.push_value(LuaValue::nil())?;
l.push_value(err_msg)?;
return Ok(2);
}
if !is_binary && !mode.contains('t') {
let err_msg = l.create_string("attempt to load a text chunk (mode is 'binary')")?;
l.push_value(LuaValue::nil())?;
l.push_value(err_msg)?;
return Ok(2);
}
}
match l.vm_mut().load_proto_from_file(&filename_str) {
Ok(proto) => {
let upvalue_count = proto.as_ref().data.upvalue_count;
let mut upvalues = Vec::with_capacity(upvalue_count);
for i in 0..upvalue_count {
if i == 0 {
let env_upvalue_id = if let Some(env) = env_arg {
l.create_upvalue_closed(env)?
} else {
let global = l.vm_mut().global;
l.create_upvalue_closed(global)?
};
upvalues.push(env_upvalue_id);
} else {
let nil_upvalue = l.create_upvalue_closed(LuaValue::nil())?;
upvalues.push(nil_upvalue);
}
}
let func = l
.vm_mut()
.create_function(proto, UpvalueStore::from_vec(upvalues))?;
l.push_value(func)?;
Ok(1)
}
Err(e) => {
let err_msg = l.create_string(&e.to_string())?;
l.push_value(LuaValue::nil())?;
l.push_value(err_msg)?;
Ok(2)
}
}
}
fn lua_dofile(l: &mut LuaState) -> LuaResult<usize> {
let arg1 = l.get_arg(1);
let filename_str = if let Some(v) = arg1 {
if v.is_nil() {
return Err(l.error("dofile: reading from stdin not yet implemented".to_string()));
}
if let Some(s) = v.as_str() {
s.to_string()
} else {
return Err(l.error("bad argument #1 to 'dofile' (string expected)".to_string()));
}
} else {
return Err(l.error("dofile: reading from stdin not yet implemented".to_string()));
};
let proto = l.vm_mut().load_proto_from_file(&filename_str)?;
let global = l.vm_mut().global;
let env_upvalue = l.create_upvalue_closed(global)?;
let func = l
.vm_mut()
.create_function(proto, UpvalueStore::from_single(env_upvalue))?;
let func_pos = l
.current_frame()
.map(|f| f.base - f.func_offset as usize)
.unwrap_or(0);
let clear_to = func_pos + 1; l.set_top(clear_to)?;
l.push_value(func)?;
let func_idx = clear_to; let num_results = l.call_stack_based(func_idx, 0)?;
Ok(num_results)
}
fn lua_warn(l: &mut LuaState) -> LuaResult<usize> {
let args = l.get_args();
if args.is_empty() {
return Err(
l.error("bad argument #1 to 'warn' (string expected, got no value)".to_string())
);
}
let mut parts: Vec<String> = Vec::with_capacity(args.len());
for (i, arg) in args.iter().enumerate() {
if let Some(s) = arg.as_str() {
parts.push(s.to_string());
} else {
return Err(l.error(format!(
"bad argument #{} to 'warn' (string expected, got {})",
i + 1,
arg.type_name()
)));
}
}
let registry = l.vm_mut().registry;
let mode_key = l.create_string("_WARN_MODE")?;
let current_mode = l
.raw_get(®istry, &mode_key)
.and_then(|v| v.as_str().map(|s| s.to_string()))
.unwrap_or_else(|| "off".to_string());
if parts.len() == 1 && parts[0].starts_with('@') {
let control = &parts[0][1..];
match control {
"on" => {
let mode_val = l.create_string("on")?;
l.raw_set(®istry, mode_key, mode_val);
}
"off" => {
let mode_val = l.create_string("off")?;
l.raw_set(®istry, mode_key, mode_val);
}
"store" => {
let mode_val = l.create_string("store")?;
l.raw_set(®istry, mode_key, mode_val);
}
"normal" => {
let mode_val = l.create_string("on")?;
l.raw_set(®istry, mode_key, mode_val);
}
_ => {
}
}
return Ok(0);
}
let message: String = parts.concat();
match current_mode.as_str() {
"on" => {
eprintln!("Lua warning: {}", message);
}
"store" => {
let warn_val = l.create_string(&message)?;
l.vm_mut().set_global("_WARN", warn_val)?;
}
_ => {
}
}
Ok(0)
}