Add stack pack and unpack methods

This commit is contained in:
lumi 2025-06-23 11:40:20 +10:00
parent e801ee468b
commit 173149f2f7
Signed by: luaneko
GPG Key ID: 406809B8763FF07A

View File

@ -851,6 +851,85 @@ impl Stack {
unsafe { lua_rawseti(self.as_ptr(), table.index(), n) }
}
/// Packs the array-part of the table at index `idx` from the stack.
///
/// This pops `n` values at the top of the stack, sets them as the fields of the table at index
/// `idx` at indices `1` to `n` inclusive, then sets the field `"n"` to `n`. Any existing values
/// in the table are overwritten. If the table already has more than `n` values in its
/// array-part, these values are **not** cleared. The number of values popped is returned, which
/// is always equal to `n`.
///
/// This method does not invoke any metamethods.
///
/// Equivalent to `table.pack(...)`.
///
/// # Panic
///
/// Panics if `n` is negative, there are not enough values on the stack, or the value at index
/// `idx` is not a table.
pub fn pack(&mut self, idx: c_int, n: c_int) -> c_int {
assert!(n >= 0, "n must be nonnegative");
let size = self.size();
let table = self.slot(idx);
assert!(
table.type_of() == Type::Table,
"expected table at index {idx}: {self:?}"
);
assert!(n <= size, "expected {n} values: {self:?}");
assert!(idx <= size - n, "cannot pack a table into itself: {self:?}");
unsafe {
(0..n).for_each(|i| lua_rawseti(self.as_ptr(), table.index(), n - i));
self.ensure(2);
lua_pushliteral(self.as_ptr(), "n");
lua_pushinteger(self.as_ptr(), n as lua_Integer);
lua_rawset(self.as_ptr(), table.index());
n
}
}
/// Unpacks the array-part of the table at index `idx` onto the stack.
///
/// If `j` is [`None`], then it is set to be the value of the field `"n"` interpreted as an
/// integer. If this field does not exist, then it is set to the be length of the table as
/// defined by the Lua `#` length operator. All values in indices `i` to `j` inclusive are
/// pushed at the top of the stack in ascending order. If `i > j`, then nothing is pushed.
/// Otherwise, `j - i + 1` values are pushed, and the number of values pushed is returned.
///
/// This method does not invoke any metamethods.
///
/// Equivalent to `table.unpack(list, i, j)`.
///
/// # Panic
///
/// Panics if the value at index `idx` is not a table.
pub fn unpack(&mut self, idx: c_int, i: c_int, j: Option<c_int>) -> c_int {
let table = self.slot(idx);
assert!(
table.type_of() == Type::Table,
"expected table at index {idx}: {self:?}"
);
let j = match j {
Some(j) => j,
None => unsafe {
self.ensure(1);
lua_pushliteral(self.as_ptr(), "n");
lua_rawget(self.as_ptr(), table.index());
let mut isnum = 0;
let n = lua_tointegerx(self.as_ptr(), -1, &raw mut isnum);
lua_pop(self.as_ptr(), 1);
(isnum != 0)
.then_some(n as c_int)
.unwrap_or_else(|| lua_objlen(self.as_ptr(), table.index()) as c_int)
},
};
let n = (j - i + 1).max(0);
if n > 0 {
self.ensure(n);
(0..n).for_each(|n| unsafe { lua_rawgeti(self.as_ptr(), table.index(), i + n) });
}
n
}
/// Pushes the given chunk as a function at the top of the stack.
///
/// Equivalent to [`lua_loadx`].
@ -1144,7 +1223,7 @@ impl Stack {
///
/// Equivalent to [`luaL_traceback`].
pub fn backtrace(&self, level: c_int) -> Option<BString> {
assert!(level >= 0, "backtrace level must be nonnegative");
assert!(level >= 0, "level must be nonnegative");
self.ensure(LUA_MINSTACK);
unsafe {
luaL_traceback(self.as_ptr(), self.as_ptr(), ptr::null(), 0);
@ -1590,8 +1669,8 @@ impl NewTable {
impl Push for NewTable {
fn push(&self, stack: &mut Stack) {
let Self { narr, nrec } = *self;
assert!(0 <= narr, "size of table array part must be nonnegative");
assert!(0 <= nrec, "size of table hash part must be nonnegative");
assert!(0 <= narr, "narr must be nonnegative");
assert!(0 <= nrec, "nrec must be nonnegative");
stack.ensure(1);
unsafe { lua_createtable(stack.as_ptr(), narr, nrec) }
}