lua_vm/api.rs
1// C: lapi.c — Lua public C API
2// C: $Id: lapi.c $
3// C: See Copyright Notice in lua.h
4//
5// PORT NOTE: This is the Rust-native translation of lapi.c.
6// The C-API surface (lua_State *, int stack-index protocol) is replaced by
7// methods on LuaState. `lua_lock` / `lua_unlock` are dropped (no-op in the
8// single-threaded default build). `api_incr_top` is dropped; `state.push()`
9// already increments. Stack pointers (StkId) become StackIdx (u32).
10
11#![allow(dead_code)]
12
13use std::convert::Infallible;
14#[allow(unused_imports)] use crate::prelude::*;
15
16use crate::state::{LuaState, LuaCFunction, GlobalState, CallInfo, CallInfoIdx, StackIdx,
17 LuaValueExt, LuaTypeExt, StackIdxExt,
18 LuaTableRefExt, LuaUserDataRefExt, LuaStringRefExt,
19 LuaLClosureRefExt, LuaClosureExt, LuaProtoExt};
20use lua_types::{
21 LuaValue, LuaType, LuaError, LuaString, LuaUserData, LuaClosure, UpVal,
22 GcRef, LuaStatus,
23};
24use lua_types::value::LuaTable;
25
26// C: const char lua_ident[] = "$LuaVersion: " LUA_COPYRIGHT " $" ...
27pub const LUA_IDENT: &[u8] =
28 b"$LuaVersion: Lua 5.4.7 Copyright (C) 1994-2024 Lua.org, PUC-Rio $\
29 $LuaAuthors: R. Ierusalimschy, L. H. Figueiredo, W. Celes $";
30
31// C: #define LUA_REGISTRYINDEX (-LUAI_MAXSTACK - 1000)
32const LUA_REGISTRYINDEX: i32 = -(1_000_000) - 1000;
33
34// C: #define LUA_MULTRET (-1)
35const LUA_MULTRET: i32 = -1;
36
37// C: #define LUA_RIDX_GLOBALS 2
38const LUA_RIDX_GLOBALS: i64 = 2;
39
40// C: #define MAXUPVAL 255
41const MAX_UPVAL: u8 = 255;
42
43// C: #define ispseudo(i) ((i) <= LUA_REGISTRYINDEX)
44#[inline]
45fn is_pseudo(idx: i32) -> bool {
46 idx <= LUA_REGISTRYINDEX
47}
48
49// C: #define isupvalue(i) ((i) < LUA_REGISTRYINDEX)
50#[inline]
51fn is_upvalue(idx: i32) -> bool {
52 idx < LUA_REGISTRYINDEX
53}
54
55// C: #define isvalid(L, o) (!ttisnil(o) || o != &G(L)->nilvalue)
56// PORT NOTE: In C, the only "invalid" TValue is the global nilvalue singleton
57// pointer returned by index2value when the index is out of range. In Rust we
58// cannot do pointer-equality on a singleton, so validity is decided by whether
59// the index resolves to a real stack/upvalue slot — see `is_valid_index`.
60#[inline]
61fn is_valid_index(state: &LuaState, idx: i32) -> bool {
62 if idx == 0 {
63 return false;
64 }
65 let ci = state.current_call_info();
66 if idx > 0 {
67 let slot = ci.func + idx;
68 slot.0 < state.top_idx().0
69 } else if !is_pseudo(idx) {
70 (-idx) as u32 <= state.top_idx().0.saturating_sub(ci.func.0 + 1)
71 } else if idx == LUA_REGISTRYINDEX {
72 true
73 } else {
74 let upval_n = (LUA_REGISTRYINDEX - idx) as usize;
75 let func_val = state.get_at(ci.func);
76 if let LuaValue::Function(LuaClosure::C(ref ccl)) = func_val {
77 upval_n >= 1 && upval_n <= ccl.upvalues.len()
78 } else {
79 false
80 }
81 }
82}
83
84// ── index helpers ─────────────────────────────────────────────────────────────
85
86// C: static TValue *index2value (lua_State *L, int idx)
87// PORT NOTE: In Rust we cannot return a pointer; we return a cloned LuaValue.
88// Writers use a companion index_to_stack_idx() for actual stack slots.
89fn index_to_value(state: &LuaState, idx: i32) -> LuaValue {
90 // C: CallInfo *ci = L->ci;
91 let ci = state.current_call_info();
92 if idx > 0 {
93 // C: StkId o = ci->func.p + idx;
94 let func_idx = ci.func;
95 let slot = func_idx + idx;
96 // C: api_check(L, idx <= ci->top.p - (ci->func.p + 1), "unacceptable index");
97 debug_assert!(
98 idx as u32 <= ci.top.saturating_sub(func_idx + 1),
99 "unacceptable index"
100 );
101 // C: if (o >= L->top.p) return &G(L)->nilvalue;
102 if slot.0 >= state.top_idx().0 {
103 LuaValue::Nil
104 } else {
105 state.get_at(slot)
106 }
107 } else if !is_pseudo(idx) {
108 // negative index
109 // C: api_check(L, idx != 0 && -idx <= L->top.p - (ci->func.p + 1), "invalid index");
110 debug_assert!(
111 idx != 0,
112 "invalid index"
113 );
114 // C: return s2v(L->top.p + idx);
115 let top = state.top_idx();
116 let slot = (top.0 as i32 + idx) as u32;
117 state.get_at(slot)
118 } else if idx == LUA_REGISTRYINDEX {
119 // C: return &G(L)->l_registry;
120 state.registry_value()
121 } else {
122 // upvalues: idx = LUA_REGISTRYINDEX - idx (idx < LUA_REGISTRYINDEX)
123 let upval_n = (LUA_REGISTRYINDEX - idx) as usize;
124 debug_assert!(upval_n <= MAX_UPVAL as usize + 1, "upvalue index too large");
125 // C: if (ttisCclosure(s2v(ci->func.p))) { ... }
126 let func_val = state.get_at(ci.func);
127 if let LuaValue::Function(LuaClosure::C(ref ccl)) = func_val {
128 // C closure upvalue
129 if upval_n >= 1 && upval_n <= ccl.upvalues.len() {
130 ccl.upvalues[upval_n - 1].clone()
131 } else {
132 LuaValue::Nil
133 }
134 } else {
135 // C: ttislcf or Lua fn called through hook — no upvalues
136 LuaValue::Nil
137 }
138 }
139}
140
141// C: l_sinline StkId index2stack (lua_State *L, int idx)
142// Returns a StackIdx for a valid (non-pseudo) actual stack slot.
143#[inline]
144fn index_to_stack_idx(state: &LuaState, idx: i32) -> StackIdx {
145 let ci = state.current_call_info();
146 if idx > 0 {
147 let slot = ci.func + idx;
148 debug_assert!(slot.0 < state.top_idx().0, "invalid index");
149 slot
150 } else {
151 // C: api_check(L, idx != 0 && -idx <= L->top.p - (ci->func.p + 1), "invalid index");
152 // C: api_check(L, !ispseudo(idx), "invalid index");
153 debug_assert!(idx != 0 && !is_pseudo(idx), "invalid index");
154 StackIdx((state.top_idx().0 as i32 + idx) as u32)
155 }
156}
157
158// ── stack manipulation ────────────────────────────────────────────────────────
159
160// C: LUA_API int lua_checkstack (lua_State *L, int n)
161pub fn check_stack(state: &mut LuaState, n: i32) -> bool {
162 // C: api_check(L, n >= 0, "negative 'n'");
163 debug_assert!(n >= 0, "negative 'n'");
164 let available = state.stack_available();
165 let res = if available > n as usize {
166 true
167 } else {
168 crate::do_::grow_stack(state, n, false).unwrap_or(false)
169 };
170 if res {
171 // C: if (res && ci->top.p < L->top.p + n) ci->top.p = L->top.p + n;
172 let needed_top = state.top_idx() + n as i32;
173 let ci_idx = state.current_ci_idx();
174 if state.get_ci(ci_idx).top.0 < needed_top.0 {
175 let live_top = state.top_idx();
176 state.get_ci_mut(ci_idx).top = needed_top;
177 state.clear_stack_range(live_top, needed_top);
178 }
179 }
180 res
181}
182
183/// Move the top `n` values from `from`'s stack onto `to`'s stack.
184///
185/// Both threads must share the same `GlobalState` (i.e. one is a
186/// coroutine the other created via `coroutine.create`). Calling with
187/// `from` == `to` is a no-op. Equivalent to:
188///
189/// ```text
190/// args = from.stack[top-n..top].clone();
191/// from.set_top(top - n);
192/// for v in args { to.push(v); }
193/// ```
194///
195/// C: `LUA_API void lua_xmove (lua_State *from, lua_State *to, int n)`
196///
197/// Phase E-3: implemented for the same-`GlobalState` case (the only one
198/// `lua-stdlib` uses today). `lua-vm` callers should prefer this helper
199/// over hand-rolling the snapshot/push dance.
200pub fn xmove(from: &mut LuaState, to: &mut LuaState, n: i32) {
201 if n <= 0 {
202 return;
203 }
204 if std::ptr::eq(from as *const LuaState, to as *const LuaState) {
205 return;
206 }
207 let abs_top = from.top_idx().0 as i32;
208 debug_assert!(abs_top >= n, "lua_xmove: from stack underflow");
209 let first_abs = abs_top - n;
210 let mut buf: Vec<lua_types::LuaValue> = Vec::with_capacity(n as usize);
211 for i in 0..n {
212 let idx = StackIdx((first_abs + i) as u32);
213 buf.push(from.get_at(idx));
214 }
215 from.set_top(StackIdx(first_abs as u32));
216 for v in buf {
217 to.push(v);
218 }
219}
220
221// C: LUA_API lua_CFunction lua_atpanic (lua_State *L, lua_CFunction panicf)
222pub fn at_panic(
223 state: &mut LuaState,
224 panicf: Option<fn(&mut LuaState) -> Result<usize, LuaError>>,
225) -> Option<fn(&mut LuaState) -> Result<usize, LuaError>> {
226 // C: old = G(L)->panic; G(L)->panic = panicf; return old;
227 let old = state.global_mut().panic;
228 state.global_mut().panic = panicf;
229 old
230}
231
232// C: LUA_API lua_Number lua_version (lua_State *L)
233pub fn version(_state: &LuaState) -> f64 {
234 // C: UNUSED(L); return LUA_VERSION_NUM;
235 504.0
236}
237
238// C: LUA_API int lua_absindex (lua_State *L, int idx)
239pub fn abs_index(state: &LuaState, idx: i32) -> i32 {
240 // C: return (idx > 0 || ispseudo(idx)) ? idx
241 // : cast_int(L->top.p - L->ci->func.p) + idx;
242 if idx > 0 || is_pseudo(idx) {
243 idx
244 } else {
245 let ci = state.current_call_info();
246 (state.top_idx().0 as i32 - ci.func.0 as i32) + idx
247 }
248}
249
250// C: LUA_API int lua_gettop (lua_State *L)
251pub fn get_top(state: &LuaState) -> i32 {
252 // C: return cast_int(L->top.p - (L->ci->func.p + 1));
253 let ci = state.current_call_info();
254 (state.top_idx().0 as i32) - (ci.func.0 as i32 + 1)
255}
256
257// C: LUA_API void lua_settop (lua_State *L, int idx)
258pub fn set_top(state: &mut LuaState, idx: i32) -> Result<(), LuaError> {
259 let func = state.current_call_info().func;
260 let ci_top = state.current_call_info().top;
261 if idx >= 0 {
262 // C: api_check(L, idx <= ci->top.p - (func + 1), "new top too large");
263 debug_assert!(
264 idx as u32 <= ci_top.saturating_sub(func + 1),
265 "new top too large"
266 );
267 let new_top = func + 1 + idx as i32;
268 let old_top = state.top_idx();
269 if new_top.0 > old_top.0 {
270 // C: for (; diff > 0; diff--) setnilvalue(s2v(L->top.p++));
271 for i in old_top.0..new_top.0 {
272 state.set_at(i, LuaValue::Nil);
273 }
274 }
275 // C: if (diff < 0 && L->tbclist.p >= newtop) { newtop = luaF_close(...) }
276 // TODO(port): to-be-closed variable closing on stack shrink;
277 // luaF_close not yet translated. Skipping close logic for Phase A.
278 state.set_top_idx(new_top);
279 } else {
280 // C: api_check(L, -(idx+1) <= (L->top.p - (func + 1)), "invalid new top");
281 debug_assert!(
282 -(idx + 1) <= (state.top_idx().0 as i32 - (func.0 as i32 + 1)),
283 "invalid new top"
284 );
285 // C: diff = idx + 1 (negative, will subtract)
286 let new_top = (state.top_idx().0 as i32 + idx + 1) as u32;
287 // TODO(port): to-be-closed variable closing on stack shrink (same as above)
288 state.set_top_idx(new_top);
289 }
290 Ok(())
291}
292
293// C: LUA_API void lua_closeslot (lua_State *L, int idx)
294pub fn close_slot(state: &mut LuaState, idx: i32) -> Result<(), LuaError> {
295 // C: level = index2stack(L, idx);
296 let level = index_to_stack_idx(state, idx);
297 // C: api_check(L, hastocloseCfunc(L->ci->nresults) && L->tbclist.p == level, ...)
298 // TODO(port): tbc-list check and luaF_close not yet translated.
299 // C: level = luaF_close(L, level, CLOSEKTOP, 0);
300 // C: setnilvalue(s2v(level));
301 state.set_at(level, LuaValue::Nil);
302 Ok(())
303}
304
305// C: l_sinline void reverse (lua_State *L, StkId from, StkId to)
306#[inline]
307fn reverse_segment(state: &mut LuaState, from: StackIdx, to: StackIdx) {
308 // C: for (; from < to; from++, to--) { TValue temp; setobj...; setobjs2s...; setobj2s...; }
309 let mut lo = from.0;
310 let mut hi = to.0;
311 while lo < hi {
312 let temp = state.get_at(StackIdx(lo));
313 let hi_val = state.get_at(StackIdx(hi));
314 state.set_at(StackIdx(lo), hi_val);
315 state.set_at(StackIdx(hi), temp);
316 lo += 1;
317 hi -= 1;
318 }
319}
320
321// C: LUA_API void lua_rotate (lua_State *L, int idx, int n)
322pub fn rotate(state: &mut LuaState, idx: i32, n: i32) {
323 // C: t = L->top.p - 1; (end of segment)
324 let t = state.top_idx() - 1;
325 // C: p = index2stack(L, idx); (start of segment)
326 let p = index_to_stack_idx(state, idx);
327 // C: api_check(L, (n >= 0 ? n : -n) <= (t - p + 1), "invalid 'n'");
328 debug_assert!((n.unsigned_abs() as i32) <= ((t.0 as i32) - (p.0 as i32) + 1), "invalid 'n'");
329 // C: m = (n >= 0 ? t - n : p - n - 1); (end of prefix)
330 let m = if n >= 0 {
331 t - n
332 } else {
333 StackIdx((p.0 as i32 - n - 1) as u32)
334 };
335 // C: reverse(L, p, m); reverse(L, m+1, t); reverse(L, p, t);
336 reverse_segment(state, p, m);
337 reverse_segment(state, m + 1, t);
338 reverse_segment(state, p, t);
339}
340
341// C: LUA_API void lua_copy (lua_State *L, int fromidx, int toidx)
342pub fn copy(state: &mut LuaState, fromidx: i32, toidx: i32) {
343 // C: fr = index2value(L, fromidx); to = index2value(L, toidx);
344 let fr = index_to_value(state, fromidx);
345 // C: api_check(L, isvalid(L, to), "invalid index");
346 // C: setobj(L, to, fr);
347 // C: if (isupvalue(toidx)) luaC_barrier(L, clCvalue(s2v(L->ci->func.p)), fr);
348 if is_upvalue(toidx) {
349 // Writing to a function upvalue pseudo-index
350 let upval_n = (LUA_REGISTRYINDEX - toidx) as usize;
351 let func_val = state.get_at(state.current_call_info().func);
352 if let LuaValue::Function(LuaClosure::C(ref ccl)) = func_val {
353 // TODO(port): CClosure upvalue write requires interior mutability on GcRef<CClosure>
354 // state.gc().barrier(ccl, &fr);
355 let _ = (upval_n, ccl);
356 }
357 // TODO(port): implement upvalue write for copy() to C closure upvalues
358 } else if toidx == LUA_REGISTRYINDEX {
359 // TODO(port): write to registry — needs GlobalState::set_registry(fr)
360 } else {
361 let to_slot = index_to_stack_idx(state, toidx);
362 state.set_at(to_slot, fr);
363 }
364}
365
366// C: LUA_API void lua_pushvalue (lua_State *L, int idx)
367pub fn push_value(state: &mut LuaState, idx: i32) {
368 // C: setobj2s(L, L->top.p, index2value(L, idx)); api_incr_top(L);
369 let v = index_to_value(state, idx);
370 state.push(v);
371}
372
373/// Inherent `push_copy` so the `LuaStateStubExt::push_copy` default
374/// `todo!()` no longer fires. Phase-A `state.push_copy(idx)` call-sites
375/// (base.rs, etc.) duplicate the value at `idx` onto the top of the stack —
376/// the same semantics as `lua_pushvalue`.
377impl LuaState {
378 pub fn push_copy(&mut self, idx: i32) -> Result<(), LuaError> {
379 push_value(self, idx);
380 Ok(())
381 }
382
383 pub fn push_value_at(&mut self, idx: i32) -> Result<(), LuaError> {
384 push_value(self, idx);
385 Ok(())
386 }
387
388 pub fn insert(&mut self, idx: i32) -> Result<(), LuaError> {
389 rotate(self, idx, 1);
390 Ok(())
391 }
392
393 /// Inherent `length_at` mirroring `luaL_len` from `lauxlib.c`: push the
394 /// value's length onto the stack (honouring `__len`), pop it as an
395 /// integer, and error if the result is not an integer. Defined on
396 /// `LuaState` so it overrides the `LuaStateStubExt::length_at` trait
397 /// default `todo!()`.
398 pub fn length_at(&mut self, idx: i32) -> Result<i64, LuaError> {
399 len(self, idx)?;
400 let l = match to_integer_x(self, -1) {
401 Some(n) => n,
402 None => {
403 return Err(LuaError::runtime(format_args!(
404 "object length is not an integer"
405 )));
406 }
407 };
408 self.pop_n(1);
409 Ok(l)
410 }
411
412 /// Write `msg` bytes verbatim to standard output. Mirrors the C macro
413 /// `lua_writestring(s, l) = fwrite(s, 1, l, stdout)` from `lauxlib.h`,
414 /// used by `print` and friends. A failed write is propagated as a
415 /// `LuaError::runtime`; this matches C-Lua's behaviour where an I/O
416 /// error during `lua_writestring` would surface through the host's
417 /// error handling.
418 pub fn write_output(&mut self, msg: &[u8]) -> Result<(), LuaError> {
419 use std::io::Write;
420 let stdout = std::io::stdout();
421 let mut handle = stdout.lock();
422 handle
423 .write_all(msg)
424 .map_err(|e| LuaError::runtime(format_args!("{}", e)))?;
425 handle
426 .flush()
427 .map_err(|e| LuaError::runtime(format_args!("{}", e)))?;
428 Ok(())
429 }
430
431 /// Convert the value at `idx` to a display string, push the result onto
432 /// the stack, and return a copy of its bytes. Mirrors `luaL_tolstring`
433 /// from `lauxlib.c`. The default Lua formatting is used for primitives
434 /// (`"true"`/`"false"`/`"nil"`, `%I` integers, `%.14g` floats); for other
435 /// reference types the result is `"<typename>: 0x<hex pointer>"`.
436 ///
437 /// If the value has a `__tostring` metamethod, it is invoked first and its
438 /// (string) result is used in place of the default formatting (matching
439 /// C: `luaL_callmeta(L, idx, "__tostring")` inside `luaL_tolstring`).
440 pub fn to_display_string(&mut self, idx: i32) -> Result<Vec<u8>, LuaError> {
441 let abs = abs_index(self, idx);
442 let v = index_to_value(self, abs);
443 let mt: Option<GcRef<LuaTable>> = match &v {
444 LuaValue::Table(t) => t.metatable(),
445 LuaValue::UserData(u) => u.metatable(),
446 _ => self.global().mt[v.base_type() as usize].clone(),
447 };
448 if let Some(mt_ref) = mt {
449 let key = self.intern_str(b"__tostring")?;
450 let f = mt_ref.get_short_str(&key);
451 if !matches!(f, LuaValue::Nil) {
452 let func_idx = self.top_idx();
453 self.push(f);
454 self.push(v.clone());
455 if self.current_ci().is_lua_code() {
456 self.do_call(func_idx, 1)?;
457 } else {
458 self.do_call_no_yield(func_idx, 1)?;
459 }
460 let top = self.top_idx();
461 let result = self.get_at(StackIdx(top.0 - 1));
462 if let LuaValue::Str(s) = result {
463 return Ok(s.as_bytes().to_vec());
464 }
465 return Err(LuaError::runtime(format_args!(
466 "'__tostring' must return a string"
467 )));
468 }
469 }
470 let bytes: Vec<u8> = match &v {
471 LuaValue::Str(s) => {
472 let out = s.as_bytes().to_vec();
473 self.push(LuaValue::Str(s.clone()));
474 out
475 }
476 LuaValue::Int(_) | LuaValue::Float(_) => {
477 let s = crate::object::num_to_string(self, &v)?;
478 let out = s.as_bytes().to_vec();
479 self.push(LuaValue::Str(s));
480 out
481 }
482 LuaValue::Bool(b) => {
483 let lit: &[u8] = if *b { b"true" } else { b"false" };
484 let s = self.intern_str(lit)?;
485 self.push(LuaValue::Str(s));
486 lit.to_vec()
487 }
488 LuaValue::Nil => {
489 let s = self.intern_str(b"nil")?;
490 self.push(LuaValue::Str(s));
491 b"nil".to_vec()
492 }
493 _ => {
494 let kind = crate::tagmethods::obj_type_name(self, &v)?;
495 let ptr = to_pointer(self, abs).unwrap_or(0);
496 let mut buf = kind;
497 buf.extend_from_slice(b": 0x");
498 buf.extend_from_slice(format!("{:x}", ptr).as_bytes());
499 let s = self.intern_str(&buf)?;
500 self.push(LuaValue::Str(s));
501 buf
502 }
503 };
504 Ok(bytes)
505 }
506
507 /// C: `lua_gettop(L)` — number of values in the active call frame
508 /// (stack top minus the slot just after the frame's `func`).
509 ///
510 /// Receiver is `&mut self` to match the `LuaStateStubExt::top` trait
511 /// signature exactly; with a different receiver shape (`&self`), Rust's
512 /// method-resolution picks the trait default and the program panics on
513 /// `todo!("phase-b-reconcile: top")`.
514 pub fn top(&mut self) -> i32 {
515 get_top(self)
516 }
517
518 /// C: `lua_gettop(L)` — same as [`Self::top`], named to match the
519 /// `LuaStateStubExt::get_top` trait method. Inherent method shadows the
520 /// trait default so the `todo!("phase-b-reconcile: get_top")` shim never
521 /// fires.
522 pub fn get_top(&mut self) -> i32 {
523 get_top(self)
524 }
525
526 /// C: `lua_type(L, idx)` — returns the `LuaType` tag of the value at
527 /// stack index `idx`, or `LuaType::None` if `idx` falls outside the
528 /// active call frame. Inherent method shadows the
529 /// `LuaStateStubExt::type_at` trait default so the `todo!()` shim
530 /// never fires.
531 pub fn type_at(&mut self, idx: i32) -> LuaType {
532 lua_type_at(self, idx)
533 }
534
535 /// C: `luaL_checkany(L, arg)` from lauxlib.c — raises a `bad argument
536 /// #N (value expected)` error if the slot at `arg` is `LUA_TNONE`
537 /// (i.e. beyond the active call frame's top). Otherwise a no-op.
538 ///
539 /// Inherent method on LuaState shadows the `LuaStateStubExt::check_arg_any`
540 /// trait default so the `todo!()` shim never fires.
541 pub fn check_arg_any(&mut self, arg: i32) -> Result<(), LuaError> {
542 if lua_type_at(self, arg) == LuaType::None {
543 return Err(LuaError::arg_error(arg, "value expected"));
544 }
545 Ok(())
546 }
547
548 /// C: `luaL_checklstring(L, arg, NULL)` from lauxlib.c — converts the slot
549 /// at `arg` to a string via `lua_tolstring` (which coerces numbers to
550 /// their string form) and returns the bytes. Raises
551 /// `bad argument #N (string expected, got <type>)` if the value is not a
552 /// string and not number-coercible.
553 ///
554 /// Inherent method on LuaState shadows the `LuaStateStubExt::check_arg_string`
555 /// trait default so the `todo!()` shim never fires. Uses the free `to_lua_string`
556 /// helper here rather than `auxlib::check_lstring`, which routes through
557 /// `state.to_lua_string` / `state.type_name` — both still trait stubs.
558 pub fn check_arg_string(&mut self, arg: i32) -> Result<Vec<u8>, LuaError> {
559 match to_lua_string(self, arg)? {
560 Some(s) => Ok(s.as_bytes().to_vec()),
561 None => {
562 let got = index_to_value(self, arg);
563 let got_name = crate::tagmethods::obj_type_name(self, &got)?;
564 let extramsg = format!(
565 "string expected, got {}",
566 String::from_utf8_lossy(&got_name)
567 );
568 Err(crate::debug::arg_error_impl(self, arg, extramsg.as_bytes()))
569 }
570 }
571 }
572
573 /// C: `luaL_checkinteger(L, arg)` from lauxlib.c — converts the slot at
574 /// `arg` to a `lua_Integer` (i64) via `lua_tointegerx` (which accepts
575 /// ints, floats with exact integer value, and string-form integers).
576 /// Raises `bad argument #N (number has no integer representation)` if
577 /// the value is a number but not representable as an integer, or
578 /// `bad argument #N (number expected, got <type>)` otherwise.
579 ///
580 /// Inherent method on LuaState shadows the `LuaStateStubExt::check_arg_integer`
581 /// trait default so the `todo!()` shim never fires. Uses the free
582 /// `to_integer_x` / `is_number` helpers in this file rather than
583 /// `auxlib::check_integer`, which routes through `state.to_integer_x`
584 /// and `state.type_name` — both still trait stubs.
585 pub fn check_arg_integer(&mut self, arg: i32) -> Result<i64, LuaError> {
586 match to_integer_x(self, arg) {
587 Some(d) => Ok(d),
588 None => {
589 if is_number(self, arg) {
590 Err(LuaError::arg_error(
591 arg,
592 "number has no integer representation",
593 ))
594 } else {
595 let got = index_to_value(self, arg);
596 let got_name = crate::tagmethods::obj_type_name(self, &got)?;
597 let extramsg = format!(
598 "number expected, got {}",
599 String::from_utf8_lossy(&got_name)
600 );
601 Err(crate::debug::arg_error_impl(self, arg, extramsg.as_bytes()))
602 }
603 }
604 }
605 }
606
607 /// C: `luaL_checknumber(L, arg)` from lauxlib.c — converts the slot at
608 /// `arg` to an `f64` via `lua_tonumberx` (which accepts ints, floats,
609 /// and number-shaped strings) and raises `bad argument #N (number
610 /// expected, got <type>)` if the value is not number-coercible.
611 ///
612 /// Inherent method on LuaState shadows the `LuaStateStubExt::check_number`
613 /// trait default so the `todo!()` shim never fires. Uses the free
614 /// `to_number_x` helper here rather than `auxlib::check_number`, which
615 /// routes through `state.to_number_x` and `state.type_name` — both still
616 /// trait stubs.
617 pub fn check_number(&mut self, arg: i32) -> Result<f64, LuaError> {
618 match to_number_x(self, arg) {
619 Some(d) => Ok(d),
620 None => {
621 let got = index_to_value(self, arg);
622 let got_name = crate::tagmethods::obj_type_name(self, &got)?;
623 let extramsg = format!(
624 "number expected, got {}",
625 String::from_utf8_lossy(&got_name)
626 );
627 Err(crate::debug::arg_error_impl(self, arg, extramsg.as_bytes()))
628 }
629 }
630 }
631
632 /// C: `luaL_optinteger(L, arg, def)` from lauxlib.c — if the slot at
633 /// `arg` is absent (`LUA_TNONE`) or `nil`, return `def`; otherwise
634 /// convert it to an integer (with the same string-to-number coercion
635 /// `lua_tointegerx` applies) and raise on failure.
636 ///
637 /// Inherent method on LuaState shadows the `LuaStateStubExt::opt_arg_integer`
638 /// trait default so the `todo!()` shim never fires. Implemented with the
639 /// free-function helpers in this file rather than `auxlib::opt_integer`
640 /// because the latter routes through `state.is_none_or_nil` and
641 /// `state.to_integer_x`, which are themselves stubbed.
642 pub fn opt_arg_integer(&mut self, arg: i32, def: i64) -> Result<i64, LuaError> {
643 match lua_type_at(self, arg) {
644 LuaType::None | LuaType::Nil => Ok(def),
645 _ => match to_integer_x(self, arg) {
646 Some(d) => Ok(d),
647 None => {
648 if is_number(self, arg) {
649 Err(LuaError::arg_error(
650 arg,
651 "number has no integer representation",
652 ))
653 } else {
654 let got = index_to_value(self, arg);
655 Err(LuaError::type_arg_error(arg, "number", &got))
656 }
657 }
658 },
659 }
660 }
661
662 /// C: `lua_pcall(L, nargs, nresults, msgh)` — a convenience wrapper over
663 /// `lua_pcallk` with no continuation. Defers to the existing `pcall_k`
664 /// free function, which routes through `protected_call_raw` and
665 /// surfaces any runtime / syntax error as `Err(LuaError::Runtime|Syntax)`.
666 ///
667 /// Inherent method on LuaState shadows the `LuaStateStubExt::protected_call`
668 /// trait default so the `todo!()` shim never fires.
669 pub fn protected_call(&mut self, nargs: i32, nresults: i32, msgh: i32) -> Result<(), LuaError> {
670 pcall_k(self, nargs, nresults, msgh, 0, None).map(|_| ())
671 }
672
673 /// C: `lua_pcallk(L, nargs, nresults, errfunc, ctx, k)` — yieldable
674 /// protected call. When `k` is set and the thread is yieldable, an
675 /// inner yield propagates as `LuaError::Yield` and the continuation
676 /// fires on resume via `finishCcall` → `finishpcallk`.
677 pub fn protected_call_k(
678 &mut self,
679 nargs: i32,
680 nresults: i32,
681 msgh: i32,
682 ctx: isize,
683 k: Option<crate::state::LuaKFunction>,
684 ) -> Result<(), LuaError> {
685 pcall_k(self, nargs, nresults, msgh, ctx, k).map(|_| ())
686 }
687
688 pub fn push_string(&mut self, s: &[u8]) -> Result<(), LuaError> {
689 push_lstring(self, s)?;
690 Ok(())
691 }
692
693 pub fn push_c_closure(
694 &mut self,
695 f: fn(&mut LuaState) -> Result<usize, LuaError>,
696 n: i32,
697 ) -> Result<(), LuaError> {
698 push_cclosure(self, f, n)
699 }
700
701 pub fn raw_seti(&mut self, idx: i32, n: i64) -> Result<(), LuaError> {
702 raw_set_i(self, idx, n)
703 }
704
705 pub fn table_set_i(&mut self, idx: i32, n: i64) -> Result<(), LuaError> {
706 set_i(self, idx, n)
707 }
708
709 /// Get `t[n]` where `t` is a pre-resolved `LuaValue`, bypassing stack-index
710 /// resolution. Use this in tight loops that operate on the same table
711 /// repeatedly to avoid the `index_to_value` call per iteration.
712 pub fn table_get_i_value(&mut self, t: &LuaValue, n: i64) -> Result<LuaType, LuaError> {
713 get_i_value(self, t, n)
714 }
715
716 /// Set `t[n] = stack_top` (then pop) where `t` is a pre-resolved `LuaValue`,
717 /// bypassing stack-index resolution. Use this in tight loops that operate on
718 /// the same table repeatedly to avoid the `index_to_value` call per iteration.
719 pub fn table_set_i_value(&mut self, t: &LuaValue, n: i64) -> Result<(), LuaError> {
720 set_i_value(self, t, n)
721 }
722
723 pub fn create_table(&mut self, narr: i32, nrec: i32) -> Result<(), LuaError> {
724 create_table(self, narr, nrec)
725 }
726
727 /// Pop the value on top of the stack and store it in the registry under
728 /// the string `key`.
729 ///
730 /// C: `lua_setfield(L, LUA_REGISTRYINDEX, key)`.
731 pub fn registry_set(&mut self, key: &[u8]) -> Result<(), LuaError> {
732 set_field(self, LUA_REGISTRYINDEX, key)
733 }
734
735 /// Create a new metatable in the registry under key `tname`. Leaves the
736 /// new metatable on top of the stack and returns `true` when newly
737 /// created. If `registry[tname]` already exists, leaves it on top of the
738 /// stack and returns `false`.
739 ///
740 /// C: `LUALIB_API int luaL_newmetatable(lua_State *L, const char *tname)`
741 pub fn new_metatable(&mut self, tname: &[u8]) -> Result<bool, LuaError> {
742 if get_field(self, LUA_REGISTRYINDEX, tname)? != LuaType::Nil {
743 return Ok(false);
744 }
745 self.pop_n(1);
746 create_table(self, 0, 2)?;
747 push_lstring(self, tname)?;
748 set_field(self, -2, b"__name")?;
749 push_value(self, -1);
750 set_field(self, LUA_REGISTRYINDEX, tname)?;
751 Ok(true)
752 }
753
754 /// Create a new library table sized for `funcs` and register each entry as
755 /// a closure field on it. Leaves the table on the top of the stack.
756 ///
757 /// C: `luaL_newlib(L, l)` =
758 /// `luaL_checkversion(L), luaL_newlibtable(L,l), luaL_setfuncs(L,l,0)`.
759 /// `luaL_checkversion` is a no-op here (no ABI-version mismatch is
760 /// possible inside the Rust port).
761 pub fn new_lib(
762 &mut self,
763 funcs: &[(&[u8], LuaCFunction)],
764 ) -> Result<(), LuaError> {
765 create_table(self, 0, funcs.len() as i32)?;
766 for (name, f) in funcs {
767 push_cclosure(self, *f, 0)?;
768 set_field(self, -2, name)?;
769 }
770 Ok(())
771 }
772
773 /// Create and populate a library table for `funcs`, leaving it on top of
774 /// the stack. The `_name` argument is informational and matches the
775 /// `luaL_register`-style call sites in the Phase-A stdlib; the actual
776 /// global binding for the library happens later via `luaL_requiref`.
777 ///
778 /// C: `luaL_newlib(L, l)`.
779 pub fn register_lib(
780 &mut self,
781 _name: &[u8],
782 funcs: &[(&[u8], LuaCFunction)],
783 ) -> Result<(), LuaError> {
784 self.new_lib(funcs)
785 }
786
787 /// Create a new empty table presized to hold every entry in `funcs`, and
788 /// leave it on top of the stack. No registration is performed — callers
789 /// typically follow up with `set_funcs` / `set_funcs_with_upvalues` to
790 /// populate the table.
791 ///
792 /// C: `luaL_newlibtable(L, l)` =
793 /// `lua_createtable(L, 0, sizeof(l)/sizeof((l)[0]) - 1)`. The C macro's
794 /// `- 1` discounts the sentinel `{NULL, NULL}` entry; the Rust slice has
795 /// no sentinel, so we use `funcs.len()` directly.
796 pub fn new_lib_table(
797 &mut self,
798 funcs: &[(&[u8], LuaCFunction)],
799 ) -> Result<(), LuaError> {
800 create_table(self, 0, funcs.len() as i32)
801 }
802
803 /// Register each entry in `funcs` as a C closure on the table at index
804 /// `-(nup + 2)`, sharing the `nup` values currently on top of the stack
805 /// as upvalues. The upvalues are popped at the end.
806 ///
807 /// C: `luaL_setfuncs(L, l, nup)`.
808 pub fn set_funcs_with_upvalues(
809 &mut self,
810 funcs: &[(&[u8], LuaCFunction)],
811 nup: i32,
812 ) -> Result<(), LuaError> {
813 check_stack(self, nup);
814 for (name, f) in funcs {
815 for _ in 0..nup {
816 push_value(self, -nup);
817 }
818 push_cclosure(self, *f, nup)?;
819 set_field(self, -(nup + 2), name)?;
820 }
821 self.pop_n(nup as usize);
822 Ok(())
823 }
824
825 pub fn set_metatable(&mut self, objindex: i32) -> Result<(), LuaError> {
826 set_metatable(self, objindex)?;
827 Ok(())
828 }
829
830 /// Fetch the metatable registered under `name` in the registry and assign
831 /// it as the metatable of the value currently on top of the stack. The
832 /// fetched metatable is popped after assignment, leaving the original top
833 /// value in place.
834 ///
835 /// C: `LUALIB_API void luaL_setmetatable(lua_State *L, const char *tname)`
836 pub fn set_metatable_by_name(&mut self, name: &[u8]) -> Result<(), LuaError> {
837 get_field(self, LUA_REGISTRYINDEX, name)?;
838 set_metatable(self, -2)?;
839 Ok(())
840 }
841
842 /// Ensure `registry[name]` is a table; push it onto the stack.
843 /// Returns `true` if the table already existed, `false` if newly created.
844 ///
845 /// C: `luaL_getsubtable(L, LUA_REGISTRYINDEX, name)`
846 pub fn get_subtable_registry(&mut self, name: &[u8]) -> Result<bool, LuaError> {
847 if get_field(self, LUA_REGISTRYINDEX, name)? == LuaType::Table {
848 return Ok(true);
849 }
850 self.pop_n(1);
851 let idx = abs_index(self, LUA_REGISTRYINDEX);
852 let new_tbl = self.new_table();
853 self.push(LuaValue::Table(new_tbl));
854 push_value(self, -1);
855 set_field(self, idx, name)?;
856 Ok(false)
857 }
858
859 /// Allocate a fresh full-userdata block of `size` bytes with `nuvalue`
860 /// nil-initialised user-value slots, push it on the stack, and return a
861 /// `GcRef` to it. The `_name` parameter is advisory — callers typically
862 /// follow up with `set_metatable_by_name(name)` to attach the registered
863 /// metatable.
864 ///
865 /// C-correspondent: `lua_newuserdatauv(L, size, nuvalue)` (no name
866 /// parameter on the C side; the Rust signature carries it for callers'
867 /// convenience).
868 pub fn new_userdata_typed(
869 &mut self,
870 _name: &[u8],
871 size: usize,
872 nuvalue: i32,
873 ) -> Result<GcRef<LuaUserData>, LuaError> {
874 debug_assert!(nuvalue >= 0 && nuvalue < u16::MAX as i32, "invalid value");
875 // TODO(D-1c-bridge): state.new_userdata is still todo!(); keep direct alloc
876 let u = GcRef::new(LuaUserData {
877 data: vec![0u8; size].into_boxed_slice(),
878 uv: vec![LuaValue::Nil; nuvalue as usize],
879 metatable: std::cell::RefCell::new(None),
880 });
881 self.push(LuaValue::UserData(u.clone()));
882 self.gc().check_step();
883 Ok(u)
884 }
885}
886
887// ── access functions (stack → Rust) ──────────────────────────────────────────
888
889// C: LUA_API int lua_type (lua_State *L, int idx)
890pub fn lua_type_at(state: &LuaState, idx: i32) -> LuaType {
891 // C: return (isvalid(L, o) ? ttype(o) : LUA_TNONE);
892 if !is_valid_index(state, idx) {
893 return LuaType::None;
894 }
895 index_to_value(state, idx).base_type()
896}
897
898// C: LUA_API const char *lua_typename (lua_State *L, int t)
899pub fn type_name(_state: &LuaState, t: LuaType) -> &'static [u8] {
900 // C: UNUSED(L); return ttypename(t);
901 t.type_name()
902}
903
904// C: LUA_API int lua_iscfunction (lua_State *L, int idx)
905pub fn is_cfunction(state: &LuaState, idx: i32) -> bool {
906 // C: return (ttislcf(o) || (ttisCclosure(o)));
907 let o = index_to_value(state, idx);
908 matches!(o, LuaValue::Function(LuaClosure::LightC(_)) | LuaValue::Function(LuaClosure::C(_)))
909}
910
911// C: LUA_API int lua_isinteger (lua_State *L, int idx)
912pub fn is_integer(state: &LuaState, idx: i32) -> bool {
913 // C: return ttisinteger(o);
914 let o = index_to_value(state, idx);
915 matches!(o, LuaValue::Int(_))
916}
917
918// C: LUA_API int lua_isnumber (lua_State *L, int idx)
919pub fn is_number(state: &LuaState, idx: i32) -> bool {
920 // C: return tonumber(o, &n);
921 let o = index_to_value(state, idx);
922 o.to_number_with_strconv().is_some()
923}
924
925// C: LUA_API int lua_isstring (lua_State *L, int idx)
926pub fn is_string(state: &LuaState, idx: i32) -> bool {
927 // C: return (ttisstring(o) || cvt2str(o));
928 let o = index_to_value(state, idx);
929 matches!(o, LuaValue::Str(_) | LuaValue::Int(_) | LuaValue::Float(_))
930}
931
932// C: LUA_API int lua_isuserdata (lua_State *L, int idx)
933pub fn is_userdata(state: &LuaState, idx: i32) -> bool {
934 // C: return (ttisfulluserdata(o) || ttislightuserdata(o));
935 let o = index_to_value(state, idx);
936 matches!(o, LuaValue::UserData(_) | LuaValue::LightUserData(_))
937}
938
939// C: LUA_API int lua_rawequal (lua_State *L, int index1, int index2)
940pub fn raw_equal(state: &LuaState, index1: i32, index2: i32) -> bool {
941 // C: return (isvalid(L, o1) && isvalid(L, o2)) ? luaV_rawequalobj(o1, o2) : 0;
942 if !is_valid_index(state, index1) || !is_valid_index(state, index2) {
943 return false;
944 }
945 let o1 = index_to_value(state, index1);
946 let o2 = index_to_value(state, index2);
947 state.equal_obj(None, &o1, &o2)
948}
949
950// C: LUA_API void lua_arith (lua_State *L, int op)
951// PORT NOTE: LUA_OPUNM / LUA_OPBNOT are unary; all others are binary.
952pub fn arith(state: &mut LuaState, op: i32) -> Result<(), LuaError> {
953 // C: if (op != LUA_OPUNM && op != LUA_OPBNOT) api_checknelems(L, 2);
954 // C: else { api_checknelems(L, 1); setobjs2s copy; api_incr_top }
955 // TODO(port): LUA_OPUNM and LUA_OPBNOT constant values not yet defined in
956 // Rust; using raw i32 comparison for now.
957 const LUA_OPUNM: i32 = 12;
958 const LUA_OPBNOT: i32 = 14;
959 if op == LUA_OPUNM || op == LUA_OPBNOT {
960 // unary — duplicate top as fake second operand
961 let top_val = state.get_at(state.top_idx() - 1);
962 state.push(top_val);
963 }
964 // C: luaO_arith(L, op, s2v(L->top.p - 2), s2v(L->top.p - 1), L->top.p - 2);
965 let top = state.top_idx();
966 let a = state.get_at(top - 2);
967 let b = state.get_at(top - 1);
968 let result = state.arith_op(op, &a, &b)?;
969 state.set_at(top - 2, result);
970 // C: L->top.p--;
971 state.pop();
972 Ok(())
973}
974
975// C: LUA_API int lua_compare (lua_State *L, int index1, int index2, int op)
976pub fn compare(state: &mut LuaState, index1: i32, index2: i32, op: i32) -> Result<bool, LuaError> {
977 // C: may call tag method (hence lua_lock)
978 let valid = is_valid_index(state, index1) && is_valid_index(state, index2);
979 let o1 = index_to_value(state, index1);
980 let o2 = index_to_value(state, index2);
981 if valid {
982 // C: LUA_OPEQ=0, LUA_OPLT=1, LUA_OPLE=2
983 match op {
984 0 => Ok(state.equal_obj_with_tm(&o1, &o2)?),
985 1 => state.less_than(&o1, &o2),
986 2 => state.less_equal(&o1, &o2),
987 _ => {
988 debug_assert!(false, "invalid option");
989 Ok(false)
990 }
991 }
992 } else {
993 Ok(false)
994 }
995}
996
997// C: LUA_API size_t lua_stringtonumber (lua_State *L, const char *s)
998pub fn string_to_number(state: &mut LuaState, s: &[u8]) -> usize {
999 // C: size_t sz = luaO_str2num(s, s2v(L->top.p));
1000 // C: if (sz != 0) api_incr_top(L);
1001 // TODO(port): luaO_str2num not yet translated; push result if successful.
1002 match state.str_to_num(s) {
1003 Some((val, consumed)) => {
1004 state.push(val);
1005 consumed
1006 }
1007 None => 0,
1008 }
1009}
1010
1011// C: LUA_API lua_Number lua_tonumberx (lua_State *L, int idx, int *pisnum)
1012pub fn to_number_x(state: &LuaState, idx: i32) -> Option<f64> {
1013 // C: int isnum = tonumber(o, &n);
1014 let o = index_to_value(state, idx);
1015 o.to_number_with_strconv()
1016}
1017
1018// C: LUA_API lua_Integer lua_tointegerx (lua_State *L, int idx, int *pisnum)
1019pub fn to_integer_x(state: &LuaState, idx: i32) -> Option<i64> {
1020 // C: int isnum = tointeger(o, &res);
1021 let o = index_to_value(state, idx);
1022 o.to_integer_with_strconv()
1023}
1024
1025// C: LUA_API int lua_toboolean (lua_State *L, int idx)
1026pub fn to_boolean(state: &LuaState, idx: i32) -> bool {
1027 // C: return !l_isfalse(o);
1028 let o = index_to_value(state, idx);
1029 !matches!(o, LuaValue::Nil | LuaValue::Bool(false))
1030}
1031
1032// C: LUA_API const char *lua_tolstring (lua_State *L, int idx, size_t *len)
1033// PORT NOTE: returns Option<GcRef<LuaString>> instead of raw C pointer+len.
1034pub fn to_lua_string(
1035 state: &mut LuaState,
1036 idx: i32,
1037) -> Result<Option<GcRef<LuaString>>, LuaError> {
1038 let o = index_to_value(state, idx);
1039 if let LuaValue::Str(s) = &o {
1040 return Ok(Some(s.clone()));
1041 }
1042 // C: if (!cvt2str(o)) return NULL; (only numbers are convertible)
1043 if !matches!(o, LuaValue::Int(_) | LuaValue::Float(_)) {
1044 return Ok(None);
1045 }
1046 // C: luaO_tostring(L, o); luaC_checkGC(L);
1047 // C: o = index2value(L, idx); /* stack may have moved */
1048 state.obj_to_string(idx)?;
1049 // C: return getstr(tsvalue(o));
1050 state.gc().check_step();
1051 let updated = index_to_value(state, idx);
1052 if let LuaValue::Str(s) = updated {
1053 Ok(Some(s))
1054 } else {
1055 Ok(None)
1056 }
1057}
1058
1059// C: LUA_API lua_Unsigned lua_rawlen (lua_State *L, int idx)
1060pub fn raw_len(state: &LuaState, idx: i32) -> u64 {
1061 let o = index_to_value(state, idx);
1062 // C: switch (ttypetag(o)) { case LUA_VSHRSTR: ... LUA_VLNGSTR: ... LUA_VUSERDATA: ... LUA_VTABLE: ... }
1063 match &o {
1064 LuaValue::Str(s) => s.len() as u64,
1065 LuaValue::UserData(u) => u.len() as u64,
1066 LuaValue::Table(t) => state.table_getn(t) as u64,
1067 _ => 0,
1068 }
1069}
1070
1071// C: LUA_API lua_CFunction lua_tocfunction (lua_State *L, int idx)
1072pub fn to_cfunction(
1073 state: &LuaState,
1074 idx: i32,
1075) -> Option<fn(&mut LuaState) -> Result<usize, LuaError>> {
1076 let o = index_to_value(state, idx);
1077 match o {
1078 // C: if (ttislcf(o)) return fvalue(o);
1079 // TODO(phase-b): lua-types `LuaClosure::LightC` carries a placeholder
1080 // `fn() -> i32` until it can reference `LuaState`. The real cast
1081 // happens once lua-types absorbs the LuaState-aware signature.
1082 LuaValue::Function(LuaClosure::LightC(_f)) => None,
1083 // C: else if (ttisCclosure(o)) return clCvalue(o)->f;
1084 LuaValue::Function(LuaClosure::C(_ccl)) => None,
1085 _ => None,
1086 }
1087}
1088
1089// C: l_sinline void *touserdata (const TValue *o)
1090#[inline]
1091fn to_userdata_ptr(o: &LuaValue) -> Option<*mut core::ffi::c_void> {
1092 match o {
1093 // C: case LUA_TUSERDATA: return getudatamem(uvalue(o));
1094 LuaValue::UserData(u) => {
1095 // TODO(port): getudatamem returns a pointer to the raw byte payload of Udata.
1096 // In Rust, LuaUserData carries a Box<[u8]>; we'd need to return a raw ptr.
1097 // This is only safe inside lua-gc; stubbing with None for Phase A.
1098 let _ = u;
1099 None
1100 }
1101 // C: case LUA_TLIGHTUSERDATA: return pvalue(o);
1102 LuaValue::LightUserData(p) => Some(*p),
1103 _ => None,
1104 }
1105}
1106
1107// C: LUA_API void *lua_touserdata (lua_State *L, int idx)
1108pub fn to_userdata(state: &LuaState, idx: i32) -> Option<*mut core::ffi::c_void> {
1109 let o = index_to_value(state, idx);
1110 to_userdata_ptr(&o)
1111}
1112
1113// C: LUA_API lua_State *lua_tothread (lua_State *L, int idx)
1114pub fn to_thread(state: &LuaState, idx: i32) -> Option<GcRef<lua_types::value::LuaThread>> {
1115 // C: return (!ttisthread(o)) ? NULL : thvalue(o);
1116 // TODO(phase-b): lua-vm's rich LuaState is not the same type as
1117 // lua_types::value::LuaThread; the latter is a placeholder. Resolve in
1118 // Phase B by unifying thread types.
1119 let o = index_to_value(state, idx);
1120 if let LuaValue::Thread(t) = o {
1121 Some(t)
1122 } else {
1123 None
1124 }
1125}
1126
1127// C: LUA_API const void *lua_topointer (lua_State *L, int idx)
1128// PORT NOTE: returns a usize (opaque identity) rather than a raw void*.
1129// Raw pointers are only allowed in lua-gc / lua-coro.
1130pub fn to_pointer(state: &LuaState, idx: i32) -> Option<usize> {
1131 let o = index_to_value(state, idx);
1132 // C: case LUA_VLCF: return cast_voidp(cast_sizet(fvalue(o)));
1133 // C: case LUA_VUSERDATA: case LUA_VLIGHTUSERDATA: return touserdata(o);
1134 // C: default: if (iscollectable(o)) return gcvalue(o); else return NULL;
1135 // TODO(port): returning a raw pointer here is not safe outside lua-gc.
1136 // Returning the GC identity as a usize for opaque pointer identity purposes.
1137 match &o {
1138 LuaValue::Function(LuaClosure::LightC(f)) => Some(*f as usize),
1139 LuaValue::LightUserData(p) => Some(*p as usize),
1140 LuaValue::Str(s) => Some(GcRef::identity(s)),
1141 LuaValue::Table(t) => Some(GcRef::identity(t)),
1142 LuaValue::Function(LuaClosure::Lua(f)) => Some(GcRef::identity(f)),
1143 LuaValue::Function(LuaClosure::C(f)) => Some(GcRef::identity(f)),
1144 LuaValue::UserData(u) => Some(GcRef::identity(u)),
1145 LuaValue::Thread(t) => Some(GcRef::identity(t)),
1146 _ => None,
1147 }
1148}
1149
1150// ── push functions (Rust → stack) ────────────────────────────────────────────
1151
1152// C: LUA_API void lua_pushnil (lua_State *L)
1153pub fn push_nil(state: &mut LuaState) {
1154 // C: setnilvalue(s2v(L->top.p)); api_incr_top(L);
1155 state.push(LuaValue::Nil);
1156}
1157
1158// C: LUA_API void lua_pushnumber (lua_State *L, lua_Number n)
1159pub fn push_number(state: &mut LuaState, n: f64) {
1160 // C: setfltvalue(s2v(L->top.p), n); api_incr_top(L);
1161 state.push(LuaValue::Float(n));
1162}
1163
1164// C: LUA_API void lua_pushinteger (lua_State *L, lua_Integer n)
1165pub fn push_integer(state: &mut LuaState, n: i64) {
1166 // C: setivalue(s2v(L->top.p), n); api_incr_top(L);
1167 state.push(LuaValue::Int(n));
1168}
1169
1170// C: LUA_API const char *lua_pushlstring (lua_State *L, const char *s, size_t len)
1171// PORT NOTE: returns the interned LuaString instead of a raw C pointer.
1172pub fn push_lstring(state: &mut LuaState, s: &[u8]) -> Result<GcRef<LuaString>, LuaError> {
1173 // C: ts = (len == 0) ? luaS_new(L, "") : luaS_newlstr(L, s, len);
1174 let ts = state.intern_str(s)?;
1175 // C: setsvalue2s(L, L->top.p, ts); api_incr_top(L);
1176 state.push(LuaValue::Str(ts.clone()));
1177 // C: luaC_checkGC(L);
1178 state.gc().check_step();
1179 Ok(ts)
1180}
1181
1182// C: LUA_API const char *lua_pushstring (lua_State *L, const char *s)
1183pub fn push_string(state: &mut LuaState, s: Option<&[u8]>) -> Result<Option<GcRef<LuaString>>, LuaError> {
1184 // C: if (s == NULL) setnilvalue(s2v(L->top.p));
1185 match s {
1186 None => {
1187 state.push(LuaValue::Nil);
1188 state.gc().check_step();
1189 Ok(None)
1190 }
1191 Some(bytes) => {
1192 let ts = state.intern_str(bytes)?;
1193 state.push(LuaValue::Str(ts.clone()));
1194 state.gc().check_step();
1195 Ok(Some(ts))
1196 }
1197 }
1198}
1199
1200// C: LUA_API const char *lua_pushvfstring (lua_State *L, const char *fmt, va_list argp)
1201// PORT NOTE: va_list is not representable in safe Rust; callers pass a pre-formatted &[u8].
1202// TODO(port): lua_pushvfstring uses C varargs (va_list); no direct Rust equivalent.
1203// The Rust API uses state.push_fstring(format_args!(...)) instead.
1204pub fn push_vfstring(state: &mut LuaState, formatted: &[u8]) -> Result<GcRef<LuaString>, LuaError> {
1205 let ts = state.intern_str(formatted)?;
1206 state.push(LuaValue::Str(ts.clone()));
1207 state.gc().check_step();
1208 Ok(ts)
1209}
1210
1211// C: LUA_API const char *lua_pushfstring (lua_State *L, const char *fmt, ...)
1212// PORT NOTE: C varargs not used; callers use format_args! and push_fstring.
1213pub fn push_fstring(state: &mut LuaState, formatted: &[u8]) -> Result<GcRef<LuaString>, LuaError> {
1214 push_vfstring(state, formatted)
1215}
1216
1217// C: LUA_API void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n)
1218pub fn push_cclosure(
1219 state: &mut LuaState,
1220 f: fn(&mut LuaState) -> Result<usize, LuaError>,
1221 n: i32,
1222) -> Result<(), LuaError> {
1223 // C: lua_lock(L);
1224 // if (n == 0) { setfvalue(s2v(L->top.p), fn); api_incr_top(L); }
1225 // else { api_checknelems(L, n); api_check(L, n <= MAXUPVAL, ...);
1226 // cl = luaF_newCclosure(L, n); cl->f = fn;
1227 // L->top.p -= n;
1228 // while (n--) setobj2n(L, &cl->upvalue[n], s2v(L->top.p + n));
1229 // setclCvalue(L, s2v(L->top.p), cl); api_incr_top(L);
1230 // luaC_checkGC(L); }
1231 // lua_unlock(L);
1232 //
1233 // PORT NOTE: `LuaClosure::LightC` and `LuaCClosure` carry a `LuaCFnPtr`
1234 // (a `usize` index into `GlobalState.c_functions`) rather than the raw
1235 // function pointer, because lua-types cannot reference `LuaState`. We
1236 // register `f` in the per-state registry and store the resulting index.
1237 let idx: lua_types::closure::LuaCFnPtr = {
1238 let mut g = state.global_mut();
1239 if n == 0 {
1240 match g.c_functions.iter().position(|&existing| existing == f) {
1241 Some(i) => i,
1242 None => {
1243 let i = g.c_functions.len();
1244 g.c_functions.push(f);
1245 i
1246 }
1247 }
1248 } else {
1249 let i = g.c_functions.len();
1250 g.c_functions.push(f);
1251 i
1252 }
1253 };
1254 if n == 0 {
1255 state.push(LuaValue::Function(LuaClosure::LightC(idx)));
1256 } else {
1257 debug_assert!(n > 0 && (n as u32) <= MAX_UPVAL as u32, "upvalue index too large");
1258 let n_usize = n as usize;
1259 let top = state.top_idx();
1260 debug_assert!((top.0 as usize) >= n_usize, "not enough elements on stack");
1261 let base = top.0 as usize - n_usize;
1262 let mut upvalues: Vec<LuaValue> = Vec::with_capacity(n_usize);
1263 for i in 0..n_usize {
1264 upvalues.push(state.get_at(crate::state::StackIdx((base + i) as u32)));
1265 }
1266 state.pop_n(n_usize);
1267 // TODO(D-1c-bridge): state.new_c_closure is still todo!(); keep direct alloc
1268 let cl = LuaClosure::C(GcRef::new(lua_types::closure::LuaCClosure {
1269 func: idx,
1270 upvalues,
1271 }));
1272 state.push(LuaValue::Function(cl));
1273 state.gc().check_step();
1274 }
1275 Ok(())
1276}
1277
1278// C: LUA_API void lua_pushboolean (lua_State *L, int b)
1279pub fn push_boolean(state: &mut LuaState, b: bool) {
1280 // C: if (b) setbtvalue(...); else setbfvalue(...);
1281 state.push(LuaValue::Bool(b));
1282}
1283
1284// C: LUA_API void lua_pushlightuserdata (lua_State *L, void *p)
1285pub fn push_light_userdata(state: &mut LuaState, p: *mut core::ffi::c_void) {
1286 // C: setpvalue(s2v(L->top.p), p); api_incr_top(L);
1287 state.push(LuaValue::LightUserData(p));
1288}
1289
1290// C: LUA_API int lua_pushthread (lua_State *L)
1291// Returns true if pushed thread is the main thread.
1292pub fn push_thread(state: &mut LuaState) -> bool {
1293 let (value, is_main) = {
1294 let g = state.global();
1295 let id = g.current_thread_id;
1296 let v = g
1297 .thread_value_for(id)
1298 .expect("current_thread_id must always resolve to a registered thread");
1299 (v, id == g.main_thread_id)
1300 };
1301 state.push(LuaValue::Thread(value));
1302 is_main
1303}
1304
1305// ── get functions (Lua → stack) ───────────────────────────────────────────────
1306
1307// C: l_sinline int auxgetstr (lua_State *L, const TValue *t, const char *k)
1308fn aux_get_str(state: &mut LuaState, t: LuaValue, k: &[u8]) -> Result<LuaType, LuaError> {
1309 // C: TString *str = luaS_new(L, k);
1310 let str_val = {
1311 let ts = state.intern_str(k)?;
1312 LuaValue::Str(ts)
1313 };
1314 // C: if (luaV_fastget(L, t, str, slot, luaH_getstr)) { setobj2s push slot }
1315 // C: else { push str; luaV_finishget(...) }
1316 // TODO(port): luaV_fastget / luaV_finishget not yet translated; using
1317 // a simplified table_get that may miss metamethod chains.
1318 let result = state.table_get_with_tm(&t, &str_val)?;
1319 state.push(result);
1320 let top = state.top_idx();
1321 Ok(state.get_at(top - 1).base_type())
1322}
1323
1324// C: #define getGtable(L) (&hvalue(&G(L)->l_registry)->array[LUA_RIDX_GLOBALS - 1])
1325fn get_global_table(state: &LuaState) -> LuaValue {
1326 // PORT NOTE (phase-b-reconcile): The lua-types LuaTable placeholder has
1327 // no storage, so we cannot fetch the globals table from the registry's
1328 // array slot. init_registry now stashes globals in a direct
1329 // GlobalState field; read it from there until the LuaTable placeholder
1330 // reconciles with lua-vm::table::LuaTable.
1331 state.global().globals.clone()
1332}
1333
1334// C: LUA_API int lua_getglobal (lua_State *L, const char *name)
1335pub fn get_global(state: &mut LuaState, name: &[u8]) -> Result<LuaType, LuaError> {
1336 // C: G = getGtable(L); return auxgetstr(L, G, name);
1337 let g = get_global_table(state);
1338 aux_get_str(state, g, name)
1339}
1340
1341// C: LUA_API int lua_gettable (lua_State *L, int idx)
1342pub fn get_table(state: &mut LuaState, idx: i32) -> Result<LuaType, LuaError> {
1343 // C: t = index2value(L, idx); key is at top-1
1344 let t = index_to_value(state, idx);
1345 let top = state.top_idx();
1346 let key = state.get_at(top - 1);
1347 // C: if (luaV_fastget(L, t, s2v(L->top.p - 1), slot, luaH_get)) ...
1348 // C: else luaV_finishget(L, t, s2v(L->top.p - 1), L->top.p - 1, slot);
1349 let result = state.table_get_with_tm(&t, &key)?;
1350 state.set_at(top - 1, result);
1351 let val = state.get_at(top - 1);
1352 Ok(val.base_type())
1353}
1354
1355// C: LUA_API int lua_getfield (lua_State *L, int idx, const char *k)
1356pub fn get_field(state: &mut LuaState, idx: i32, k: &[u8]) -> Result<LuaType, LuaError> {
1357 // C: return auxgetstr(L, index2value(L, idx), k);
1358 let t = index_to_value(state, idx);
1359 aux_get_str(state, t, k)
1360}
1361
1362// C: LUA_API int lua_geti (lua_State *L, int idx, lua_Integer n)
1363pub fn get_i(state: &mut LuaState, idx: i32, n: i64) -> Result<LuaType, LuaError> {
1364 // C: t = index2value(L, idx);
1365 // C: if (luaV_fastgeti(L, t, n, slot)) setobj2s push slot
1366 // C: else { TValue aux; setivalue(&aux, n); luaV_finishget(...) }
1367 let t = index_to_value(state, idx);
1368 let key = LuaValue::Int(n);
1369 let result = state.table_get_with_tm(&t, &key)?;
1370 state.push(result);
1371 let top = state.top_idx();
1372 Ok(state.get_at(top - 1).base_type())
1373}
1374
1375/// Variant of `get_i` that accepts a pre-resolved table value instead of a
1376/// stack index. Callers that invoke `get_i` repeatedly on the same table
1377/// (e.g. the shift loops in `table.remove` / `table.insert`) should resolve
1378/// the table once and use this function to avoid calling `index_to_value`
1379/// on every iteration.
1380pub fn get_i_value(state: &mut LuaState, t: &LuaValue, n: i64) -> Result<LuaType, LuaError> {
1381 let key = LuaValue::Int(n);
1382 let result = state.table_get_with_tm(t, &key)?;
1383 state.push(result);
1384 let top = state.top_idx();
1385 Ok(state.get_at(top - 1).base_type())
1386}
1387
1388// C: l_sinline int finishrawget (lua_State *L, const TValue *val)
1389fn finish_raw_get(state: &mut LuaState, val: Option<LuaValue>) -> LuaType {
1390 // C: if (isempty(val)) setnilvalue(s2v(L->top.p)); else setobj2s(...)
1391 let v = val.unwrap_or(LuaValue::Nil);
1392 state.push(v);
1393 let top = state.top_idx();
1394 state.get_at(top - 1).base_type()
1395}
1396
1397// C: static Table *gettable (lua_State *L, int idx)
1398fn get_table_value(state: &LuaState, idx: i32) -> Option<GcRef<LuaTable>> {
1399 // C: TValue *t = index2value(L, idx); api_check(L, ttistable(t), "table expected");
1400 let t = index_to_value(state, idx);
1401 debug_assert!(matches!(t, LuaValue::Table(_)), "table expected");
1402 if let LuaValue::Table(tbl) = t {
1403 Some(tbl)
1404 } else {
1405 None
1406 }
1407}
1408
1409// C: LUA_API int lua_rawget (lua_State *L, int idx)
1410pub fn raw_get(state: &mut LuaState, idx: i32) -> LuaType {
1411 // C: t = gettable(L, idx); val = luaH_get(t, s2v(L->top.p - 1)); L->top.p--;
1412 let t = get_table_value(state, idx);
1413 let top = state.top_idx();
1414 let key = state.get_at(top - 1);
1415 let val = t.as_ref().map(|tbl| tbl.get(&key));
1416 state.set_top_idx(top - 1);
1417 finish_raw_get(state, val)
1418}
1419
1420// C: LUA_API int lua_rawgeti (lua_State *L, int idx, lua_Integer n)
1421pub fn raw_get_i(state: &mut LuaState, idx: i32, n: i64) -> LuaType {
1422 // C: t = gettable(L, idx); return finishrawget(L, luaH_getint(t, n));
1423 let t = get_table_value(state, idx);
1424 let val = t.as_ref().map(|tbl| tbl.get_int(n));
1425 finish_raw_get(state, val)
1426}
1427
1428// C: LUA_API int lua_rawgetp (lua_State *L, int idx, const void *p)
1429pub fn raw_get_p(state: &mut LuaState, idx: i32, p: *const core::ffi::c_void) -> LuaType {
1430 // C: setpvalue(&k, cast_voidp(p)); return finishrawget(L, luaH_get(t, &k));
1431 let t = get_table_value(state, idx);
1432 let key = LuaValue::LightUserData(p as *mut core::ffi::c_void);
1433 let val = t.as_ref().map(|tbl| tbl.get(&key));
1434 finish_raw_get(state, val)
1435}
1436
1437// C: LUA_API void lua_createtable (lua_State *L, int narray, int nrec)
1438pub fn create_table(state: &mut LuaState, narray: i32, nrec: i32) -> Result<(), LuaError> {
1439 // C: t = luaH_new(L); sethvalue2s ...; api_incr_top;
1440 // C: if (narray > 0 || nrec > 0) luaH_resize(L, t, narray, nrec);
1441 let t = state.new_table();
1442 if narray > 0 || nrec > 0 {
1443 t.resize(state, narray as usize, nrec as usize)?;
1444 }
1445 state.push(LuaValue::Table(t));
1446 state.gc().check_step();
1447 Ok(())
1448}
1449
1450// C: LUA_API int lua_getmetatable (lua_State *L, int objindex)
1451pub fn get_metatable(state: &mut LuaState, objindex: i32) -> bool {
1452 // C: obj = index2value(L, objindex);
1453 let obj = index_to_value(state, objindex);
1454 // C: switch (ttype(obj)) { LUA_TTABLE: ... LUA_TUSERDATA: ... default: G(L)->mt[ttype] }
1455 let mt: Option<GcRef<LuaTable>> = match &obj {
1456 LuaValue::Table(t) => t.metatable(),
1457 LuaValue::UserData(u) => u.metatable(),
1458 other => {
1459 let idx = other.base_type() as usize;
1460 state.global().mt[idx].clone()
1461 }
1462 };
1463 if let Some(mt_table) = mt {
1464 state.push(LuaValue::Table(mt_table));
1465 true
1466 } else {
1467 false
1468 }
1469}
1470
1471// C: LUA_API int lua_getiuservalue (lua_State *L, int idx, int n)
1472pub fn get_i_uservalue(state: &mut LuaState, idx: i32, n: i32) -> LuaType {
1473 // C: o = index2value(L, idx); api_check(L, ttisfulluserdata(o), ...);
1474 let o = index_to_value(state, idx);
1475 debug_assert!(matches!(o, LuaValue::UserData(_)), "full userdata expected");
1476 if let LuaValue::UserData(ref u) = o {
1477 let uv_count = u.uv.len() as i32;
1478 if n <= 0 || n > uv_count {
1479 // C: setnilvalue(s2v(L->top.p)); t = LUA_TNONE;
1480 state.push(LuaValue::Nil);
1481 LuaType::None
1482 } else {
1483 // C: setobj2s(L, L->top.p, &uvalue(o)->uv[n - 1].uv);
1484 let val = u.uv[(n - 1) as usize].clone();
1485 let t = val.base_type();
1486 state.push(val);
1487 t
1488 }
1489 } else {
1490 state.push(LuaValue::Nil);
1491 LuaType::None
1492 }
1493}
1494
1495// ── set functions (stack → Lua) ───────────────────────────────────────────────
1496
1497// C: static void auxsetstr (lua_State *L, const TValue *t, const char *k)
1498fn aux_set_str(state: &mut LuaState, t: LuaValue, k: &[u8]) -> Result<(), LuaError> {
1499 // C: TString *str = luaS_new(L, k); api_checknelems(L, 1);
1500 let str_val = {
1501 let ts = state.intern_str(k)?;
1502 LuaValue::Str(ts)
1503 };
1504 // C: if (luaV_fastget(L, t, str, slot, luaH_getstr))
1505 // luaV_finishfastset(L, t, slot, s2v(L->top.p - 1)); L->top.p--;
1506 // else { setsvalue2s L->top.p str; api_incr_top;
1507 // luaV_finishset(L, t, s2v(L->top.p-1), s2v(L->top.p-2), slot);
1508 // L->top.p -= 2; }
1509 let top = state.top_idx();
1510 let val = state.get_at(top - 1);
1511 state.table_set_with_tm(&t, str_val, val)?;
1512 state.pop();
1513 Ok(())
1514}
1515
1516// C: LUA_API void lua_setglobal (lua_State *L, const char *name)
1517pub fn set_global(state: &mut LuaState, name: &[u8]) -> Result<(), LuaError> {
1518 // C: G = getGtable(L); auxsetstr(L, G, name);
1519 let g = get_global_table(state);
1520 aux_set_str(state, g, name)
1521}
1522
1523// C: LUA_API void lua_settable (lua_State *L, int idx)
1524pub fn set_table(state: &mut LuaState, idx: i32) -> Result<(), LuaError> {
1525 // C: t = index2value(L, idx); api_checknelems(L, 2);
1526 // C: key at top-2, value at top-1
1527 let t = index_to_value(state, idx);
1528 let top = state.top_idx();
1529 let key = state.get_at(top - 2);
1530 let val = state.get_at(top - 1);
1531 state.table_set_with_tm(&t, key, val)?;
1532 // C: L->top.p -= 2;
1533 state.set_top_idx(top - 2);
1534 Ok(())
1535}
1536
1537// C: LUA_API void lua_setfield (lua_State *L, int idx, const char *k)
1538pub fn set_field(state: &mut LuaState, idx: i32, k: &[u8]) -> Result<(), LuaError> {
1539 // C: auxsetstr(L, index2value(L, idx), k);
1540 let t = index_to_value(state, idx);
1541 aux_set_str(state, t, k)
1542}
1543
1544// C: LUA_API void lua_seti (lua_State *L, int idx, lua_Integer n)
1545pub fn set_i(state: &mut LuaState, idx: i32, n: i64) -> Result<(), LuaError> {
1546 // C: t = index2value(L, idx); api_checknelems(L, 1);
1547 let t = index_to_value(state, idx);
1548 let top = state.top_idx();
1549 let val = state.get_at(top - 1);
1550 let key = LuaValue::Int(n);
1551 state.table_set_with_tm(&t, key, val)?;
1552 // C: L->top.p--;
1553 state.pop();
1554 Ok(())
1555}
1556
1557/// Variant of `set_i` that accepts a pre-resolved table value instead of a
1558/// stack index. Callers that invoke `set_i` repeatedly on the same table
1559/// (e.g. the shift loops in `table.remove` / `table.insert`) should resolve
1560/// the table once and use this function to avoid calling `index_to_value`
1561/// on every iteration.
1562pub fn set_i_value(state: &mut LuaState, t: &LuaValue, n: i64) -> Result<(), LuaError> {
1563 let top = state.top_idx();
1564 let val = state.get_at(top - 1);
1565 let key = LuaValue::Int(n);
1566 state.table_set_with_tm(t, key, val)?;
1567 state.pop();
1568 Ok(())
1569}
1570
1571// C: static void aux_rawset (lua_State *L, int idx, TValue *key, int n)
1572fn aux_raw_set(state: &mut LuaState, idx: i32, key: LuaValue, n: u32) -> Result<(), LuaError> {
1573 // C: t = gettable(L, idx); luaH_set(L, t, key, s2v(L->top.p - 1));
1574 let t = get_table_value(state, idx)
1575 .ok_or_else(|| LuaError::runtime(format_args!("table expected")))?;
1576 let top = state.top_idx();
1577 let val = state.get_at(top - 1);
1578 t.raw_set(state, key, val)?;
1579 t.invalidate_tm_cache();
1580 let top_val = state.get_at(top - 1);
1581 state.gc().barrier_back(&t, &top_val);
1582 // C: L->top.p -= n;
1583 state.set_top_idx(top - n as i32);
1584 Ok(())
1585}
1586
1587// C: LUA_API void lua_rawset (lua_State *L, int idx)
1588pub fn raw_set(state: &mut LuaState, idx: i32) -> Result<(), LuaError> {
1589 // C: aux_rawset(L, idx, s2v(L->top.p - 2), 2);
1590 let top = state.top_idx();
1591 let key = state.get_at(top - 2);
1592 aux_raw_set(state, idx, key, 2)
1593}
1594
1595// C: LUA_API void lua_rawsetp (lua_State *L, int idx, const void *p)
1596pub fn raw_set_p(state: &mut LuaState, idx: i32, p: *const core::ffi::c_void) -> Result<(), LuaError> {
1597 // C: setpvalue(&k, cast_voidp(p)); aux_rawset(L, idx, &k, 1);
1598 let key = LuaValue::LightUserData(p as *mut core::ffi::c_void);
1599 aux_raw_set(state, idx, key, 1)
1600}
1601
1602// C: LUA_API void lua_rawseti (lua_State *L, int idx, lua_Integer n)
1603pub fn raw_set_i(state: &mut LuaState, idx: i32, n: i64) -> Result<(), LuaError> {
1604 // C: t = gettable(L, idx); luaH_setint(L, t, n, s2v(L->top.p - 1));
1605 let t = get_table_value(state, idx)
1606 .ok_or_else(|| LuaError::runtime(format_args!("table expected")))?;
1607 let top = state.top_idx();
1608 let val = state.get_at(top - 1);
1609 t.raw_set_int(state, n, val)?;
1610 // C: luaC_barrierback(L, obj2gco(t), s2v(L->top.p - 1));
1611 let top_val = state.get_at(top - 1);
1612 state.gc().barrier_back(&t, &top_val);
1613 // C: L->top.p--;
1614 state.pop();
1615 Ok(())
1616}
1617
1618/// Returns true if `mt` (a metatable) holds a non-nil `__gc` entry.
1619///
1620/// PORT NOTE: Mirrors the body of C's `tofinalize` in `lgc.c` minus the bits
1621/// that consult per-object GC bits (irrelevant in Phase B's Rc world).
1622fn metatable_has_gc(state: &LuaState, mt: &GcRef<LuaTable>) -> bool {
1623 let name = state.global().tmname[crate::tagmethods::TagMethod::Gc as usize].clone();
1624 !matches!(mt.get_short_str(&name), LuaValue::Nil)
1625}
1626
1627/// Pin `tbl` in `pending_finalizers` if not already present.
1628fn register_finalizable_table(state: &mut LuaState, tbl: &GcRef<LuaTable>) {
1629 let already = state
1630 .global()
1631 .pending_finalizers
1632 .iter()
1633 .any(|t| GcRef::ptr_eq(t, tbl));
1634 if !already {
1635 state.global_mut().pending_finalizers.push(tbl.clone());
1636 }
1637}
1638
1639/// Phase-B `__gc` driver.
1640///
1641/// Scans `pending_finalizers` for tables whose only strong ref is the list
1642/// itself (`Rc::strong_count == 1`), runs their `__gc` metamethod in a
1643/// protected call, then drops the list's pin so the table can be freed.
1644/// Iterates in reverse so the most-recently registered finalizers run first,
1645/// matching C-Lua's order (`finobj` is a LIFO stack).
1646///
1647/// PORT NOTE: This stands in for C-Lua's `GCSatomic` finalizer-promotion step
1648/// plus `GCTM`. The real GC walks the heap to decide which `finobj` entries
1649/// are unreachable; in Phase B we use the `Rc` strong-count as the proxy.
1650/// Replaced by `lua_gc::run_pending_finalizers` when Phase D's incremental
1651/// GC lands.
1652pub fn run_pending_finalizers(state: &mut LuaState) {
1653 let mut did_run = false;
1654 loop {
1655 // `to_be_finalized` was populated by the most recent
1656 // `collect_via_heap` mark phase. Drain in LIFO order so the most
1657 // recently dead object runs its `__gc` first — matches C-Lua's
1658 // `finobj` stack ordering.
1659 let target_idx = {
1660 let to_fin = &state.global().to_be_finalized;
1661 if to_fin.is_empty() { None } else { Some(to_fin.len() - 1) }
1662 };
1663 let Some(i) = target_idx else { break; };
1664 // The Phase-A pre-finalizer weak-value sweep (mirroring C-Lua's
1665 // `clearbyvalues(g, g->weak, NULL)` from `atomic()`) is no longer
1666 // needed: under D-2, weak-table sweeping runs inside the post-mark
1667 // hook of `Heap::full_collect_with_post_mark`, which uses
1668 // reachability instead of strong_count and therefore clears such
1669 // entries BEFORE this finalizer pass runs. The full "bug-in-5.1"
1670 // ordering (finalizer-visible state) still requires reachability-
1671 // based detection of which finalizable tables are about to die — a
1672 // gap tracked under D-2 ephemeron/finalizer follow-up.
1673 let tbl = state.global_mut().to_be_finalized.swap_remove(i);
1674 let mt = tbl.metatable();
1675 let gc_fn = match mt {
1676 Some(ref m) => {
1677 let name = state.global().tmname[crate::tagmethods::TagMethod::Gc as usize].clone();
1678 m.get_short_str(&name)
1679 }
1680 None => LuaValue::Nil,
1681 };
1682 if !matches!(gc_fn, LuaValue::Function(_)) {
1683 continue;
1684 }
1685 did_run = true;
1686 let saved_top = state.top_idx();
1687 let ci_top = state.current_call_info().top;
1688 if saved_top.0 < ci_top.0 {
1689 state.clear_stack_range(saved_top, ci_top);
1690 state.set_top(ci_top);
1691 }
1692 state.push(gc_fn);
1693 state.push(LuaValue::Table(tbl));
1694 let func_idx = state.top_idx() - 2;
1695 let _heap_guard = {
1696 let g = state.global.borrow();
1697 lua_gc::HeapGuard::push(&g.heap)
1698 };
1699 let old_allowhook = state.allowhook;
1700 let old_gcstp = state.global_mut().stop_gc_internal();
1701 state.allowhook = false;
1702 let caller_ci = state.ci;
1703 let caller_status = state.get_ci(caller_ci).callstatus;
1704 state.get_ci_mut(caller_ci).callstatus = caller_status | crate::state::CIST_FIN;
1705 let _ = crate::do_::pcall(
1706 state,
1707 |s| s.call_no_yield(func_idx, 0),
1708 func_idx,
1709 0,
1710 );
1711 state.get_ci_mut(caller_ci).callstatus = caller_status;
1712 state.allowhook = old_allowhook;
1713 state.global_mut().set_gc_stop_flags(old_gcstp);
1714 state.set_top(saved_top);
1715 }
1716 // Post-finalizer weak sweep is also obsolete: any weak entries newly
1717 // exposed by the finalizer pass will be cleared on the NEXT
1718 // `Heap::full_collect_with_post_mark`. We accept the one-cycle lag.
1719 let _ = did_run;
1720}
1721
1722/// Snapshot the currently-live weak tables from
1723/// `GlobalState.weak_tables_registry`, deduplicating by Rc pointer and
1724/// dropping any whose backing storage has been freed. Used by both the
1725/// pre-finalizer and post-finalizer sweeps in [`run_pending_finalizers`]
1726/// and by the explicit `collectgarbage("collect")` path.
1727fn collect_live_weak_tables(state: &mut LuaState) -> Vec<GcRef<lua_types::value::LuaTable>> {
1728 let mut g = state.global_mut();
1729 g.weak_tables_registry.retain(|w| w.strong_count() > 0);
1730 let mut seen = std::collections::HashSet::<usize>::new();
1731 g.weak_tables_registry
1732 .iter()
1733 .filter_map(|w| w.upgrade())
1734 .filter_map(|rc| {
1735 let id = rc.identity();
1736 if seen.insert(id) {
1737 Some(rc)
1738 } else {
1739 None
1740 }
1741 })
1742 .collect()
1743}
1744
1745// C: LUA_API int lua_setmetatable (lua_State *L, int objindex)
1746pub fn set_metatable(state: &mut LuaState, objindex: i32) -> Result<bool, LuaError> {
1747 // C: api_checknelems(L, 1);
1748 let top = state.top_idx();
1749 let mt_val = state.get_at(top - 1);
1750 // C: if (ttisnil(s2v(L->top.p - 1))) mt = NULL; else mt = hvalue(...)
1751 let mt: Option<GcRef<LuaTable>> = if matches!(mt_val, LuaValue::Nil) {
1752 None
1753 } else {
1754 debug_assert!(matches!(mt_val, LuaValue::Table(_)), "table expected");
1755 if let LuaValue::Table(t) = mt_val {
1756 Some(t)
1757 } else {
1758 None
1759 }
1760 };
1761
1762 let obj = index_to_value(state, objindex);
1763 // C: switch (ttype(obj)) { LUA_TTABLE: ... LUA_TUSERDATA: ... default: G(L)->mt[ttype] }
1764 match obj {
1765 LuaValue::Table(ref tbl) => {
1766 if mt.is_some() {
1767 // C: luaC_objbarrier(L, gcvalue(obj), mt);
1768 state.gc().obj_barrier(tbl, mt.as_ref().unwrap());
1769 }
1770 // C: hvalue(obj)->metatable = mt;
1771 tbl.set_metatable(mt.clone());
1772 if tbl.weak_mode() != 0 {
1773 state
1774 .global_mut()
1775 .weak_tables_registry
1776 .push(tbl.downgrade());
1777 }
1778 // C: luaC_checkfinalizer(L, gcvalue(obj), mt);
1779 // Phase-B finalizer registration: if the new metatable carries
1780 // `__gc` and `obj` was not already registered, pin `obj` in the
1781 // pending-finalizers list so that `run_pending_finalizers` can
1782 // invoke the finalizer before the object is freed.
1783 if let Some(ref mt_table) = mt {
1784 if metatable_has_gc(state, mt_table) {
1785 register_finalizable_table(state, tbl);
1786 }
1787 }
1788 }
1789 LuaValue::UserData(ref ud) => {
1790 if let Some(ref mt_table) = mt {
1791 state.gc().obj_barrier(ud, mt_table);
1792 // TODO(port): luaC_checkfinalizer
1793 }
1794 ud.set_metatable(mt);
1795 }
1796 ref other => {
1797 let idx = other.base_type() as usize;
1798 state.global_mut().mt[idx] = mt;
1799 }
1800 }
1801 // C: L->top.p--;
1802 state.pop();
1803 Ok(true)
1804}
1805
1806// C: LUA_API int lua_setiuservalue (lua_State *L, int idx, int n)
1807pub fn set_i_uservalue(state: &mut LuaState, idx: i32, n: i32) -> Result<bool, LuaError> {
1808 // C: api_checknelems(L, 1);
1809 let o = index_to_value(state, idx);
1810 debug_assert!(matches!(o, LuaValue::UserData(_)), "full userdata expected");
1811 let top = state.top_idx();
1812 let val = state.get_at(top - 1);
1813 let res = if let LuaValue::UserData(ref ud) = o {
1814 let nuvalue = ud.uv.len() as i32;
1815 // C: !(cast_uint(n) - 1u < cast_uint(uvalue(o)->nuvalue))
1816 if n < 1 || n > nuvalue {
1817 false
1818 } else {
1819 // C: setobj(L, &uvalue(o)->uv[n - 1].uv, s2v(L->top.p - 1));
1820 // TODO(port): LuaUserData uv field needs interior mutability for write
1821 // ud.uv[(n - 1) as usize] = val.clone();
1822 // C: luaC_barrierback(L, gcvalue(o), s2v(L->top.p - 1));
1823 state.gc().barrier_back(ud, &val);
1824 let _ = (n, ud);
1825 true
1826 }
1827 } else {
1828 false
1829 };
1830 // C: L->top.p--;
1831 state.pop();
1832 Ok(res)
1833}
1834
1835// ── load/call functions ───────────────────────────────────────────────────────
1836
1837// C: LUA_API void lua_callk (lua_State *L, int nargs, int nresults,
1838// lua_KContext ctx, lua_KFunction k)
1839pub fn call_k(
1840 state: &mut LuaState,
1841 nargs: i32,
1842 nresults: i32,
1843 ctx: isize,
1844 k: Option<fn(&mut LuaState, i32, isize) -> Result<usize, LuaError>>,
1845) -> Result<(), LuaError> {
1846 // C: api_check(L, k == NULL || !isLua(L->ci), "cannot use continuations inside hooks");
1847 // C: api_checknelems(L, nargs+1);
1848 // C: api_check(L, L->status == LUA_OK, "cannot do calls on non-normal thread");
1849 // C: func = L->top.p - (nargs+1);
1850 let top = state.top_idx();
1851 let func_idx = top - (nargs + 1);
1852 // C: if (k != NULL && yieldable(L)) {
1853 // L->ci->u.c.k = k; L->ci->u.c.ctx = ctx;
1854 // luaD_call(L, func, nresults);
1855 // } else {
1856 // luaD_callnoyield(L, func, nresults);
1857 // }
1858 if k.is_some() && state.is_yieldable() {
1859 let ci_idx = state.ci;
1860 {
1861 let ci = state.get_ci_mut(ci_idx);
1862 ci.set_u_c_k(k);
1863 ci.set_u_c_ctx(ctx);
1864 }
1865 state.call_at(func_idx, nresults)?;
1866 } else {
1867 state.call_no_yield(func_idx, nresults)?;
1868 }
1869 // C: adjustresults(L, nresults);
1870 state.adjust_results(nresults);
1871 Ok(())
1872}
1873
1874// C: LUA_API int lua_pcallk (lua_State *L, int nargs, int nresults, int errfunc,
1875// lua_KContext ctx, lua_KFunction k)
1876pub fn pcall_k(
1877 state: &mut LuaState,
1878 nargs: i32,
1879 nresults: i32,
1880 errfunc: i32,
1881 ctx: isize,
1882 k: Option<fn(&mut LuaState, i32, isize) -> Result<usize, LuaError>>,
1883) -> Result<LuaStatus, LuaError> {
1884 // Phase D-1c: activate the heap for the duration of this protected call.
1885 // GcRef::new (post D-1e) and any future allocator-aware code will route
1886 // through state.global.heap via with_current_heap(...). Stacked so nested
1887 // pcalls inside the same thread don't clobber each other.
1888 let _heap_guard = {
1889 let g = state.global.borrow();
1890 // The HeapGuard borrows &Heap; we let it live for the function scope.
1891 // The borrow of `g` is dropped immediately; the guard's NonNull
1892 // outlives it (the heap field is pinned inside GlobalState which
1893 // is Rc-managed and won't move).
1894 lua_gc::HeapGuard::push(&g.heap)
1895 };
1896 // C: api_checknelems(L, nargs+1);
1897 // C: func (error handler) stack offset
1898 let err_handler_idx: isize = if errfunc == 0 {
1899 0
1900 } else {
1901 let o = index_to_stack_idx(state, errfunc);
1902 debug_assert!(
1903 matches!(state.get_at(o), LuaValue::Function(_)),
1904 "error handler must be a function"
1905 );
1906 o.0 as isize
1907 };
1908 let top = state.top_idx();
1909 let func_idx = top - (nargs + 1);
1910 // C: if (k == NULL || !yieldable(L)) { conventional protected call }
1911 if k.is_none() || !state.is_yieldable() {
1912 state.protected_call_raw(func_idx, nresults, StackIdx(err_handler_idx as u32))?;
1913 state.adjust_results(nresults);
1914 return Ok(LuaStatus::Ok);
1915 }
1916 // Yieldable continuation path: arrange for an interrupted call (yield or
1917 // recoverable error) to be resumable. The call is already protected by
1918 // `lua_resume`; real errors must propagate with CIST_YPCALL still set so
1919 // `precover` can run `finish_pcallk`.
1920 //
1921 // C: lapi.c:1066-1080 — yieldable-pcall branch.
1922 let ci_idx = state.ci;
1923 let allow = state.allowhook;
1924 let saved_errfunc = state.errfunc;
1925 {
1926 let ci = state.get_ci_mut(ci_idx);
1927 // C: ci->u.c.k = k; ci->u.c.ctx = ctx;
1928 ci.set_u_c_k(k);
1929 ci.set_u_c_ctx(ctx);
1930 // C: ci->u2.funcidx = cast_int(savestack(L, c.func));
1931 ci.set_u2_funcidx(func_idx.0 as i32);
1932 // C: ci->u.c.old_errfunc = L->errfunc; L->errfunc = func;
1933 ci.set_u_c_old_errfunc(saved_errfunc);
1934 // C: setoah(ci->callstatus, L->allowhook);
1935 ci.set_oah(allow);
1936 // C: ci->callstatus |= CIST_YPCALL;
1937 ci.callstatus |= crate::state::CIST_YPCALL;
1938 }
1939 state.errfunc = err_handler_idx;
1940 // C: luaD_call(L, c.func, nresults) — yieldable call (NOT call_no_yield).
1941 let call_result = crate::do_::call(state, func_idx, nresults);
1942 match call_result {
1943 Ok(()) => {
1944 // C: ci->callstatus &= ~CIST_YPCALL;
1945 // L->errfunc = ci->u.c.old_errfunc;
1946 // status = LUA_OK;
1947 state.get_ci_mut(ci_idx).callstatus &= !crate::state::CIST_YPCALL;
1948 state.errfunc = saved_errfunc;
1949 state.adjust_results(nresults);
1950 Ok(LuaStatus::Ok)
1951 }
1952 Err(crate::state::LuaError::Yield) => {
1953 // Yield must propagate up to lua_resume. The recovery prep stays
1954 // on `ci_idx` so that on resume, `finishCcall` will call
1955 // `finishpcallk` followed by the continuation `k`.
1956 Err(crate::state::LuaError::Yield)
1957 }
1958 Err(e) => {
1959 // Real errors take the same path as C longjmp: they unwind to
1960 // lua_resume's protected runner, which calls precover and then
1961 // finish_pcallk while this C frame still advertises CIST_YPCALL.
1962 Err(e)
1963 }
1964 }
1965}
1966
1967// C: LUA_API int lua_load (lua_State *L, lua_Reader reader, void *data,
1968// const char *chunkname, const char *mode)
1969// PORT NOTE: lua_Reader (void* callback) is replaced by Box<dyn FnMut>; mode
1970// is &[u8].
1971pub fn load(
1972 state: &mut LuaState,
1973 reader: Box<dyn FnMut() -> Option<Vec<u8>>>,
1974 chunkname: Option<&[u8]>,
1975 mode: Option<&[u8]>,
1976) -> Result<LuaStatus, LuaError> {
1977 let name = chunkname.unwrap_or(b"?");
1978 // C: luaZ_init(L, &z, reader, data); status = luaD_protectedparser(L, &z, chunkname, mode);
1979 let z = crate::zio::ZIO::new(reader);
1980 let status = state.protected_parser(z, name, mode);
1981 if status == LuaStatus::Ok {
1982 // C: LClosure *f = clLvalue(s2v(L->top.p - 1));
1983 // C: if (f->nupvalues >= 1) { set global table as 1st upvalue }
1984 let top = state.top_idx();
1985 let func_val = state.get_at(top - 1);
1986 if let LuaValue::Function(LuaClosure::Lua(lcl)) = func_val {
1987 if !lcl.upvals.is_empty() {
1988 // C: const TValue *gt = getGtable(L); setobj(L, f->upvals[0]->v.p, gt);
1989 let gt = get_global_table(state);
1990 let uv = state.new_upval_closed(gt);
1991 lcl.set_upval(0, uv);
1992 }
1993 }
1994 }
1995 Ok(status)
1996}
1997
1998// C: LUA_API int lua_dump (lua_State *L, lua_Writer writer, void *data, int strip)
1999pub fn dump(
2000 state: &LuaState,
2001 writer: &mut dyn FnMut(&[u8]) -> Result<(), LuaError>,
2002 strip: bool,
2003) -> Result<bool, LuaError> {
2004 // C: api_checknelems(L, 1); o = s2v(L->top.p - 1);
2005 let top = state.top_idx();
2006 let o = state.get_at(top - 1);
2007 // C: if (isLfunction(o)) status = luaU_dump(L, getproto(o), writer, data, strip);
2008 if let LuaValue::Function(LuaClosure::Lua(ref lcl)) = o {
2009 crate::dump::dump(state, &lcl.proto, writer, strip)?;
2010 Ok(true)
2011 } else {
2012 Ok(false)
2013 }
2014}
2015
2016// C: LUA_API int lua_status (lua_State *L)
2017pub fn status(state: &LuaState) -> LuaStatus {
2018 LuaStatus::from_raw(state.status as i32)
2019}
2020
2021// ── garbage collection ────────────────────────────────────────────────────────
2022
2023/// GC operation codes (C: LUA_GC* constants)
2024#[repr(i32)]
2025#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2026pub enum GcWhat {
2027 Stop = 0,
2028 Restart = 1,
2029 Collect = 2,
2030 Count = 3,
2031 CountB = 4,
2032 Step = 5,
2033 SetPause = 6,
2034 SetStepMul = 7,
2035 IsRunning = 9,
2036 Gen = 10,
2037 Inc = 11,
2038}
2039
2040// C: LUA_API int lua_gc (lua_State *L, int what, ...)
2041// PORT NOTE: C varargs replaced by explicit GcArgs enum; callers supply parameters directly.
2042pub enum GcArgs {
2043 Stop,
2044 Restart,
2045 Collect,
2046 Count,
2047 CountB,
2048 Step { data: i32 },
2049 SetPause { value: i32 },
2050 SetStepMul { value: i32 },
2051 IsRunning,
2052 Gen { minormul: i32, majormul: i32 },
2053 Inc { pause: i32, stepmul: i32, stepsize: i32 },
2054}
2055
2056pub fn gc(state: &mut LuaState, args: GcArgs) -> i32 {
2057 // C: if (g->gcstp & GCSTPGC) return -1;
2058 if state.global().is_gc_stopped_internally() {
2059 return -1;
2060 }
2061 match args {
2062 // C: case LUA_GCSTOP: g->gcstp = GCSTPUSR;
2063 GcArgs::Stop => {
2064 state.global_mut().set_gc_stop_user();
2065 }
2066 // C: case LUA_GCRESTART: luaE_setdebt(g, 0); g->gcstp = 0;
2067 GcArgs::Restart => {
2068 {
2069 let mut g = state.global_mut();
2070 crate::state::set_debt(&mut *g, 0);
2071 }
2072 state.global_mut().clear_gc_stop();
2073 }
2074 // C: case LUA_GCCOLLECT: luaC_fullgc(L, 0);
2075 GcArgs::Collect => {
2076 if !state.allowhook {
2077 return 0;
2078 }
2079 // Under D-2, weak-table sweep happens INSIDE the heap's
2080 // post-mark hook (see GcHandle::full_collect), driven by
2081 // reachability rather than strong_count. The standalone weak
2082 // sweep that used to run here would now be a no-op against an
2083 // already-clean state and is removed.
2084 state.gc().full_collect();
2085 // Phase-B: drain pending __gc finalizers for tables whose user
2086 // refs have all been dropped. Kept for legacy compat; runs
2087 // after the heap's collect so weak entries have been cleared.
2088 run_pending_finalizers(state);
2089 // PORT NOTE: Phase-B long-string accounting. Reclaim `gc_debt`
2090 // for any tracked long-string Rc whose strong count has dropped
2091 // to zero (either because the weak-table sweep above released
2092 // the last reference, or because the user dropped it directly).
2093 // Without this, `collectgarbage("count")` would report peak
2094 // allocation rather than live bytes — gc.lua's weak-string-key
2095 // block depends on the post-collect count being lower than the
2096 // pre-collect count.
2097 {
2098 let mut g = state.global_mut();
2099 crate::state::reclaim_dead_long_strings(&mut *g);
2100 }
2101 // PORT NOTE: Phase B has no per-allocation totalbytes tracking,
2102 // so total_bytes() only ever shrinks (each `Step` simulates
2103 // freed memory). Refill to a baseline here so subsequent Step
2104 // calls have headroom to actually drop count*1024 — the test
2105 // pattern `collectgarbage(); local x = gcinfo(); collectgarbage('step'); assert(gcinfo()<x)`
2106 // needs gcinfo to be high enough that decrementing by 1 KB is
2107 // observable. Removed in Phase D when real GC tracks bytes.
2108 {
2109 let mut g = state.global_mut();
2110 let target_tb = 32_768_isize;
2111 let cur_tb = g.totalbytes + g.gc_debt;
2112 if cur_tb < target_tb {
2113 g.totalbytes += target_tb - cur_tb;
2114 }
2115 }
2116 }
2117 // C: case LUA_GCCOUNT: res = cast_int(gettotalbytes(g) >> 10);
2118 GcArgs::Count => {
2119 {
2120 let mut g = state.global_mut();
2121 crate::state::reclaim_dead_long_strings(&mut *g);
2122 }
2123 let g = state.global();
2124 let long_string_bytes: usize = g.gc_tracked_long_strings.iter().map(|(_, sz)| sz).sum();
2125 let total = g.heap.bytes_used() + long_string_bytes;
2126 return (total >> 10) as i32;
2127 }
2128 // C: case LUA_GCCOUNTB: res = cast_int(gettotalbytes(g) & 0x3ff);
2129 GcArgs::CountB => {
2130 {
2131 let mut g = state.global_mut();
2132 crate::state::reclaim_dead_long_strings(&mut *g);
2133 }
2134 let g = state.global();
2135 let long_string_bytes: usize = g.gc_tracked_long_strings.iter().map(|(_, sz)| sz).sum();
2136 let total = g.heap.bytes_used() + long_string_bytes;
2137 return (total & 0x3ff) as i32;
2138 }
2139 // C: case LUA_GCSTEP: ...
2140 GcArgs::Step { data } => {
2141 // C: lu_byte oldstp = g->gcstp; g->gcstp = 0;
2142 let old_stp = {
2143 let mut g = state.global_mut();
2144 let old = g.gc_stop_flags();
2145 g.clear_gc_stop();
2146 old
2147 };
2148 // C-Lua converts `data` KiB of added debt into work units via
2149 // `stepmul`. We use a simpler mapping: the work-unit count is
2150 // `data * stepmul / 4` (stepmul is the user-tunable speed,
2151 // /4-encoded in `gcstepmul`), with a floor of 1 unit. When
2152 // `data == 0` the call still performs one basic step (matching
2153 // C-Lua's `luaC_step(L)` after `setdebt(g, 0)`).
2154 let stepmul = (state.global().gc_stepmul_param() as isize | 1).max(1);
2155 let work_units = if data == 0 {
2156 stepmul
2157 } else {
2158 let raw = (data as isize).saturating_mul(stepmul);
2159 raw.max(1)
2160 };
2161 if data == 0 {
2162 let mut g = state.global_mut();
2163 crate::state::set_debt(&mut *g, 0);
2164 } else {
2165 let debt = data as isize * 1024 + state.global().gc_debt();
2166 let mut g = state.global_mut();
2167 crate::state::set_debt(&mut *g, debt);
2168 }
2169 let cycle_complete = state.gc().incremental_step(work_units);
2170 if state.global().is_gen_mode() {
2171 state.gc().prune_weak_tables_mark_only();
2172 }
2173 state.global_mut().set_gc_stop_flags(old_stp);
2174 // Phase-B byte accounting: real allocation isn't tracked, so
2175 // simulate C-Lua's post-sweep totalbytes drop here. Halving
2176 // the current `tb` makes `gcinfo() < x` hold across a step
2177 // that completes a cycle (gc.lua `dosteps()` line 194), while
2178 // the floor at 1 KB preserves `set_debt`'s `tb > 0` invariant
2179 // across many back-to-back step calls.
2180 if cycle_complete {
2181 let mut g = state.global_mut();
2182 let floor: isize = 1024;
2183 let cur_tb = g.totalbytes + g.gc_debt;
2184 let new_tb = (cur_tb / 2).max(floor);
2185 if new_tb < cur_tb {
2186 g.totalbytes -= cur_tb - new_tb;
2187 }
2188 }
2189 // Sync the global gcstate byte for `gc_at_pause()` callers.
2190 {
2191 let heap_state = state.global().heap.gc_state();
2192 let mut g = state.global_mut();
2193 g.gcstate = if heap_state.is_pause() { 0 } else { 1 };
2194 }
2195 return if cycle_complete { 1 } else { 0 };
2196 }
2197 // C: case LUA_GCSETPAUSE:
2198 GcArgs::SetPause { value } => {
2199 // C: res = getgcparam(g->gcpause); setgcparam(g->gcpause, data);
2200 let old = state.global().gc_pause_param() as i32;
2201 state.global_mut().set_gc_pause_param(value as u8);
2202 return old;
2203 }
2204 // C: case LUA_GCSETSTEPMUL:
2205 GcArgs::SetStepMul { value } => {
2206 let old = state.global().gc_stepmul_param() as i32;
2207 state.global_mut().set_gc_stepmul_param(value as u8);
2208 return old;
2209 }
2210 // C: case LUA_GCISRUNNING: res = gcrunning(g);
2211 GcArgs::IsRunning => {
2212 return state.global().gc_running() as i32;
2213 }
2214 // C: case LUA_GCGEN:
2215 GcArgs::Gen { minormul, majormul } => {
2216 // C: res = isdecGCmodegen(g) ? LUA_GCGEN : LUA_GCINC;
2217 let old_mode = if state.global().is_gen_mode() { 10i32 } else { 11i32 };
2218 if minormul != 0 {
2219 state.global_mut().genminormul = minormul as u8;
2220 }
2221 if majormul != 0 {
2222 state.global_mut().set_gc_genmajormul(majormul as u8);
2223 }
2224 // C: luaC_changemode(L, KGC_GEN);
2225 state.gc().change_mode(crate::state::GcKind::Generational);
2226 return old_mode;
2227 }
2228 // C: case LUA_GCINC:
2229 GcArgs::Inc { pause, stepmul, stepsize } => {
2230 let old_mode = if state.global().is_gen_mode() { 10i32 } else { 11i32 };
2231 if pause != 0 {
2232 state.global_mut().set_gc_pause_param(pause as u8);
2233 }
2234 if stepmul != 0 {
2235 state.global_mut().set_gc_stepmul_param(stepmul as u8);
2236 }
2237 if stepsize != 0 {
2238 state.global_mut().gcstepsize = stepsize as u8;
2239 }
2240 // C: luaC_changemode(L, KGC_INC);
2241 state.gc().change_mode(crate::state::GcKind::Incremental);
2242 return old_mode;
2243 }
2244 }
2245 0
2246}
2247
2248// ── miscellaneous functions ───────────────────────────────────────────────────
2249
2250// C: LUA_API int lua_error (lua_State *L)
2251// PORT NOTE: returns Result<Infallible, _> — semantically "always Err". The
2252// translator originally wrote `Result<!, _>` but the `!` type in a return
2253// position is still nightly-only as of Rust 1.93; Infallible is the stable
2254// stand-in. Callsites just pattern-match on Err.
2255pub fn lua_error(state: &mut LuaState) -> Result<Infallible, LuaError> {
2256 // C: errobj = s2v(L->top.p - 1);
2257 // C: api_checknelems(L, 1);
2258 // C: if (ttisshrstring(errobj) && eqshrstr(tsvalue(errobj), G(L)->memerrmsg))
2259 // luaM_error(L); /* memory error */
2260 // else
2261 // luaG_errormsg(L); /* regular error */
2262 let top = state.top_idx();
2263 let errobj = state.get_at(top - 1);
2264 // C: special-case OOM string
2265 let is_mem_err = if let LuaValue::Str(ref s) = errobj {
2266 let memerr = state.global().memerrmsg.clone();
2267 // C: eqshrstr(tsvalue(errobj), G(L)->memerrmsg) — short-string pointer equality
2268 GcRef::ptr_eq(s, &memerr)
2269 } else {
2270 false
2271 };
2272 if is_mem_err {
2273 Err(LuaError::Memory)
2274 } else {
2275 Err(LuaError::from_value(errobj))
2276 }
2277}
2278
2279// C: LUA_API int lua_next (lua_State *L, int idx)
2280pub fn next(state: &mut LuaState, idx: i32) -> Result<bool, LuaError> {
2281 // C: t = gettable(L, idx); api_checknelems(L, 1);
2282 let t = get_table_value(state, idx)
2283 .ok_or_else(|| LuaError::runtime(format_args!("table expected")))?;
2284 let top = state.top_idx();
2285 let key = state.get_at(top - 1);
2286 // C: more = luaH_next(L, t, L->top.p - 1);
2287 match t.next(key)? {
2288 Some((next_key, next_val)) => {
2289 // C: if (more) api_incr_top(L); (key already at top-1, push value above)
2290 state.set_at(top - 1, next_key);
2291 state.push(next_val);
2292 Ok(true)
2293 }
2294 None => {
2295 // C: else L->top.p -= 1; (remove key)
2296 state.set_top_idx(top - 1);
2297 Ok(false)
2298 }
2299 }
2300}
2301
2302// C: LUA_API void lua_toclose (lua_State *L, int idx)
2303pub fn to_close(state: &mut LuaState, idx: i32) -> Result<(), LuaError> {
2304 // C: o = index2stack(L, idx); nresults = L->ci->nresults;
2305 // C: api_check(L, L->tbclist.p < o, "given index below or equal a marked one");
2306 // C: luaF_newtbcupval(L, o);
2307 // C: if (!hastocloseCfunc(nresults)) L->ci->nresults = codeNresults(nresults);
2308 let _level = index_to_stack_idx(state, idx);
2309 // TODO(port): luaF_newtbcupval and to-be-closed variable infrastructure
2310 // not yet translated. Stubbing for Phase A.
2311 Ok(())
2312}
2313
2314// C: LUA_API void lua_concat (lua_State *L, int n)
2315pub fn concat(state: &mut LuaState, n: i32) -> Result<(), LuaError> {
2316 // C: api_checknelems(L, n);
2317 if n > 0 {
2318 // C: luaV_concat(L, n);
2319 state.concat(n)?;
2320 } else {
2321 // C: setsvalue2s(L, L->top.p, luaS_newlstr(L, "", 0)); api_incr_top(L);
2322 let empty = state.intern_str(b"")?;
2323 state.push(LuaValue::Str(empty));
2324 }
2325 state.gc().check_step();
2326 Ok(())
2327}
2328
2329// C: LUA_API void lua_len (lua_State *L, int idx)
2330pub fn len(state: &mut LuaState, idx: i32) -> Result<(), LuaError> {
2331 // C: t = index2value(L, idx); luaV_objlen(L, L->top.p, t);
2332 let t = index_to_value(state, idx);
2333 let result = state.obj_len(&t)?;
2334 state.push(result);
2335 Ok(())
2336}
2337
2338// C: LUA_API lua_Alloc lua_getallocf / lua_setallocf
2339// PORT NOTE: The custom allocator hook is not exposed in the Rust-native API.
2340// Rust's allocator handles all allocation.
2341// These are intentionally omitted.
2342
2343// C: void lua_setwarnf (lua_State *L, lua_WarnFunction f, void *ud)
2344pub fn set_warn_f(
2345 state: &mut LuaState,
2346 f: Option<Box<dyn FnMut(&[u8], bool)>>,
2347) {
2348 // C: G(L)->ud_warn = ud; G(L)->warnf = f;
2349 // PORT NOTE: ud_warn userdata is folded into the closure per types.tsv.
2350 state.global_mut().warnf = f;
2351}
2352
2353// C: void lua_warning (lua_State *L, const char *msg, int tocont)
2354pub fn warning(state: &mut LuaState, msg: &[u8], tocont: bool) {
2355 // C: luaE_warning(L, msg, tocont);
2356 state.emit_warning(msg, tocont);
2357}
2358
2359// C: LUA_API void *lua_newuserdatauv (lua_State *L, size_t size, int nuvalue)
2360pub fn new_userdata_uv(
2361 state: &mut LuaState,
2362 size: usize,
2363 nuvalue: i32,
2364) -> Result<GcRef<LuaUserData>, LuaError> {
2365 // C: api_check(L, 0 <= nuvalue && nuvalue < USHRT_MAX, "invalid value");
2366 debug_assert!(nuvalue >= 0 && nuvalue < u16::MAX as i32, "invalid value");
2367 // C: u = luaS_newudata(L, size, nuvalue);
2368 let u = state.new_userdata(size, nuvalue as usize)?;
2369 state.push(LuaValue::UserData(u.clone()));
2370 state.gc().check_step();
2371 Ok(u)
2372}
2373
2374// ── upvalue access ────────────────────────────────────────────────────────────
2375
2376// C: static const char *aux_upvalue (TValue *fi, int n, TValue **val, GCObject **owner)
2377// PORT NOTE: Returns (name, value) instead of mutating output pointers. The name
2378// is returned as an owned Vec<u8> because Lua upvalue names live in the proto's
2379// LuaString table (GC heap), not in static storage.
2380fn aux_upvalue(
2381 state: &LuaState,
2382 fi: &LuaValue,
2383 n: i32,
2384) -> Option<(Vec<u8>, LuaValue)> {
2385 match fi {
2386 // C: case LUA_VCCL:
2387 LuaValue::Function(LuaClosure::C(ccl)) => {
2388 let nupvalues = ccl.upvalues.len() as i32;
2389 // C: if (!(cast_uint(n) - 1u < cast_uint(f->nupvalues))) return NULL;
2390 if n < 1 || n > nupvalues {
2391 return None;
2392 }
2393 // C: *val = &f->upvalue[n-1]; return "";
2394 Some((Vec::new(), ccl.upvalues[(n - 1) as usize].clone()))
2395 }
2396 // C: case LUA_VLCL:
2397 LuaValue::Function(LuaClosure::Lua(lcl)) => {
2398 let nupvalues = lcl.upvals.len() as i32;
2399 // C: if (!(cast_uint(n) - 1u < cast_uint(p->sizeupvalues))) return NULL;
2400 if n < 1 || n > nupvalues {
2401 return None;
2402 }
2403 // C: *val = f->upvals[n-1]->v.p;
2404 let val = state.upvalue_get(lcl, (n - 1) as usize);
2405 // C: name = p->upvalues[n-1].name;
2406 // The proto records the static name of each upvalue (e.g. "_ENV"
2407 // for the main chunk's environment upvalue). Stripped chunks have
2408 // no upvalue-name debug info; Lua reports those as "(no name)".
2409 let name: Vec<u8> = lcl
2410 .proto
2411 .upvalues
2412 .get((n - 1) as usize)
2413 .and_then(|ud| ud.name.as_ref())
2414 .map(|s| s.as_bytes().to_vec())
2415 .unwrap_or_else(|| b"(no name)".to_vec());
2416 Some((name, val))
2417 }
2418 _ => None,
2419 }
2420}
2421
2422// C: LUA_API const char *lua_getupvalue (lua_State *L, int funcindex, int n)
2423pub fn get_upvalue(state: &mut LuaState, funcindex: i32, n: i32) -> Option<Vec<u8>> {
2424 // C: name = aux_upvalue(index2value(L, funcindex), n, &val, NULL);
2425 let fi = index_to_value(state, funcindex);
2426 if let Some((name, val)) = aux_upvalue(state, &fi, n) {
2427 // C: setobj2s(L, L->top.p, val); api_incr_top(L);
2428 state.push(val);
2429 Some(name)
2430 } else {
2431 None
2432 }
2433}
2434
2435// C: LUA_API const char *lua_setupvalue (lua_State *L, int funcindex, int n)
2436pub fn setup_value(state: &mut LuaState, funcindex: i32, n: i32) -> Option<Vec<u8>> {
2437 // C: fi = index2value(L, funcindex); api_checknelems(L, 1);
2438 let fi = index_to_value(state, funcindex);
2439 // C: name = aux_upvalue(fi, n, &val, &owner);
2440 let (name, _) = aux_upvalue(state, &fi, n)?;
2441 // C: L->top.p--; setobj(L, val, s2v(L->top.p)); luaC_barrier(L, owner, val);
2442 let new_val = state.pop();
2443 match &fi {
2444 LuaValue::Function(LuaClosure::Lua(lcl)) => {
2445 state.upvalue_set(lcl, (n - 1) as usize, new_val).ok()?;
2446 }
2447 LuaValue::Function(LuaClosure::C(_ccl)) => {
2448 // TODO(port): C-closure upvalue writes need interior mutability on
2449 // LuaCClosure.upvalues. Not exercised by current tests.
2450 let _ = new_val;
2451 }
2452 _ => return None,
2453 }
2454 Some(name)
2455}
2456
2457// C: static UpVal **getupvalref (lua_State *L, int fidx, int n, LClosure **pf)
2458// PORT NOTE: returns an index into the upvals vec rather than a pointer-to-pointer.
2459// Returns None if n is out of range.
2460fn get_upval_ref_idx(state: &LuaState, fidx: i32, n: i32) -> Option<usize> {
2461 let fi = index_to_value(state, fidx);
2462 debug_assert!(matches!(fi, LuaValue::Function(LuaClosure::Lua(_))), "Lua function expected");
2463 if let LuaValue::Function(LuaClosure::Lua(ref lcl)) = fi {
2464 let sizeupvalues = lcl.upvals.len() as i32;
2465 if n >= 1 && n <= sizeupvalues {
2466 Some((n - 1) as usize)
2467 } else {
2468 None
2469 }
2470 } else {
2471 None
2472 }
2473}
2474
2475// C: LUA_API void *lua_upvalueid (lua_State *L, int fidx, int n)
2476// PORT NOTE: Returns Option<usize> identity instead of raw void*.
2477pub fn upvalue_id(state: &LuaState, fidx: i32, n: i32) -> Option<usize> {
2478 let fi = index_to_value(state, fidx);
2479 match &fi {
2480 // C: case LUA_VLCL: return *getupvalref(L, fidx, n, NULL);
2481 LuaValue::Function(LuaClosure::Lua(lcl)) => {
2482 let idx = get_upval_ref_idx(state, fidx, n)?;
2483 // Return the identity of the UpVal GcRef
2484 Some(GcRef::identity(&lcl.upval(idx)))
2485 }
2486 // C: case LUA_VCCL: if (1 <= n && n <= f->nupvalues) return &f->upvalue[n-1];
2487 LuaValue::Function(LuaClosure::C(ccl)) => {
2488 if n >= 1 && n <= ccl.upvalues.len() as i32 {
2489 // TODO(port): returning address of upvalue slot not possible without raw ptr.
2490 // Return a synthetic identity based on the closure's identity + n.
2491 Some(GcRef::identity(ccl) ^ (n as usize))
2492 } else {
2493 None
2494 }
2495 }
2496 // C: case LUA_VLCF: return NULL;
2497 LuaValue::Function(LuaClosure::LightC(_)) => None,
2498 _ => {
2499 debug_assert!(false, "function expected");
2500 None
2501 }
2502 }
2503}
2504
2505// C: LUA_API void lua_upvaluejoin (lua_State *L, int fidx1, int n1,
2506// int fidx2, int n2)
2507pub fn upvalue_join(state: &mut LuaState, fidx1: i32, n1: i32, fidx2: i32, n2: i32) {
2508 // C: LClosure *f1; UpVal **up1 = getupvalref(L, fidx1, n1, &f1);
2509 // C: UpVal **up2 = getupvalref(L, fidx2, n2, NULL);
2510 // C: api_check(L, *up1 != NULL && *up2 != NULL, "invalid upvalue index");
2511 // C: *up1 = *up2; luaC_objbarrier(L, f1, *up1);
2512 let idx1 = match get_upval_ref_idx(state, fidx1, n1) {
2513 Some(i) => i,
2514 None => return,
2515 };
2516 let idx2 = match get_upval_ref_idx(state, fidx2, n2) {
2517 Some(i) => i,
2518 None => return,
2519 };
2520 let f1 = index_to_value(state, fidx1);
2521 let f2 = index_to_value(state, fidx2);
2522 if let (
2523 LuaValue::Function(LuaClosure::Lua(lcl1)),
2524 LuaValue::Function(LuaClosure::Lua(lcl2)),
2525 ) = (&f1, &f2)
2526 {
2527 let shared = lcl2.upval(idx2);
2528 lcl1.set_upval(idx1, shared);
2529 }
2530}
2531
2532// ──────────────────────────────────────────────────────────────────────────
2533// PORT STATUS
2534// source: src/lapi.c (1464 lines, ~47 functions)
2535// target_crate: lua-vm
2536// confidence: low
2537// todos: 18
2538// port_notes: 8
2539// unsafe_blocks: 0 (must be 0 outside explicit unsafe-budget crates)
2540// notes: Heavy use of interior mutability TODOs (GcRef writes for
2541// metatables, upvalue writes, userdata uv writes). The
2542// index2value helper returns cloned LuaValue not a pointer,
2543// so write-back paths that C achieves with TValue* are
2544// stubbed. Stack pointer arithmetic faithfully translated to
2545// StackIdx (u32) arithmetic. va_list functions (pushvfstring,
2546// pushfstring) replaced by &[u8] forwarders. lua_gc varargs
2547// replaced by explicit GcArgs enum. Raw pointer returns
2548// (topointer, touserdata, upvalueid) return Option<usize>
2549// identity values; actual *mut void only legal in lua-gc.
2550// lua_pushthread stubbed (needs self_gcref()), lua_xmove
2551// stubbed (split-borrow), upvalue_join stubbed (GcRef write).
2552// Phase B must wire up: state.grow_stack, state.call_no_yield,
2553// state.protected_call_raw, state.adjust_results,
2554// state.table_get_with_tm, state.table_set_with_tm,
2555// state.arith_op, state.concat, state.obj_len,
2556// state.obj_to_string, state.str_to_num, state.table_getn,
2557// state.registry_value, state.registry_get,
2558// GcRef::identity, GcRef::ptr_eq, GlobalState GC accessors.
2559// ──────────────────────────────────────────────────────────────────────────