use std::io::Write;
use crate::error::{LuaError, LuaResult, RuntimeError};
use crate::vm::callinfo::LUA_MULTRET;
use crate::vm::execute;
use crate::vm::metatable::{self, val_raw_equal};
use crate::vm::state::LuaState;
use crate::vm::table::Table;
use crate::vm::value::{Userdata, Val};
#[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_args(name: &str, state: &LuaState, min: usize) -> LuaResult<()> {
if nargs(state) < min {
Err(bad_argument(name, min, "value expected"))
} else {
Ok(())
}
}
fn get_metafield(gc: &mut crate::vm::state::Gc, val: Val, name: &[u8]) -> Option<Val> {
let mt = match val {
Val::Table(r) => gc.tables.get(r).and_then(Table::metatable)?,
Val::Userdata(r) => gc.userdata.get(r).and_then(Userdata::metatable)?,
_ => gc.type_metatables[metatable::type_tag(val)]?,
};
let key_ref = gc.intern_string(name);
let table = gc.tables.get(mt)?;
let result = table.get_str(key_ref, &gc.string_arena);
if result.is_nil() { None } else { Some(result) }
}
pub fn lua_print(state: &mut LuaState) -> LuaResult<u32> {
let base = state.base;
let top = state.top;
let n = top.saturating_sub(base);
let stdout = std::io::stdout();
let mut out = stdout.lock();
for i in 0..n {
if i > 0 {
let _ = out.write_all(b"\t");
}
let val = state.stack_get(base + i);
let _ = match val {
Val::Nil => out.write_all(b"nil"),
Val::Bool(b) => {
if b {
out.write_all(b"true")
} else {
out.write_all(b"false")
}
}
Val::Str(r) => {
if let Some(s) = state.gc.string_arena.get(r) {
out.write_all(s.data())
} else {
out.write_all(b"string: ???")
}
}
_ => {
let s = format!("{val}");
out.write_all(s.as_bytes())
}
};
}
let _ = out.write_all(b"\n");
let _ = out.flush();
Ok(0)
}
pub fn lua_type(state: &mut LuaState) -> LuaResult<u32> {
check_args("type", state, 1)?;
let val = arg(state, 0);
let name = val.type_name();
let r = state.gc.intern_string(name.as_bytes());
state.push(Val::Str(r));
Ok(1)
}
pub fn lua_tostring(state: &mut LuaState) -> LuaResult<u32> {
check_args("tostring", state, 1)?;
let val = arg(state, 0);
if let Some(tm_val) = get_metafield(&mut state.gc, val, b"__tostring") {
let call_base = state.top;
state.ensure_stack(call_base + 3);
state.stack_set(call_base, tm_val);
state.stack_set(call_base + 1, val);
state.top = call_base + 2;
state.call_function(call_base, 1)?;
let result = state.stack_get(call_base);
state.push(result);
return Ok(1);
}
let result = val_to_str(state, val);
state.push(result);
Ok(1)
}
fn val_to_str(state: &mut LuaState, val: Val) -> Val {
match val {
Val::Str(_) => val,
Val::Nil => {
let r = state.gc.intern_string(b"nil");
Val::Str(r)
}
Val::Bool(b) => {
let s = if b {
b"true".as_ref()
} else {
b"false".as_ref()
};
let r = state.gc.intern_string(s);
Val::Str(r)
}
Val::Num(_) => {
let s = format!("{val}");
let r = state.gc.intern_string(s.as_bytes());
Val::Str(r)
}
_ => {
let s = format!("{val}");
let r = state.gc.intern_string(s.as_bytes());
Val::Str(r)
}
}
}
pub fn lua_tonumber(state: &mut LuaState) -> LuaResult<u32> {
check_args("tonumber", state, 1)?;
let val = arg(state, 0);
let base_arg = arg(state, 1);
let base = match base_arg {
Val::Nil => 10,
Val::Num(n) => n as i64,
_ => {
return Err(bad_argument(
"tonumber",
2,
"number expected, got non-number",
));
}
};
if base == 10 {
if let Some(n) = execute::coerce_to_number(val, &state.gc) {
state.push(Val::Num(n));
} else {
state.push(Val::Nil);
}
} else {
if !(2..=36).contains(&base) {
return Err(bad_argument("tonumber", 2, "base out of range"));
}
let Val::Str(r) = val else {
return Err(bad_argument("tonumber", 1, "string expected"));
};
let s = state
.gc
.string_arena
.get(r)
.map(|ls| String::from_utf8_lossy(ls.data()).to_string())
.unwrap_or_default();
let trimmed = s.trim();
#[allow(clippy::cast_precision_loss)]
if let Ok(n) = u64::from_str_radix(trimmed, base as u32) {
state.push(Val::Num(n as f64));
} else {
state.push(Val::Nil);
}
}
Ok(1)
}
pub fn lua_assert(state: &mut LuaState) -> LuaResult<u32> {
check_args("assert", state, 1)?;
let val = arg(state, 0);
if !val.is_truthy() {
let msg = arg(state, 1);
let error_msg = if let Val::Str(r) = msg {
state.gc.string_arena.get(r).map_or_else(
|| "assertion failed!".to_string(),
|s| String::from_utf8_lossy(s.data()).to_string(),
)
} else if msg.is_nil() {
"assertion failed!".to_string()
} else {
format!("{msg}")
};
return Err(simple_error(error_msg));
}
let n = nargs(state);
Ok(n as u32)
}
pub fn lua_error(state: &mut LuaState) -> LuaResult<u32> {
let msg = arg(state, 0);
let level_arg = arg(state, 1);
let level: u32 = match level_arg {
Val::Num(n) => {
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let l = n as u32;
l
}
_ => 1, };
let is_stringable = matches!(msg, Val::Str(_) | Val::Num(_));
let error_val = if is_stringable && level > 0 {
let where_prefix = execute::get_where(state, level);
let original = match msg {
Val::Str(r) => state
.gc
.string_arena
.get(r)
.map(|s| String::from_utf8_lossy(s.data()).to_string())
.unwrap_or_default(),
_ => format!("{msg}"),
};
let full_msg = format!("{where_prefix}{original}");
let new_r = state.gc.intern_string(full_msg.as_bytes());
Val::Str(new_r)
} else {
msg };
state.error_object = Some(error_val);
let display_msg = match error_val {
Val::Str(r) => state
.gc
.string_arena
.get(r)
.map(|s| String::from_utf8_lossy(s.data()).to_string())
.unwrap_or_default(),
Val::Nil => "nil".to_string(),
_ => format!("{error_val}"),
};
Err(LuaError::Runtime(RuntimeError {
message: display_msg,
level,
traceback: vec![],
}))
}
pub fn lua_pcall(state: &mut LuaState) -> LuaResult<u32> {
check_args("pcall", state, 1)?;
let func_pos = state.base; let n_call_args = nargs(state) - 1;
let saved_ci = state.ci;
let saved_n_ccalls = state.n_ccalls;
let saved_call_depth = state.call_depth;
state.error_object = None;
state.top = func_pos + 1 + n_call_args;
let call_result = state.call_function(func_pos, LUA_MULTRET);
match call_result {
Ok(()) => {
let n_inner = state.top - func_pos;
state.ensure_stack(state.top + 1);
for i in (func_pos..state.top).rev() {
let v = state.stack_get(i);
state.stack_set(i + 1, v);
}
state.stack_set(func_pos, Val::Bool(true));
state.top = func_pos + 1 + n_inner;
Ok((1 + n_inner) as u32)
}
Err(err) => {
state.ci = saved_ci;
state.base = state.call_stack[state.ci].base;
state.n_ccalls = saved_n_ccalls;
state.call_depth = saved_call_depth;
if state.ci < crate::vm::state::MAXCALLS {
state.ci_overflow = false;
}
state.close_upvalues(func_pos);
let error_val = state.error_object.take().unwrap_or_else(|| {
let r = state.gc.intern_string(err.to_string().as_bytes());
Val::Str(r)
});
state.stack_set(func_pos, Val::Bool(false));
state.stack_set(func_pos + 1, error_val);
state.top = func_pos + 2;
Ok(2)
}
}
}
pub fn lua_xpcall(state: &mut LuaState) -> LuaResult<u32> {
check_args("xpcall", state, 2)?;
let func_val = arg(state, 0);
let handler_val = arg(state, 1);
let func_pos = state.base;
let handler_slot = func_pos;
let call_pos = func_pos + 1;
let saved_ci = state.ci;
let saved_n_ccalls = state.n_ccalls;
let saved_call_depth = state.call_depth;
state.error_object = None;
state.ensure_stack(call_pos + 1);
state.stack_set(handler_slot, handler_val);
state.stack_set(call_pos, func_val);
state.top = call_pos + 1;
let call_result = state.call_function(call_pos, LUA_MULTRET);
match call_result {
Ok(()) => {
let n_results = state.top - call_pos;
state.stack_set(func_pos, Val::Bool(true));
state.top = func_pos + 1 + n_results;
Ok((1 + n_results) as u32)
}
Err(err) => {
let error_val = state.error_object.take().unwrap_or_else(|| {
let r = state.gc.intern_string(err.to_string().as_bytes());
Val::Str(r)
});
state.close_upvalues(func_pos);
let handler_from_stack = state.stack_get(handler_slot);
let handler_pos = state.top;
state.ensure_stack(handler_pos + 2);
state.stack_set(handler_pos, handler_from_stack);
state.stack_set(handler_pos + 1, error_val);
state.top = handler_pos + 2;
let handler_result = state.call_function(handler_pos, 1);
let handler_ret = if handler_result.is_ok() {
state.stack_get(handler_pos)
} else {
let msg = state.gc.intern_string(b"error in error handling");
Val::Str(msg)
};
state.ci = saved_ci;
state.base = state.call_stack[state.ci].base;
state.n_ccalls = saved_n_ccalls;
state.call_depth = saved_call_depth;
if state.ci < crate::vm::state::MAXCALLS {
state.ci_overflow = false;
}
state.stack_set(func_pos, Val::Bool(false));
state.stack_set(func_pos + 1, handler_ret);
state.top = func_pos + 2;
Ok(2)
}
}
}
pub fn lua_setmetatable(state: &mut LuaState) -> LuaResult<u32> {
check_args("setmetatable", state, 2)?;
let table_val = arg(state, 0);
let mt_val = arg(state, 1);
let Val::Table(table_ref) = table_val else {
return Err(bad_argument("setmetatable", 1, "table expected"));
};
let new_mt = match mt_val {
Val::Nil => None,
Val::Table(r) => Some(r),
_ => {
return Err(bad_argument("setmetatable", 2, "nil or table expected"));
}
};
if let Some(existing_mt) = state.gc.tables.get(table_ref).and_then(Table::metatable) {
let key_ref = state.gc.intern_string(b"__metatable");
let has_protection = state
.gc
.tables
.get(existing_mt)
.is_some_and(|t| !t.get_str(key_ref, &state.gc.string_arena).is_nil());
if has_protection {
return Err(simple_error(
"cannot change a protected metatable".to_string(),
));
}
}
if let Some(t) = state.gc.tables.get_mut(table_ref) {
t.set_metatable(new_mt);
}
state.gc.barrier_back(table_ref);
state.push(table_val);
Ok(1)
}
pub fn lua_getmetatable(state: &mut LuaState) -> LuaResult<u32> {
check_args("getmetatable", state, 1)?;
let val = arg(state, 0);
let mt = match val {
Val::Table(r) => state.gc.tables.get(r).and_then(Table::metatable),
Val::Userdata(r) => state.gc.userdata.get(r).and_then(Userdata::metatable),
_ => state.gc.type_metatables[metatable::type_tag(val)],
};
let Some(mt_ref) = mt else {
state.push(Val::Nil);
return Ok(1);
};
if let Some(protection) = get_metafield(&mut state.gc, val, b"__metatable") {
state.push(protection);
} else {
state.push(Val::Table(mt_ref));
}
Ok(1)
}
pub fn lua_rawget(state: &mut LuaState) -> LuaResult<u32> {
check_args("rawget", state, 2)?;
let table_val = arg(state, 0);
let key = arg(state, 1);
let Val::Table(table_ref) = table_val else {
return Err(bad_argument("rawget", 1, "table expected"));
};
let result = state
.gc
.tables
.get(table_ref)
.map_or(Val::Nil, |t| t.get(key, &state.gc.string_arena));
state.push(result);
Ok(1)
}
pub fn lua_rawset(state: &mut LuaState) -> LuaResult<u32> {
check_args("rawset", state, 3)?;
let table_val = arg(state, 0);
let key = arg(state, 1);
let value = arg(state, 2);
let Val::Table(table_ref) = table_val else {
return Err(bad_argument("rawset", 1, "table expected"));
};
let table = state
.gc
.tables
.get_mut(table_ref)
.ok_or_else(|| bad_argument("rawset", 1, "invalid table"))?;
table.raw_set(key, value, &state.gc.string_arena)?;
state.gc.barrier_back(table_ref);
state.push(table_val);
Ok(1)
}
pub fn lua_rawequal(state: &mut LuaState) -> LuaResult<u32> {
check_args("rawequal", state, 2)?;
let v1 = arg(state, 0);
let v2 = arg(state, 1);
let result = val_raw_equal(v1, v2, &state.gc.tables, &state.gc.string_arena);
state.push(Val::Bool(result));
Ok(1)
}
pub fn lua_select(state: &mut LuaState) -> LuaResult<u32> {
check_args("select", state, 1)?;
let n = nargs(state);
let idx_val = arg(state, 0);
if let Val::Str(r) = idx_val
&& let Some(s) = state.gc.string_arena.get(r)
&& s.data() == b"#"
{
#[allow(clippy::cast_precision_loss)]
let count = (n - 1) as f64;
state.push(Val::Num(count));
return Ok(1);
}
let Val::Num(idx_f) = idx_val else {
return Err(bad_argument("select", 1, "number or string expected"));
};
#[allow(clippy::cast_possible_truncation)]
let mut idx = idx_f as i64;
let n_i64 = n as i64;
if idx < 0 {
idx += n_i64; } else if idx > n_i64 {
idx = n_i64; }
if idx < 1 {
return Err(bad_argument("select", 1, "index out of range"));
}
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let result_count = (n_i64 - idx) as u32;
Ok(result_count)
}
pub fn lua_unpack(state: &mut LuaState) -> LuaResult<u32> {
check_args("unpack", state, 1)?;
let list_val = arg(state, 0);
let i_val = arg(state, 1);
let j_val = arg(state, 2);
let Val::Table(table_ref) = list_val else {
return Err(bad_argument("unpack", 1, "table expected"));
};
#[allow(clippy::cast_possible_truncation)]
let i = match i_val {
Val::Num(n) => n as i64,
Val::Nil => 1,
_ => {
return Err(bad_argument("unpack", 2, "number expected"));
}
};
#[allow(clippy::cast_possible_truncation)]
let j = match j_val {
Val::Nil => {
let table = state
.gc
.tables
.get(table_ref)
.ok_or_else(|| bad_argument("unpack", 1, "invalid table"))?;
table.len(&state.gc.string_arena) as i64
}
Val::Num(n) => n as i64,
_ => {
return Err(bad_argument("unpack", 3, "number expected"));
}
};
let n = j - i + 1;
if n <= 0 {
return Ok(0);
}
#[allow(clippy::cast_sign_loss)]
let n_usize = n as usize;
state.ensure_stack(state.top + n_usize);
for k in i..=j {
#[allow(clippy::cast_precision_loss)]
let key = Val::Num(k as f64);
let val = state
.gc
.tables
.get(table_ref)
.map_or(Val::Nil, |t| t.get(key, &state.gc.string_arena));
state.push(val);
}
Ok(n_usize as u32)
}
pub fn lua_next(state: &mut LuaState) -> LuaResult<u32> {
check_args("next", state, 1)?;
let table_val = arg(state, 0);
let key = arg(state, 1);
let Val::Table(table_ref) = table_val else {
return Err(bad_argument("next", 1, "table expected"));
};
let result = state
.gc
.tables
.get(table_ref)
.ok_or_else(|| bad_argument("next", 1, "invalid table"))?
.next(key, &state.gc.string_arena)?;
if let Some((k, v)) = result {
state.push(k);
state.push(v);
Ok(2)
} else {
state.push(Val::Nil);
Ok(1)
}
}
pub fn lua_pairs(state: &mut LuaState) -> LuaResult<u32> {
check_args("pairs", state, 1)?;
let table_val = arg(state, 0);
let Val::Table(_) = table_val else {
return Err(bad_argument("pairs", 1, "table expected"));
};
let next_key = state.gc.intern_string(b"next");
let next_fn = state
.gc
.tables
.get(state.global)
.map_or(Val::Nil, |t| t.get_str(next_key, &state.gc.string_arena));
state.push(next_fn);
state.push(table_val);
state.push(Val::Nil);
Ok(3)
}
fn ipairs_aux(state: &mut LuaState) -> LuaResult<u32> {
let table_val = arg(state, 0);
let idx_val = arg(state, 1);
let Val::Table(table_ref) = table_val else {
return Err(bad_argument("ipairs", 1, "table expected"));
};
let Val::Num(idx_f) = idx_val else {
return Err(bad_argument("ipairs", 2, "number expected"));
};
let next_idx = idx_f + 1.0;
#[allow(clippy::cast_precision_loss)]
let key = Val::Num(next_idx);
let val = state
.gc
.tables
.get(table_ref)
.map_or(Val::Nil, |t| t.get(key, &state.gc.string_arena));
if val.is_nil() {
Ok(0)
} else {
state.push(Val::Num(next_idx));
state.push(val);
Ok(2)
}
}
pub fn lua_ipairs(state: &mut LuaState) -> LuaResult<u32> {
check_args("ipairs", state, 1)?;
let table_val = arg(state, 0);
let Val::Table(_) = table_val else {
return Err(bad_argument("ipairs", 1, "table expected"));
};
let closure = crate::vm::closure::Closure::Rust(crate::vm::closure::RustClosure::new(
ipairs_aux,
"ipairs_aux",
));
let closure_ref = state.gc.alloc_closure(closure);
state.push(Val::Function(closure_ref));
state.push(table_val);
state.push(Val::Num(0.0));
Ok(3)
}
pub fn lua_loadstring(state: &mut LuaState) -> LuaResult<u32> {
check_args("loadstring", state, 1)?;
let src_val = arg(state, 0);
let name_val = arg(state, 1);
let Val::Str(src_ref) = src_val else {
return Err(bad_argument("loadstring", 1, "string expected"));
};
let source = state
.gc
.string_arena
.get(src_ref)
.map(|s| s.data().to_vec())
.unwrap_or_default();
let chunk_name = if let Val::Str(r) = name_val {
state.gc.string_arena.get(r).map_or_else(
|| String::from_utf8_lossy(&source).into_owned(),
|s| String::from_utf8_lossy(s.data()).into_owned(),
)
} else {
String::from_utf8_lossy(&source).into_owned()
};
load_string_impl(state, &source, &chunk_name)
}
pub fn lua_loadfile(state: &mut LuaState) -> LuaResult<u32> {
let filename_val = arg(state, 0);
let source = if filename_val.is_nil() {
use std::io::Read;
let mut buf = Vec::new();
if std::io::stdin().read_to_end(&mut buf).is_err() {
state.push(Val::Nil);
let msg = state.gc.intern_string(b"cannot read stdin");
state.push(Val::Str(msg));
return Ok(2);
}
(buf, "=stdin".to_string())
} else {
let Val::Str(r) = filename_val else {
return Err(bad_argument("loadfile", 1, "string expected"));
};
let filename = state
.gc
.string_arena
.get(r)
.map(|s| String::from_utf8_lossy(s.data()).into_owned())
.unwrap_or_default();
match std::fs::read(&filename) {
Ok(contents) => (contents, format!("@{filename}")),
Err(e) => {
state.push(Val::Nil);
let msg = state
.gc
.intern_string(format!("cannot open {filename}: {e}").as_bytes());
state.push(Val::Str(msg));
return Ok(2);
}
}
};
load_string_impl(state, &source.0, &source.1)
}
pub fn lua_dofile(state: &mut LuaState) -> LuaResult<u32> {
let filename_val = arg(state, 0);
let (source, chunk_name) = if filename_val.is_nil() {
use std::io::Read;
let mut buf = Vec::new();
std::io::stdin()
.read_to_end(&mut buf)
.map_err(|e| simple_error(format!("cannot read stdin: {e}")))?;
(buf, "=stdin".to_string())
} else {
let Val::Str(r) = filename_val else {
return Err(bad_argument("dofile", 1, "string expected"));
};
let filename = state
.gc
.string_arena
.get(r)
.map(|s| String::from_utf8_lossy(s.data()).into_owned())
.unwrap_or_default();
let contents = std::fs::read(&filename)
.map_err(|e| simple_error(format!("cannot open {filename}: {e}")))?;
(contents, format!("@{filename}"))
};
let proto = crate::compile_or_undump(&source, &chunk_name)?;
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(crate::vm::closure::Closure::Lua(lua_cl));
let call_base = state.top;
state.ensure_stack(call_base + 2);
state.stack_set(call_base, Val::Function(closure_ref));
state.top = call_base + 1;
state.call_function(call_base, LUA_MULTRET)?;
let n_results = state.top - call_base;
Ok(n_results as u32)
}
fn call_load_reader(state: &mut LuaState, func_val: Val) -> Result<Option<Vec<u8>>, LuaError> {
let call_base = state.top;
state.ensure_stack(call_base + 2);
state.stack_set(call_base, func_val);
state.top = call_base + 1;
state.call_function(call_base, 1)?;
let result = state.stack_get(call_base);
state.top = call_base;
match result {
Val::Nil => Ok(None),
Val::Str(r) => {
let chunk = state
.gc
.string_arena
.get(r)
.map(|s| s.data().to_vec())
.unwrap_or_default();
if chunk.is_empty() {
Ok(None)
} else {
Ok(Some(chunk))
}
}
_ => Err(simple_error(
"reader function must return a string".to_string(),
)),
}
}
pub fn lua_load(state: &mut LuaState) -> LuaResult<u32> {
check_args("load", state, 1)?;
let func_val = arg(state, 0);
let name_val = arg(state, 1);
if !matches!(func_val, Val::Function(_)) {
return Err(bad_argument("load", 1, "function expected"));
}
let chunk_name = if let Val::Str(r) = name_val {
state.gc.string_arena.get(r).map_or_else(
|| "=(load)".to_string(),
|s| String::from_utf8_lossy(s.data()).to_string(),
)
} else {
"=(load)".to_string()
};
let saved_top = state.top;
let saved_ci = state.ci;
let saved_n_ccalls = state.n_ccalls;
let saved_call_depth = state.call_depth;
let first_chunk = match call_load_reader(state, func_val) {
Ok(Some(bytes)) => bytes,
Ok(None) => {
return load_string_impl(state, b"", &chunk_name);
}
Err(e) => {
restore_state(state, saved_ci, saved_n_ccalls, saved_call_depth);
state.top = saved_top;
state.push(Val::Nil);
let msg = state.gc.intern_string(e.to_string().as_bytes());
state.push(Val::Str(msg));
return Ok(2);
}
};
if first_chunk.first() == Some(&0x1b) {
return load_binary_from_reader(
state,
func_val,
first_chunk,
&chunk_name,
saved_top,
saved_ci,
saved_n_ccalls,
saved_call_depth,
);
}
let compile_result = {
let state_ref: &mut LuaState = state;
let mut reader =
move || -> Result<Option<Vec<u8>>, LuaError> { call_load_reader(state_ref, func_val) };
let lexer =
crate::compiler::lexer::Lexer::from_reader(first_chunk, &mut reader, &chunk_name);
crate::compiler::compile_with_lexer(lexer, &chunk_name)
};
match compile_result {
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 num_upvalues = proto.num_upvalues as usize;
let mut lua_cl = crate::vm::closure::LuaClosure::new(proto, state.global);
for _ in 0..num_upvalues {
let uv = crate::vm::closure::Upvalue::new_closed(Val::Nil);
let uv_ref = state.gc.alloc_upvalue(uv);
lua_cl.upvalues.push(uv_ref);
}
let closure_ref = state
.gc
.alloc_closure(crate::vm::closure::Closure::Lua(lua_cl));
state.push(Val::Function(closure_ref));
Ok(1)
}
Err(e) => {
state.push(Val::Nil);
let msg_bytes = match &e {
crate::error::LuaError::Syntax(syn) => syn.to_lua_bytes(),
_ => e.to_string().into_bytes(),
};
let msg = state.gc.intern_string(&msg_bytes);
state.push(Val::Str(msg));
Ok(2)
}
}
}
#[allow(clippy::too_many_arguments)]
fn load_binary_from_reader(
state: &mut LuaState,
func_val: Val,
first_chunk: Vec<u8>,
chunk_name: &str,
saved_top: usize,
saved_ci: usize,
saved_n_ccalls: u16,
saved_call_depth: u16,
) -> LuaResult<u32> {
const MAX_LOAD_SIZE: usize = 10 * 1024 * 1024;
let mut collected = first_chunk;
loop {
match call_load_reader(state, func_val) {
Ok(Some(bytes)) => {
collected.extend_from_slice(&bytes);
if collected.len() > MAX_LOAD_SIZE {
break;
}
}
Ok(None) => break,
Err(e) => {
restore_state(state, saved_ci, saved_n_ccalls, saved_call_depth);
state.top = saved_top;
state.push(Val::Nil);
let msg = state.gc.intern_string(e.to_string().as_bytes());
state.push(Val::Str(msg));
return Ok(2);
}
}
}
load_string_impl(state, &collected, chunk_name)
}
fn restore_state(
state: &mut LuaState,
saved_ci: usize,
saved_n_ccalls: u16,
saved_call_depth: u16,
) {
state.ci = saved_ci;
state.base = state.call_stack[state.ci].base;
state.n_ccalls = saved_n_ccalls;
state.call_depth = saved_call_depth;
if state.ci < crate::vm::state::MAXCALLS {
state.ci_overflow = false;
}
}
#[allow(clippy::unnecessary_wraps)]
fn load_string_impl(state: &mut LuaState, source: &[u8], name: &str) -> LuaResult<u32> {
match crate::compile_or_undump(source, 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 num_upvalues = proto.num_upvalues as usize;
let mut lua_cl = crate::vm::closure::LuaClosure::new(proto, state.global);
for _ in 0..num_upvalues {
let uv = crate::vm::closure::Upvalue::new_closed(Val::Nil);
let uv_ref = state.gc.alloc_upvalue(uv);
lua_cl.upvalues.push(uv_ref);
}
let closure_ref = state
.gc
.alloc_closure(crate::vm::closure::Closure::Lua(lua_cl));
state.push(Val::Function(closure_ref));
Ok(1)
}
Err(e) => {
state.push(Val::Nil);
let msg_bytes = match &e {
crate::error::LuaError::Syntax(syn) => syn.to_lua_bytes(),
_ => e.to_string().into_bytes(),
};
let msg = state.gc.intern_string(&msg_bytes);
state.push(Val::Str(msg));
Ok(2)
}
}
}
pub fn lua_collectgarbage(state: &mut LuaState) -> LuaResult<u32> {
let opt_val = arg(state, 0);
let opt = if opt_val.is_nil() {
"collect"
} else if let Val::Str(r) = opt_val {
let s = state
.gc
.string_arena
.get(r)
.map(|s| String::from_utf8_lossy(s.data()).to_string())
.unwrap_or_default();
return collectgarbage_dispatch(state, &s);
} else {
return Err(bad_argument("collectgarbage", 1, "string expected"));
};
collectgarbage_dispatch(state, opt)
}
fn collectgarbage_dispatch(state: &mut LuaState, opt: &str) -> LuaResult<u32> {
match opt {
"collect" => {
state.full_gc()?;
state.push(Val::Num(0.0));
Ok(1)
}
"stop" => {
state.gc.gc_state.gc_threshold = usize::MAX;
state.push(Val::Num(0.0));
Ok(1)
}
"restart" => {
state.gc.gc_state.gc_threshold = state.gc.gc_state.total_bytes;
state.push(Val::Num(0.0));
Ok(1)
}
"count" => {
let bytes = state.gc.gc_state.total_bytes;
let kb = bytes as f64 / 1024.0;
state.push(Val::Num(kb));
state.push(Val::Num((bytes % 1024) as f64));
Ok(2)
}
"step" => {
use crate::vm::gc::collector::{GCSTEPSIZE, GcPhase};
let data_val = arg(state, 1);
let data = match data_val {
Val::Num(n) => n as u64,
_ => 0,
};
let simulated_alloc = data << 10;
if simulated_alloc <= state.gc.gc_state.total_bytes as u64 {
state.gc.gc_state.gc_threshold =
state.gc.gc_state.total_bytes - simulated_alloc as usize;
} else {
state.gc.gc_state.gc_threshold = 0;
}
while state.gc.gc_state.gc_threshold <= state.gc.gc_state.total_bytes {
let stepmul = i64::from(state.gc.gc_state.gc_stepmul);
let budget = if stepmul == 0 {
i64::MAX / 2
} else {
(GCSTEPSIZE as i64 / 100) * stepmul
};
state.gc.gc_state.gc_debt +=
state.gc.gc_state.total_bytes as i64 - state.gc.gc_state.gc_threshold as i64;
let completed = state.gc_step(budget)?;
if completed {
break;
}
}
let completed = state.gc.gc_state.phase == GcPhase::Pause;
state.push(Val::Bool(completed));
Ok(1)
}
"setpause" => {
let arg_val = arg(state, 1);
let new_pause = match arg_val {
Val::Num(n) => n as u32,
_ => 200,
};
let old = state.gc.gc_state.gc_pause;
state.gc.gc_state.gc_pause = new_pause;
state.push(Val::Num(f64::from(old)));
Ok(1)
}
"setstepmul" => {
let arg_val = arg(state, 1);
let new_mul = match arg_val {
Val::Num(n) => n as u32,
_ => 200,
};
let old = state.gc.gc_state.gc_stepmul;
state.gc.gc_state.gc_stepmul = new_mul;
state.push(Val::Num(f64::from(old)));
Ok(1)
}
_ => Err(bad_argument(
"collectgarbage",
1,
&format!("invalid option '{opt}'"),
)),
}
}
pub fn lua_getfenv(state: &mut LuaState) -> LuaResult<u32> {
let val = arg(state, 0);
let func_val = match val {
Val::Nil => get_func_at_level(state, 1)?,
Val::Num(n) => {
#[allow(clippy::cast_possible_truncation)]
let level = n as i64;
if level == 0 {
state.push(Val::Table(state.global));
return Ok(1);
}
get_func_at_level(state, level as u32)?
}
Val::Function(_) => val,
_ => {
return Err(bad_argument("getfenv", 1, "number or function expected"));
}
};
if let Val::Function(r) = func_val {
let env = state.gc.closures.get(r).map(|cl| match cl {
crate::vm::closure::Closure::Lua(lua_cl) => lua_cl.env,
crate::vm::closure::Closure::Rust(_) => state.global,
});
state.push(Val::Table(env.unwrap_or(state.global)));
} else {
state.push(Val::Table(state.global));
}
Ok(1)
}
pub fn lua_setfenv(state: &mut LuaState) -> LuaResult<u32> {
check_args("setfenv", state, 2)?;
let f_val = arg(state, 0);
let table_val = arg(state, 1);
let Val::Table(new_env) = table_val else {
return Err(bad_argument("setfenv", 2, "table expected"));
};
match f_val {
Val::Num(n) => {
#[allow(clippy::cast_possible_truncation)]
let level = n as i64;
if level == 0 {
state.global = new_env;
return Ok(0);
}
let func_val = get_func_at_level(state, level as u32)?;
set_func_env(state, func_val, new_env)?;
state.push(func_val);
Ok(1)
}
Val::Function(_) => {
set_func_env(state, f_val, new_env)?;
state.push(f_val);
Ok(1)
}
_ => Err(bad_argument("setfenv", 1, "number or function expected")),
}
}
fn get_func_at_level(state: &LuaState, level: u32) -> LuaResult<Val> {
use crate::stdlib::debug::{StackLevel, resolve_stack_level};
match resolve_stack_level(state, level as usize) {
Some(StackLevel::Real(ci_idx)) => {
let func_idx = state.call_stack[ci_idx].func;
Ok(state.stack_get(func_idx))
}
Some(StackLevel::TailCall) => {
Err(bad_argument(
"getfenv",
1,
&format!("invalid level {level}"),
))
}
None => Err(bad_argument(
"getfenv",
1,
&format!("invalid level {level}"),
)),
}
}
fn set_func_env(
state: &mut LuaState,
func_val: Val,
new_env: crate::vm::gc::arena::GcRef<Table>,
) -> LuaResult<()> {
let Val::Function(closure_ref) = func_val else {
return Err(simple_error(
"'setfenv' cannot change environment of given object".to_string(),
));
};
let cl = state.gc.closures.get_mut(closure_ref).ok_or_else(|| {
simple_error("'setfenv' cannot change environment of given object".to_string())
})?;
match cl {
crate::vm::closure::Closure::Lua(lua_cl) => {
lua_cl.env = new_env;
Ok(())
}
crate::vm::closure::Closure::Rust(_) => Err(simple_error(
"'setfenv' cannot change environment of given object".to_string(),
)),
}
}
pub fn lua_newproxy(state: &mut LuaState) -> LuaResult<u32> {
let arg_val = arg(state, 0);
let ud = Userdata::new(Box::new(()));
let ud_ref = state.gc.alloc_userdata(ud);
if arg_val == Val::Bool(true) {
let mt = state.gc.alloc_table(Table::new());
if let Some(u) = state.gc.userdata.get_mut(ud_ref) {
u.set_metatable(Some(mt));
}
} else if let Val::Userdata(other_ref) = arg_val {
let other_mt = state
.gc
.userdata
.get(other_ref)
.and_then(Userdata::metatable);
if let (Some(mt), Some(u)) = (other_mt, state.gc.userdata.get_mut(ud_ref)) {
u.set_metatable(Some(mt));
}
}
state.push(Val::Userdata(ud_ref));
Ok(1)
}
pub fn lua_gcinfo(state: &mut LuaState) -> LuaResult<u32> {
let bytes = state.gc.estimate_memory();
let kb = bytes as f64 / 1024.0;
#[allow(clippy::cast_possible_truncation)]
state.push(Val::Num(f64::from(kb.floor() as i32)));
Ok(1)
}