use crate::runtime::Value;
use crate::vm::builtins::{arg_error, check_table, raise_str};
use crate::vm::error::LuaError;
use crate::vm::exec::Vm;
pub(crate) fn open_table(vm: &mut Vm) {
let t = vm.heap.new_table();
let set = |vm: &mut Vm, name: &str, f| {
let fv = vm.native(f);
let k = Value::Str(vm.heap.intern(name.as_bytes()));
unsafe { t.as_mut() }
.set(&mut vm.heap, k, fv)
.expect("valid key");
};
use crate::version::LuaVersion as V;
let ver = vm.version();
set(vm, "insert", t_insert);
set(vm, "remove", t_remove);
set(vm, "concat", t_concat);
set(vm, "sort", t_sort);
if ver >= V::Lua52 {
set(vm, "unpack", t_unpack);
set(vm, "pack", t_pack);
}
if ver >= V::Lua53 {
set(vm, "move", t_move);
}
if ver >= V::Lua55 {
set(vm, "create", t_create);
}
if ver <= V::Lua52 {
set(vm, "maxn", t_maxn);
}
if ver == V::Lua51 {
set(vm, "getn", t_getn);
set(vm, "foreach", t_foreach);
set(vm, "foreachi", t_foreachi);
set(vm, "setn", t_setn);
}
vm.set_global("table", Value::Table(t))
.expect("stdlib registration");
vm.barrier_back_table(t);
}
const TAB_R: u8 = 1;
const TAB_W: u8 = 2;
const TAB_L: u8 = 4;
const TAB_RW: u8 = TAB_R | TAB_W;
fn checktab(vm: &mut Vm, v: Value, what: u8, argn: u32, who: &str) -> Result<(), LuaError> {
if matches!(v, Value::Table(_)) {
return Ok(());
}
let reject = |vm: &mut Vm| {
let got = vm.obj_typename(v);
Err(arg_error(
vm,
argn,
who,
&format!("table expected, got {got}"),
))
};
if vm.version() <= crate::version::LuaVersion::Lua52 {
return reject(vm);
}
if vm.metatable_of(v).is_none() {
return reject(vm);
}
let strings_skip_len =
vm.version() >= crate::version::LuaVersion::Lua55 && matches!(v, Value::Str(_));
let ok = (what & TAB_R == 0 || !vm.get_mm(v, crate::vm::exec::Mm::Index).is_nil())
&& (what & TAB_W == 0 || !vm.get_mm(v, crate::vm::exec::Mm::NewIndex).is_nil())
&& (what & TAB_L == 0
|| strings_skip_len
|| !vm.get_mm(v, crate::vm::exec::Mm::Len).is_nil());
if ok { Ok(()) } else { reject(vm) }
}
fn tab_geti(vm: &mut Vm, tv: Value, i: i64) -> Result<Value, LuaError> {
if vm.version() <= crate::version::LuaVersion::Lua52 {
return Ok(match tv {
Value::Table(t) => t.get(Value::Int(i)),
_ => Value::Nil,
});
}
vm.index_value(tv, Value::Int(i))
}
fn tab_seti(vm: &mut Vm, tv: Value, i: i64, v: Value) -> Result<(), LuaError> {
if vm.version() <= crate::version::LuaVersion::Lua52 {
if let Value::Table(t) = tv {
let r = unsafe { t.as_mut() }.set(&mut vm.heap, Value::Int(i), v);
debug_assert!(r.is_ok(), "integer key is never nil/NaN");
let _ = r;
vm.barrier_back_table(t);
}
return Ok(());
}
vm.newindex_value(tv, Value::Int(i), v)
}
fn t_setn(vm: &mut Vm, _fs: u32, _nargs: u32) -> Result<u32, LuaError> {
Err(raise_str(vm, "'setn' is obsolete"))
}
fn t_getn(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let tv = vm.nat_arg(fs, nargs, 0);
check_table(vm, tv, "getn")?;
let n = vm.checked_len(tv)?;
Ok(vm.nat_return(fs, &[Value::Int(n)]))
}
fn t_foreach(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let tv = vm.nat_arg(fs, nargs, 0);
let t = check_table(vm, tv, "foreach")?;
let f = vm.nat_arg(fs, nargs, 1);
let mut key = Value::Nil;
loop {
match t
.next(key)
.map_err(|_| vm.plain_err("invalid key to 'next'"))?
{
Some((k, v)) => {
let rs = vm.call_value(f, &[k, v])?;
if let Some(r) = rs.first().copied()
&& !r.is_nil()
{
return Ok(vm.nat_return(fs, &[r]));
}
key = k;
}
None => return Ok(vm.nat_return(fs, &[Value::Nil])),
}
}
}
fn t_foreachi(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let tv = vm.nat_arg(fs, nargs, 0);
check_table(vm, tv, "foreachi")?;
let f = vm.nat_arg(fs, nargs, 1);
let n = vm.checked_len(tv)?;
for i in 1..=n {
let v = tab_geti(vm, tv, i)?;
let rs = vm.call_value(f, &[Value::Int(i), v])?;
if let Some(r) = rs.first().copied()
&& !r.is_nil()
{
return Ok(vm.nat_return(fs, &[r]));
}
}
Ok(vm.nat_return(fs, &[Value::Nil]))
}
fn t_maxn(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let tv = vm.nat_arg(fs, nargs, 0);
let t = check_table(vm, tv, "maxn")?;
let mut max: f64 = 0.0;
let mut key = Value::Nil;
loop {
let entry = t
.next(key)
.map_err(|_| vm.plain_err("invalid key to 'next'"))?;
match entry {
Some((k, _)) => {
if let Some(n) = match k {
Value::Int(i) => Some(i as f64),
Value::Float(f) => Some(f),
_ => None,
} && n > max
{
max = n;
}
key = k;
}
None => break,
}
}
Ok(vm.nat_return(fs, &[Value::Float(max)]))
}
fn t_insert(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let tv = vm.nat_arg(fs, nargs, 0);
checktab(vm, tv, TAB_RW | TAB_L, 1, "insert")?;
let n = vm.checked_len(tv)?;
let e = n.wrapping_add(1);
let (pos, v) = match nargs {
2 => (e, vm.nat_arg(fs, nargs, 1)),
3 => {
let pos = vm.int_from(vm.nat_arg(fs, nargs, 1), "use as a position")?;
if (pos as u64).wrapping_sub(1) >= e as u64 {
return Err(arg_error(vm, 2, "insert", "position out of bounds"));
}
let mut i = e;
while i > pos {
let mv = tab_geti(vm, tv, i - 1)?;
tab_seti(vm, tv, i, mv)?;
i -= 1;
}
(pos, vm.nat_arg(fs, nargs, 2))
}
_ => return Err(raise_str(vm, "wrong number of arguments to 'insert'")),
};
tab_seti(vm, tv, pos, v)?;
Ok(0)
}
fn t_remove(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let tv = vm.nat_arg(fs, nargs, 0);
checktab(vm, tv, TAB_RW | TAB_L, 1, "remove")?;
let n = vm.checked_len(tv)?;
let pos = if nargs >= 2 {
let pos = vm.int_from(vm.nat_arg(fs, nargs, 1), "use as a position")?;
if n > 0 && (pos < 1 || pos > n + 1) {
return Err(arg_error(vm, 2, "remove", "position out of bounds"));
}
pos
} else {
n
};
let removed = tab_geti(vm, tv, pos)?;
if pos <= n {
let mut i = pos;
while i < n {
let mv = tab_geti(vm, tv, i + 1)?;
tab_seti(vm, tv, i, mv)?;
i += 1;
}
tab_seti(vm, tv, n, Value::Nil)?;
}
Ok(vm.nat_return(fs, &[removed]))
}
fn t_concat(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let tv = vm.nat_arg(fs, nargs, 0);
checktab(vm, tv, TAB_R | TAB_L, 1, "concat")?;
let float_fmt = vm.float_fmt();
let sep: Vec<u8> = match vm.nat_arg(fs, nargs, 1) {
Value::Nil => Vec::new(),
Value::Str(s) => s.as_bytes().to_vec(),
Value::Int(i) => crate::numeric::num_to_string(crate::numeric::Num::Int(i)).into_bytes(),
Value::Float(f) => {
crate::numeric::num_to_string_for(crate::numeric::Num::Float(f), float_fmt).into_bytes()
}
v => {
return Err(arg_error(
vm,
2,
"concat",
&format!("string expected, got {}", v.type_name()),
));
}
};
let i = if nargs >= 3 {
vm.int_from(vm.nat_arg(fs, nargs, 2), "use as an index")?
} else {
1
};
let j = if nargs >= 4 {
vm.int_from(vm.nat_arg(fs, nargs, 3), "use as an index")?
} else {
vm.checked_len(tv)?
};
let mut out: Vec<u8> = Vec::new();
let mut k = i;
while k < j {
concat_field(vm, tv, k, &mut out, float_fmt)?;
out.extend_from_slice(&sep);
k += 1;
}
if i <= j {
concat_field(vm, tv, j, &mut out, float_fmt)?;
}
let s = Value::Str(vm.heap.intern(&out));
Ok(vm.nat_return(fs, &[s]))
}
fn concat_field(
vm: &mut Vm,
tv: Value,
k: i64,
out: &mut Vec<u8>,
float_fmt: crate::numeric::FloatFmt,
) -> Result<(), LuaError> {
match tab_geti(vm, tv, k)? {
Value::Str(s) => out.extend_from_slice(s.as_bytes()),
Value::Int(x) => {
let mut buf = [0u8; 20];
out.extend_from_slice(crate::numeric::write_i64_dec(x, &mut buf))
}
Value::Float(x) => out.extend_from_slice(
crate::numeric::num_to_string_for(crate::numeric::Num::Float(x), float_fmt).as_bytes(),
),
v => {
let tn = vm.obj_typename(v);
return Err(raise_str(
vm,
&format!("invalid value ({tn}) at index {k} in table for 'concat'"),
));
}
}
Ok(())
}
const MAX_UNPACK: i64 = 1_000_000;
pub(crate) fn t_unpack(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let tv = vm.nat_arg(fs, nargs, 0);
if vm.version() >= crate::version::LuaVersion::Lua55
|| vm.version() <= crate::version::LuaVersion::Lua52
{
checktab(vm, tv, TAB_R | TAB_L, 1, "unpack")?;
}
let i = if nargs >= 2 && !vm.nat_arg(fs, nargs, 1).is_nil() {
vm.int_from(vm.nat_arg(fs, nargs, 1), "use as an index")?
} else {
1
};
let j = if nargs >= 3 && !vm.nat_arg(fs, nargs, 2).is_nil() {
vm.int_from(vm.nat_arg(fs, nargs, 2), "use as an index")?
} else {
vm.checked_len(tv)?
};
if i > j {
return Ok(0);
}
let count = (j as i128) - (i as i128) + 1;
if count >= MAX_UNPACK as i128 || count + 1 > vm.stack_room() as i128 {
return Err(raise_str(vm, "too many results to unpack"));
}
let mut vals: Vec<Value> = Vec::with_capacity(count as usize);
for k in i..=j {
vals.push(tab_geti(vm, tv, k)?);
}
Ok(vm.nat_return(fs, &vals))
}
fn t_pack(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let t = vm.heap.new_table();
{
let tm = unsafe { t.as_mut() };
for i in 0..nargs {
let v = vm.nat_arg(fs, nargs, i);
let _ = tm.set_int(&mut vm.heap, i as i64 + 1, v);
}
}
let nk = Value::Str(vm.heap.intern(b"n"));
unsafe { t.as_mut() }
.set(&mut vm.heap, nk, Value::Int(nargs as i64))
.expect("valid key");
vm.barrier_back_table(t);
Ok(vm.nat_return(fs, &[Value::Table(t)]))
}
fn t_move(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let a1v = vm.nat_arg(fs, nargs, 0);
checktab(vm, a1v, TAB_R, 1, "move")?;
let f = vm.int_from(vm.nat_arg(fs, nargs, 1), "use as an index")?;
let e = vm.int_from(vm.nat_arg(fs, nargs, 2), "use as an index")?;
let d = vm.int_from(vm.nat_arg(fs, nargs, 3), "use as an index")?;
let a2v = if nargs >= 5 {
let v = vm.nat_arg(fs, nargs, 4);
checktab(vm, v, TAB_W, 5, "move")?;
v
} else {
a1v
};
if e >= f {
if !(f > 0 || (e as i128) < i64::MAX as i128 + f as i128) {
return Err(arg_error(vm, 3, "move", "too many elements to move"));
}
let n = e as i128 - f as i128 + 1;
if (d as i128) > i64::MAX as i128 - n + 1 {
return Err(arg_error(vm, 4, "move", "destination wrap around"));
}
if d > f && d <= e && a1v.raw_eq(a2v) {
let mut i = e;
while i >= f {
let v = tab_geti(vm, a1v, i)?;
tab_seti(vm, a2v, d + (i - f), v)?;
i -= 1;
}
} else {
for i in f..=e {
let v = tab_geti(vm, a1v, i)?;
tab_seti(vm, a2v, d + (i - f), v)?;
}
}
}
Ok(vm.nat_return(fs, &[a2v]))
}
fn t_create(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let n = vm.int_from(vm.nat_arg(fs, nargs, 0), "use as a size")?;
if !(0..=i32::MAX as i64).contains(&n) {
return Err(arg_error(vm, 1, "create", "out of range"));
}
let m = match vm.nat_arg(fs, nargs, 1) {
Value::Nil => 0,
v => vm.int_from(v, "use as a size")?,
};
if !(0..=i32::MAX as i64).contains(&m) {
return Err(arg_error(vm, 2, "create", "out of range"));
}
if m > (1 << 30) {
return Err(raise_str(vm, "table overflow"));
}
let t = vm.heap.new_table();
unsafe { t.as_mut() }.ensure_array(&mut vm.heap, n as usize);
unsafe { t.as_mut() }.ensure_hash(&mut vm.heap, m as usize);
Ok(vm.nat_return(fs, &[Value::Table(t)]))
}
fn t_sort(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let tv = vm.nat_arg(fs, nargs, 0);
checktab(vm, tv, TAB_RW | TAB_L, 1, "sort")?;
let comp = match vm.nat_arg(fs, nargs, 1) {
Value::Nil => None,
f @ (Value::Closure(_) | Value::Native(_)) => Some(f),
v => {
return Err(arg_error(
vm,
2,
"sort",
&format!("function expected, got {}", v.type_name()),
));
}
};
let n = vm.checked_len(tv)?;
if n > i64::from(u32::MAX) {
return Err(arg_error(vm, 1, "sort", "array too big"));
}
let cap = n.max(0) as usize;
vm.sort_scratch.push(Vec::with_capacity(cap));
let result = (|| -> Result<(), LuaError> {
for i in 1..=n {
let val = tab_geti(vm, tv, i)?;
vm.sort_scratch.last_mut().unwrap().push(val);
}
sort_scratch_top(vm, comp)?;
for i in 0..cap {
let val = vm.sort_scratch.last().unwrap()[i];
tab_seti(vm, tv, i as i64 + 1, val)?;
}
Ok(())
})();
vm.sort_scratch.pop();
result?;
Ok(0)
}
fn lt(vm: &mut Vm, comp: Option<Value>, a: Value, b: Value) -> Result<bool, LuaError> {
match comp {
Some(f) => Ok(vm
.call_noyield(f, &[a, b])?
.first()
.copied()
.unwrap_or(Value::Nil)
.truthy()),
None => vm.less_than(a, b, false),
}
}
#[inline]
fn scratch_get(vm: &Vm, i: usize) -> Value {
vm.sort_scratch.last().unwrap()[i]
}
#[inline]
fn scratch_swap(vm: &mut Vm, i: usize, j: usize) {
vm.sort_scratch.last_mut().unwrap().swap(i, j);
}
#[inline]
fn scratch_len(vm: &Vm) -> usize {
vm.sort_scratch.last().unwrap().len()
}
fn sort_scratch_top(vm: &mut Vm, comp: Option<Value>) -> Result<(), LuaError> {
fn quick(
vm: &mut Vm,
comp: Option<Value>,
mut lo: usize,
mut hi: usize,
) -> Result<(), LuaError> {
while lo < hi {
if hi - lo < 3 {
for i in lo + 1..=hi {
let mut j = i;
loop {
if j <= lo {
break;
}
let a = scratch_get(vm, j);
let b = scratch_get(vm, j - 1);
if !lt(vm, comp, a, b)? {
break;
}
scratch_swap(vm, j, j - 1);
j -= 1;
}
}
return Ok(());
}
let mid = lo + (hi - lo) / 2;
{
let a = scratch_get(vm, mid);
let b = scratch_get(vm, lo);
if lt(vm, comp, a, b)? {
scratch_swap(vm, mid, lo);
}
}
{
let a = scratch_get(vm, hi);
let b = scratch_get(vm, mid);
if lt(vm, comp, a, b)? {
scratch_swap(vm, hi, mid);
let c = scratch_get(vm, mid);
let d = scratch_get(vm, lo);
if lt(vm, comp, c, d)? {
scratch_swap(vm, mid, lo);
}
}
}
scratch_swap(vm, mid, hi - 1);
let pivot_idx = hi - 1;
let (mut i, mut j) = (lo, hi - 1);
loop {
i += 1;
loop {
let a = scratch_get(vm, i);
let p = scratch_get(vm, pivot_idx);
if !lt(vm, comp, a, p)? {
break;
}
if i >= hi {
return Err(raise_str(vm, "invalid order function for sorting"));
}
i += 1;
}
j -= 1;
loop {
let p = scratch_get(vm, pivot_idx);
let b = scratch_get(vm, j);
if !lt(vm, comp, p, b)? {
break;
}
if j <= lo {
return Err(raise_str(vm, "invalid order function for sorting"));
}
j -= 1;
}
if i >= j {
break;
}
scratch_swap(vm, i, j);
}
scratch_swap(vm, i, hi - 1);
if i - lo < hi - i {
if i > 0 {
quick(vm, comp, lo, i - 1)?;
}
lo = i + 1;
} else {
quick(vm, comp, i + 1, hi)?;
if i == 0 {
break;
}
hi = i - 1;
}
}
Ok(())
}
let n = scratch_len(vm);
if n > 0 {
quick(vm, comp, 0, n - 1)?;
}
Ok(())
}