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// ──────────────────────────────────────────────────────────────────────────────