lua_stdlib/table_lib.rs
1//! Rust port of `ltablib.c` — Lua `table` standard library.
2//!
3//! Provides: `table.concat`, `table.insert`, `table.move`, `table.pack`,
4//! `table.remove`, `table.sort`, `table.unpack`.
5//!
6//! C source: `reference/lua-5.4.7/src/ltablib.c` (430 lines, 14 functions)
7
8use lua_types::{GcRef, LuaError, LuaTable, LuaType, LuaValue};
9use crate::state_stub::{LuaState, LuaStateStubExt as _, CompareOp};
10use lua_vm::state::LuaTableRefExt as _;
11
12// ─── Operation flags ──────────────────────────────────────────────────────────
13const TAB_R: u32 = 1;
14const TAB_W: u32 = 2;
15const TAB_L: u32 = 4;
16const TAB_RW: u32 = TAB_R | TAB_W;
17
18const RANLIMIT: u32 = 100;
19
20type IdxT = u32;
21
22// ─── Internal helpers ─────────────────────────────────────────────────────────
23
24/// currently sitting at stack depth `n`; returns `true` if the result is not nil.
25///
26/// ```c
27/// static int checkfield (lua_State *L, const char *key, int n) {
28/// lua_pushstring(L, key);
29/// return (lua_rawget(L, -n) != LUA_TNIL);
30/// }
31/// ```
32fn check_field(state: &mut LuaState, key: &[u8], n: i32) -> Result<bool, LuaError> {
33 // TODO(port): state.push_string pushes a Lua string from &[u8]; verify method name
34 state.push_string(key)?;
35 // raw_get(-n): looks up MT[key] (MT is at -n after the key push), replaces key with value
36 let ty = state.raw_get(-n)?;
37 Ok(ty != LuaType::Nil)
38}
39
40/// metatable with the metamethods required by `what`
41/// (`TAB_R` → `__index`, `TAB_W` → `__newindex`, `TAB_L` → `__len`).
42///
43/// ```c
44/// static void checktab (lua_State *L, int arg, int what) {
45/// if (lua_type(L, arg) != LUA_TTABLE) {
46/// int n = 1;
47/// if (lua_getmetatable(L, arg) &&
48/// (!(what & TAB_R) || checkfield(L, "__index", ++n)) &&
49/// (!(what & TAB_W) || checkfield(L, "__newindex", ++n)) &&
50/// (!(what & TAB_L) || checkfield(L, "__len", ++n))) {
51/// lua_pop(L, n);
52/// }
53/// else
54/// luaL_checktype(L, arg, LUA_TTABLE);
55/// }
56/// }
57/// ```
58///
59/// PORT NOTE: stack cleanup on the error path is elided here (in C, `longjmp`
60/// unwinds automatically). Phase B should add cleanup before the `check_arg_type`
61/// call to leave the stack consistent.
62fn check_tab(state: &mut LuaState, arg: i32, what: u32) -> Result<(), LuaError> {
63 if state.type_at(arg) == LuaType::Table {
64 return Ok(());
65 }
66 // `n` tracks how many items have been pushed (MT + checked field values).
67 let mut n: i32 = 1;
68 // TODO(port): state.get_metatable returns bool (pushes MT if found); verify method name
69 let has_mt = state.get_metatable(arg)?;
70 let mut ok = has_mt;
71
72 // Short-circuit: each field is only checked if all previous checks passed.
73 if ok && (what & TAB_R) != 0 {
74 n += 1;
75 ok = check_field(state, b"__index", n)?;
76 }
77 if ok && (what & TAB_W) != 0 {
78 n += 1;
79 ok = check_field(state, b"__newindex", n)?;
80 }
81 if ok && (what & TAB_L) != 0 {
82 n += 1;
83 ok = check_field(state, b"__len", n)?;
84 }
85
86 if ok {
87 state.pop_n(n as usize);
88 Ok(())
89 } else {
90 state.check_arg_type(arg, LuaType::Table)
91 }
92}
93
94///
95/// Check that argument `n` is a table (or table-like per `w`) and return its length.
96fn aux_getn(state: &mut LuaState, n: i32, w: u32) -> Result<i64, LuaError> {
97 check_tab(state, n, w | TAB_L)?;
98 // TODO(port): state.length_at applies the `#` operator and returns i64; verify method name
99 state.length_at(n)
100}
101
102#[inline]
103fn plain_table_at(state: &mut LuaState, idx: i32) -> Option<GcRef<LuaTable>> {
104 match state.value_at(idx) {
105 LuaValue::Table(tbl) if tbl.metatable().is_none() => Some(tbl),
106 _ => None,
107 }
108}
109
110#[inline]
111fn raw_set_int(
112 state: &mut LuaState,
113 tbl: GcRef<LuaTable>,
114 key: i64,
115 value: LuaValue,
116) -> Result<(), LuaError> {
117 let parent = LuaValue::Table(tbl);
118 state.gc_barrier_back(&parent, &value);
119 tbl.raw_set_int(state, key, value)
120}
121
122// ─── table.insert ─────────────────────────────────────────────────────────────
123
124///
125/// ```c
126/// static int tinsert (lua_State *L) {
127/// lua_Integer pos;
128/// lua_Integer e = aux_getn(L, 1, TAB_RW);
129/// e = luaL_intop(+, e, 1);
130/// switch (lua_gettop(L)) {
131/// case 2: { pos = e; break; }
132/// case 3: {
133/// lua_Integer i;
134/// pos = luaL_checkinteger(L, 2);
135/// luaL_argcheck(L, (lua_Unsigned)pos - 1u < (lua_Unsigned)e, 2,
136/// "position out of bounds");
137/// for (i = e; i > pos; i--) {
138/// lua_geti(L, 1, i - 1);
139/// lua_seti(L, 1, i);
140/// }
141/// break;
142/// }
143/// default:
144/// return luaL_error(L, "wrong number of arguments to 'insert'");
145/// }
146/// lua_seti(L, 1, pos);
147/// return 0;
148/// }
149/// ```
150pub fn insert(state: &mut LuaState) -> Result<usize, LuaError> {
151 let mut e = aux_getn(state, 1, TAB_RW)?;
152 e = (e as u64).wrapping_add(1) as i64;
153 let plain_table = plain_table_at(state, 1);
154
155 let pos: i64 = match state.get_top() {
156 2 => {
157 if let Some(tbl) = plain_table {
158 let value = state.value_at(2);
159 raw_set_int(state, tbl, e, value)?;
160 state.pop_n(1);
161 return Ok(0);
162 }
163 e
164 }
165 3 => {
166 let pos = state.check_arg_integer(2)?;
167 // Checks 1 <= pos <= e (wrapping subtraction catches pos <= 0)
168 if !((pos as u64).wrapping_sub(1) < (e as u64)) {
169 return Err(lua_vm::debug::arg_error_impl(state, 2, b"position out of bounds"));
170 }
171 if let Some(tbl) = plain_table {
172 let value = state.value_at(3);
173 let mut i = e;
174 while i > pos {
175 let shifted = tbl.get_int(i - 1);
176 raw_set_int(state, tbl, i, shifted)?;
177 i -= 1;
178 }
179 raw_set_int(state, tbl, pos, value)?;
180 state.pop_n(1);
181 return Ok(0);
182 }
183 // Cache the table once to avoid re-resolving stack slot 1 on every
184 // iteration of the shift loop. C's lua_geti is a single pointer
185 // arithmetic operation; our index_to_value is a function call with
186 // branches, so this saves ~2N index resolutions for shift count N.
187 let tbl = state.value_at(1);
188 let mut i = e;
189 while i > pos {
190 state.table_get_i_value(&tbl, i - 1)?;
191 state.table_set_i_value(&tbl, i)?;
192 i -= 1;
193 }
194 pos
195 }
196 _ => {
197 return Err(LuaError::runtime(format_args!(
198 "wrong number of arguments to 'insert'"
199 )));
200 }
201 };
202 state.table_set_i(1, pos)?;
203 Ok(0)
204}
205
206// ─── table.remove ─────────────────────────────────────────────────────────────
207
208///
209/// ```c
210/// static int tremove (lua_State *L) {
211/// lua_Integer size = aux_getn(L, 1, TAB_RW);
212/// lua_Integer pos = luaL_optinteger(L, 2, size);
213/// if (pos != size)
214/// luaL_argcheck(L, (lua_Unsigned)pos - 1u <= (lua_Unsigned)size, 2,
215/// "position out of bounds");
216/// lua_geti(L, 1, pos);
217/// for ( ; pos < size; pos++) {
218/// lua_geti(L, 1, pos + 1);
219/// lua_seti(L, 1, pos);
220/// }
221/// lua_pushnil(L);
222/// lua_seti(L, 1, pos);
223/// return 1;
224/// }
225/// ```
226pub fn remove(state: &mut LuaState) -> Result<usize, LuaError> {
227 let size = aux_getn(state, 1, TAB_RW)?;
228 let mut pos = state.opt_arg_integer(2, size)?;
229 if pos != size {
230 if !((pos as u64).wrapping_sub(1) <= (size as u64)) {
231 let argn = if state.global().lua_version == lua_types::LuaVersion::V53 { 1 } else { 2 };
232 return Err(lua_vm::debug::arg_error_impl(state, argn, b"position out of bounds"));
233 }
234 }
235 // Cache the table once to avoid re-resolving stack slot 1 on every
236 // iteration of the shift loop. C's lua_geti is a single pointer
237 // arithmetic operation; our index_to_value is a function call with
238 // branches, so this saves ~2N index resolutions for shift count N.
239 if let Some(tbl) = plain_table_at(state, 1) {
240 let result = tbl.get_int(pos);
241 state.push(result);
242 while pos < size {
243 let shifted = tbl.get_int(pos + 1);
244 raw_set_int(state, tbl, pos, shifted)?;
245 pos += 1;
246 }
247 raw_set_int(state, tbl, pos, LuaValue::Nil)?;
248 return Ok(1);
249 }
250 let tbl = state.value_at(1);
251 state.table_get_i_value(&tbl, pos)?; // push element to be returned
252 while pos < size {
253 state.table_get_i_value(&tbl, pos + 1)?;
254 state.table_set_i_value(&tbl, pos)?;
255 pos += 1;
256 }
257 state.push(LuaValue::Nil);
258 state.table_set_i_value(&tbl, pos)?; // remove last slot (table[pos] = nil)
259 Ok(1)
260}
261
262// ─── table.move ───────────────────────────────────────────────────────────────
263
264///
265/// Copies elements `a1[f..e]` into `a2[t..]` (or `a1[t..]` if `a2` is absent).
266/// Copies in increasing order when safe, decreasing when ranges overlap.
267///
268/// ```c
269/// static int tmove (lua_State *L) {
270/// lua_Integer f = luaL_checkinteger(L, 2);
271/// lua_Integer e = luaL_checkinteger(L, 3);
272/// lua_Integer t = luaL_checkinteger(L, 4);
273/// int tt = !lua_isnoneornil(L, 5) ? 5 : 1;
274/// checktab(L, 1, TAB_R);
275/// checktab(L, tt, TAB_W);
276/// if (e >= f) {
277/// lua_Integer n, i;
278/// luaL_argcheck(L, f > 0 || e < LUA_MAXINTEGER + f, 3, "too many elements to move");
279/// n = e - f + 1;
280/// luaL_argcheck(L, t <= LUA_MAXINTEGER - n + 1, 4, "destination wrap around");
281/// if (t > e || t <= f || (tt != 1 && !lua_compare(L, 1, tt, LUA_OPEQ))) {
282/// for (i = 0; i < n; i++) { lua_geti(L, 1, f + i); lua_seti(L, tt, t + i); }
283/// } else {
284/// for (i = n - 1; i >= 0; i--) { lua_geti(L, 1, f + i); lua_seti(L, tt, t + i); }
285/// }
286/// }
287/// lua_pushvalue(L, tt);
288/// return 1;
289/// }
290/// ```
291pub fn tmove(state: &mut LuaState) -> Result<usize, LuaError> {
292 let f = state.check_arg_integer(2)?;
293 let e = state.check_arg_integer(3)?;
294 let t = state.check_arg_integer(4)?;
295 let tt: i32 = if !matches!(state.type_at(5), LuaType::None | LuaType::Nil) {
296 5
297 } else {
298 1
299 };
300 check_tab(state, 1, TAB_R)?;
301 check_tab(state, tt, TAB_W)?;
302
303 if e >= f {
304 if !(f > 0 || e < i64::MAX + f) {
305 return Err(lua_vm::debug::arg_error_impl(state, 3, b"too many elements to move"));
306 }
307 let n = e - f + 1;
308 if !(t <= i64::MAX - n + 1) {
309 return Err(lua_vm::debug::arg_error_impl(state, 4, b"destination wrap around"));
310 }
311 // Copy forward (increasing) when safe to do so; backward when ranges overlap.
312 // TODO(port): state.compare(a, b, CompareOp::Eq) → lua_compare LUA_OPEQ; verify method
313 let copy_forward = t > e
314 || t <= f
315 || (tt != 1 && !state.compare(1, tt, CompareOp::Eq)?);
316 if copy_forward {
317 for i in 0..n {
318 state.table_get_i(1, f + i)?;
319 state.table_set_i(tt, t + i)?;
320 }
321 } else {
322 for i in (0..n).rev() {
323 state.table_get_i(1, f + i)?;
324 state.table_set_i(tt, t + i)?;
325 }
326 }
327 }
328 // TODO(port): state.push_value_at → lua_pushvalue; verify method name
329 state.push_value_at(tt)?;
330 Ok(1)
331}
332
333// ─── table.concat ─────────────────────────────────────────────────────────────
334
335/// a string-or-number, add its string representation to `buf`, then pop it.
336///
337/// ```c
338/// static void addfield (lua_State *L, luaL_Buffer *b, lua_Integer i) {
339/// lua_geti(L, 1, i);
340/// if (l_unlikely(!lua_isstring(L, -1)))
341/// luaL_error(L, "invalid value (%s) at index %I in table for 'concat'",
342/// luaL_typename(L, -1), (LUAI_UACINT)i);
343/// luaL_addvalue(b);
344/// }
345/// ```
346///
347/// PORT NOTE: `luaL_Buffer` in C accumulates bytes and then pushes the result;
348/// Rust uses a `Vec<u8>` accumulator passed by mutable reference instead.
349fn add_field(state: &mut LuaState, buf: &mut Vec<u8>, idx: i64) -> Result<(), LuaError> {
350 state.table_get_i(1, idx)?;
351 if !matches!(state.type_at(-1), LuaType::String | LuaType::Number) {
352 let type_name = state.type_name_str_at(-1);
353 let msg = format!(
354 "invalid value ({}) at index {} in table for 'concat'",
355 String::from_utf8_lossy(type_name),
356 idx
357 );
358 return crate::auxlib::lua_error(state, msg.as_bytes()).map(|_| ());
359 }
360 // TODO(port): state.to_bytes_at(-1) converts via Lua's tostring coercion; verify method name
361 let bytes = state.to_bytes_at(-1).ok_or_else(|| LuaError::runtime(format_args!("invalid value at index {}", idx)))?;
362 buf.extend_from_slice(&bytes);
363 state.pop_n(1);
364 Ok(())
365}
366
367///
368/// ```c
369/// static int tconcat (lua_State *L) {
370/// luaL_Buffer b;
371/// lua_Integer last = aux_getn(L, 1, TAB_R);
372/// size_t lsep;
373/// const char *sep = luaL_optlstring(L, 2, "", &lsep);
374/// lua_Integer i = luaL_optinteger(L, 3, 1);
375/// last = luaL_optinteger(L, 4, last);
376/// luaL_buffinit(L, &b);
377/// for (; i < last; i++) { addfield(L, &b, i); luaL_addlstring(&b, sep, lsep); }
378/// if (i == last) addfield(L, &b, i);
379/// luaL_pushresult(&b);
380/// return 1;
381/// }
382/// ```
383pub fn concat(state: &mut LuaState) -> Result<usize, LuaError> {
384 let last = aux_getn(state, 1, TAB_R)?;
385 // TODO(port): state.opt_arg_lstring(n, default) → luaL_optlstring; verify method name
386 // Clone the separator before any stack-mutating calls that might invalidate it.
387 let sep: Vec<u8> = state.opt_arg_lstring(2, Some(b""))?.unwrap_or_default();
388 let mut i = state.opt_arg_integer(3, 1)?;
389 let last = state.opt_arg_integer(4, last)?;
390
391 // PORT NOTE: C uses luaL_Buffer (which may back-patch the stack);
392 // Rust uses a plain Vec<u8> accumulator and pushes the result at the end.
393 let mut buf: Vec<u8> = Vec::new();
394 while i < last {
395 add_field(state, &mut buf, i)?;
396 buf.extend_from_slice(&sep);
397 i += 1;
398 }
399 if i == last {
400 add_field(state, &mut buf, i)?;
401 }
402 // TODO(port): state.push_lstring pushes a Lua string from &[u8]; verify method name
403 state.push_lstring(&buf)?;
404 Ok(1)
405}
406
407// ─── table.pack / table.unpack ────────────────────────────────────────────────
408
409///
410/// Creates a new table `t` with all arguments as integer keys and `t.n` set
411/// to the argument count.
412///
413/// ```c
414/// static int tpack (lua_State *L) {
415/// int i;
416/// int n = lua_gettop(L);
417/// lua_createtable(L, n, 1);
418/// lua_insert(L, 1);
419/// for (i = n; i >= 1; i--)
420/// lua_seti(L, 1, i);
421/// lua_pushinteger(L, n);
422/// lua_setfield(L, 1, "n");
423/// return 1;
424/// }
425/// ```
426pub fn pack(state: &mut LuaState) -> Result<usize, LuaError> {
427 let n = state.get_top();
428 // TODO(port): state.create_table(narr, nrec) → lua_createtable; verify method name
429 state.create_table(n, 1)?;
430 state.insert(1)?;
431 // table_set_i pops the top; args shift from n+1..=2 down to 1..=n as we pop
432 for i in (1..=n).rev() {
433 state.table_set_i(1, i as i64)?;
434 }
435 state.push(LuaValue::Int(n as i64));
436 // TODO(port): state.set_field(stack_pos, key_bytes) → lua_setfield; verify method name
437 state.set_field(1, b"n")?;
438 Ok(1)
439}
440
441///
442/// Pushes `t[i], t[i+1], …, t[j]` and returns the count.
443///
444/// ```c
445/// static int tunpack (lua_State *L) {
446/// lua_Unsigned n;
447/// lua_Integer i = luaL_optinteger(L, 2, 1);
448/// lua_Integer e = luaL_opt(L, luaL_checkinteger, 3, luaL_len(L, 1));
449/// if (i > e) return 0;
450/// n = (lua_Unsigned)e - i;
451/// if (l_unlikely(n >= (unsigned int)INT_MAX ||
452/// !lua_checkstack(L, (int)(++n))))
453/// return luaL_error(L, "too many results to unpack");
454/// for (; i < e; i++) lua_geti(L, 1, i);
455/// lua_geti(L, 1, e);
456/// return (int)n;
457/// }
458/// ```
459pub fn unpack(state: &mut LuaState) -> Result<usize, LuaError> {
460 let i = state.opt_arg_integer(2, 1)?;
461 let e = if matches!(state.type_at(3), LuaType::None | LuaType::Nil) {
462 state.length_at(1)?
463 } else {
464 state.check_arg_integer(3)?
465 };
466 if i > e {
467 return Ok(0); // empty range
468 }
469 let n = (e as u64).wrapping_sub(i as u64);
470 // The size check uses the pre-increment value so that a wrapped-to-0 result
471 // (e.g. i=minI, e=maxI yields n = 2^64-1 pre-inc, 0 post-inc) still trips
472 // the error rather than silently entering a 2^64-iteration loop.
473 if n >= i32::MAX as u64 {
474 return Err(LuaError::runtime(format_args!("too many results to unpack")));
475 }
476 let n = n + 1;
477 if !state.check_stack_growth(n as i32) {
478 return Err(LuaError::runtime(format_args!("too many results to unpack")));
479 }
480 let n = n as i64;
481 let mut k = i;
482 while k < e {
483 state.table_get_i(1, k)?;
484 k += 1;
485 }
486 state.table_get_i(1, e)?; // push last element
487 Ok(n as usize)
488}
489
490// ─── Quicksort ────────────────────────────────────────────────────────────────
491
492/// selection when a partition is severely imbalanced.
493///
494/// `unsigned int` array whose elements are summed.
495///
496/// PORT NOTE: C uses a small randomised pivot guard to avoid pathological sort
497/// partitions. The Rust port asks the host for entropy when available and falls
498/// back to a deterministic pivot value in sandboxed/bare-WASM hosts.
499fn randomize_pivot(state: &LuaState) -> u32 {
500 let entropy = state.global().entropy_hook.map(|hook| hook()).unwrap_or(0);
501 let mixed = entropy ^ entropy.wrapping_shr(32);
502 (mixed as u32) ^ (mixed as u32).wrapping_shr(16)
503}
504
505/// `table[i]` and `table[j]` respectively (table is at stack position 1).
506///
507/// ```c
508/// static void set2 (lua_State *L, IdxT i, IdxT j) {
509/// lua_seti(L, 1, i);
510/// lua_seti(L, 1, j);
511/// }
512/// ```
513fn set2(state: &mut LuaState, i: IdxT, j: IdxT) -> Result<(), LuaError> {
514 // First seti pops the stack top; second seti pops the new top.
515 state.table_set_i(1, i as i64)?;
516 state.table_set_i(1, j as i64)?;
517 Ok(())
518}
519
520/// sort order: either the `<` operator (if arg 2 is nil) or the user's
521/// comparison function at stack position 2.
522///
523/// ```c
524/// static int sort_comp (lua_State *L, int a, int b) {
525/// if (lua_isnil(L, 2))
526/// return lua_compare(L, a, b, LUA_OPLT);
527/// else {
528/// int res;
529/// lua_pushvalue(L, 2);
530/// lua_pushvalue(L, a-1);
531/// lua_pushvalue(L, b-2);
532/// lua_call(L, 2, 1);
533/// res = lua_toboolean(L, -1);
534/// lua_pop(L, 1);
535/// return res;
536/// }
537/// }
538/// ```
539///
540/// The offsets `a-1` and `b-2` compensate for the function and first-argument
541/// copies pushed before the respective values: `a-1` accounts for the function
542/// push; `b-2` accounts for both the function push and the copy of `a`.
543fn sort_comp(state: &mut LuaState, a: i32, b: i32) -> Result<bool, LuaError> {
544 if state.type_at(2) == LuaType::Nil {
545 // No user comparator: use the default `<` operator.
546 return state.compare(a, b, CompareOp::Lt);
547 }
548 // User comparator at stack position 2.
549 state.push_value_at(2)?; // push function
550 state.push_value_at(a - 1)?; // push copy of a (compensate for function push)
551 state.push_value_at(b - 2)?; // push copy of b (compensate for function + a copy)
552 state.call(2, 1)?;
553 // TODO(port): state.to_boolean(-1) → lua_toboolean (never fails); verify method name
554 let res = state.to_boolean(-1);
555 state.pop_n(1);
556 Ok(res)
557}
558
559/// is already on the top of the Lua stack.
560///
561/// Precondition: `a[lo] <= P == a[up-1] <= a[up]` and `P` is at stack top.
562/// Postcondition: `a[lo..i-1] <= a[i] == P <= a[i+1..up]`; stack is clean.
563/// Returns the final pivot index `i`.
564///
565/// ```c
566/// static IdxT partition (lua_State *L, IdxT lo, IdxT up) {
567/// IdxT i = lo;
568/// IdxT j = up - 1;
569/// for (;;) {
570/// while ((void)lua_geti(L, 1, ++i), sort_comp(L, -1, -2)) {
571/// if (l_unlikely(i == up - 1))
572/// luaL_error(L, "invalid order function for sorting");
573/// lua_pop(L, 1);
574/// }
575/// while ((void)lua_geti(L, 1, --j), sort_comp(L, -3, -1)) {
576/// if (l_unlikely(j < i))
577/// luaL_error(L, "invalid order function for sorting");
578/// lua_pop(L, 1);
579/// }
580/// if (j < i) {
581/// lua_pop(L, 1);
582/// set2(L, up - 1, i);
583/// return i;
584/// }
585/// set2(L, i, j);
586/// }
587/// }
588/// ```
589fn partition(state: &mut LuaState, lo: IdxT, up: IdxT) -> Result<IdxT, LuaError> {
590 let mut i: IdxT = lo;
591 let mut j: IdxT = up - 1;
592 // Entry: stack top is P (pivot value).
593 loop {
594 // Advance i: find first a[i] >= P.
595 // Stack during i-loop body: P(-2), a[i](-1)
596 loop {
597 i += 1;
598 state.table_get_i(1, i as i64)?; // push a[i]
599 if !sort_comp(state, -1, -2)? {
600 // a[i] >= P: leave a[i] on stack and exit
601 break;
602 }
603 // a[i] < P; check for invalid comparator
604 if i == up - 1 {
605 return Err(LuaError::runtime(format_args!(
606 "invalid order function for sorting"
607 )));
608 }
609 state.pop_n(1); // remove a[i]
610 }
611 // Retreat j: find last a[j] <= P.
612 // Stack during j-loop body: P(-3), a[i](-2), a[j](-1)
613 loop {
614 // PERF(port): wrapping_sub mirrors C unsigned IdxT behaviour for edge cases
615 j = j.wrapping_sub(1);
616 state.table_get_i(1, j as i64)?; // push a[j]
617 if !sort_comp(state, -3, -1)? {
618 // P >= a[j]: leave a[j] on stack and exit
619 break;
620 }
621 // P < a[j]; check for invalid comparator
622 if j < i {
623 return Err(LuaError::runtime(format_args!(
624 "invalid order function for sorting"
625 )));
626 }
627 state.pop_n(1); // remove a[j]
628 }
629 // Stack: P(-3), a[i](-2), a[j](-1)
630 if j < i {
631 // No out-of-place elements; finalize: place pivot at position i.
632 state.pop_n(1); // pop a[j]; stack: P(-2), a[i](-1)
633 set2(state, up - 1, i)?; // table[up-1] = a[i], table[i] = P; stack clean
634 return Ok(i);
635 }
636 // Swap a[i] and a[j] to restore loop invariant.
637 // set2: table[i] = a[j] (pops -1), table[j] = a[i] (pops new -1); stack: P(-1)
638 set2(state, i, j)?;
639 }
640}
641
642/// `[lo, up]`, randomised by `rnd`.
643///
644/// ```c
645/// static IdxT choosePivot (IdxT lo, IdxT up, unsigned int rnd) {
646/// IdxT r4 = (up - lo) / 4;
647/// IdxT p = rnd % (r4 * 2) + (lo + r4);
648/// lua_assert(lo + r4 <= p && p <= up - r4);
649/// return p;
650/// }
651/// ```
652fn choose_pivot(lo: IdxT, up: IdxT, rnd: u32) -> IdxT {
653 let r4 = (up - lo) / 4; // range / 4
654 let p = rnd % (r4 * 2) + (lo + r4);
655 debug_assert!(lo + r4 <= p && p <= up - r4);
656 p
657}
658
659///
660/// Sorts `table[lo..=up]` in place, recursing on the smaller partition and
661/// tail-looping on the larger (to bound Rust's call stack). Randomises pivot
662/// selection when a partition is badly imbalanced.
663///
664/// ```c
665/// static void auxsort (lua_State *L, IdxT lo, IdxT up, unsigned int rnd) {
666/// while (lo < up) {
667/// IdxT p, n;
668/// lua_geti(L, 1, lo); lua_geti(L, 1, up);
669/// if (sort_comp(L, -1, -2)) set2(L, lo, up); else lua_pop(L, 2);
670/// if (up - lo == 1) return;
671/// if (up - lo < RANLIMIT || rnd == 0) p = (lo + up)/2;
672/// else p = choosePivot(lo, up, rnd);
673/// lua_geti(L, 1, p); lua_geti(L, 1, lo);
674/// if (sort_comp(L, -2, -1)) set2(L, p, lo);
675/// else {
676/// lua_pop(L, 1); lua_geti(L, 1, up);
677/// if (sort_comp(L, -1, -2)) set2(L, p, up); else lua_pop(L, 2);
678/// }
679/// if (up - lo == 2) return;
680/// lua_geti(L, 1, p); lua_pushvalue(L, -1); lua_geti(L, 1, up - 1);
681/// set2(L, p, up - 1);
682/// p = partition(L, lo, up);
683/// if (p - lo < up - p) {
684/// auxsort(L, lo, p - 1, rnd); n = p - lo; lo = p + 1;
685/// } else {
686/// auxsort(L, p + 1, up, rnd); n = up - p; up = p - 1;
687/// }
688/// if ((up - lo) / 128 > n) rnd = l_randomizePivot();
689/// }
690/// }
691/// ```
692fn aux_sort(state: &mut LuaState, mut lo: IdxT, mut up: IdxT, mut rnd: u32) -> Result<(), LuaError> {
693 while lo < up {
694 // Step 1: ensure a[lo] <= a[up] (cheap two-element sort)
695 state.table_get_i(1, lo as i64)?; // push a[lo]
696 state.table_get_i(1, up as i64)?; // push a[up]
697 if sort_comp(state, -1, -2)? {
698 set2(state, lo, up)?; // swap so a[lo] <= a[up]
699 } else {
700 state.pop_n(2);
701 }
702 if up - lo == 1 {
703 return Ok(()); // only 2 elements, now sorted
704 }
705
706 // Step 2: choose pivot index
707 let mut p: IdxT = if up - lo < RANLIMIT || rnd == 0 {
708 (lo + up) / 2 // midpoint pivot for small/non-random runs
709 } else {
710 choose_pivot(lo, up, rnd)
711 };
712
713 // Step 3: median-of-three: sort a[lo], a[p], a[up]
714 state.table_get_i(1, p as i64)?; // push a[p]
715 state.table_get_i(1, lo as i64)?; // push a[lo]
716 if sort_comp(state, -2, -1)? {
717 set2(state, p, lo)?; // swap a[p] ↔ a[lo]; stack clean
718 } else {
719 state.pop_n(1); // remove a[lo]; stack: a[p]
720 state.table_get_i(1, up as i64)?; // push a[up]; stack: a[p], a[up]
721 if sort_comp(state, -1, -2)? {
722 set2(state, p, up)?; // swap a[p] ↔ a[up]; stack clean
723 } else {
724 state.pop_n(2); // remove a[up] and a[p]; stack clean
725 }
726 }
727 // Stack is clean at this point.
728 if up - lo == 2 {
729 return Ok(()); // only 3 elements, now sorted
730 }
731
732 // Step 4: move pivot to a[up-1] and call partition.
733 //
734 // Stack evolution:
735 // table_get_i(p): a[p] (-1)
736 // push_value_at(-1): a[p] (-2), a[p]_copy (-1)
737 // table_get_i(up-1): a[p] (-3), a[p]_copy (-2), a[up-1] (-1)
738 // set2(p, up-1): table[p] = a[up-1], table[up-1] = a[p]_copy;
739 // stack: a[p] (-1) ← pivot for partition
740 state.table_get_i(1, p as i64)?;
741 state.push_value_at(-1)?; // duplicate: two copies of pivot on stack
742 state.table_get_i(1, (up - 1) as i64)?;
743 set2(state, p, up - 1)?;
744 // One copy of the pivot value remains at the stack top for partition.
745
746 p = partition(state, lo, up)?;
747 // Stack is clean after partition returns.
748
749 // Step 5: recurse on smaller partition; tail-loop on larger.
750 let n: IdxT;
751 if p - lo < up - p {
752 aux_sort(state, lo, p - 1, rnd)?;
753 n = p - lo;
754 lo = p + 1; // tail: sort [p+1 .. up]
755 } else {
756 aux_sort(state, p + 1, up, rnd)?;
757 n = up - p;
758 up = p - 1; // tail: sort [lo .. p-1]
759 }
760
761 // Re-randomise if the partition was severely imbalanced.
762 if (up - lo) / 128 > n {
763 rnd = randomize_pivot(state);
764 }
765 }
766 Ok(())
767}
768
769///
770/// ```c
771/// static int sort (lua_State *L) {
772/// lua_Integer n = aux_getn(L, 1, TAB_RW);
773/// if (n > 1) {
774/// luaL_argcheck(L, n < INT_MAX, 1, "array too big");
775/// if (!lua_isnoneornil(L, 2))
776/// luaL_checktype(L, 2, LUA_TFUNCTION);
777/// lua_settop(L, 2);
778/// auxsort(L, 1, (IdxT)n, 0);
779/// }
780/// return 0;
781/// }
782/// ```
783pub fn sort(state: &mut LuaState) -> Result<usize, LuaError> {
784 let n = aux_getn(state, 1, TAB_RW)?;
785 if n > 1 {
786 if !(n < i32::MAX as i64) {
787 return Err(lua_vm::debug::arg_error_impl(state, 1, b"array too big"));
788 }
789 if !matches!(state.type_at(2), LuaType::None | LuaType::Nil) {
790 state.check_arg_type(2, LuaType::Function)?;
791 }
792 // Must go through the public C-API set_top (relative to the call
793 // frame); the inherent LuaState::set_top treats its argument as
794 // an absolute stack slot and would corrupt the frame.
795 lua_vm::api::set_top(state, 2)?;
796 aux_sort(state, 1, n as IdxT, 0)?;
797 }
798 Ok(0)
799}
800
801// ─── Registration ─────────────────────────────────────────────────────────────
802
803///
804/// ```c
805/// static const luaL_Reg tab_funcs[] = {
806/// {"concat", tconcat}, {"insert", tinsert}, {"pack", tpack},
807/// {"unpack", tunpack}, {"remove", tremove}, {"move", tmove},
808/// {"sort", sort}, {NULL, NULL}
809/// };
810/// ```
811///
812/// PORT NOTE: In Rust we represent this as a slice of `(&[u8], fn-ptr)` pairs;
813/// the sentinel `{NULL, NULL}` is implicit (the slice has a known length).
814pub const TABLE_FUNCS: &[(&[u8], fn(&mut LuaState) -> Result<usize, LuaError>)] = &[
815 (b"concat", concat),
816 (b"insert", insert),
817 (b"pack", pack),
818 (b"unpack", unpack),
819 (b"remove", remove),
820 (b"move", tmove),
821 (b"sort", sort),
822];
823
824/// `table.create(nseq [, nrec])` — Lua 5.5 addition
825/// (`specs/research/5.5-upstream-delta.md` §5, `ltablib.c`).
826///
827/// Preallocates a table with `nseq` array (sequence) slots and `nrec` hash
828/// (record) slots, returning the empty table. Preallocation is purely a
829/// capacity hint; the returned table is observably empty (length 0, no keys),
830/// so this implementation is behaviorally faithful even though our
831/// `create_table` may treat the sizes as advisory.
832///
833/// Registered into the `table` roster only under [`lua_types::LuaVersion::V55`]
834/// (see [`open_table`]); absent under 5.1-5.4, matching upstream.
835pub fn create(state: &mut LuaState) -> Result<usize, LuaError> {
836 let nseq = state.check_arg_integer(1)?;
837 let nrec = state.opt_arg_integer(2, 0)?;
838 if nseq < 0 || nseq > i32::MAX as i64 {
839 return Err(LuaError::runtime(format_args!(
840 "bad argument #1 to 'create' (size out of range)"
841 )));
842 }
843 if nrec < 0 || nrec > i32::MAX as i64 {
844 return Err(LuaError::runtime(format_args!(
845 "bad argument #2 to 'create' (size out of range)"
846 )));
847 }
848 state.create_table(nseq as i32, nrec as i32)?;
849 Ok(1)
850}
851
852// ─── Lua 5.1 legacy compat functions (`getn`/`setn`/`maxn`/`foreach`/`foreachi`) ──
853//
854// These predate the `#` operator and the 5.2 roster cleanup; they ship only in
855// the default lua5.1.5 build (`ltablib.c`) and are registered under the V51
856// backend by `open_table`. Verified against lua5.1.5; see
857// specs/followup/5.1-roster-syntax.md §1.
858
859/// `table.getn(t)` — the "size" of a sequence, i.e. the border `#t` reports.
860///
861/// In 5.1 `aux_getn` is `luaL_checktype(TABLE)` followed by `luaL_getn`, which
862/// resolves to the primitive length. Mirrors `getn` in 5.1 `ltablib.c`.
863fn getn(state: &mut LuaState) -> Result<usize, LuaError> {
864 state.check_arg_type(1, LuaType::Table)?;
865 let n = state.length_at(1)?;
866 state.push(LuaValue::Int(n));
867 Ok(1)
868}
869
870/// `table.setn(t, n)` — obsolete gravestone. In 5.1 the default build defines
871/// `luaL_setn` as a no-op, so `setn` raises `'setn' is obsolete`. Verified
872/// against lua5.1.5 (`pcall`-able to that exact message).
873fn setn(state: &mut LuaState) -> Result<usize, LuaError> {
874 state.check_arg_type(1, LuaType::Table)?;
875 Err(LuaError::runtime(format_args!("'setn' is obsolete")))
876}
877
878/// `table.maxn(t)` — the largest positive numeric key (0 if none). Iterates the
879/// raw table via `next`, tracking the max numeric key. Mirrors `maxn` in 5.1
880/// `ltablib.c`.
881fn maxn(state: &mut LuaState) -> Result<usize, LuaError> {
882 state.check_arg_type(1, LuaType::Table)?;
883 let mut max: f64 = 0.0;
884 state.push(LuaValue::Nil);
885 while state.table_next(1)? {
886 // Stack: ..., key, value. Drop the value, inspect the key.
887 state.pop_n(1);
888 if matches!(state.type_at(-1), LuaType::Number) {
889 if let Some(v) = state.to_number(-1) {
890 if v > max {
891 max = v;
892 }
893 }
894 }
895 }
896 state.push(LuaValue::Float(max));
897 Ok(1)
898}
899
900/// `table.foreachi(t, f)` — call `f(i, t[i])` for `i` in `1..#t`, stopping early
901/// if `f` returns a non-nil value (which is then returned). Mirrors `foreachi`
902/// in 5.1 `ltablib.c`.
903fn foreachi(state: &mut LuaState) -> Result<usize, LuaError> {
904 state.check_arg_type(1, LuaType::Table)?;
905 state.check_arg_type(2, LuaType::Function)?;
906 let n = state.length_at(1)?;
907 let mut i: i64 = 1;
908 while i <= n {
909 state.push_value_at(2)?;
910 state.push(LuaValue::Int(i));
911 state.table_get_i(1, i)?;
912 state.call(2, 1)?;
913 if !matches!(state.type_at(-1), LuaType::Nil) {
914 return Ok(1);
915 }
916 state.pop_n(1);
917 i += 1;
918 }
919 Ok(0)
920}
921
922/// `table.foreach(t, f)` — call `f(k, v)` for every pair, stopping early if `f`
923/// returns a non-nil value (which is then returned). Mirrors `foreach` in 5.1
924/// `ltablib.c`.
925fn foreach(state: &mut LuaState) -> Result<usize, LuaError> {
926 state.check_arg_type(1, LuaType::Table)?;
927 state.check_arg_type(2, LuaType::Function)?;
928 state.push(LuaValue::Nil);
929 while state.table_next(1)? {
930 // Stack: ..., key, value.
931 state.push_value_at(2)?; // function
932 state.push_value_at(-3)?; // key copy
933 state.push_value_at(-3)?; // value copy
934 state.call(2, 1)?;
935 if !matches!(state.type_at(-1), LuaType::Nil) {
936 return Ok(1);
937 }
938 state.pop_n(2); // remove value and result, leaving key for next()
939 }
940 Ok(0)
941}
942
943// ─── Module opener ────────────────────────────────────────────────────────────
944
945///
946/// ```c
947/// LUAMOD_API int luaopen_table (lua_State *L) {
948/// luaL_newlib(L, tab_funcs);
949/// return 1;
950/// }
951/// ```
952pub fn open_table(state: &mut LuaState) -> Result<usize, LuaError> {
953 // TODO(port): state.new_lib → luaL_newlib; creates a new table and registers functions;
954 // verify method name and signature
955 //
956 // Per-version roster deltas:
957 // - `table.move` is a Lua 5.3 addition, absent in 5.1/5.2 (verified against
958 // lua5.2.4: `type(table.move)` == "nil").
959 // - `table.pack`/`table.unpack` are Lua 5.2 additions; in 5.1 `unpack` is a
960 // *global* and there is no `table.pack` (verified against lua5.1.5: both
961 // `table.unpack` and `table.pack` are nil). 5.1 instead carries the legacy
962 // `getn`/`setn`/`maxn`/`foreach`/`foreachi` roster.
963 if matches!(state.global().lua_version, lua_types::LuaVersion::V51) {
964 let legacy: Vec<(&[u8], fn(&mut LuaState) -> Result<usize, LuaError>)> = TABLE_FUNCS
965 .iter()
966 .filter(|(name, _)| {
967 *name != b"move".as_slice()
968 && *name != b"pack".as_slice()
969 && *name != b"unpack".as_slice()
970 })
971 .copied()
972 .collect();
973 state.new_lib(&legacy)?;
974 const LEGACY_FUNCS: &[(&[u8], fn(&mut LuaState) -> Result<usize, LuaError>)] = &[
975 (b"getn", getn),
976 (b"setn", setn),
977 (b"maxn", maxn),
978 (b"foreach", foreach),
979 (b"foreachi", foreachi),
980 ];
981 state.set_funcs_with_upvalues(LEGACY_FUNCS, 0)?;
982 } else if matches!(state.global().lua_version, lua_types::LuaVersion::V52) {
983 let without_move: Vec<(&[u8], fn(&mut LuaState) -> Result<usize, LuaError>)> = TABLE_FUNCS
984 .iter()
985 .filter(|(name, _)| *name != b"move".as_slice())
986 .copied()
987 .collect();
988 state.new_lib(&without_move)?;
989 } else {
990 state.new_lib(TABLE_FUNCS)?;
991 }
992 // Per-version roster delta: `table.create` is a Lua 5.5 addition
993 // (`specs/research/5.5-upstream-delta.md` §5), absent in 5.1-5.4. Register
994 // it only on the V55 backend so the version seam carries a real,
995 // script-observable stdlib difference. `new_lib` leaves the new table on
996 // the stack top, so we register `create` into it directly.
997 if matches!(state.global().lua_version, lua_types::LuaVersion::V55) {
998 const CREATE_FUNCS: &[(&[u8], fn(&mut LuaState) -> Result<usize, LuaError>)] =
999 &[(b"create", create)];
1000 state.set_funcs_with_upvalues(CREATE_FUNCS, 0)?;
1001 }
1002 Ok(1)
1003}
1004
1005// ──────────────────────────────────────────────────────────────────────────────
1006// PORT STATUS
1007// source: src/ltablib.c (430 lines, 14 functions)
1008// target_crate: lua-stdlib
1009// confidence: medium
1010// todos: 17
1011// port_notes: 5
1012// unsafe_blocks: 0
1013// notes: Logic is faithfully translated. All TODOs are method-name
1014// uncertainties for LuaState API calls (table_get_i, table_set_i,
1015// opt_arg_integer, opt_arg_lstring, get_metatable, to_bytes_at,
1016// push_lstring, push_value_at, compare, new_lib, set_top,
1017// check_stack_growth, type_name_str_at, create_table) — Phase B
1018// maps these to the real method names once lua-vm is drafted.
1019// Stack cleanup on error paths is elided (C uses longjmp);
1020// needs Phase B attention in check_tab and add_field.
1021// PERF: remove() and insert() shift loops now cache the table
1022// value once (via value_at) and call table_get_i_value /
1023// table_set_i_value, bypassing the per-iteration index_to_value
1024// call. This shrank the index_to_value hot frame and improved
1025// table_ops_long from ~4.76x to ~4.02x vs reference.
1026// ──────────────────────────────────────────────────────────────────────────────