use crate::error::{LuaError, LuaResult, RuntimeError};
use crate::vm::gc::arena::GcRef;
use crate::vm::metatable::{TMS, gettmbyobj, val_raw_equal};
use crate::vm::state::LuaState;
use crate::vm::table::Table;
use crate::vm::value::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![],
})
}
#[allow(dead_code)]
fn check_args(name: &str, state: &LuaState, min: usize) -> LuaResult<()> {
if nargs(state) < min {
Err(bad_argument(name, min, "value expected"))
} else {
Ok(())
}
}
fn simple_error(msg: String) -> LuaError {
LuaError::Runtime(RuntimeError {
message: msg,
level: 0,
traceback: vec![],
})
}
fn check_table(state: &LuaState, name: &str, n: usize) -> LuaResult<GcRef<Table>> {
match arg(state, n) {
Val::Table(r) => Ok(r),
_ => Err(bad_argument(name, n + 1, "table expected")),
}
}
fn get_raw(state: &LuaState, tref: GcRef<Table>, idx: i64) -> Val {
state
.gc
.tables
.get(tref)
.map_or(Val::Nil, |t| t.get_int(idx))
}
fn set_raw(state: &mut LuaState, tref: GcRef<Table>, idx: i64, val: Val) -> LuaResult<()> {
let strings = &state.gc.string_arena;
let t = state
.gc
.tables
.get_mut(tref)
.ok_or_else(|| simple_error("table not found".into()))?;
#[allow(clippy::cast_precision_loss)]
t.raw_set(Val::Num(idx as f64), val, strings)
}
fn table_len(state: &LuaState, tref: GcRef<Table>) -> usize {
state
.gc
.tables
.get(tref)
.map_or(0, |t| t.len(&state.gc.string_arena))
}
pub fn tab_getn(state: &mut LuaState) -> LuaResult<u32> {
let tref = check_table(state, "getn", 0)?;
let n = table_len(state, tref);
#[allow(clippy::cast_precision_loss)]
state.stack_set(state.base, Val::Num(n as f64));
state.top = state.base + 1;
Ok(1)
}
pub fn tab_setn(state: &mut LuaState) -> LuaResult<u32> {
check_table(state, "setn", 0)?;
Err(simple_error("'setn' is obsolete".into()))
}
pub fn tab_maxn(state: &mut LuaState) -> LuaResult<u32> {
let tref = check_table(state, "maxn", 0)?;
let mut max = 0.0_f64;
let mut key = Val::Nil;
loop {
let next = {
let t = state
.gc
.tables
.get(tref)
.ok_or_else(|| simple_error("table not found".into()))?;
t.next(key, &state.gc.string_arena)?
};
match next {
Some((k, _v)) => {
if let Val::Num(n) = k
&& n > max
{
max = n;
}
key = k;
}
None => break,
}
}
state.stack_set(state.base, Val::Num(max));
state.top = state.base + 1;
Ok(1)
}
#[allow(clippy::many_single_char_names)]
pub fn tab_concat(state: &mut LuaState) -> LuaResult<u32> {
let tref = check_table(state, "concat", 0)?;
let sep = match arg(state, 1) {
Val::Nil => Vec::new(),
Val::Str(r) => state
.gc
.string_arena
.get(r)
.map_or_else(Vec::new, |s| s.data().to_vec()),
Val::Num(n) => format!("{}", Val::Num(n)).into_bytes(),
_ => return Err(bad_argument("concat", 2, "string expected")),
};
let i = match arg(state, 2) {
Val::Nil => 1_i64,
Val::Num(n) => n as i64,
_ => return Err(bad_argument("concat", 3, "number expected")),
};
let j = match arg(state, 3) {
Val::Nil => {
#[allow(clippy::cast_possible_wrap)]
let len = table_len(state, tref) as i64;
len
}
Val::Num(n) => n as i64,
_ => return Err(bad_argument("concat", 4, "number expected")),
};
let mut result: Vec<u8> = Vec::new();
let mut idx = i;
while idx <= j {
if idx != i {
result.extend_from_slice(&sep);
}
let val = get_raw(state, tref, idx);
match val {
Val::Str(r) => {
let data = state
.gc
.string_arena
.get(r)
.map_or(b"".as_slice(), |s| s.data());
result.extend_from_slice(data);
}
Val::Num(n) => {
let formatted = format!("{}", Val::Num(n));
result.extend_from_slice(formatted.as_bytes());
}
_ => {
return Err(bad_argument("concat", 1, "table contains non-strings"));
}
}
idx += 1;
}
let str_ref = state.gc.intern_string(&result);
state.stack_set(state.base, Val::Str(str_ref));
state.top = state.base + 1;
Ok(1)
}
pub fn tab_insert(state: &mut LuaState) -> LuaResult<u32> {
let tref = check_table(state, "insert", 0)?;
#[allow(clippy::cast_possible_wrap)]
let e = table_len(state, tref) as i64 + 1;
let n = nargs(state);
match n {
2 => {
let value = arg(state, 1);
set_raw(state, tref, e, value)?;
}
3 => {
let pos = match arg(state, 1) {
Val::Num(n) => n as i64,
_ => return Err(bad_argument("insert", 2, "number expected")),
};
let value = arg(state, 2);
let end = if pos > e { pos } else { e };
let mut idx = end;
while idx > pos {
let v = get_raw(state, tref, idx - 1);
set_raw(state, tref, idx, v)?;
idx -= 1;
}
set_raw(state, tref, pos, value)?;
}
_ => {
return Err(simple_error("wrong number of arguments to 'insert'".into()));
}
}
state.gc.barrier_back(tref);
state.top = state.base;
Ok(0)
}
pub fn tab_remove(state: &mut LuaState) -> LuaResult<u32> {
let tref = check_table(state, "remove", 0)?;
#[allow(clippy::cast_possible_wrap)]
let e = table_len(state, tref) as i64;
if e == 0 {
state.top = state.base;
return Ok(0);
}
let pos = match arg(state, 1) {
Val::Nil => e,
Val::Num(n) => n as i64,
_ => return Err(bad_argument("remove", 2, "number expected")),
};
let removed = get_raw(state, tref, pos);
let mut p = pos;
while p < e {
let v = get_raw(state, tref, p + 1);
set_raw(state, tref, p, v)?;
p += 1;
}
set_raw(state, tref, e, Val::Nil)?;
state.gc.barrier_back(tref);
state.stack_set(state.base, removed);
state.top = state.base + 1;
Ok(1)
}
pub fn tab_foreach(state: &mut LuaState) -> LuaResult<u32> {
let tref = check_table(state, "foreach", 0)?;
let func_val @ Val::Function(_) = arg(state, 1) else {
return Err(bad_argument("foreach", 2, "function expected"));
};
let mut key = Val::Nil;
loop {
let next = {
let t = state
.gc
.tables
.get(tref)
.ok_or_else(|| simple_error("table not found".into()))?;
t.next(key, &state.gc.string_arena)?
};
let Some((k, v)) = next else { break };
let call_base = state.top;
state.ensure_stack(call_base + 4);
state.stack_set(call_base, func_val);
state.stack_set(call_base + 1, k);
state.stack_set(call_base + 2, v);
state.top = call_base + 3;
state.call_function(call_base, 1)?;
let result = state.stack_get(call_base);
state.top = call_base;
if !result.is_nil() {
state.stack_set(state.base, result);
state.top = state.base + 1;
return Ok(1);
}
key = k;
}
state.top = state.base;
Ok(0)
}
pub fn tab_foreachi(state: &mut LuaState) -> LuaResult<u32> {
let tref = check_table(state, "foreachi", 0)?;
let func_val @ Val::Function(_) = arg(state, 1) else {
return Err(bad_argument("foreachi", 2, "function expected"));
};
#[allow(clippy::cast_possible_wrap)]
let n = table_len(state, tref) as i64;
for i in 1..=n {
let v = get_raw(state, tref, i);
let call_base = state.top;
state.ensure_stack(call_base + 4);
state.stack_set(call_base, func_val);
#[allow(clippy::cast_precision_loss)]
state.stack_set(call_base + 1, Val::Num(i as f64));
state.stack_set(call_base + 2, v);
state.top = call_base + 3;
state.call_function(call_base, 1)?;
let result = state.stack_get(call_base);
state.top = call_base;
if !result.is_nil() {
state.stack_set(state.base, result);
state.top = state.base + 1;
return Ok(1);
}
}
state.top = state.base;
Ok(0)
}
pub fn tab_sort(state: &mut LuaState) -> LuaResult<u32> {
let tref = check_table(state, "sort", 0)?;
#[allow(clippy::cast_possible_wrap)]
let n = table_len(state, tref) as i64;
let comp = match arg(state, 1) {
Val::Nil => None,
v @ Val::Function(_) => Some(v),
_ => return Err(bad_argument("sort", 2, "function expected")),
};
if n > 1 {
auxsort(state, tref, 1, n, comp)?;
state.gc.barrier_back(tref);
}
state.top = state.base;
Ok(0)
}
fn sort_comp(state: &mut LuaState, a: Val, b: Val, comp: Option<Val>) -> LuaResult<bool> {
if let Some(func_val) = comp {
let call_base = state.top;
state.ensure_stack(call_base + 4);
state.stack_set(call_base, func_val);
state.stack_set(call_base + 1, a);
state.stack_set(call_base + 2, b);
state.top = call_base + 3;
state.call_function(call_base, 1)?;
let result = state.stack_get(call_base);
state.top = call_base;
Ok(result.is_truthy())
} else {
default_less_than(state, a, b)
}
}
fn default_less_than(state: &mut LuaState, a: Val, b: Val) -> LuaResult<bool> {
match (&a, &b) {
(Val::Num(x), Val::Num(y)) => Ok(*x < *y),
(Val::Str(x), Val::Str(y)) => {
let xdata = state
.gc
.string_arena
.get(*x)
.map_or(b"".as_slice(), |s| s.data());
let ydata = state
.gc
.string_arena
.get(*y)
.map_or(b"".as_slice(), |s| s.data());
Ok(xdata < ydata)
}
_ => {
let tm_a = gettmbyobj(
a,
TMS::Lt,
&state.gc.tables,
&state.gc.string_arena,
&state.gc.type_metatables,
&state.gc.tm_names,
&state.gc.userdata,
);
let tm_b = gettmbyobj(
b,
TMS::Lt,
&state.gc.tables,
&state.gc.string_arena,
&state.gc.type_metatables,
&state.gc.tm_names,
&state.gc.userdata,
);
match (tm_a, tm_b) {
(Some(tm_val_a), Some(tm_val_b)) => {
if !val_raw_equal(tm_val_a, tm_val_b, &state.gc.tables, &state.gc.string_arena)
{
return Err(simple_error(format!(
"attempt to compare two {} values",
a.type_name()
)));
}
let call_base = state.top;
state.ensure_stack(call_base + 4);
state.stack_set(call_base, tm_val_a);
state.stack_set(call_base + 1, a);
state.stack_set(call_base + 2, b);
state.top = call_base + 3;
state.call_function(call_base, 1)?;
let result = state.stack_get(call_base);
state.top = call_base;
Ok(result.is_truthy())
}
_ => Err(simple_error(format!(
"attempt to compare two {} values",
a.type_name()
))),
}
}
}
}
fn auxsort(
state: &mut LuaState,
tref: GcRef<Table>,
mut l: i64,
mut u: i64,
comp: Option<Val>,
) -> LuaResult<()> {
while l < u {
let al = get_raw(state, tref, l);
let au = get_raw(state, tref, u);
if sort_comp(state, au, al, comp)? {
set_raw(state, tref, l, au)?;
set_raw(state, tref, u, al)?;
}
if u - l == 1 {
break; }
let mid = i64::midpoint(l, u);
let amid = get_raw(state, tref, mid);
let al = get_raw(state, tref, l);
if sort_comp(state, amid, al, comp)? {
set_raw(state, tref, mid, al)?;
set_raw(state, tref, l, amid)?;
} else {
let au = get_raw(state, tref, u);
if sort_comp(state, au, amid, comp)? {
set_raw(state, tref, mid, au)?;
set_raw(state, tref, u, amid)?;
}
}
if u - l == 2 {
break; }
let pivot = get_raw(state, tref, mid);
let au1 = get_raw(state, tref, u - 1);
set_raw(state, tref, mid, au1)?;
set_raw(state, tref, u - 1, pivot)?;
let mut i = l;
let mut j = u - 1;
loop {
i += 1;
while sort_comp(state, get_raw(state, tref, i), pivot, comp)? {
if i > u {
return Err(simple_error("invalid order function for sorting".into()));
}
i += 1;
}
j -= 1;
while sort_comp(state, pivot, get_raw(state, tref, j), comp)? {
if j < l {
return Err(simple_error("invalid order function for sorting".into()));
}
j -= 1;
}
if j < i {
break;
}
let ai = get_raw(state, tref, i);
let aj = get_raw(state, tref, j);
set_raw(state, tref, i, aj)?;
set_raw(state, tref, j, ai)?;
}
let au1 = get_raw(state, tref, u - 1);
let ai = get_raw(state, tref, i);
set_raw(state, tref, u - 1, ai)?;
set_raw(state, tref, i, au1)?;
if i - l < u - i {
auxsort(state, tref, l, i - 1, comp)?;
l = i + 1;
} else {
auxsort(state, tref, i + 1, u, comp)?;
u = i - 1;
}
}
Ok(())
}