Skip to main content

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