use crate::lib_registry::LibraryModule;
use crate::lua_value::LuaValue;
use crate::lua_vm::{LuaResult, LuaState};
use crate::stdlib::basic::lua_float_to_string;
use crate::stdlib::sort_table::table_sort;
pub fn create_table_lib() -> LibraryModule {
crate::lib_module!("table", {
"concat" => table_concat,
"create" => table_create,
"insert" => table_insert,
"move" => table_move,
"pack" => table_pack,
"remove" => table_remove,
"sort" => table_sort,
"unpack" => table_unpack,
})
}
fn table_create(l: &mut LuaState) -> LuaResult<usize> {
let narray = l.get_arg(1).and_then(|v| v.as_integer()).unwrap_or(0);
let nhash = l.get_arg(2).and_then(|v| v.as_integer()).unwrap_or(0);
if narray < 0 {
return Err(l.error("bad argument #1 to 'create' (out of range)".to_string()));
}
if nhash < 0 {
return Err(l.error("bad argument #2 to 'create' (out of range)".to_string()));
}
if narray > i32::MAX as i64 {
return Err(l.error("bad argument #1 to 'create' (out of range)".to_string()));
}
if nhash > i32::MAX as i64 {
return Err(l.error("bad argument #2 to 'create' (out of range)".to_string()));
}
let max_safe = 1 << 24; let na = std::cmp::min(narray as usize, max_safe);
let nh = if nhash as usize > max_safe {
return Err(l.error("table overflow".to_string()));
} else {
nhash as usize
};
let table = l.create_table(na, nh)?;
l.push_value(table)?;
Ok(1)
}
fn table_concat(l: &mut LuaState) -> LuaResult<usize> {
let table_val = l
.get_arg(1)
.ok_or_else(|| l.error("bad argument #1 to 'concat' (table expected)".to_string()))?;
let sep_value = l.get_arg(2);
let sep_owned: Vec<u8>;
let sep_text_owned: String;
let (sep_bytes, sep_text): (&[u8], Option<&str>) = match &sep_value {
Some(v) => {
if v.is_nil() {
(&[], Some(""))
} else if let Some(bytes) = v.as_bytes() {
sep_owned = bytes.to_vec();
if let Some(s) = v.as_str() {
sep_text_owned = s.to_string();
(&sep_owned, Some(&sep_text_owned))
} else {
(&sep_owned, None)
}
} else {
return Err(l.error("bad argument #2 to 'concat' (string expected)".to_string()));
}
}
None => (&[], Some("")),
};
if !table_val.is_table() {
return Err(l.error("bad argument #1 to 'concat' (table expected)".to_string()));
}
let has_meta = table_val
.as_table_mut()
.map(|t| t.has_metatable())
.unwrap_or(true);
let i = l.get_arg(3).and_then(|v| v.as_integer()).unwrap_or(1);
let j = match l.get_arg(4).and_then(|v| v.as_integer()) {
Some(j) => j,
None => {
if has_meta {
l.obj_len(&table_val)?
} else {
table_val.as_table_mut().unwrap().len() as i64
}
}
};
if i > j {
let result = l.create_string("")?;
l.push_value(result)?;
return Ok(1);
}
let count = (j - i + 1) as usize;
if !has_meta {
let table = table_val.as_table_mut().unwrap();
let sep_total = sep_bytes.len().saturating_mul(count.saturating_sub(1));
let mut total_len: usize = sep_total;
let mut needs_bytes = sep_text.is_none();
let mut has_numbers = false;
for idx in i..=j {
let value = table.raw_geti(idx).unwrap_or(LuaValue::nil());
if let Some(bytes) = value.as_bytes() {
total_len += bytes.len();
needs_bytes |= value.as_str().is_none();
} else if value.as_integer().is_some() {
total_len += 20; has_numbers = true;
} else if value.as_number().is_some() {
total_len += 24; has_numbers = true;
} else {
let msg = format!("invalid value (at index {}) in table for 'concat'", idx);
return Err(l.error(msg));
}
}
let table = table_val.as_table_mut().unwrap();
if needs_bytes {
let mut buf: Vec<u8> = Vec::with_capacity(total_len);
for idx in i..=j {
if idx > i {
buf.extend_from_slice(sep_bytes);
}
let value = table.raw_geti(idx).unwrap_or(LuaValue::nil());
if let Some(bytes) = value.as_bytes() {
buf.extend_from_slice(bytes);
} else if let Some(ival) = value.as_integer() {
let mut itoa_buf = itoa::Buffer::new();
buf.extend_from_slice(itoa_buf.format(ival).as_bytes());
} else if let Some(f) = value.as_number() {
buf.extend_from_slice(lua_float_to_string(f).as_bytes());
}
}
let result = l.create_bytes(&buf)?;
l.push_value(result)?;
} else if !has_numbers {
let mut result = String::with_capacity(total_len);
let sep = sep_text.unwrap_or("");
for idx in i..=j {
if idx > i {
result.push_str(sep);
}
let value = table.raw_geti(idx).unwrap_or(LuaValue::nil());
result.push_str(unsafe { value.as_str().unwrap_unchecked() });
}
let result = l.create_string_owned(result)?;
l.push_value(result)?;
} else {
let mut result = String::with_capacity(total_len);
let sep = sep_text.unwrap_or("");
for idx in i..=j {
if idx > i {
result.push_str(sep);
}
let value = table.raw_geti(idx).unwrap_or(LuaValue::nil());
if let Some(s) = value.as_str() {
result.push_str(s);
} else if let Some(ival) = value.as_integer() {
let mut itoa_buf = itoa::Buffer::new();
result.push_str(itoa_buf.format(ival));
} else if let Some(f) = value.as_number() {
result.push_str(&lua_float_to_string(f));
}
}
let result = l.create_string_owned(result)?;
l.push_value(result)?;
}
} else {
let mut total_len: usize = sep_bytes.len().saturating_mul(count.saturating_sub(1));
let mut needs_bytes = sep_text.is_none();
for idx in i..=j {
let value = l.table_geti(&table_val, idx)?;
if let Some(bytes) = value.as_bytes() {
total_len += bytes.len();
needs_bytes |= value.as_str().is_none();
} else if value.as_integer().is_some() {
total_len += 20;
} else if value.as_number().is_some() {
total_len += 24;
} else {
let msg = format!("invalid value (at index {}) in table for 'concat'", idx);
return Err(l.error(msg));
}
}
if needs_bytes {
let mut buf: Vec<u8> = Vec::with_capacity(total_len);
for idx in i..=j {
if idx > i {
buf.extend_from_slice(sep_bytes);
}
let value = l.table_geti(&table_val, idx)?;
if let Some(bytes) = value.as_bytes() {
buf.extend_from_slice(bytes);
} else if let Some(ival) = value.as_integer() {
let mut itoa_buf = itoa::Buffer::new();
buf.extend_from_slice(itoa_buf.format(ival).as_bytes());
} else if let Some(f) = value.as_number() {
buf.extend_from_slice(lua_float_to_string(f).as_bytes());
}
}
let result = l.create_bytes(&buf)?;
l.push_value(result)?;
} else {
let mut result = String::with_capacity(total_len);
let sep = sep_text.unwrap_or("");
for idx in i..=j {
if idx > i {
result.push_str(sep);
}
let value = l.table_geti(&table_val, idx)?;
if let Some(s) = value.as_str() {
result.push_str(s);
} else if let Some(ival) = value.as_integer() {
let mut itoa_buf = itoa::Buffer::new();
result.push_str(itoa_buf.format(ival));
} else if let Some(f) = value.as_number() {
result.push_str(&lua_float_to_string(f));
}
}
let result = l.create_string_owned(result)?;
l.push_value(result)?;
}
}
Ok(1)
}
fn table_insert(l: &mut LuaState) -> LuaResult<usize> {
let table_val = l
.get_arg(1)
.ok_or_else(|| l.error("bad argument #1 to 'insert' (table expected)".to_string()))?;
let argc = l.arg_count();
if !table_val.is_table() {
return Err(l.error("bad argument #1 to 'insert' (table expected)".to_string()));
}
let has_meta = table_val
.as_table_mut()
.map(|t| t.has_metatable())
.unwrap_or(true);
if argc == 2 {
let value = l
.get_arg(2)
.ok_or_else(|| l.error("bad argument #2 to 'insert' (value expected)".to_string()))?;
if has_meta {
let len = l.obj_len(&table_val)?;
l.table_seti(&table_val, len.wrapping_add(1), value)?;
} else {
let table = table_val.as_table_mut().unwrap();
let len = table.len() as i64;
let delta = table.raw_seti(len + 1, value);
if delta != 0
&& let Some(table_ptr) = table_val.as_table_ptr()
{
l.gc_track_table_resize(table_ptr, delta);
}
if value.iscollectable()
&& let Some(gc_ptr) = table_val.as_gc_ptr()
{
l.gc_barrier_back(gc_ptr);
}
}
} else if argc == 3 {
let pos = l
.get_arg(2)
.ok_or_else(|| l.error("bad argument #2 to 'insert' (number expected)".to_string()))?
.as_integer()
.ok_or_else(|| l.error("bad argument #2 to 'insert' (number expected)".to_string()))?;
let value = l
.get_arg(3)
.ok_or_else(|| l.error("bad argument #3 to 'insert' (value expected)".to_string()))?;
if has_meta {
let len = l.obj_len(&table_val)?;
if pos < 1 || pos > len + 1 {
return Err(
l.error("bad argument #2 to 'insert' (position out of bounds)".to_string())
);
}
let mut i = len;
while i >= pos {
let val = l.table_geti(&table_val, i)?;
l.table_seti(&table_val, i + 1, val)?;
i -= 1;
}
l.table_seti(&table_val, pos, value)?;
} else {
let table = table_val.as_table_mut().unwrap();
let len = table.len() as i64;
if pos < 1 || pos > len + 1 {
return Err(
l.error("bad argument #2 to 'insert' (position out of bounds)".to_string())
);
}
let mut total_delta: isize = 0;
let mut i = len;
while i >= pos {
let val = table.raw_geti(i).unwrap_or(LuaValue::nil());
total_delta += table.raw_seti(i + 1, val);
i -= 1;
}
total_delta += table.raw_seti(pos, value);
if total_delta != 0
&& let Some(table_ptr) = table_val.as_table_ptr()
{
l.gc_track_table_resize(table_ptr, total_delta);
}
if value.iscollectable()
&& let Some(gc_ptr) = table_val.as_gc_ptr()
{
l.gc_barrier_back(gc_ptr);
}
}
} else {
return Err(l.error("wrong number of arguments to 'insert'".to_string()));
}
Ok(0)
}
fn table_remove(l: &mut LuaState) -> LuaResult<usize> {
let table_val = l
.get_arg(1)
.ok_or_else(|| l.error("bad argument #1 to 'remove' (table expected)".to_string()))?;
if !table_val.is_table() {
return Err(l.error("bad argument #1 to 'remove' (table expected)".to_string()));
}
let has_meta = table_val
.as_table_mut()
.map(|t| t.has_metatable())
.unwrap_or(true);
let has_pos_arg = l.get_arg(2).is_some();
if has_meta {
let len = l.obj_len(&table_val)?;
let pos = l.get_arg(2).and_then(|v| v.as_integer()).unwrap_or(len);
if has_pos_arg && pos != len && (pos < 1 || pos > len.wrapping_add(1)) {
return Err(l.error("bad argument #2 to 'remove' (position out of bounds)".to_string()));
}
let removed = l.table_geti(&table_val, pos)?;
let mut i = pos;
while i < len {
let next_val = l.table_geti(&table_val, i.wrapping_add(1))?;
l.table_seti(&table_val, i, next_val)?;
i += 1;
}
l.table_seti(&table_val, i, LuaValue::nil())?;
l.push_value(removed)?;
} else {
let table = table_val.as_table_mut().unwrap();
let len = table.len() as i64;
let pos = l.get_arg(2).and_then(|v| v.as_integer()).unwrap_or(len);
if has_pos_arg && pos != len && (pos < 1 || pos > len.wrapping_add(1)) {
return Err(l.error("bad argument #2 to 'remove' (position out of bounds)".to_string()));
}
let removed = table.raw_geti(pos).unwrap_or(LuaValue::nil());
let mut i = pos;
while i < len {
let next_val = table.raw_geti(i + 1).unwrap_or(LuaValue::nil());
table.raw_seti(i, next_val);
i += 1;
}
table.raw_seti(i, LuaValue::nil());
l.push_value(removed)?;
}
Ok(1)
}
fn table_move(l: &mut LuaState) -> LuaResult<usize> {
let src_val = l
.get_arg(1)
.ok_or_else(|| l.error("bad argument #1 to 'move' (table expected)".to_string()))?;
let f = l
.get_arg(2)
.ok_or_else(|| l.error("bad argument #2 to 'move' (number expected)".to_string()))?
.as_integer()
.ok_or_else(|| l.error("bad argument #2 to 'move' (number expected)".to_string()))?;
let e = l
.get_arg(3)
.ok_or_else(|| l.error("bad argument #3 to 'move' (number expected)".to_string()))?
.as_integer()
.ok_or_else(|| l.error("bad argument #3 to 'move' (number expected)".to_string()))?;
let t = l
.get_arg(4)
.ok_or_else(|| l.error("bad argument #4 to 'move' (number expected)".to_string()))?
.as_integer()
.ok_or_else(|| l.error("bad argument #4 to 'move' (number expected)".to_string()))?;
let dst_value = l.get_arg(5).unwrap_or(src_val);
if !src_val.is_table() {
return Err(l.error("bad argument #1 to 'move' (table expected)".to_string()));
}
if !dst_value.is_table() {
return Err(l.error("bad argument #5 to 'move' (table expected)".to_string()));
}
if e >= f {
let n = (e as u64).wrapping_sub(f as u64);
if n >= i64::MAX as u64 {
return Err(l.error("too many elements to move".to_string()));
}
let n = n + 1; let limit = i64::MAX - (n as i64) + 1;
if t > limit {
return Err(l.error("destination wrap around".to_string()));
}
}
if f <= e {
let use_raw = src_val.as_table().is_some_and(|t| !t.has_metatable())
&& dst_value.as_table().is_some_and(|t| !t.has_metatable());
if use_raw {
if t > f && t <= e && src_val.as_gc_ptr() == dst_value.as_gc_ptr() {
for i in (0..=(e - f)).rev() {
let val = l.raw_geti(&src_val, f.wrapping_add(i)).unwrap_or_default();
l.raw_seti(&dst_value, t.wrapping_add(i), val);
}
} else {
for i in 0..=(e - f) {
let val = l.raw_geti(&src_val, f.wrapping_add(i)).unwrap_or_default();
l.raw_seti(&dst_value, t.wrapping_add(i), val);
}
}
} else if t > f && t <= e {
for i in (0..=(e - f)).rev() {
let key = LuaValue::integer(f.wrapping_add(i));
let val = l.table_get(&src_val, &key)?.unwrap_or(LuaValue::nil());
let dst_key = LuaValue::integer(t.wrapping_add(i));
l.table_set(&dst_value, dst_key, val)?;
}
} else {
for i in 0..=(e - f) {
let key = LuaValue::integer(f.wrapping_add(i));
let val = l.table_get(&src_val, &key)?.unwrap_or(LuaValue::nil());
let dst_key = LuaValue::integer(t.wrapping_add(i));
l.table_set(&dst_value, dst_key, val)?;
}
}
}
l.push_value(dst_value)?;
Ok(1)
}
fn table_pack(l: &mut LuaState) -> LuaResult<usize> {
let n = l.arg_count();
let table = l.create_table(n, 1)?;
let n_key = l.create_string("n")?;
let table_mut = table
.as_table_mut()
.expect("create_table must return a table");
let impl_table = &mut table_mut.impl_table;
let mut has_collectable = false;
let frame_base = l.call_stack[l.call_depth() - 1].base;
for i in 0..n {
let stack_idx = frame_base + i;
let arg = if stack_idx < l.stack.len() {
l.stack[stack_idx]
} else {
LuaValue::nil()
};
if arg.iscollectable() {
has_collectable = true;
}
unsafe {
impl_table.write_array((i + 1) as i64, arg);
}
}
table_mut.raw_set(&n_key, LuaValue::integer(n as i64));
if has_collectable && let Some(gc_ptr) = table.as_gc_ptr() {
l.gc_barrier_back(gc_ptr);
}
l.push_value(table)?;
Ok(1)
}
fn table_unpack(l: &mut LuaState) -> LuaResult<usize> {
let table_val = l
.get_arg(1)
.ok_or_else(|| l.error("bad argument #1 to 'unpack' (table expected)".to_string()))?;
if !table_val.is_table() {
return Err(l.error("bad argument #1 to 'unpack' (table expected)".to_string()));
}
let has_meta = table_val
.as_table_mut()
.map(|t| t.has_metatable())
.unwrap_or(true);
let i = l.get_arg(2).and_then(|v| v.as_integer()).unwrap_or(1);
let j = match l.get_arg(3).and_then(|v| v.as_integer()) {
Some(j) => j,
None => {
if has_meta {
l.obj_len(&table_val)?
} else {
table_val.as_table_mut().unwrap().len() as i64
}
}
};
if i > j {
return Ok(0);
}
let n = (j as u64).wrapping_sub(i as u64); if n >= i32::MAX as u64 || !l.check_stack((n as usize).wrapping_add(1)) {
return Err(l.error("too many results to unpack".to_string()));
}
let count = (n + 1) as usize;
l.ensure_stack_capacity(count)?;
if !has_meta {
let table = table_val.as_table_mut().unwrap();
for idx in i..=j {
let val = table.raw_geti(idx).unwrap_or(LuaValue::nil());
l.push_value(val)?;
}
} else {
for idx in i..=j {
let val = l.table_geti(&table_val, idx)?;
l.push_value(val)?;
}
}
Ok(count)
}