use lua_types::{GcRef, LuaError, LuaTable, LuaType, LuaValue};
use crate::state_stub::{LuaState, LuaStateStubExt as _, CompareOp};
const TAB_R: u32 = 1;
const TAB_W: u32 = 2;
const TAB_L: u32 = 4;
const TAB_RW: u32 = TAB_R | TAB_W;
const RANLIMIT: u32 = 100;
type IdxT = u32;
fn check_field(state: &mut LuaState, key: &[u8], n: i32) -> Result<bool, LuaError> {
state.push_string(key)?;
let ty = state.raw_get(-n)?;
Ok(ty != LuaType::Nil)
}
fn check_tab(state: &mut LuaState, arg: i32, what: u32) -> Result<(), LuaError> {
if state.type_at(arg) == LuaType::Table {
return Ok(());
}
let mut n: i32 = 1;
let has_mt = state.get_metatable(arg)?;
let mut ok = has_mt;
if ok && (what & TAB_R) != 0 {
n += 1;
ok = check_field(state, b"__index", n)?;
}
if ok && (what & TAB_W) != 0 {
n += 1;
ok = check_field(state, b"__newindex", n)?;
}
if ok && (what & TAB_L) != 0 {
n += 1;
ok = check_field(state, b"__len", n)?;
}
if ok {
state.pop_n(n as usize);
Ok(())
} else {
state.check_arg_type(arg, LuaType::Table)
}
}
fn aux_getn(state: &mut LuaState, n: i32, w: u32) -> Result<i64, LuaError> {
check_tab(state, n, w | TAB_L)?;
state.length_at(n)
}
#[inline]
fn plain_table_at(state: &mut LuaState, idx: i32) -> Option<GcRef<LuaTable>> {
match state.value_at(idx) {
LuaValue::Table(tbl) if tbl.metatable().is_none() => Some(tbl),
_ => None,
}
}
#[inline]
fn raw_set_int(
state: &mut LuaState,
tbl: GcRef<LuaTable>,
key: i64,
value: LuaValue,
) -> Result<(), LuaError> {
state.gc_barrier_back(LuaValue::Table(tbl), value);
tbl.try_raw_set_int(key, value)
}
pub fn insert(state: &mut LuaState) -> Result<usize, LuaError> {
let mut e = aux_getn(state, 1, TAB_RW)?;
e = (e as u64).wrapping_add(1) as i64;
let plain_table = plain_table_at(state, 1);
let pos: i64 = match state.get_top() {
2 => {
if let Some(tbl) = plain_table {
let value = state.value_at(2);
raw_set_int(state, tbl, e, value)?;
state.pop_n(1);
return Ok(0);
}
e
}
3 => {
let pos = state.check_arg_integer(2)?;
if !((pos as u64).wrapping_sub(1) < (e as u64)) {
return Err(LuaError::arg_error(2, "position out of bounds"));
}
if let Some(tbl) = plain_table {
let value = state.value_at(3);
let mut i = e;
while i > pos {
let shifted = tbl.get_int(i - 1);
raw_set_int(state, tbl, i, shifted)?;
i -= 1;
}
raw_set_int(state, tbl, pos, value)?;
state.pop_n(1);
return Ok(0);
}
let tbl = state.value_at(1);
let mut i = e;
while i > pos {
state.table_get_i_value(&tbl, i - 1)?;
state.table_set_i_value(&tbl, i)?;
i -= 1;
}
pos
}
_ => {
return Err(LuaError::runtime(format_args!(
"wrong number of arguments to 'insert'"
)));
}
};
state.table_set_i(1, pos)?;
Ok(0)
}
pub fn remove(state: &mut LuaState) -> Result<usize, LuaError> {
let size = aux_getn(state, 1, TAB_RW)?;
let mut pos = state.opt_arg_integer(2, size)?;
if pos != size {
if !((pos as u64).wrapping_sub(1) <= (size as u64)) {
return Err(LuaError::arg_error(2, "position out of bounds"));
}
}
if let Some(tbl) = plain_table_at(state, 1) {
let result = tbl.get_int(pos);
state.push(result);
while pos < size {
let shifted = tbl.get_int(pos + 1);
raw_set_int(state, tbl, pos, shifted)?;
pos += 1;
}
raw_set_int(state, tbl, pos, LuaValue::Nil)?;
return Ok(1);
}
let tbl = state.value_at(1);
state.table_get_i_value(&tbl, pos)?; while pos < size {
state.table_get_i_value(&tbl, pos + 1)?;
state.table_set_i_value(&tbl, pos)?;
pos += 1;
}
state.push(LuaValue::Nil);
state.table_set_i_value(&tbl, pos)?; Ok(1)
}
pub fn tmove(state: &mut LuaState) -> Result<usize, LuaError> {
let f = state.check_arg_integer(2)?;
let e = state.check_arg_integer(3)?;
let t = state.check_arg_integer(4)?;
let tt: i32 = if !matches!(state.type_at(5), LuaType::None | LuaType::Nil) {
5
} else {
1
};
check_tab(state, 1, TAB_R)?;
check_tab(state, tt, TAB_W)?;
if e >= f {
if !(f > 0 || e < i64::MAX + f) {
return Err(LuaError::arg_error(3, "too many elements to move"));
}
let n = e - f + 1;
if !(t <= i64::MAX - n + 1) {
return Err(LuaError::arg_error(4, "destination wrap around"));
}
let copy_forward = t > e
|| t <= f
|| (tt != 1 && !state.compare(1, tt, CompareOp::Eq)?);
if copy_forward {
for i in 0..n {
state.table_get_i(1, f + i)?;
state.table_set_i(tt, t + i)?;
}
} else {
for i in (0..n).rev() {
state.table_get_i(1, f + i)?;
state.table_set_i(tt, t + i)?;
}
}
}
state.push_value_at(tt)?;
Ok(1)
}
fn add_field(state: &mut LuaState, buf: &mut Vec<u8>, idx: i64) -> Result<(), LuaError> {
state.table_get_i(1, idx)?;
if !matches!(state.type_at(-1), LuaType::String | LuaType::Number) {
let type_name = state.type_name_str_at(-1);
return Err(LuaError::runtime(format_args!(
"invalid value ({:?}) at index {} in table for 'concat'",
type_name, idx
)));
}
let bytes = state.to_bytes_at(-1).ok_or_else(|| LuaError::runtime(format_args!("invalid value at index {}", idx)))?;
buf.extend_from_slice(&bytes);
state.pop_n(1);
Ok(())
}
pub fn concat(state: &mut LuaState) -> Result<usize, LuaError> {
let last = aux_getn(state, 1, TAB_R)?;
let sep: Vec<u8> = state.opt_arg_lstring(2, Some(b""))?.unwrap_or_default();
let mut i = state.opt_arg_integer(3, 1)?;
let last = state.opt_arg_integer(4, last)?;
let mut buf: Vec<u8> = Vec::new();
while i < last {
add_field(state, &mut buf, i)?;
buf.extend_from_slice(&sep);
i += 1;
}
if i == last {
add_field(state, &mut buf, i)?;
}
state.push_lstring(&buf)?;
Ok(1)
}
pub fn pack(state: &mut LuaState) -> Result<usize, LuaError> {
let n = state.get_top();
state.create_table(n, 1)?;
state.insert(1)?;
for i in (1..=n).rev() {
state.table_set_i(1, i as i64)?;
}
state.push(LuaValue::Int(n as i64));
state.set_field(1, b"n")?;
Ok(1)
}
pub fn unpack(state: &mut LuaState) -> Result<usize, LuaError> {
let i = state.opt_arg_integer(2, 1)?;
let e = if matches!(state.type_at(3), LuaType::None | LuaType::Nil) {
state.length_at(1)?
} else {
state.check_arg_integer(3)?
};
if i > e {
return Ok(0); }
let n = (e as u64).wrapping_sub(i as u64);
if n >= i32::MAX as u64 {
return Err(LuaError::runtime(format_args!("too many results to unpack")));
}
let n = n + 1;
if !state.check_stack_growth(n as i32) {
return Err(LuaError::runtime(format_args!("too many results to unpack")));
}
let n = n as i64;
let mut k = i;
while k < e {
state.table_get_i(1, k)?;
k += 1;
}
state.table_get_i(1, e)?; Ok(n as usize)
}
fn randomize_pivot(state: &LuaState) -> u32 {
let entropy = state.global().entropy_hook.map(|hook| hook()).unwrap_or(0);
let mixed = entropy ^ entropy.wrapping_shr(32);
(mixed as u32) ^ (mixed as u32).wrapping_shr(16)
}
fn set2(state: &mut LuaState, i: IdxT, j: IdxT) -> Result<(), LuaError> {
state.table_set_i(1, i as i64)?;
state.table_set_i(1, j as i64)?;
Ok(())
}
fn sort_comp(state: &mut LuaState, a: i32, b: i32) -> Result<bool, LuaError> {
if state.type_at(2) == LuaType::Nil {
return state.compare(a, b, CompareOp::Lt);
}
state.push_value_at(2)?; state.push_value_at(a - 1)?; state.push_value_at(b - 2)?; state.call(2, 1)?;
let res = state.to_boolean(-1);
state.pop_n(1);
Ok(res)
}
fn partition(state: &mut LuaState, lo: IdxT, up: IdxT) -> Result<IdxT, LuaError> {
let mut i: IdxT = lo;
let mut j: IdxT = up - 1;
loop {
loop {
i += 1;
state.table_get_i(1, i as i64)?; if !sort_comp(state, -1, -2)? {
break;
}
if i == up - 1 {
return Err(LuaError::runtime(format_args!(
"invalid order function for sorting"
)));
}
state.pop_n(1); }
loop {
j = j.wrapping_sub(1);
state.table_get_i(1, j as i64)?; if !sort_comp(state, -3, -1)? {
break;
}
if j < i {
return Err(LuaError::runtime(format_args!(
"invalid order function for sorting"
)));
}
state.pop_n(1); }
if j < i {
state.pop_n(1); set2(state, up - 1, i)?; return Ok(i);
}
set2(state, i, j)?;
}
}
fn choose_pivot(lo: IdxT, up: IdxT, rnd: u32) -> IdxT {
let r4 = (up - lo) / 4; let p = rnd % (r4 * 2) + (lo + r4);
debug_assert!(lo + r4 <= p && p <= up - r4);
p
}
fn aux_sort(state: &mut LuaState, mut lo: IdxT, mut up: IdxT, mut rnd: u32) -> Result<(), LuaError> {
while lo < up {
state.table_get_i(1, lo as i64)?; state.table_get_i(1, up as i64)?; if sort_comp(state, -1, -2)? {
set2(state, lo, up)?; } else {
state.pop_n(2);
}
if up - lo == 1 {
return Ok(()); }
let mut p: IdxT = if up - lo < RANLIMIT || rnd == 0 {
(lo + up) / 2 } else {
choose_pivot(lo, up, rnd)
};
state.table_get_i(1, p as i64)?; state.table_get_i(1, lo as i64)?; if sort_comp(state, -2, -1)? {
set2(state, p, lo)?; } else {
state.pop_n(1); state.table_get_i(1, up as i64)?; if sort_comp(state, -1, -2)? {
set2(state, p, up)?; } else {
state.pop_n(2); }
}
if up - lo == 2 {
return Ok(()); }
state.table_get_i(1, p as i64)?;
state.push_value_at(-1)?; state.table_get_i(1, (up - 1) as i64)?;
set2(state, p, up - 1)?;
p = partition(state, lo, up)?;
let n: IdxT;
if p - lo < up - p {
aux_sort(state, lo, p - 1, rnd)?;
n = p - lo;
lo = p + 1; } else {
aux_sort(state, p + 1, up, rnd)?;
n = up - p;
up = p - 1; }
if (up - lo) / 128 > n {
rnd = randomize_pivot(state);
}
}
Ok(())
}
pub fn sort(state: &mut LuaState) -> Result<usize, LuaError> {
let n = aux_getn(state, 1, TAB_RW)?;
if n > 1 {
if !(n < i32::MAX as i64) {
return Err(LuaError::arg_error(1, "array too big"));
}
if !matches!(state.type_at(2), LuaType::None | LuaType::Nil) {
state.check_arg_type(2, LuaType::Function)?;
}
lua_vm::api::set_top(state, 2)?;
aux_sort(state, 1, n as IdxT, 0)?;
}
Ok(0)
}
pub const TABLE_FUNCS: &[(&[u8], fn(&mut LuaState) -> Result<usize, LuaError>)] = &[
(b"concat", concat),
(b"insert", insert),
(b"pack", pack),
(b"unpack", unpack),
(b"remove", remove),
(b"move", tmove),
(b"sort", sort),
];
pub fn open_table(state: &mut LuaState) -> Result<usize, LuaError> {
state.new_lib(TABLE_FUNCS)?;
Ok(1)
}