use crate::compiler::codegen::int2fb;
use crate::error::{LuaError, LuaResult, RuntimeError};
use crate::vm::closure::{Closure, RustClosure};
use crate::vm::execute::fb2int;
use crate::vm::gc::arena::GcRef;
use crate::vm::instructions::{Instruction, OpMode};
use crate::vm::state::{LuaState, MASK_COUNT, MASK_LINE};
use crate::vm::table::Table;
use crate::vm::value::{Userdata, Val};
use super::coroutine;
use super::{arg_error, type_error};
fn rt_error(msg: &str) -> LuaError {
LuaError::Runtime(RuntimeError {
message: msg.into(),
level: 0,
traceback: vec![],
})
}
pub fn t_testc(state: &mut LuaState) -> LuaResult<u32> {
if state.base >= state.top {
return Err(rt_error("testC: missing program string"));
}
let first = state.stack_get(state.base);
if let Val::Userdata(ud_ref) = first {
return testc_remote(state, ud_ref);
}
let prog = get_string(state, state.base)?;
let caller_func_idx = state.call_stack[state.ci].func;
let caller_closure_ref = match state.stack_get(caller_func_idx) {
Val::Function(r) => Some(r),
_ => None,
};
let tc_base = state.base;
run_testc_program(state, &prog, tc_base, caller_closure_ref)
}
fn is_valid_index(state: &LuaState, tc_base: usize, si: &SpecialIndex) -> bool {
match si {
SpecialIndex::Stack(i) => {
if let Some(pos) = resolve_index(state, tc_base, *i) {
pos < state.top
} else {
false
}
}
_ => true, }
}
fn resolve_index(state: &LuaState, tc_base: usize, idx: i32) -> Option<usize> {
match idx.cmp(&0) {
std::cmp::Ordering::Greater => {
let pos = tc_base + (idx as usize - 1);
Some(pos)
}
std::cmp::Ordering::Less => {
let pos = state.top as i64 + i64::from(idx);
if pos >= 0 { Some(pos as usize) } else { None }
}
std::cmp::Ordering::Equal => None,
}
}
enum SpecialIndex {
Stack(i32),
Global,
Env,
Registry,
Upvalue(usize),
}
fn get_special(
state: &LuaState,
tc_base: usize,
idx: &SpecialIndex,
caller_closure_ref: Option<GcRef<Closure>>,
) -> Val {
match idx {
SpecialIndex::Stack(i) => {
if let Some(pos) = resolve_index(state, tc_base, *i) {
if pos < state.top {
state.stack_get(pos)
} else {
Val::Nil
}
} else {
Val::Nil
}
}
SpecialIndex::Global => Val::Table(state.global),
SpecialIndex::Env => {
if let Some(cr) = caller_closure_ref {
if let Some(cl) = state.gc.closures.get(cr) {
match cl {
Closure::Lua(lc) => Val::Table(lc.env),
Closure::Rust(rc) => Val::Table(rc.env.unwrap_or(state.global)),
}
} else {
Val::Table(state.global)
}
} else {
Val::Table(state.global)
}
}
SpecialIndex::Registry => Val::Table(state.registry),
SpecialIndex::Upvalue(n) => {
if *n == 0 {
return Val::Table(state.global);
}
let idx = *n - 1;
if let Some(cr) = caller_closure_ref
&& let Some(Closure::Rust(rc)) = state.gc.closures.get(cr)
&& idx < rc.upvalues.len()
{
return rc.upvalues[idx];
}
Val::Nil
}
}
}
fn set_special(
state: &mut LuaState,
tc_base: usize,
idx: &SpecialIndex,
val: Val,
caller_closure_ref: Option<GcRef<Closure>>,
) {
match idx {
SpecialIndex::Stack(i) => {
if let Some(pos) = resolve_index(state, tc_base, *i) {
state.ensure_stack(pos + 1);
state.stack_set(pos, val);
}
}
SpecialIndex::Env => {
if let Val::Table(t) = val
&& let Some(cr) = caller_closure_ref
&& let Some(cl) = state.gc.closures.get_mut(cr)
{
match cl {
Closure::Lua(lc) => lc.env = t,
Closure::Rust(rc) => rc.env = Some(t),
}
}
}
SpecialIndex::Global => {
if let Val::Table(t) = val {
state.global = t;
}
}
SpecialIndex::Registry => {
}
SpecialIndex::Upvalue(n) => {
if *n == 0 {
if let Val::Table(t) = val {
state.global = t;
}
return;
}
let idx = *n - 1;
if let Some(cr) = caller_closure_ref
&& let Some(Closure::Rust(rc)) = state.gc.closures.get_mut(cr)
&& idx < rc.upvalues.len()
{
rc.upvalues[idx] = val;
}
}
}
}
fn get_string(state: &LuaState, pos: usize) -> LuaResult<String> {
match state.stack_get(pos) {
Val::Str(r) => state
.gc
.string_arena
.get(r)
.map(|s| String::from_utf8_lossy(s.data()).to_string())
.ok_or_else(|| rt_error("invalid string")),
_ => Err(rt_error("string expected")),
}
}
fn get_string_bytes(state: &LuaState, pos: usize) -> LuaResult<Vec<u8>> {
match state.stack_get(pos) {
Val::Str(r) => state
.gc
.string_arena
.get(r)
.map(|s| s.data().to_vec())
.ok_or_else(|| rt_error("invalid string")),
_ => Err(rt_error("string expected")),
}
}
fn load_file_for_testc(filename: &str) -> Result<Vec<u8>, String> {
use std::io::Read;
let mut file =
std::fs::File::open(filename).map_err(|e| format!("cannot open {filename}: {e}"))?;
let mut buf = Vec::new();
file.read_to_end(&mut buf)
.map_err(|e| format!("cannot read {filename}: {e}"))?;
Ok(buf)
}
fn is_delimit(c: char) -> bool {
matches!(c, ' ' | '\t' | '\n' | '\r' | ',' | ';')
}
fn tokenize_testc(prog: &str) -> Vec<&str> {
prog.split(is_delimit).filter(|s| !s.is_empty()).collect()
}
fn next_num(tokens: &mut &[&str], state: &mut LuaState) -> i32 {
let tok = if tokens.is_empty() {
"0"
} else {
let t = tokens[0];
*tokens = &tokens[1..];
t
};
if tok == "." {
let val = state.pop();
match val {
Val::Num(n) => n as i32,
_ => 0,
}
} else {
tok.parse().unwrap_or(0)
}
}
fn next_name<'a>(tokens: &mut &[&'a str]) -> &'a str {
if tokens.is_empty() {
""
} else {
let t = tokens[0];
*tokens = &tokens[1..];
t
}
}
fn next_index(tokens: &mut &[&str], state: &mut LuaState) -> SpecialIndex {
let tok = if tokens.is_empty() {
"0"
} else {
let t = tokens[0];
*tokens = &tokens[1..];
t
};
match tok {
"G" => SpecialIndex::Global,
"E" => SpecialIndex::Env,
"R" => SpecialIndex::Registry,
_ if tok.starts_with('U') => {
if let Ok(n) = tok[1..].parse::<usize>() {
SpecialIndex::Upvalue(n)
} else {
SpecialIndex::Stack(0)
}
}
"." => {
let val = state.pop();
let n = match val {
Val::Num(n) => n as i32,
_ => 0,
};
SpecialIndex::Stack(n)
}
_ => SpecialIndex::Stack(tok.parse().unwrap_or(0)),
}
}
fn run_testc_program(
state: &mut LuaState,
prog: &str,
tc_base: usize,
caller_closure_ref: Option<GcRef<Closure>>,
) -> LuaResult<u32> {
let all_tokens = tokenize_testc(prog);
let mut tokens: &[&str] = &all_tokens;
loop {
let cmd = next_name(&mut tokens);
if cmd.is_empty() {
break;
}
match cmd {
"pushnum" => {
let n = next_num(&mut tokens, state);
state.ensure_stack(state.top + 1);
state.push(Val::Num(f64::from(n)));
}
"pushbool" => {
let b = next_num(&mut tokens, state);
state.ensure_stack(state.top + 1);
state.push(Val::Bool(b != 0));
}
"pushnil" => {
state.ensure_stack(state.top + 1);
state.push(Val::Nil);
}
"pushstring" => {
let s = next_name(&mut tokens);
let sr = state.gc.intern_string(s.as_bytes());
state.ensure_stack(state.top + 1);
state.push(Val::Str(sr));
}
"pushvalue" => {
let si = next_index(&mut tokens, state);
let val = get_special(state, tc_base, &si, caller_closure_ref);
state.ensure_stack(state.top + 1);
state.push(val);
}
"pushcclosure" => {
let n = next_num(&mut tokens, state) as usize;
let mut upvalues = Vec::with_capacity(n);
for _ in 0..n {
upvalues.push(state.pop());
}
upvalues.reverse();
let env = if let Some(cr) = caller_closure_ref {
if let Some(cl) = state.gc.closures.get(cr) {
match cl {
Closure::Lua(lc) => Some(lc.env),
Closure::Rust(rc) => rc.env.or(Some(state.global)),
}
} else {
Some(state.global)
}
} else {
Some(state.global)
};
let closure = Closure::Rust(RustClosure {
func: t_testc,
upvalues,
name: "testC_closure".to_string(),
env,
});
let cr = state.gc.alloc_closure(closure);
state.push(Val::Function(cr));
}
"pushupvalueindex" => {
let n = next_num(&mut tokens, state) as usize;
let val = get_special(
state,
tc_base,
&SpecialIndex::Upvalue(n),
caller_closure_ref,
);
state.push(val);
}
"gettop" => {
let n = state.top.saturating_sub(tc_base);
state.ensure_stack(state.top + 1);
state.push(Val::Num(n as f64));
}
"settop" => {
let n = next_num(&mut tokens, state);
let new_top = if n >= 0 {
tc_base + n as usize
} else {
let target = state.top as i64 + i64::from(n);
if target < tc_base as i64 {
tc_base
} else {
target as usize
}
};
state.ensure_stack(new_top);
for i in state.top..new_top {
state.stack_set(i, Val::Nil);
}
state.top = new_top;
}
"pop" => {
let n = next_num(&mut tokens, state) as usize;
let new_top = if state.top >= n {
state.top - n
} else {
tc_base
};
state.top = new_top;
}
"remove" => {
let idx = next_num(&mut tokens, state);
if let Some(pos) = resolve_index(state, tc_base, idx)
&& pos < state.top
{
for i in pos..state.top - 1 {
let v = state.stack_get(i + 1);
state.stack_set(i, v);
}
state.top -= 1;
}
}
"insert" => {
let idx = next_num(&mut tokens, state);
if let Some(pos) = resolve_index(state, tc_base, idx)
&& pos < state.top
{
let top_val = state.stack_get(state.top - 1);
for i in (pos..state.top - 1).rev() {
let v = state.stack_get(i);
state.stack_set(i + 1, v);
}
state.stack_set(pos, top_val);
}
}
"replace" => {
let si = next_index(&mut tokens, state);
match si {
SpecialIndex::Stack(i) => {
if let Some(abs_pos) = resolve_index(state, tc_base, i) {
let top_val = state.pop();
state.ensure_stack(abs_pos + 1);
state.stack_set(abs_pos, top_val);
} else {
state.pop();
}
}
other => {
let top_val = state.pop();
set_special(state, tc_base, &other, top_val, caller_closure_ref);
}
}
}
"isnumber" => {
let si = next_index(&mut tokens, state);
let val = get_special(state, tc_base, &si, caller_closure_ref);
let is = match val {
Val::Num(_) => true,
Val::Str(r) => {
if let Some(s) = state.gc.string_arena.get(r) {
let text = String::from_utf8_lossy(s.data());
text.trim().parse::<f64>().is_ok()
} else {
false
}
}
_ => false,
};
state.push(Val::Num(if is { 1.0 } else { 0.0 }));
}
"isstring" => {
let si = next_index(&mut tokens, state);
let val = get_special(state, tc_base, &si, caller_closure_ref);
let is = matches!(val, Val::Str(_) | Val::Num(_));
state.push(Val::Num(if is { 1.0 } else { 0.0 }));
}
"isfunction" => {
let si = next_index(&mut tokens, state);
let val = get_special(state, tc_base, &si, caller_closure_ref);
let is = matches!(val, Val::Function(_));
state.push(Val::Num(if is { 1.0 } else { 0.0 }));
}
"iscfunction" => {
let si = next_index(&mut tokens, state);
let val = get_special(state, tc_base, &si, caller_closure_ref);
let is = if let Val::Function(r) = val {
matches!(state.gc.closures.get(r), Some(Closure::Rust(_)))
} else {
false
};
state.push(Val::Num(if is { 1.0 } else { 0.0 }));
}
"istable" => {
let si = next_index(&mut tokens, state);
let val = get_special(state, tc_base, &si, caller_closure_ref);
let is = matches!(val, Val::Table(_));
state.push(Val::Num(if is { 1.0 } else { 0.0 }));
}
"isuserdata" => {
let si = next_index(&mut tokens, state);
let val = get_special(state, tc_base, &si, caller_closure_ref);
let is = matches!(val, Val::Userdata(_) | Val::LightUserdata(_));
state.push(Val::Num(if is { 1.0 } else { 0.0 }));
}
"isudataval" => {
let si = next_index(&mut tokens, state);
let val = get_special(state, tc_base, &si, caller_closure_ref);
let is = matches!(val, Val::LightUserdata(_));
state.push(Val::Num(if is { 1.0 } else { 0.0 }));
}
"isnil" => {
let si = next_index(&mut tokens, state);
let is = if is_valid_index(state, tc_base, &si) {
let val = get_special(state, tc_base, &si, caller_closure_ref);
val.is_nil()
} else {
false
};
state.push(Val::Num(if is { 1.0 } else { 0.0 }));
}
"isnull" => {
let si = next_index(&mut tokens, state);
let is_null = match si {
SpecialIndex::Stack(i) => {
if let Some(pos) = resolve_index(state, tc_base, i) {
pos >= state.top
} else {
true
}
}
_ => false,
};
state.push(Val::Num(if is_null { 1.0 } else { 0.0 }));
}
"tostring" => {
let si = next_index(&mut tokens, state);
let val = get_special(state, tc_base, &si, caller_closure_ref);
let result = match val {
Val::Str(_) => val,
Val::Num(_) => {
let s = format!("{val}");
let sr = state.gc.intern_string(s.as_bytes());
Val::Str(sr)
}
Val::Bool(b) => {
let sr = state.gc.intern_string(if b { b"true" } else { b"false" });
Val::Str(sr)
}
_ => Val::Nil,
};
state.push(result);
}
"tonumber" => {
let si = next_index(&mut tokens, state);
let val = get_special(state, tc_base, &si, caller_closure_ref);
let n = match val {
Val::Num(n) => n,
Val::Str(r) => {
if let Some(s) = state.gc.string_arena.get(r) {
let text = String::from_utf8_lossy(s.data());
text.trim().parse::<f64>().unwrap_or(0.0)
} else {
0.0
}
}
_ => 0.0,
};
state.push(Val::Num(n));
}
"tobool" => {
let si = next_index(&mut tokens, state);
let val = get_special(state, tc_base, &si, caller_closure_ref);
let b = val.is_truthy();
state.push(Val::Num(if b { 1.0 } else { 0.0 }));
}
"tocfunction" => {
let si = next_index(&mut tokens, state);
let val = get_special(state, tc_base, &si, caller_closure_ref);
if let Val::Function(r) = val {
if let Some(Closure::Rust(rc)) = state.gc.closures.get(r) {
let func = rc.func;
let name = rc.name.clone();
let new_cl = Closure::Rust(RustClosure {
func,
upvalues: Vec::new(),
name,
env: None,
});
let cr = state.gc.alloc_closure(new_cl);
state.push(Val::Function(cr));
} else {
state.push(Val::Nil);
}
} else {
state.push(Val::Nil);
}
}
"objsize" => {
let si = next_index(&mut tokens, state);
let val = get_special(state, tc_base, &si, caller_closure_ref);
let size = match val {
Val::Str(r) => state.gc.string_arena.get(r).map_or(0, |s| s.data().len()),
Val::Table(r) => {
let strings = &state.gc.string_arena;
state.gc.tables.get(r).map_or(0, |t| t.len(strings))
}
Val::Userdata(r) => state.gc.userdata.get(r).map_or(0, |ud| {
ud.downcast_ref::<TestUserdata>().map_or(0, |tu| tu.size)
}),
Val::Num(_) => {
let s = format!("{val}");
s.len()
}
_ => 0,
};
state.push(Val::Num(size as f64));
}
"type" => {
let idx = next_num(&mut tokens, state);
if let Some(pos) = resolve_index(state, tc_base, idx) {
if pos < state.top {
let val = state.stack_get(pos);
let name = val.type_name();
let s = state.gc.intern_string(name.as_bytes());
state.push(Val::Str(s));
} else {
let s = state.gc.intern_string(b"no value");
state.push(Val::Str(s));
}
} else {
let s = state.gc.intern_string(b"nil");
state.push(Val::Str(s));
}
}
"newuserdata" => {
let size = next_num(&mut tokens, state) as usize;
let ud = create_test_userdata(state, size);
if let Some(cr) = caller_closure_ref
&& let Some(cl) = state.gc.closures.get(cr)
{
let env = match cl {
Closure::Lua(lc) => lc.env,
Closure::Rust(rc) => rc.env.unwrap_or(state.global),
};
if let Some(ud_obj) = state.gc.userdata.get_mut(ud) {
ud_obj.set_env(Some(env));
}
}
state.push(Val::Userdata(ud));
}
"gettable" => {
let si = next_index(&mut tokens, state);
let table_val = get_special(state, tc_base, &si, caller_closure_ref);
let key = state.pop();
let result = state.gettable(table_val, key)?;
state.push(result);
}
"settable" => {
let si = next_index(&mut tokens, state);
let table_val = get_special(state, tc_base, &si, caller_closure_ref);
let value = state.pop();
let key = state.pop();
state.settable(table_val, key, value)?;
}
"next" => {
let key = state.pop();
let table_val = state.stack_get(state.top - 1);
let Val::Table(table_ref) = table_val else {
return Err(rt_error("table expected for next"));
};
let result = state
.gc
.tables
.get(table_ref)
.ok_or_else(|| rt_error("invalid table"))?
.next(key, &state.gc.string_arena)?;
if let Some((k, v)) = result {
state.push(k);
state.push(v);
} else {
state.push(Val::Nil);
}
}
"concat" => {
let n = next_num(&mut tokens, state) as usize;
state.api_concat(n)?;
}
"lessthan" => {
let a_si = next_index(&mut tokens, state);
let b_si = next_index(&mut tokens, state);
let a_val = get_special(state, tc_base, &a_si, caller_closure_ref);
let b_val = get_special(state, tc_base, &b_si, caller_closure_ref);
let result = state.api_lessthan(a_val, b_val)?;
state.push(Val::Bool(result));
}
"equal" => {
let a_si = next_index(&mut tokens, state);
let b_si = next_index(&mut tokens, state);
let a_valid = is_valid_index(state, tc_base, &a_si);
let b_valid = is_valid_index(state, tc_base, &b_si);
if !a_valid || !b_valid {
state.push(Val::Bool(false));
} else {
let a_val = get_special(state, tc_base, &a_si, caller_closure_ref);
let b_val = get_special(state, tc_base, &b_si, caller_closure_ref);
let result = state.api_equal(a_val, b_val)?;
state.push(Val::Bool(result));
}
}
"setmetatable" => {
let si = next_index(&mut tokens, state);
let target = get_special(state, tc_base, &si, caller_closure_ref);
let mt = state.pop();
let mt_ref = if let Val::Table(r) = mt {
Some(r)
} else {
None
};
match target {
Val::Table(r) => {
if let Some(t) = state.gc.tables.get_mut(r) {
t.set_metatable(mt_ref);
}
}
Val::Userdata(r) => {
if let Some(ud) = state.gc.userdata.get_mut(r) {
ud.set_metatable(mt_ref);
}
}
_ => {}
}
}
"getmetatable" => {
let si = next_index(&mut tokens, state);
let target = get_special(state, tc_base, &si, caller_closure_ref);
let mt = match target {
Val::Table(r) => state.gc.tables.get(r).and_then(Table::metatable),
Val::Userdata(r) => state.gc.userdata.get(r).and_then(Userdata::metatable),
_ => None,
};
if let Some(mt_ref) = mt {
state.push(Val::Table(mt_ref));
} else {
state.push(Val::Nil);
}
}
"rawcall" => {
let nargs = next_num(&mut tokens, state);
let nresults = next_num(&mut tokens, state);
let func_pos = state.top - nargs as usize - 1;
state.call_function(func_pos, nresults)?;
}
"call" => {
let nargs = next_num(&mut tokens, state);
let nresults = next_num(&mut tokens, state);
let func_pos = state.top - nargs as usize - 1;
let saved_ci = state.ci;
let saved_n_ccalls = state.n_ccalls;
let saved_call_depth = state.call_depth;
state.error_object = None;
match state.call_function(func_pos, nresults) {
Ok(()) => {}
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.top = func_pos;
state.push(error_val);
}
}
}
"pcall" => {
let nargs = next_num(&mut tokens, state);
let nresults = next_num(&mut tokens, state);
let func_pos = state.top - nargs as usize - 1;
let saved_ci = state.ci;
let saved_n_ccalls = state.n_ccalls;
let saved_call_depth = state.call_depth;
state.error_object = None;
match state.call_function(func_pos, nresults) {
Ok(()) => {
state.push(Val::Num(0.0));
}
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.top = func_pos;
state.push(error_val);
}
}
}
"loadstring" => {
let idx = next_num(&mut tokens, state);
let pos = resolve_index(state, tc_base, idx).unwrap_or(tc_base);
if pos < state.top {
let source = get_string_bytes(state, pos)?;
let name = String::from_utf8_lossy(&source).to_string();
let chunk_name = format!("={name}");
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 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 cr = state.gc.alloc_closure(Closure::Lua(lua_cl));
state.push(Val::Function(cr));
}
Err(e) => {
let msg_bytes = match &e {
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));
}
}
} else {
let s = state.gc.intern_string(b"no string to load");
state.push(Val::Str(s));
}
}
"loadfile" => {
let idx = next_num(&mut tokens, state);
let pos = resolve_index(state, tc_base, idx).unwrap_or(tc_base);
if pos < state.top {
let filename = get_string(state, pos)?;
match load_file_for_testc(&filename) {
Ok(source) => {
let name = format!("@{filename}");
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 cr = state.gc.alloc_closure(Closure::Lua(lua_cl));
state.push(Val::Function(cr));
}
Err(e) => {
let msg_bytes = match &e {
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));
}
}
}
Err(msg) => {
let s = state.gc.intern_string(msg.as_bytes());
state.push(Val::Str(s));
}
}
} else {
let s = state.gc.intern_string(b"cannot read file");
state.push(Val::Str(s));
}
}
"getn" => {
let si = next_index(&mut tokens, state);
let val = get_special(state, tc_base, &si, caller_closure_ref);
let len = if let Val::Table(r) = val {
let strings = &state.gc.string_arena;
state.gc.tables.get(r).map_or(0, |t| t.len(strings))
} else {
0
};
state.push(Val::Num(len as f64));
}
"return" => {
let n = next_num(&mut tokens, state) as usize;
let start = if state.top >= n {
state.top - n
} else {
tc_base
};
let actual = state.top - start;
for i in 0..actual {
let v = state.stack_get(start + i);
state.stack_set(state.base + i, v);
}
state.top = state.base + actual;
return Ok(actual as u32);
}
_ => {
return Err(rt_error(&format!("unknown testC instruction: {cmd}")));
}
}
}
state.top = state.base;
Ok(0)
}
fn testc_remote(state: &mut LuaState, ud_ref: GcRef<Userdata>) -> LuaResult<u32> {
if state.base + 1 >= state.top {
return Err(rt_error("testC: missing program string for remote state"));
}
let prog = get_string(state, state.base + 1)?;
let remote_ptr = state
.gc
.userdata
.get(ud_ref)
.and_then(|ud| ud.downcast_ref::<RemoteState>())
.map(|rs| rs.ptr)
.ok_or_else(|| rt_error("testC: invalid remote state"))?;
#[allow(unsafe_code)]
let remote = unsafe { &mut *remote_ptr };
run_testc_program(remote, &prog, remote.base, None)?;
state.top = state.base;
Ok(0)
}
struct TestUserdata {
size: usize,
id: usize,
_data: Vec<u8>,
}
static USERDATA_NEXT_ID: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
fn create_test_userdata(state: &mut LuaState, size: usize) -> GcRef<Userdata> {
let id = USERDATA_NEXT_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let ud = Userdata::new(Box::new(TestUserdata {
size,
id,
_data: vec![0u8; size],
}));
state.gc.gc_state.total_bytes += size;
state.gc.alloc_userdata(ud)
}
pub fn t_newuserdata(state: &mut LuaState) -> LuaResult<u32> {
let size: usize = if state.base < state.top {
match state.stack_get(state.base) {
Val::Num(n) => n as usize,
_ => 0,
}
} else {
0
};
let ud_ref = create_test_userdata(state, size);
state.push(Val::Userdata(ud_ref));
Ok(1)
}
pub fn t_udataval(state: &mut LuaState) -> LuaResult<u32> {
if state.base >= state.top {
return Err(type_error(state, 0, "userdata"));
}
let val = state.stack_get(state.base);
let Val::Userdata(r) = val else {
return Err(type_error(state, 0, "userdata"));
};
let id = state
.gc
.userdata
.get(r)
.and_then(|ud| ud.downcast_ref::<TestUserdata>())
.map_or(0, |tu| tu.id);
state.push(Val::Num(id as f64));
Ok(1)
}
pub fn t_pushuserdata(state: &mut LuaState) -> LuaResult<u32> {
let id: usize = if state.base < state.top {
match state.stack_get(state.base) {
Val::Num(n) => n as usize,
_ => 0,
}
} else {
0
};
let existing: Option<GcRef<Userdata>> = state
.gc
.userdata
.iter()
.find(|(_, ud, _)| {
ud.downcast_ref::<TestUserdata>()
.is_some_and(|tu| tu.id == id)
})
.map(|(r, _, _)| r);
if let Some(ud_ref) = existing {
state.push(Val::Userdata(ud_ref));
return Ok(1);
}
let ud = Userdata::new(Box::new(TestUserdata {
size: 0,
id,
_data: Vec::new(),
}));
let ud_ref = state.gc.alloc_userdata(ud);
state.push(Val::Userdata(ud_ref));
Ok(1)
}
const LUA_REFNIL: i32 = -1;
pub fn t_ref(state: &mut LuaState) -> LuaResult<u32> {
let val = if state.base < state.top {
state.stack_get(state.base)
} else {
Val::Nil
};
if val.is_nil() {
state.push(Val::Num(f64::from(LUA_REFNIL)));
return Ok(1);
}
let registry = state.registry;
let n_key = state.gc.intern_string(b"n");
let current_n = state
.gc
.tables
.get(registry)
.map_or(Val::Nil, |t| t.get(Val::Str(n_key), &state.gc.string_arena));
let zero_key = Val::Num(0.0);
let free_ref = state
.gc
.tables
.get(registry)
.map_or(Val::Nil, |t| t.get(zero_key, &state.gc.string_arena));
let ref_key: i32;
if let Val::Num(free_n) = free_ref {
let free_idx = free_n as i32;
if free_idx > 0 {
let next_free = state.gc.tables.get(registry).map_or(Val::Nil, |t| {
t.get(Val::Num(f64::from(free_idx)), &state.gc.string_arena)
});
if let Some(t) = state.gc.tables.get_mut(registry) {
t.raw_set(zero_key, next_free, &state.gc.string_arena)?;
}
ref_key = free_idx;
} else {
let n = match current_n {
Val::Num(x) => x as i32,
_ => 0,
};
ref_key = n + 1;
if let Some(t) = state.gc.tables.get_mut(registry) {
t.raw_set(
Val::Str(n_key),
Val::Num(f64::from(ref_key)),
&state.gc.string_arena,
)?;
}
}
} else {
let n = match current_n {
Val::Num(x) => x as i32,
_ => 0,
};
ref_key = n + 1;
if let Some(t) = state.gc.tables.get_mut(registry) {
t.raw_set(
Val::Str(n_key),
Val::Num(f64::from(ref_key)),
&state.gc.string_arena,
)?;
}
}
if let Some(t) = state.gc.tables.get_mut(registry) {
t.raw_set(Val::Num(f64::from(ref_key)), val, &state.gc.string_arena)?;
}
state.push(Val::Num(f64::from(ref_key)));
Ok(1)
}
pub fn t_unref(state: &mut LuaState) -> LuaResult<u32> {
let key: i32 = if state.base < state.top {
match state.stack_get(state.base) {
Val::Num(n) => n as i32,
_ => return Ok(0),
}
} else {
return Ok(0);
};
if key <= 0 {
return Ok(0); }
let registry = state.registry;
let zero_key = Val::Num(0.0);
let current_free = state
.gc
.tables
.get(registry)
.map_or(Val::Nil, |t| t.get(zero_key, &state.gc.string_arena));
if let Some(t) = state.gc.tables.get_mut(registry) {
t.raw_set(
Val::Num(f64::from(key)),
current_free,
&state.gc.string_arena,
)?;
}
if let Some(t) = state.gc.tables.get_mut(registry) {
t.raw_set(zero_key, Val::Num(f64::from(key)), &state.gc.string_arena)?;
}
Ok(0)
}
pub fn t_getref(state: &mut LuaState) -> LuaResult<u32> {
let key: i32 = if state.base < state.top {
if let Val::Num(n) = state.stack_get(state.base) {
n as i32
} else {
state.push(Val::Nil);
return Ok(1);
}
} else {
state.push(Val::Nil);
return Ok(1);
};
if key == LUA_REFNIL {
state.push(Val::Nil);
return Ok(1);
}
let registry = state.registry;
let val = state.gc.tables.get(registry).map_or(Val::Nil, |t| {
t.get(Val::Num(f64::from(key)), &state.gc.string_arena)
});
state.push(val);
Ok(1)
}
pub fn t_upvalue(state: &mut LuaState) -> LuaResult<u32> {
if state.base >= state.top {
return Err(arg_error(state, 1, "function expected"));
}
let func_val = state.stack_get(state.base);
let Val::Function(func_ref) = func_val else {
return Err(arg_error(state, 1, "function expected"));
};
let n: usize = if state.base + 1 < state.top {
match state.stack_get(state.base + 1) {
Val::Num(x) => x as usize,
_ => 1,
}
} else {
1
};
let has_new_val = state.base + 2 < state.top;
let new_val = if has_new_val {
state.stack_get(state.base + 2)
} else {
Val::Nil
};
let cl = state
.gc
.closures
.get(func_ref)
.ok_or_else(|| arg_error(state, 1, "invalid function"))?;
match cl {
Closure::Lua(lc) => {
let idx = n - 1;
if idx >= lc.upvalues.len() {
state.push(Val::Nil);
return Ok(1);
}
let uv_ref = lc.upvalues[idx];
let old_val = state
.gc
.upvalues
.get(uv_ref)
.map_or(Val::Nil, |uv| uv.get(&state.stack));
if has_new_val && let Some(uv) = state.gc.upvalues.get_mut(uv_ref) {
uv.set(&mut state.stack, new_val);
}
state.push(old_val);
}
Closure::Rust(rc) => {
let idx = n - 1;
if idx >= rc.upvalues.len() {
state.push(Val::Nil);
return Ok(1);
}
let old_val = rc.upvalues[idx];
if has_new_val {
let _ = cl;
if let Some(Closure::Rust(rc)) = state.gc.closures.get_mut(func_ref) {
rc.upvalues[idx] = new_val;
}
}
state.push(old_val);
}
}
Ok(1)
}
pub fn t_checkmemory(state: &mut LuaState) -> LuaResult<u32> {
let _ = state;
Ok(0)
}
pub fn t_gsub(state: &mut LuaState) -> LuaResult<u32> {
let s = get_string(state, state.base)?;
let p = get_string(state, state.base + 1)?;
let r = get_string(state, state.base + 2)?;
let result = if p.is_empty() {
s
} else {
let mut out = String::new();
let mut rest = s.as_str();
while let Some(pos) = rest.find(&p) {
out.push_str(&rest[..pos]);
out.push_str(&r);
rest = &rest[pos + p.len()..];
}
out.push_str(rest);
out
};
let sr = state.gc.intern_string(result.as_bytes());
state.push(Val::Str(sr));
Ok(1)
}
pub fn t_doonnewstack(state: &mut LuaState) -> LuaResult<u32> {
let code = get_string_bytes(state, state.base)?;
match crate::compile_or_undump(&code, "=doonnewstack") {
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 cr = state.gc.alloc_closure(Closure::Lua(lua_cl));
let func_val = Val::Function(cr);
let thread = crate::vm::state::LuaThread::new(func_val, state.global);
let co_ref = state.gc.alloc_thread(thread);
match coroutine::auxresume(state, co_ref, &[]) {
Ok(_) => {
state.push(Val::Num(0.0));
}
Err(err) => {
state.push(Val::Num(1.0));
state.push(err);
return Ok(2);
}
}
}
Err(e) => {
state.push(Val::Num(1.0));
let msg = state.gc.intern_string(e.to_string().as_bytes());
state.push(Val::Str(msg));
return Ok(2);
}
}
Ok(1)
}
struct RemoteState {
ptr: *mut LuaState,
}
#[allow(unsafe_code)]
unsafe impl Send for RemoteState {}
pub fn t_newstate(state: &mut LuaState) -> LuaResult<u32> {
let new_state = Box::new(LuaState::new());
let ptr = Box::into_raw(new_state);
let ud = Userdata::new(Box::new(RemoteState { ptr }));
let ud_ref = state.gc.alloc_userdata(ud);
state.push(Val::Userdata(ud_ref));
Ok(1)
}
pub fn t_closestate(state: &mut LuaState) -> LuaResult<u32> {
if state.base >= state.top {
return Err(rt_error("closestate: missing state argument"));
}
let val = state.stack_get(state.base);
let Val::Userdata(ud_ref) = val else {
return Err(rt_error("closestate: userdata expected"));
};
let ptr = state
.gc
.userdata
.get(ud_ref)
.and_then(|ud| ud.downcast_ref::<RemoteState>())
.map(|rs| rs.ptr)
.ok_or_else(|| rt_error("closestate: invalid remote state"))?;
#[allow(unsafe_code)]
unsafe {
drop(Box::from_raw(ptr));
}
Ok(0)
}
pub fn t_doremote(state: &mut LuaState) -> LuaResult<u32> {
if state.base + 1 >= state.top {
return Err(rt_error("doremote: missing arguments"));
}
let ud_val = state.stack_get(state.base);
let Val::Userdata(ud_ref) = ud_val else {
return Err(rt_error("doremote: userdata expected"));
};
let code = get_string_bytes(state, state.base + 1)?;
let remote_ptr = state
.gc
.userdata
.get(ud_ref)
.and_then(|ud| ud.downcast_ref::<RemoteState>())
.map(|rs| rs.ptr)
.ok_or_else(|| rt_error("doremote: invalid remote state"))?;
#[allow(unsafe_code)]
let remote = unsafe { &mut *remote_ptr };
match crate::compile_or_undump(&code, "=doremote") {
Ok(proto) => {
let mut proto = std::rc::Rc::try_unwrap(proto).unwrap_or_else(|rc| (*rc).clone());
crate::patch_string_constants(&mut proto, &mut remote.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, remote.global);
for _ in 0..num_upvalues {
let uv = crate::vm::closure::Upvalue::new_closed(Val::Nil);
let uv_ref = remote.gc.alloc_upvalue(uv);
lua_cl.upvalues.push(uv_ref);
}
let cr = remote.gc.alloc_closure(Closure::Lua(lua_cl));
let func_pos = remote.top;
remote.ensure_stack(func_pos + 1);
remote.stack_set(func_pos, Val::Function(cr));
remote.top = func_pos + 1;
match remote.call_function(func_pos, -1) {
Ok(()) => {
let n_results = remote.top - func_pos;
for i in 0..n_results {
let v = remote.stack_get(func_pos + i);
let s = match v {
Val::Str(r) => remote
.gc
.string_arena
.get(r)
.map(|s| s.data().to_vec())
.unwrap_or_default(),
Val::Bool(true) => b"true".to_vec(),
Val::Bool(false) => b"false".to_vec(),
Val::Nil => b"nil".to_vec(),
_ => format!("{v}").into_bytes(),
};
let sr = state.gc.intern_string(&s);
state.push(Val::Str(sr));
}
remote.top = func_pos;
Ok(n_results as u32)
}
Err(err) => {
remote.top = func_pos;
state.push(Val::Nil);
let code = match &err {
LuaError::Syntax(_) => 3,
_ => 2,
};
state.push(Val::Num(f64::from(code)));
let msg = state.gc.intern_string(err.to_string().as_bytes());
state.push(Val::Str(msg));
Ok(3)
}
}
}
Err(e) => {
state.push(Val::Nil);
state.push(Val::Num(3.0)); let msg = state.gc.intern_string(e.to_string().as_bytes());
state.push(Val::Str(msg));
Ok(3)
}
}
}
pub fn t_loadlib(state: &mut LuaState) -> LuaResult<u32> {
if state.base >= state.top {
return Err(rt_error("loadlib: missing state argument"));
}
let ud_val = state.stack_get(state.base);
let Val::Userdata(ud_ref) = ud_val else {
return Err(rt_error("loadlib: userdata expected"));
};
let remote_ptr = state
.gc
.userdata
.get(ud_ref)
.and_then(|ud| ud.downcast_ref::<RemoteState>())
.map(|rs| rs.ptr)
.ok_or_else(|| rt_error("loadlib: invalid remote state"))?;
#[allow(unsafe_code)]
let remote = unsafe { &mut *remote_ptr };
#[allow(clippy::type_complexity)]
let openers: &[(&str, fn(&mut LuaState) -> LuaResult<u32>)] = &[
("baselibopen", lib_open_base),
("strlibopen", lib_open_string),
("tablibopen", lib_open_table),
("iolibopen", lib_open_io),
("mathlibopen", lib_open_math),
("dblibopen", lib_open_debug),
("packageopen", lib_open_package),
];
for &(name, func) in openers {
let closure = Closure::Rust(RustClosure::new(func, name));
let cr = remote.gc.alloc_closure(closure);
let key = remote.gc.intern_string(name.as_bytes());
if let Some(t) = remote.gc.tables.get_mut(remote.global) {
t.raw_set(Val::Str(key), Val::Function(cr), &remote.gc.string_arena)?;
}
}
Ok(0)
}
fn set_package_loaded(state: &mut LuaState, name: &[u8], val: Val) {
let loaded_key = state.gc.intern_string(b"_LOADED");
if let Some(reg) = state.gc.tables.get(state.registry) {
let loaded_val = reg.get(Val::Str(loaded_key), &state.gc.string_arena);
if let Val::Table(loaded_ref) = loaded_val {
let name_key = state.gc.intern_string(name);
if let Some(loaded_t) = state.gc.tables.get_mut(loaded_ref) {
let _ = loaded_t.raw_set(Val::Str(name_key), val, &state.gc.string_arena);
}
}
}
}
fn lib_open_base(state: &mut LuaState) -> LuaResult<u32> {
use crate::stdlib::StdLib;
crate::stdlib::open_libs_selective(state, StdLib::BASE)?;
let g = Val::Table(state.global);
set_package_loaded(state, b"_G", g);
state.push(g);
Ok(1)
}
fn lib_open_named(state: &mut LuaState, lib: crate::stdlib::StdLib, name: &[u8]) -> LuaResult<u32> {
crate::stdlib::open_libs_selective(state, lib)?;
let key = state.gc.intern_string(name);
let val = state
.gc
.tables
.get(state.global)
.map_or(Val::Nil, |t| t.get(Val::Str(key), &state.gc.string_arena));
set_package_loaded(state, name, val);
state.push(val);
Ok(1)
}
fn lib_open_string(state: &mut LuaState) -> LuaResult<u32> {
lib_open_named(state, crate::stdlib::StdLib::STRING, b"string")
}
fn lib_open_table(state: &mut LuaState) -> LuaResult<u32> {
lib_open_named(state, crate::stdlib::StdLib::TABLE, b"table")
}
fn lib_open_io(state: &mut LuaState) -> LuaResult<u32> {
lib_open_named(state, crate::stdlib::StdLib::IO, b"io")
}
fn lib_open_math(state: &mut LuaState) -> LuaResult<u32> {
lib_open_named(state, crate::stdlib::StdLib::MATH, b"math")
}
fn lib_open_debug(state: &mut LuaState) -> LuaResult<u32> {
lib_open_named(state, crate::stdlib::StdLib::DEBUG, b"debug")
}
fn lib_open_package(state: &mut LuaState) -> LuaResult<u32> {
use crate::stdlib::StdLib;
crate::stdlib::open_libs_selective(state, StdLib::PACKAGE)?;
let key = state.gc.intern_string(b"package");
let val = state
.gc
.tables
.get(state.global)
.map_or(Val::Nil, |t| t.get(Val::Str(key), &state.gc.string_arena));
state.push(val);
Ok(1)
}
pub fn t_totalmem(state: &mut LuaState) -> LuaResult<u32> {
if state.base < state.top {
let limit = match state.stack_get(state.base) {
Val::Num(n) => n as usize,
_ => usize::MAX,
};
state.gc.set_alloc_limit(limit);
Ok(0)
} else {
let total = state.gc.total_alloc();
let numblocks = state.gc.count_blocks();
let maxmem = state.gc.gc_state.max_bytes;
state.push(Val::Num(total as f64));
state.push(Val::Num(numblocks as f64));
state.push(Val::Num(maxmem as f64));
Ok(3)
}
}
pub fn t_querytab(state: &mut LuaState) -> LuaResult<u32> {
let arg0 = if state.base < state.top {
state.stack_get(state.base)
} else {
return Err(type_error(state, 0, "table"));
};
let Val::Table(table_ref) = arg0 else {
return Err(type_error(state, 0, "table"));
};
let i: i32 = if state.base + 1 < state.top {
match state.stack_get(state.base + 1) {
Val::Num(n) => n as i32,
_ => -1,
}
} else {
-1
};
let (asize, hsize, last_free, array_val, node_info) = {
let table = state
.gc
.tables
.get(table_ref)
.ok_or_else(|| type_error(state, 0, "table"))?;
let asize = table.array_len();
let hsize = table.hash_size();
let last_free = table.last_free_index();
let array_val = if i >= 0 && (i as usize) < asize {
Some(table.array_get(i as usize).unwrap_or(Val::Nil))
} else {
None
};
let node_info = if i >= 0 && (i as usize) >= asize {
let hash_idx = (i as usize) - asize;
if (hash_idx as u32) < hsize {
table.query_node(hash_idx as u32)
} else {
None
}
} else {
None
};
(asize, hsize, last_free, array_val, node_info)
};
state.ensure_stack(3);
if i == -1 {
state.stack_set(state.base, Val::Num(asize as f64));
state.stack_set(state.base + 1, Val::Num(f64::from(hsize)));
state.stack_set(state.base + 2, Val::Num(f64::from(last_free)));
} else if let Some(val) = array_val {
state.stack_set(state.base, Val::Num(f64::from(i)));
state.stack_set(state.base + 1, val);
state.stack_set(state.base + 2, Val::Nil);
} else if let Some((key, value, next)) = node_info {
let display_key = if !value.is_nil() || key.is_nil() || matches!(key, Val::Num(_)) {
key
} else {
let s = state.gc.intern_string(b"<undef>");
Val::Str(s)
};
state.stack_set(state.base, display_key);
state.stack_set(state.base + 1, value);
match next {
Some(idx) => state.stack_set(state.base + 2, Val::Num(f64::from(idx))),
None => state.stack_set(state.base + 2, Val::Nil),
}
} else {
state.stack_set(state.base, Val::Nil);
state.stack_set(state.base + 1, Val::Nil);
state.stack_set(state.base + 2, Val::Nil);
}
state.top = state.base + 3;
Ok(3)
}
pub fn t_hash(state: &mut LuaState) -> LuaResult<u32> {
let arg0 = if state.base < state.top {
state.stack_get(state.base)
} else {
return Err(type_error(state, 0, "value"));
};
let has_arg2 = state.base + 1 < state.top;
let result = if has_arg2 {
let arg1 = state.stack_get(state.base + 1);
let Val::Table(table_ref) = arg1 else {
return Err(type_error(state, 1, "table"));
};
let table = state
.gc
.tables
.get(table_ref)
.ok_or_else(|| type_error(state, 1, "table"))?;
let mp = if table.hash_size() == 0 {
0
} else {
table.main_position(&arg0, &state.gc.string_arena)
};
f64::from(mp)
} else {
let Val::Str(str_ref) = arg0 else {
return Err(arg_error(state, 1, "string expected"));
};
let hash = state
.gc
.string_arena
.get(str_ref)
.map_or(0, super::super::vm::string::LuaString::hash);
f64::from(hash)
};
state.stack_set(state.base, Val::Num(result));
state.top = state.base + 1;
Ok(1)
}
pub fn t_int2fb(state: &mut LuaState) -> LuaResult<u32> {
let arg0 = if state.base < state.top {
state.stack_get(state.base)
} else {
return Err(type_error(state, 0, "number"));
};
let Val::Num(n) = arg0 else {
return Err(type_error(state, 0, "number"));
};
let x = n as u32;
let encoded = int2fb(x);
let decoded = fb2int(encoded);
state.ensure_stack(2);
state.stack_set(state.base, Val::Num(f64::from(encoded)));
state.stack_set(state.base + 1, Val::Num(f64::from(decoded)));
state.top = state.base + 2;
Ok(2)
}
pub fn t_log2(state: &mut LuaState) -> LuaResult<u32> {
let arg0 = if state.base < state.top {
state.stack_get(state.base)
} else {
return Err(type_error(state, 0, "number"));
};
let Val::Num(n) = arg0 else {
return Err(type_error(state, 0, "number"));
};
let result = lua_o_log2(n as u32);
state.stack_set(state.base, Val::Num(f64::from(result)));
state.top = state.base + 1;
Ok(1)
}
fn lua_o_log2(mut x: u32) -> i32 {
#[rustfmt::skip]
const LOG_2: [u8; 256] = [
0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
];
let mut l: i32 = -1;
while x >= 256 {
l += 8;
x >>= 8;
}
l + i32::from(LOG_2[x as usize])
}
pub fn t_listcode(state: &mut LuaState) -> LuaResult<u32> {
let arg0 = if state.base < state.top {
state.stack_get(state.base)
} else {
return Err(arg_error(state, 1, "Lua function expected"));
};
let Val::Function(func_ref) = arg0 else {
return Err(arg_error(state, 1, "Lua function expected"));
};
let proto = {
let closure = state
.gc
.closures
.get(func_ref)
.ok_or_else(|| arg_error(state, 1, "Lua function expected"))?;
match closure {
Closure::Lua(lc) => lc.proto.clone(),
Closure::Rust(_) => {
return Err(arg_error(state, 1, "Lua function expected"));
}
}
};
let result_table = state.gc.alloc_table(Table::new());
let maxstack_key = state.gc.intern_string(b"maxstack");
if let Some(t) = state.gc.tables.get_mut(result_table) {
t.raw_set(
Val::Str(maxstack_key),
Val::Num(f64::from(proto.max_stack_size)),
&state.gc.string_arena,
)?;
}
let numparams_key = state.gc.intern_string(b"numparams");
if let Some(t) = state.gc.tables.get_mut(result_table) {
t.raw_set(
Val::Str(numparams_key),
Val::Num(f64::from(proto.num_params)),
&state.gc.string_arena,
)?;
}
for pc in 0..proto.code.len() {
let instr = Instruction::from_raw(proto.code[pc]);
let line = proto.line_info.get(pc).copied().unwrap_or(0);
let op_str = buildop(instr, line, pc);
let key = Val::Num((pc + 1) as f64);
let val_str = state.gc.intern_string(op_str.as_bytes());
let val = Val::Str(val_str);
if let Some(t) = state.gc.tables.get_mut(result_table) {
t.raw_set(key, val, &state.gc.string_arena)?;
}
}
state.stack_set(state.base, Val::Table(result_table));
state.top = state.base + 1;
Ok(1)
}
fn buildop(instr: Instruction, line: u32, pc: usize) -> String {
let op = instr.opcode();
let name = op.name();
let a = instr.a();
match op.mode() {
OpMode::IABC => {
let b = instr.b();
let c = instr.c();
format!("({line:4}) {pc:4} - {name:<12}{a:4} {b:4} {c:4}")
}
OpMode::IABx => {
let bx = instr.bx();
format!("({line:4}) {pc:4} - {name:<12}{a:4} {bx:4}")
}
OpMode::IAsBx => {
let sbx = instr.sbx();
format!("({line:4}) {pc:4} - {name:<12}{a:4} {sbx:4}")
}
}
}
pub fn t_setyhook(state: &mut LuaState) -> LuaResult<u32> {
let arg0 = if state.base < state.top {
state.stack_get(state.base)
} else {
state.hook.hook_mask = 0;
state.hook.yield_on_hook = false;
return Ok(0);
};
if arg0.is_nil() {
state.hook.hook_mask = 0;
state.hook.yield_on_hook = false;
return Ok(0);
}
let mask_str = match arg0 {
Val::Str(r) => state
.gc
.string_arena
.get(r)
.map(|s| String::from_utf8_lossy(s.data()).to_string())
.unwrap_or_default(),
_ => String::new(),
};
let count: i32 = if state.base + 1 < state.top {
match state.stack_get(state.base + 1) {
Val::Num(n) => n as i32,
_ => 0,
}
} else {
0
};
let mut mask: u8 = 0;
for ch in mask_str.chars() {
if ch == 'l' {
mask |= MASK_LINE;
}
}
if count > 0 {
mask |= MASK_COUNT;
}
state.hook.hook_mask = mask;
state.hook.base_hook_count = count;
state.hook.hook_count = count;
state.hook.yield_on_hook = mask != 0;
Ok(0)
}
pub fn t_resume(state: &mut LuaState) -> LuaResult<u32> {
let arg0 = if state.base < state.top {
state.stack_get(state.base)
} else {
return Err(type_error(state, 0, "thread"));
};
let Val::Thread(co_ref) = arg0 else {
return Err(type_error(state, 0, "thread"));
};
match coroutine::auxresume(state, co_ref, &[]) {
Ok(_results) => {
let base = state.base;
state.stack_set(base, Val::Bool(true));
state.top = base + 1;
Ok(1)
}
Err(error_val) => {
let base = state.base;
state.stack_set(base, Val::Bool(false));
state.stack_set(base + 1, error_val);
state.top = base + 2;
Ok(2)
}
}
}
pub fn t_d2s(state: &mut LuaState) -> LuaResult<u32> {
let arg0 = if state.base < state.top {
state.stack_get(state.base)
} else {
return Err(type_error(state, 0, "number"));
};
let Val::Num(n) = arg0 else {
return Err(type_error(state, 0, "number"));
};
let bytes = n.to_ne_bytes();
let s = state.gc.intern_string(&bytes);
state.stack_set(state.base, Val::Str(s));
state.top = state.base + 1;
Ok(1)
}
pub fn t_s2d(state: &mut LuaState) -> LuaResult<u32> {
let arg0 = if state.base < state.top {
state.stack_get(state.base)
} else {
return Err(type_error(state, 0, "string"));
};
let Val::Str(str_ref) = arg0 else {
return Err(type_error(state, 0, "string"));
};
let empty: &[u8] = &[];
let data = state
.gc
.string_arena
.get(str_ref)
.map_or(empty, super::super::vm::string::LuaString::data);
if data.len() < 8 {
return Err(arg_error(state, 1, "string must be at least 8 bytes"));
}
let mut buf = [0u8; 8];
buf.copy_from_slice(&data[..8]);
let n = f64::from_ne_bytes(buf);
state.stack_set(state.base, Val::Num(n));
state.top = state.base + 1;
Ok(1)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::vm::instructions::OpCode;
#[test]
fn log2_values() {
assert_eq!(lua_o_log2(0), -1);
assert_eq!(lua_o_log2(1), 0);
assert_eq!(lua_o_log2(2), 1);
assert_eq!(lua_o_log2(3), 1);
assert_eq!(lua_o_log2(4), 2);
assert_eq!(lua_o_log2(7), 2);
assert_eq!(lua_o_log2(8), 3);
assert_eq!(lua_o_log2(255), 7);
assert_eq!(lua_o_log2(256), 8);
assert_eq!(lua_o_log2(1024), 10);
}
#[test]
fn buildop_iabc() {
let instr = Instruction::abc(OpCode::Move, 1, 2, 0);
let s = buildop(instr, 5, 0);
assert!(s.contains("MOVE"));
assert!(s.starts_with("( 5) 0 - "));
}
#[test]
fn buildop_iabx() {
let instr = Instruction::a_bx(OpCode::LoadK, 0, 1);
let s = buildop(instr, 1, 0);
assert!(s.contains("LOADK"));
}
#[test]
fn buildop_iasbx() {
let instr = Instruction::a_sbx(OpCode::Jmp, 0, 10);
let s = buildop(instr, 1, 0);
assert!(s.contains("JMP"));
assert!(s.contains(" 10"));
}
}