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