lua_stdlib/auxlib.rs
1//! Auxiliary library: helper functions for building Lua libraries.
2//!
3//! C source: `reference/lua-5.4.7/src/lauxlib.c` (1127 lines, ~50 functions)
4//! Target crate: `lua-stdlib`
5//!
6//! This module provides the high-level `luaL_*` API layer that sits on top of
7//! the raw `lua_*` C API. In Rust we translate each `luaL_*` function as a
8//! free function receiving `&mut LuaState` rather than a method, matching the
9//! structure of the other stdlib modules.
10//!
11//! PORT NOTE: The C buffer system (`luaL_Buffer`) uses a small inline initial
12//! buffer backed by a Lua-stack userdata box on overflow. In Rust we replace
13//! this with a plain `Vec<u8>` (`LuaBuffer`), dropping all the C-internal
14//! `UBox` / `resizebox` / `boxgc` / `boxmt` / `newbox` / `buffonstack`
15//! machinery. The public interface remains compatible.
16//!
17//! PORT NOTE: File-loading functions (`load_filex`) reference `std::fs` which
18//! is banned outside `lua-cli`. Those functions carry `TODO(port)` markers.
19
20// TODO(port): LuaState, LuaValue, LuaError, GcRef, LuaString, LuaUserData,
21// LuaDebug, and LuaType are defined across lua-vm / lua-types. Imports will be
22// wired in Phase B. Using local stubs for Phase A so rustc can parse the file.
23
24use lua_types::{
25 error::LuaError,
26 value::LuaValue,
27 gc::GcRef,
28 string::LuaString,
29 userdata::LuaUserData,
30 LuaType,
31 LuaStatus,
32 arith::ArithOp,
33};
34use crate::state_stub::{LuaState, LuaStateStubExt as _, lua_CFunction, upvalue_index, CompareOp, LuaDebug};
35
36// ── Constants ─────────────────────────────────────────────────────────────────
37
38/// Number of stack frames to show in the first part of a traceback.
39/// C: `#define LEVELS1 10`
40const LEVELS1: i32 = 10;
41
42/// Number of stack frames to show in the second part of a traceback.
43/// C: `#define LEVELS2 11`
44const LEVELS2: i32 = 11;
45
46/// Index (1-based) in the reference table that heads the free-list of recycled
47/// references. Placed after the last predefined registry key.
48/// C: `#define freelist (LUA_RIDX_LAST + 1)` where `LUA_RIDX_LAST = 2`.
49const FREELIST_REF: i64 = 3; // LUA_RIDX_GLOBALS (2) + 1
50
51/// Pseudo-reference returned by `lua_ref` when the pushed value was `nil`.
52/// C: `#define LUA_REFNIL (-1)`
53pub const LUA_REFNIL: i32 = -1;
54
55/// Pseudo-reference meaning "no reference" (never created by `lua_ref`).
56/// C: `#define LUA_NOREF (-2)`
57pub const LUA_NOREF: i32 = -2;
58
59/// Extended error code: file-related I/O error from `load_filex`.
60/// C: `#define LUA_ERRFILE (LUA_ERRERR + 1)` = 6.
61pub const LUA_ERRFILE: i32 = 6;
62
63/// Registry key for the table of loaded modules.
64/// C: `#define LUA_LOADED_TABLE "_LOADED"`
65pub const LUA_LOADED_TABLE: &[u8] = b"_LOADED";
66
67/// Registry key for the table of preloaded loaders.
68/// C: `#define LUA_PRELOAD_TABLE "_PRELOAD"`
69pub const LUA_PRELOAD_TABLE: &[u8] = b"_PRELOAD";
70
71/// Name of the global environment table.
72/// C: `#define LUA_GNAME "_G"`
73pub const LUA_GNAME: &[u8] = b"_G";
74
75/// Metatable name / file-handle key for the IO library.
76/// C: `#define LUA_FILEHANDLE "FILE*"`
77pub const LUA_FILE_HANDLE: &[u8] = b"FILE*";
78
79/// Pseudo-index for the Lua registry.
80/// C: `#define LUA_REGISTRYINDEX (-LUAI_MAXSTACK - 1000)`
81const LUA_REGISTRYINDEX: i32 = -1_001_000;
82
83/// Minimum number of extra stack slots `lua_checkstack` guarantees per call.
84/// C: `LUA_MINSTACK = 20`
85const LUA_MINSTACK: i32 = 20;
86
87// ── Public types ──────────────────────────────────────────────────────────────
88
89/// A function-registration entry for `set_funcs`.
90///
91/// C: `typedef struct luaL_Reg { const char *name; lua_CFunction func; } luaL_Reg;`
92///
93/// In Rust, `name` is `&'static [u8]` (never `&str`). A `None` func is a
94/// placeholder that pushes `false` rather than a closure.
95pub struct LuaReg {
96 pub name: &'static [u8],
97 pub func: Option<fn(&mut LuaState) -> Result<usize, LuaError>>,
98}
99
100/// Growable byte-buffer used by the auxiliary library for building strings.
101///
102/// C: `luaL_Buffer` from `lauxlib.h`
103///
104/// The C version uses a small inline initial buffer with overflow managed via
105/// a Lua-stack userdata box. The Rust port collapses this to a plain `Vec<u8>`.
106/// All buffer mutating functions take `&mut LuaState` as a separate parameter.
107pub struct LuaBuffer {
108 pub data: Vec<u8>,
109}
110
111/// File-stream handle used by the IO library.
112///
113/// C: `luaL_Stream` from `lauxlib.h`
114///
115/// `closef` in C is a `lua_CFunction`. In Rust we store an optional closer.
116// TODO(port): file I/O belongs in lua-stdlib/src/io_lib.rs; this definition
117// may move there. Keeping here to mirror the C header.
118pub struct LuaStream {
119 /// The underlying file handle. `None` for incompletely opened or closed streams.
120 // TODO(port): use a real File type (e.g. `std::fs::File`) in Phase B,
121 // noting std::fs is allowed in lua-stdlib for I/O library support.
122 pub f: Option<Box<dyn std::io::Read>>,
123 /// Optional close function (None for already-closed streams).
124 pub closef: Option<fn(&mut LuaState) -> Result<usize, LuaError>>,
125}
126
127// ── Traceback ─────────────────────────────────────────────────────────────────
128
129/// Search for `objidx` in the table at the top of the stack.
130/// `objidx` must be an absolute API stack index.
131/// Returns `true` (and leaves name string on top) when found.
132///
133/// C: `static int findfield(lua_State *L, int objidx, int level)`
134fn find_field(
135 state: &mut LuaState,
136 objidx: i32,
137 level: i32,
138) -> Result<bool, LuaError> {
139 // C: if (level == 0 || !lua_istable(L, -1)) return 0;
140 if level == 0 || state.type_at(-1) != LuaType::Table {
141 return Ok(false);
142 }
143 // C: lua_pushnil(L); /* start 'next' loop */
144 state.push(LuaValue::Nil);
145 // C: while (lua_next(L, -2))
146 while state.table_next(-2)? {
147 // C: if (lua_type(L, -2) == LUA_TSTRING)
148 if state.type_at(-2) == LuaType::String {
149 // C: if (lua_rawequal(L, objidx, -1))
150 if state.raw_equal(objidx, -1)? {
151 state.pop_n(1); // remove value (keep name)
152 return Ok(true);
153 } else if find_field(state, objidx, level - 1)? {
154 // stack: lib_name, lib_table, field_name (top)
155 state.push_string(b".")?; // place '.' between the two names
156 state.replace(-3); // in the slot occupied by table
157 state.concat(3)?; // lib_name.field_name
158 return Ok(true);
159 }
160 }
161 state.pop_n(1); // remove value
162 }
163 Ok(false)
164}
165
166/// Search all loaded modules for a global name for the function at `top+1`.
167/// Returns `true` and leaves name string on top (at `top+1`) if found.
168///
169/// C: `static int pushglobalfuncname(lua_State *L, lua_Debug *ar)`
170fn push_global_func_name(
171 state: &mut LuaState,
172 ar: &mut LuaDebug,
173) -> Result<bool, LuaError> {
174 // C: int top = lua_gettop(L);
175 let top = state.top_count();
176 // C: lua_getinfo(L, "f", ar); /* push function */
177 state.get_info(b"f", ar)?;
178 // C: lua_getfield(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE);
179 state.get_field(LUA_REGISTRYINDEX, LUA_LOADED_TABLE)?;
180 // C: luaL_checkstack(L, 6, "not enough stack");
181 check_stack(state, 6, Some(b"not enough stack"))?;
182 if find_field(state, top + 1, 2)? {
183 // C: const char *name = lua_tostring(L, -1);
184 // C: if (strncmp(name, LUA_GNAME ".", 3) == 0)
185 if state.peek_bytes(-1).map_or(false, |n| n.starts_with(b"_G.")) {
186 // C: lua_pushstring(L, name + 3); /* push name without prefix */
187 let suffix = state.peek_bytes(-1)
188 .map(|n| n[3..].to_vec())
189 .unwrap_or_default();
190 state.push_bytes(&suffix)?;
191 // C: lua_remove(L, -2); /* remove original name */
192 state.remove(-2)?;
193 }
194 // C: lua_copy(L, -1, top + 1);
195 state.copy_value(-1, top + 1)?;
196 // C: lua_settop(L, top + 1);
197 lua_vm::api::set_top(state, top + 1)?;
198 Ok(true)
199 } else {
200 // C: lua_settop(L, top);
201 lua_vm::api::set_top(state, top)?;
202 Ok(false)
203 }
204}
205
206fn push_global_func_name_from_target(
207 state: &mut LuaState,
208 target: &mut LuaState,
209 ar: &mut LuaDebug,
210) -> Result<bool, LuaError> {
211 let top = state.top_count();
212 target.get_info(b"f", ar)?;
213 let func = target.get_at(target.top_idx() - 1);
214 target.pop_n(1);
215 state.push(func);
216 state.get_field(LUA_REGISTRYINDEX, LUA_LOADED_TABLE)?;
217 check_stack(state, 6, Some(b"not enough stack"))?;
218 if find_field(state, top + 1, 2)? {
219 if state.peek_bytes(-1).map_or(false, |n| n.starts_with(b"_G.")) {
220 let suffix = state.peek_bytes(-1)
221 .map(|n| n[3..].to_vec())
222 .unwrap_or_default();
223 state.push_bytes(&suffix)?;
224 state.remove(-2)?;
225 }
226 state.copy_value(-1, top + 1)?;
227 lua_vm::api::set_top(state, top + 1)?;
228 Ok(true)
229 } else {
230 lua_vm::api::set_top(state, top)?;
231 Ok(false)
232 }
233}
234
235/// Push a human-readable name for the function described by `ar`.
236///
237/// C: `static void pushfuncname(lua_State *L, lua_Debug *ar)`
238fn push_func_name(
239 state: &mut LuaState,
240 ar: &mut LuaDebug,
241 global_lookup_target: Option<&mut LuaState>,
242) -> Result<(), LuaError> {
243 let found_global = match global_lookup_target {
244 Some(target) => push_global_func_name_from_target(state, target, ar)?,
245 None => push_global_func_name(state, ar)?,
246 };
247 if found_global {
248 // C: lua_pushfstring(L, "function '%s'", lua_tostring(L, -1));
249 let name = state.peek_bytes(-1).unwrap_or_else(|| b"?".to_vec());
250 state.push_fstring(format_args!("function '{}'", BStr(&name)))?;
251 // C: lua_remove(L, -2);
252 state.remove(-2)?;
253 } else if !ar.namewhat.is_empty() {
254 // C: lua_pushfstring(L, "%s '%s'", ar->namewhat, ar->name);
255 let namewhat = ar.namewhat.clone();
256 let name = ar.name.clone().unwrap_or_else(|| b"?".to_vec());
257 state.push_fstring(format_args!("{} '{}'", BStr(&namewhat), BStr(&name)))?;
258 } else if ar.what == b'm' {
259 // C: lua_pushliteral(L, "main chunk");
260 state.push_string(b"main chunk")?;
261 } else if ar.what != b'C' {
262 // C: lua_pushfstring(L, "function <%s:%d>", ar->short_src, ar->linedefined);
263 let src = ar.short_src.clone();
264 let line = ar.linedefined;
265 state.push_fstring(format_args!("function <{}:{}>", BStr(&src), line))?;
266 } else {
267 // C: lua_pushliteral(L, "?");
268 state.push_string(b"?")?;
269 }
270 Ok(())
271}
272
273/// Binary-search for the last valid stack level in `state`.
274///
275/// C: `static int lastlevel(lua_State *L)`
276fn last_level(state: &mut LuaState) -> i32 {
277 let mut ar = LuaDebug::default();
278 let mut li: i32 = 1;
279 let mut le: i32 = 1;
280 // C: while (lua_getstack(L, le, &ar)) { li = le; le *= 2; }
281 while state.get_stack(le, &mut ar) {
282 li = le;
283 le *= 2;
284 }
285 // binary search
286 while li < le {
287 let m = (li + le) / 2;
288 if state.get_stack(m, &mut ar) {
289 li = m + 1;
290 } else {
291 le = m;
292 }
293 }
294 le - 1
295}
296
297/// Build a stack traceback string from thread `other` starting at `level`.
298/// If `msg` is non-None it is prepended on its own line.
299/// Leaves the result string on top of `state`.
300///
301/// When `other` is `None`, the traceback is built for `state` itself (the
302/// common single-thread case). Rust's borrow checker forbids passing the same
303/// `&mut LuaState` twice, so we use an `Option` to express the aliasing intent
304/// rather than a separate parameter.
305///
306/// C: `LUALIB_API void luaL_traceback(lua_State *L, lua_State *L1, const char *msg, int level)`
307pub fn traceback(
308 state: &mut LuaState,
309 mut other: Option<&mut LuaState>,
310 msg: Option<&[u8]>,
311 level: i32,
312) -> Result<(), LuaError> {
313 let mut b = LuaBuffer::new();
314 let mut ar = LuaDebug::default();
315 let last = match &mut other {
316 Some(o) => last_level(o),
317 None => last_level(state),
318 };
319 // C: int limit2show = (last - level > LEVELS1 + LEVELS2) ? LEVELS1 : -1;
320 let mut limit2show: i32 = if last - level > LEVELS1 + LEVELS2 { LEVELS1 } else { -1 };
321 buf_init(state, &mut b);
322 if let Some(m) = msg {
323 add_lstring(&mut b, m);
324 add_char(&mut b, b'\n');
325 }
326 add_lstring(&mut b, b"stack traceback:");
327 let mut level = level;
328 loop {
329 let got = match &mut other {
330 Some(o) => o.get_stack(level, &mut ar),
331 None => state.get_stack(level, &mut ar),
332 };
333 if !got {
334 break;
335 }
336 level += 1;
337 if limit2show == 0 {
338 // C: int n = last - level - LEVELS2 + 1;
339 let n = last - level - LEVELS2 + 1;
340 // C: lua_pushfstring(L, "\n\t...\t(skipping %d levels)", n);
341 state.push_fstring(format_args!("\n\t...\t(skipping {} levels)", n))?;
342 add_value(state, &mut b)?;
343 level += n;
344 limit2show = LEVELS2;
345 } else {
346 limit2show -= 1;
347 // C: lua_getinfo(L1, "Slnt", &ar).
348 match &mut other {
349 Some(o) => o.get_info(b"Slnt", &mut ar)?,
350 None => state.get_info(b"Slnt", &mut ar)?,
351 }
352 if ar.currentline <= 0 {
353 // C: lua_pushfstring(L, "\n\t%s: in ", ar.short_src);
354 let src = ar.short_src.clone();
355 state.push_fstring(format_args!("\n\t{}: in ", BStr(&src)))?;
356 } else {
357 // C: lua_pushfstring(L, "\n\t%s:%d: in ", ar.short_src, ar.currentline);
358 let src = ar.short_src.clone();
359 let line = ar.currentline;
360 state.push_fstring(format_args!("\n\t{}:{}: in ", BStr(&src), line))?;
361 }
362 add_value(state, &mut b)?;
363 match &mut other {
364 Some(o) => push_func_name(state, &mut ar, Some(&mut **o))?,
365 None => push_func_name(state, &mut ar, None)?,
366 }
367 add_value(state, &mut b)?;
368 if ar.istailcall {
369 add_lstring(&mut b, b"\n\t(...tail calls...)");
370 }
371 }
372 }
373 push_result(state, &mut b)?;
374 Ok(())
375}
376
377// ── Error-report functions ─────────────────────────────────────────────────────
378
379/// Push an error for argument `arg` with extra message `extramsg`.
380/// Attempts to enrich the message with the calling function's name.
381/// Always returns `Err`.
382///
383/// C: `LUALIB_API int luaL_argerror(lua_State *L, int arg, const char *extramsg)`
384pub fn arg_error(
385 state: &mut LuaState,
386 mut arg: i32,
387 extramsg: &[u8],
388) -> Result<usize, LuaError> {
389 let mut ar = LuaDebug::default();
390 if !state.get_stack(0, &mut ar) {
391 // C: return luaL_error(L, "bad argument #%d (%s)", arg, extramsg);
392 return Err(LuaError::runtime(format_args!(
393 "bad argument #{} ({})",
394 arg,
395 BStr(extramsg)
396 )));
397 }
398 state.get_info(b"n", &mut ar)?;
399 if ar.namewhat == b"method" {
400 arg -= 1; // do not count 'self'
401 if arg == 0 {
402 let name = ar.name.clone().unwrap_or_else(|| b"?".to_vec());
403 return Err(LuaError::runtime(format_args!(
404 "calling '{}' on bad self ({})",
405 BStr(&name),
406 BStr(extramsg)
407 )));
408 }
409 }
410 let fname = if ar.name.is_none() {
411 if push_global_func_name(state, &mut ar)? {
412 state.peek_bytes(-1).unwrap_or_else(|| b"?".to_vec())
413 } else {
414 b"?".to_vec()
415 }
416 } else {
417 ar.name.clone().unwrap_or_else(|| b"?".to_vec())
418 };
419 Err(LuaError::runtime(format_args!(
420 "bad argument #{} to '{}' ({})",
421 arg,
422 BStr(&fname),
423 BStr(extramsg)
424 )))
425}
426
427/// Push a type-mismatch error for argument `arg`, stating `tname` was expected.
428/// Always returns `Err`.
429///
430/// C: `LUALIB_API int luaL_typeerror(lua_State *L, int arg, const char *tname)`
431pub fn type_error_arg(
432 state: &mut LuaState,
433 arg: i32,
434 tname: &[u8],
435) -> Result<usize, LuaError> {
436 // C: if (luaL_getmetafield(L, arg, "__name") == LUA_TSTRING)
437 // typearg = lua_tostring(L, -1);
438 // else if (lua_type(L, arg) == LUA_TLIGHTUSERDATA)
439 // typearg = "light userdata";
440 // else
441 // typearg = luaL_typename(L, arg);
442 let typearg: Vec<u8> = if get_metafield(state, arg, b"__name")? == LuaType::String {
443 let bytes = state.peek_bytes(-1).unwrap_or_else(|| b"?".to_vec());
444 state.pop_n(1);
445 bytes
446 } else if state.type_at(arg) == LuaType::LightUserData {
447 b"light userdata".to_vec()
448 } else {
449 state.type_name_at(arg).to_vec()
450 };
451 // C: msg = lua_pushfstring(L, "%s expected, got %s", tname, typearg);
452 // C: return luaL_argerror(L, arg, msg);
453 let msg_owned = format!(
454 "{} expected, got {}",
455 BStr(tname),
456 BStr(&typearg)
457 );
458 arg_error(state, arg, msg_owned.as_bytes())
459}
460
461/// Push a type-tag error for `arg`, using the Lua type name for `tag`.
462///
463/// C: `static void tag_error(lua_State *L, int arg, int tag)`
464fn tag_error(state: &mut LuaState, arg: i32, tag: LuaType) -> Result<(), LuaError> {
465 let name = state.type_name(tag);
466 type_error_arg(state, arg, name)?;
467 Ok(())
468}
469
470/// Push a string describing the location of the call at `level` onto the stack.
471/// If no location is available, pushes an empty string.
472///
473/// C: `LUALIB_API void luaL_where(lua_State *L, int level)`
474pub fn push_where(state: &mut LuaState, level: i32) -> Result<(), LuaError> {
475 let mut ar = LuaDebug::default();
476 if state.get_stack(level, &mut ar) {
477 state.get_info(b"Sl", &mut ar)?;
478 if ar.currentline > 0 {
479 let src = ar.short_src.clone();
480 let line = ar.currentline;
481 state.push_fstring(format_args!("{}:{}: ", BStr(&src), line))?;
482 return Ok(());
483 }
484 }
485 // C: lua_pushfstring(L, ""); /* no information available */
486 state.push_string(b"")?;
487 Ok(())
488}
489
490/// Format a runtime error with source location and raise it.
491/// Always returns `Err`.
492///
493/// C: `LUALIB_API int luaL_error(lua_State *L, const char *fmt, ...)`
494///
495/// PORT NOTE: C uses varargs + `lua_pushvfstring`. Rust callers pass a
496/// pre-formatted `&[u8]` message; use `format_args!` at the call site.
497pub fn lua_error(state: &mut LuaState, msg: &[u8]) -> Result<usize, LuaError> {
498 push_where(state, 1)?;
499 let where_str = state.pop_bytes();
500 let full = [where_str.as_slice(), msg].concat();
501 Err(LuaError::runtime(format_args!("{}", BStr(&full))))
502}
503
504/// Push the result of a POSIX-style file operation onto the stack.
505/// On success pushes `true`; on failure pushes `nil, errmsg, errno`.
506/// Returns the number of pushed values.
507///
508/// C: `LUALIB_API int luaL_fileresult(lua_State *L, int stat, const char *fname)`
509pub fn file_result(
510 state: &mut LuaState,
511 stat: bool,
512 fname: Option<&[u8]>,
513) -> Result<usize, LuaError> {
514 if stat {
515 // C: lua_pushboolean(L, 1);
516 state.push(LuaValue::Bool(true));
517 Ok(1)
518 } else {
519 // C: luaL_pushfail(L); = lua_pushnil
520 state.push(LuaValue::Nil);
521 // TODO(port): use std::io::Error::last_os_error() for errno-style message.
522 let errmsg = b"(errno unavailable in Rust port)".to_vec();
523 if let Some(name) = fname {
524 let full = [name, b": ".as_slice(), &errmsg].concat();
525 state.push_bytes(&full)?;
526 } else {
527 state.push_bytes(&errmsg)?;
528 }
529 // C: lua_pushinteger(L, en);
530 // TODO(port): push actual errno integer once os-error helpers are available.
531 state.push(LuaValue::Int(0));
532 Ok(3)
533 }
534}
535
536/// Push the result of a process-exit status onto the stack.
537/// Returns 3 values: success-bool-or-nil, exit-kind string, status code.
538///
539/// C: `LUALIB_API int luaL_execresult(lua_State *L, int stat)`
540// TODO(port): POSIX WIFEXITED / WIFSIGNALED inspection requires cfg(unix).
541pub fn exec_result(state: &mut LuaState, stat: i32) -> Result<usize, LuaError> {
542 if stat != 0 {
543 return file_result(state, false, None);
544 }
545 // C: const char *what = "exit";
546 let what = b"exit".as_slice();
547 // C: if (*what == 'e' && stat == 0) lua_pushboolean(L, 1);
548 state.push(LuaValue::Bool(true));
549 state.push_bytes(what)?;
550 state.push(LuaValue::Int(stat as i64));
551 Ok(3)
552}
553
554// ── Userdata / metatable helpers ──────────────────────────────────────────────
555
556/// Create a new metatable for type `tname` and register it in the registry.
557/// Returns `true` (and leaves new metatable on stack) if the table was created;
558/// returns `false` (and leaves existing table on stack) if already existed.
559///
560/// C: `LUALIB_API int luaL_newmetatable(lua_State *L, const char *tname)`
561pub fn new_metatable(state: &mut LuaState, tname: &[u8]) -> Result<bool, LuaError> {
562 // C: if (luaL_getmetatable(L, tname) != LUA_TNIL) return 0;
563 if get_metatable(state, tname)? != LuaType::Nil {
564 return Ok(false); // leave previous value on top
565 }
566 state.pop_n(1);
567 // C: lua_createtable(L, 0, 2);
568 state.create_table(0, 2)?;
569 // C: lua_pushstring(L, tname); lua_setfield(L, -2, "__name");
570 state.push_bytes(tname)?;
571 state.set_field(-2, b"__name")?;
572 // C: lua_pushvalue(L, -1); lua_setfield(L, LUA_REGISTRYINDEX, tname);
573 state.push_value(-1)?;
574 state.set_field(LUA_REGISTRYINDEX, tname)?;
575 Ok(true)
576}
577
578/// Set the metatable of the value at stack top to the one registered as `tname`.
579///
580/// C: `LUALIB_API void luaL_setmetatable(lua_State *L, const char *tname)`
581pub fn set_metatable(state: &mut LuaState, tname: &[u8]) -> Result<(), LuaError> {
582 // C: luaL_getmetatable(L, tname); lua_setmetatable(L, -2);
583 get_metatable(state, tname)?;
584 state.set_metatable(-2)?;
585 Ok(())
586}
587
588/// Check whether the value at `ud` is a full userdata with metatable `tname`.
589/// Returns `Some(userdata)` if yes, `None` otherwise.
590///
591/// C: `LUALIB_API void *luaL_testudata(lua_State *L, int ud, const char *tname)`
592pub fn test_udata(
593 state: &mut LuaState,
594 ud: i32,
595 tname: &[u8],
596) -> Result<Option<GcRef<LuaUserData>>, LuaError> {
597 // C: void *p = lua_touserdata(L, ud);
598 let p = state.to_userdata(ud);
599 if let Some(p) = p {
600 // C: if (lua_getmetatable(L, ud))
601 if state.get_metatable(ud)? {
602 // C: luaL_getmetatable(L, tname);
603 get_metatable(state, tname)?;
604 // C: if (!lua_rawequal(L, -1, -2)) p = NULL;
605 let eq = state.raw_equal(-1, -2)?;
606 state.pop_n(2); // remove both metatables
607 if eq {
608 return Ok(Some(p));
609 }
610 }
611 }
612 Ok(None)
613}
614
615/// Like `test_udata` but raises a type error if the check fails.
616///
617/// C: `LUALIB_API void *luaL_checkudata(lua_State *L, int ud, const char *tname)`
618pub fn check_udata(
619 state: &mut LuaState,
620 ud: i32,
621 tname: &[u8],
622) -> Result<GcRef<LuaUserData>, LuaError> {
623 // C: void *p = luaL_testudata(L, ud, tname);
624 // C: luaL_argexpected(L, p != NULL, ud, tname);
625 match test_udata(state, ud, tname)? {
626 Some(p) => Ok(p),
627 None => {
628 type_error_arg(state, ud, tname)?;
629 unreachable!()
630 }
631 }
632}
633
634// ── Argument-check functions ──────────────────────────────────────────────────
635
636/// Check that `arg` is one of the strings in `lst` and return its index.
637/// If `def` is `Some` it is used as default when `arg` is absent/nil.
638///
639/// C: `LUALIB_API int luaL_checkoption(lua_State *L, int arg, const char *def, const char *const lst[])`
640pub fn check_option(
641 state: &mut LuaState,
642 arg: i32,
643 def: Option<&[u8]>,
644 lst: &[&[u8]],
645) -> Result<usize, LuaError> {
646 let name: Vec<u8> = match def {
647 Some(d) if state.is_none_or_nil(arg) => d.to_vec(),
648 _ => check_lstring(state, arg)?.as_bytes().to_vec(),
649 };
650 for (i, entry) in lst.iter().enumerate() {
651 if *entry == name.as_slice() {
652 return Ok(i);
653 }
654 }
655 Err(LuaError::runtime(format_args!(
656 "invalid option '{}'",
657 BStr(&name)
658 )))
659}
660
661/// Ensure the stack has at least `space` extra slots; raise on failure.
662///
663/// C: `LUALIB_API void luaL_checkstack(lua_State *L, int space, const char *msg)`
664pub fn check_stack(
665 state: &mut LuaState,
666 space: i32,
667 msg: Option<&[u8]>,
668) -> Result<(), LuaError> {
669 // C: if (l_unlikely(!lua_checkstack(L, space)))
670 if !state.check_stack_space(space) {
671 match msg {
672 Some(m) => {
673 return Err(LuaError::runtime(format_args!(
674 "stack overflow ({})",
675 BStr(m)
676 )));
677 }
678 None => {
679 return Err(LuaError::runtime(format_args!("stack overflow")));
680 }
681 }
682 }
683 Ok(())
684}
685
686/// Assert that the value at `arg` has Lua type `t`; raise type error otherwise.
687///
688/// C: `LUALIB_API void luaL_checktype(lua_State *L, int arg, int t)`
689pub fn check_type(state: &mut LuaState, arg: i32, t: LuaType) -> Result<(), LuaError> {
690 // C: if (l_unlikely(lua_type(L, arg) != t)) tag_error(L, arg, t);
691 if state.type_at(arg) != t {
692 tag_error(state, arg, t)?;
693 }
694 Ok(())
695}
696
697/// Assert that a value (not `none`) is present at `arg`.
698///
699/// C: `LUALIB_API void luaL_checkany(lua_State *L, int arg)`
700pub fn check_any(state: &mut LuaState, arg: i32) -> Result<(), LuaError> {
701 // C: if (l_unlikely(lua_type(L, arg) == LUA_TNONE))
702 if state.type_at(arg) == LuaType::None {
703 return Err(LuaError::arg_error(arg, "value expected"));
704 }
705 Ok(())
706}
707
708/// Return the string at `arg` as bytes; raise a type error if not a string.
709///
710/// C: `LUALIB_API const char *luaL_checklstring(lua_State *L, int arg, size_t *len)`
711pub fn check_lstring(state: &mut LuaState, arg: i32) -> Result<GcRef<LuaString>, LuaError> {
712 // C: const char *s = lua_tolstring(L, arg, len);
713 match state.to_lua_string(arg) {
714 Some(s) => Ok(s),
715 None => {
716 tag_error(state, arg, LuaType::String)?;
717 unreachable!()
718 }
719 }
720}
721
722/// Return the string at `arg`; if absent/nil return `def`.
723///
724/// C: `LUALIB_API const char *luaL_optlstring(lua_State *L, int arg, const char *def, size_t *len)`
725pub fn opt_lstring(
726 state: &mut LuaState,
727 arg: i32,
728 def: Option<&[u8]>,
729) -> Result<Option<Vec<u8>>, LuaError> {
730 // C: if (lua_isnoneornil(L, arg)) { ... return def; }
731 if state.is_none_or_nil(arg) {
732 return Ok(def.map(|d| d.to_vec()));
733 }
734 let s = check_lstring(state, arg)?;
735 Ok(Some(s.as_bytes().to_vec()))
736}
737
738/// Return the number at `arg` as `f64`; raise a type error if not a number.
739///
740/// C: `LUALIB_API lua_Number luaL_checknumber(lua_State *L, int arg)`
741pub fn check_number(state: &mut LuaState, arg: i32) -> Result<f64, LuaError> {
742 // C: int isnum; lua_Number d = lua_tonumberx(L, arg, &isnum);
743 match state.to_number_x(arg) {
744 Some(d) => Ok(d),
745 None => {
746 tag_error(state, arg, LuaType::Number)?;
747 unreachable!()
748 }
749 }
750}
751
752/// Return the number at `arg`; if absent/nil return `def`.
753///
754/// C: `LUALIB_API lua_Number luaL_optnumber(lua_State *L, int arg, lua_Number def)`
755pub fn opt_number(state: &mut LuaState, arg: i32, def: f64) -> Result<f64, LuaError> {
756 // C: return luaL_opt(L, luaL_checknumber, arg, def);
757 if state.is_none_or_nil(arg) {
758 Ok(def)
759 } else {
760 check_number(state, arg)
761 }
762}
763
764/// Raise an error for a non-integer number argument.
765///
766/// C: `static void interror(lua_State *L, int arg)`
767///
768/// Always returns `Err`. The `Ok` arm uses `unreachable!()` to satisfy the
769/// return type; `!` (never) is nightly-only so we use `Result<usize, LuaError>`.
770fn int_error(state: &mut LuaState, arg: i32) -> Result<usize, LuaError> {
771 if state.is_number(arg) {
772 Err(LuaError::arg_error(
773 arg,
774 "number has no integer representation",
775 ))
776 } else {
777 tag_error(state, arg, LuaType::Number)?;
778 unreachable!("tag_error always returns Err")
779 }
780}
781
782/// Return the integer at `arg` as `i64`; raise if not an integer-convertible number.
783///
784/// C: `LUALIB_API lua_Integer luaL_checkinteger(lua_State *L, int arg)`
785pub fn check_integer(state: &mut LuaState, arg: i32) -> Result<i64, LuaError> {
786 // C: int isnum; lua_Integer d = lua_tointegerx(L, arg, &isnum);
787 match state.to_integer_x(arg) {
788 Some(d) => Ok(d),
789 None => {
790 int_error(state, arg)?;
791 unreachable!("int_error always returns Err")
792 }
793 }
794}
795
796/// Return the integer at `arg`; if absent/nil return `def`.
797///
798/// C: `LUALIB_API lua_Integer luaL_optinteger(lua_State *L, int arg, lua_Integer def)`
799pub fn opt_integer(state: &mut LuaState, arg: i32, def: i64) -> Result<i64, LuaError> {
800 // C: return luaL_opt(L, luaL_checkinteger, arg, def);
801 if state.is_none_or_nil(arg) {
802 Ok(def)
803 } else {
804 check_integer(state, arg)
805 }
806}
807
808// ── Buffer manipulation ────────────────────────────────────────────────────────
809
810impl LuaBuffer {
811 /// Create a new empty buffer.
812 ///
813 /// C: the initial `luaL_Buffer` has a small inline array of `LUAL_BUFFERSIZE` bytes.
814 /// Rust uses `Vec::new()` which starts at zero capacity; capacity is managed by Vec.
815 pub fn new() -> Self {
816 LuaBuffer { data: Vec::new() }
817 }
818
819 /// Returns the number of bytes currently in the buffer.
820 pub fn len(&self) -> usize {
821 self.data.len()
822 }
823}
824
825impl Default for LuaBuffer {
826 fn default() -> Self {
827 LuaBuffer::new()
828 }
829}
830
831/// Initialize `buf` and associate it with `state`.
832/// Pushes a placeholder light-userdata onto `state` to anchor the buffer in C.
833/// In Rust the Vec is self-contained; we still push a placeholder for stack-slot
834/// compatibility with code that later calls `add_value` / `push_result`.
835///
836/// C: `LUALIB_API void luaL_buffinit(lua_State *L, luaL_Buffer *B)`
837pub fn buf_init(state: &mut LuaState, buf: &mut LuaBuffer) {
838 // PORT NOTE: C pushes a light-userdata placeholder onto the stack to hold
839 // the buffer's position. We still push nil as a stack slot placeholder so
840 // that add_value / push_result see the same stack layout.
841 *buf = LuaBuffer::new();
842 // C: lua_pushlightuserdata(L, (void*)B); /* push placeholder */
843 // We push nil; Phase B can revisit if this matters for GC interaction.
844 let _ = state.push(LuaValue::Nil);
845}
846
847/// Initialize `buf`, reserve `sz` bytes, and return the writable region.
848///
849/// C: `LUALIB_API char *luaL_buffinitsize(lua_State *L, luaL_Buffer *B, size_t sz)`
850pub fn buf_init_size(
851 state: &mut LuaState,
852 buf: &mut LuaBuffer,
853 sz: usize,
854) -> Result<(), LuaError> {
855 buf_init(state, buf);
856 buf.data.reserve(sz);
857 Ok(())
858}
859
860/// Compute a new buffer capacity that accommodates `sz` more bytes,
861/// growing by ×1.5 or more.
862///
863/// C: `static size_t newbuffsize(luaL_Buffer *B, size_t sz)`
864fn new_buff_size(buf: &LuaBuffer, sz: usize) -> Result<usize, LuaError> {
865 // C: if (l_unlikely(MAX_SIZET - sz < B->n)) return luaL_error(...)
866 if usize::MAX - sz < buf.len() {
867 return Err(LuaError::runtime(format_args!("buffer too large")));
868 }
869 let newsize = (buf.data.capacity() / 2) * 3; // ×1.5
870 if newsize < buf.len() + sz {
871 Ok(buf.len() + sz)
872 } else {
873 Ok(newsize)
874 }
875}
876
877/// Ensure at least `sz` free bytes are available in `buf`.
878///
879/// C: `static char *prepbuffsize(luaL_Buffer *B, size_t sz, int boxidx)`
880/// C: `LUALIB_API char *luaL_prepbuffsize(luaL_Buffer *B, size_t sz)`
881pub fn prep_buff_size(buf: &mut LuaBuffer, sz: usize) -> Result<(), LuaError> {
882 if buf.data.capacity() - buf.data.len() < sz {
883 let newcap = new_buff_size(buf, sz)?;
884 buf.data.reserve(newcap - buf.data.len());
885 }
886 Ok(())
887}
888
889/// Append `s` to `buf`.
890///
891/// C: `LUALIB_API void luaL_addlstring(luaL_Buffer *B, const char *s, size_t l)`
892pub fn add_lstring(buf: &mut LuaBuffer, s: &[u8]) {
893 if !s.is_empty() {
894 buf.data.extend_from_slice(s);
895 }
896}
897
898/// Append a single byte to `buf`.
899///
900/// C: `#define luaL_addchar(B,c) ...`
901pub fn add_char(buf: &mut LuaBuffer, c: u8) {
902 buf.data.push(c);
903}
904
905/// Append `sz` to the length counter (used after writing directly into the buffer).
906///
907/// C: `#define luaL_addsize(B,s) ((B)->n += (s))`
908pub fn add_size(buf: &mut LuaBuffer, sz: usize) {
909 // PORT NOTE: In C this is a direct `n += sz` on the inline length field.
910 // With Vec, length is implicit; this is a no-op unless caller wrote past len.
911 // TODO(port): if direct-write into spare capacity is needed, switch to `unsafe`
912 // set_len or redesign; for Phase A this is a no-op.
913 let _ = sz;
914}
915
916/// Pop the string at top of `state`'s stack and append it to `buf`.
917///
918/// C: `LUALIB_API void luaL_addvalue(luaL_Buffer *B)`
919pub fn add_value(state: &mut LuaState, buf: &mut LuaBuffer) -> Result<(), LuaError> {
920 // C: const char *s = lua_tolstring(L, -1, &len);
921 if let Some(bytes) = state.peek_bytes(-1) {
922 let owned = bytes.to_vec();
923 add_lstring(buf, &owned);
924 }
925 // C: lua_pop(L, 1);
926 state.pop_n(1);
927 Ok(())
928}
929
930/// Push the buffer contents as a Lua string onto `state`'s stack.
931///
932/// C: `LUALIB_API void luaL_pushresult(luaL_Buffer *B)`
933pub fn push_result(state: &mut LuaState, buf: &mut LuaBuffer) -> Result<(), LuaError> {
934 // C: lua_pushlstring(L, B->b, B->n);
935 state.push_bytes(&buf.data)?;
936 // C: if (buffonstack(B)) lua_closeslot(L, -2);
937 // C: lua_remove(L, -2); /* remove box or placeholder */
938 state.remove(-2)?;
939 Ok(())
940}
941
942/// Add `sz` bytes to the buffer count then call `push_result`.
943///
944/// C: `LUALIB_API void luaL_pushresultsize(luaL_Buffer *B, size_t sz)`
945pub fn push_result_size(
946 state: &mut LuaState,
947 buf: &mut LuaBuffer,
948 sz: usize,
949) -> Result<(), LuaError> {
950 add_size(buf, sz);
951 push_result(state, buf)
952}
953
954/// Perform global byte-string substitution: replace all occurrences of `pat`
955/// with `repl` in `s`, appending results into `buf`.
956///
957/// C: `LUALIB_API void luaL_addgsub(luaL_Buffer *b, const char *s, const char *p, const char *r)`
958pub fn add_gsub(buf: &mut LuaBuffer, s: &[u8], pat: &[u8], repl: &[u8]) {
959 if pat.is_empty() {
960 add_lstring(buf, s);
961 return;
962 }
963 let mut remaining = s;
964 while let Some(pos) = find_bytes(remaining, pat) {
965 // C: luaL_addlstring(b, s, wild - s);
966 add_lstring(buf, &remaining[..pos]);
967 // C: luaL_addstring(b, r);
968 add_lstring(buf, repl);
969 remaining = &remaining[pos + pat.len()..];
970 }
971 // C: luaL_addstring(b, s); /* push last suffix */
972 add_lstring(buf, remaining);
973}
974
975/// Build a string from `s` by replacing `pat` with `repl`, push it on the stack,
976/// and return the bytes of the pushed string.
977///
978/// C: `LUALIB_API const char *luaL_gsub(lua_State *L, const char *s, const char *p, const char *r)`
979pub fn gsub<'a>(
980 state: &'a mut LuaState,
981 s: &[u8],
982 pat: &[u8],
983 repl: &[u8],
984) -> Result<Vec<u8>, LuaError> {
985 let mut b = LuaBuffer::new();
986 buf_init(state, &mut b);
987 add_gsub(&mut b, s, pat, repl);
988 push_result(state, &mut b)?;
989 // C: return lua_tostring(L, -1);
990 Ok(state.peek_bytes(-1).unwrap_or_default())
991}
992
993/// Find `needle` in `haystack`, returning the byte offset or `None`.
994///
995/// Internal helper replacing C's `strstr`.
996fn find_bytes(haystack: &[u8], needle: &[u8]) -> Option<usize> {
997 if needle.is_empty() {
998 return Some(0);
999 }
1000 haystack.windows(needle.len()).position(|w| w == needle)
1001}
1002
1003// ── Reference system ──────────────────────────────────────────────────────────
1004
1005/// Store the value at the top of the stack in table `t` and return a unique
1006/// integer reference. If the value is `nil`, returns `LUA_REFNIL` without
1007/// modifying the table.
1008///
1009/// C: `LUALIB_API int luaL_ref(lua_State *L, int t)`
1010pub fn lua_ref(state: &mut LuaState, t: i32) -> Result<i32, LuaError> {
1011 // C: if (lua_isnil(L, -1)) { lua_pop(L, 1); return LUA_REFNIL; }
1012 if state.type_at(-1) == LuaType::Nil {
1013 state.pop_n(1);
1014 return Ok(LUA_REFNIL);
1015 }
1016 let t = state.abs_index(t);
1017 // C: if (lua_rawgeti(L, t, freelist) == LUA_TNIL)
1018 let ref_val: i32;
1019 if state.raw_get_i(t, FREELIST_REF)? == LuaType::Nil {
1020 ref_val = 0; // list is empty
1021 // C: lua_pushinteger(L, 0); lua_rawseti(L, t, freelist);
1022 state.push(LuaValue::Int(0));
1023 state.raw_set_i(t, FREELIST_REF)?;
1024 } else {
1025 // C: lua_assert(lua_isinteger(L, -1)); ref = (int)lua_tointeger(L, -1);
1026 debug_assert!(state.type_at(-1) == LuaType::Number);
1027 ref_val = state.to_integer_x(-1).unwrap_or(0) as i32;
1028 }
1029 state.pop_n(1); // remove element from stack
1030 let next_ref: i32;
1031 if ref_val != 0 {
1032 // C: lua_rawgeti(L, t, ref); lua_rawseti(L, t, freelist);
1033 state.raw_get_i(t, ref_val as i64)?;
1034 state.raw_set_i(t, FREELIST_REF)?;
1035 next_ref = ref_val;
1036 } else {
1037 // C: ref = (int)lua_rawlen(L, t) + 1;
1038 next_ref = (state.raw_len(t) as i32) + 1;
1039 }
1040 // C: lua_rawseti(L, t, ref);
1041 state.raw_set_i(t, next_ref as i64)?;
1042 Ok(next_ref)
1043}
1044
1045/// Release reference `ref` from table `t`, adding it to the free list.
1046///
1047/// C: `LUALIB_API void luaL_unref(lua_State *L, int t, int ref)`
1048pub fn lua_unref(state: &mut LuaState, t: i32, r: i32) -> Result<(), LuaError> {
1049 if r >= 0 {
1050 let t = state.abs_index(t);
1051 // C: lua_rawgeti(L, t, freelist);
1052 state.raw_get_i(t, FREELIST_REF)?;
1053 debug_assert!(state.type_at(-1) == LuaType::Number);
1054 // C: lua_rawseti(L, t, ref); /* t[ref] = t[freelist] */
1055 state.raw_set_i(t, r as i64)?;
1056 // C: lua_pushinteger(L, ref); lua_rawseti(L, t, freelist);
1057 state.push(LuaValue::Int(r as i64));
1058 state.raw_set_i(t, FREELIST_REF)?;
1059 }
1060 Ok(())
1061}
1062
1063// ── Load functions ─────────────────────────────────────────────────────────────
1064
1065/// Internal chunk reader that returns a single buffer slice then signals EOF.
1066///
1067/// C: `static const char *getS(lua_State *L, void *ud, size_t *size)`
1068fn make_string_reader(data: Vec<u8>) -> impl FnMut() -> Option<Vec<u8>> {
1069 let mut remaining = Some(data);
1070 move || remaining.take()
1071}
1072
1073/// Strip an optional UTF-8 BOM (EF BB BF) and any `#`-prefixed first line.
1074///
1075/// PORT NOTE: C reads byte-by-byte with `getc`/`feof` and lazily reopens the
1076/// file in binary mode if it looks like a binary chunk. Here we slurp the file
1077/// into memory (`std::fs::read`), strip the BOM, and let `lua_vm::api::load`
1078/// dispatch text vs. binary by the first byte. The "binary chunk" branch in
1079/// `luaL_loadfilex` exists in C because text mode does newline translation;
1080/// `std::fs::read` already returns raw bytes on every platform we support.
1081fn skip_bom_and_shebang(buf: &[u8]) -> Vec<u8> {
1082 let s = if buf.starts_with(b"\xEF\xBB\xBF") { &buf[3..] } else { buf };
1083 if s.first() == Some(&b'#') {
1084 let nl = s.iter().position(|&b| b == b'\n').map(|p| p + 1).unwrap_or(s.len());
1085 let rest = &s[nl..];
1086 if rest.first() == Some(&0x1B) {
1087 rest.to_vec()
1088 } else {
1089 let mut out = Vec::with_capacity(rest.len() + 1);
1090 out.push(b'\n');
1091 out.extend_from_slice(rest);
1092 out
1093 }
1094 } else {
1095 s.to_vec()
1096 }
1097}
1098
1099/// Load a file as a Lua chunk. Returns `LUA_OK` on success or an error code.
1100///
1101/// C: `LUALIB_API int luaL_loadfilex(lua_State *L, const char *filename, const char *mode)`
1102///
1103/// PORT NOTE: PORTING.md §1 bans `std::fs` outside `lua-cli`, but C-Lua's
1104/// `luaL_loadfilex` is part of the auxiliary library (`lauxlib.c`) and is
1105/// reachable from the base library (`loadfile`/`dofile`). Phase A's stub
1106/// raised an error here, which broke `loadfile(missing)` returning `nil, err`.
1107/// The real C semantics push an error string onto the stack and return a
1108/// non-zero status, which `load_aux` then converts to `(nil, errmsg)`.
1109pub fn load_filex(
1110 state: &mut LuaState,
1111 filename: Option<&[u8]>,
1112 mode: Option<&[u8]>,
1113) -> Result<i32, LuaError> {
1114 let _ = mode;
1115 let fname = match filename {
1116 Some(f) => f,
1117 None => {
1118 // TODO(port): stdin loading not yet supported in lua-stdlib; return
1119 // an error string matching C's "cannot read stdin" shape.
1120 state.push_string(b"cannot read stdin: no filename given")?;
1121 return Ok(LUA_ERRFILE);
1122 }
1123 };
1124 let path = match std::str::from_utf8(fname) {
1125 Ok(s) => std::path::PathBuf::from(s),
1126 Err(_) => {
1127 state.push_fstring(format_args!(
1128 "cannot open {}: invalid utf-8 in filename",
1129 BStr(fname)
1130 ))?;
1131 return Ok(LUA_ERRFILE);
1132 }
1133 };
1134 let raw = match std::fs::read(&path) {
1135 Ok(bytes) => bytes,
1136 Err(e) => {
1137 state.push_fstring(format_args!(
1138 "cannot open {}: {}",
1139 BStr(fname),
1140 e
1141 ))?;
1142 return Ok(LUA_ERRFILE);
1143 }
1144 };
1145 let payload = skip_bom_and_shebang(&raw);
1146 let mut once = Some(payload);
1147 let boxed: Box<dyn FnMut() -> Option<Vec<u8>>> =
1148 Box::new(move || once.take());
1149 let mut chunkname = b"@".to_vec();
1150 chunkname.extend_from_slice(fname);
1151 let status = lua_vm::api::load(state, boxed, Some(&chunkname), mode)?;
1152 Ok(if status == LuaStatus::Ok { 0 } else { status as i32 })
1153}
1154
1155/// Load a buffer as a Lua chunk.
1156///
1157/// C: `LUALIB_API int luaL_loadbufferx(lua_State *L, const char *buff, size_t size, const char *name, const char *mode)`
1158pub fn load_bufferx(
1159 state: &mut LuaState,
1160 buff: &[u8],
1161 name: &[u8],
1162 mode: Option<&[u8]>,
1163) -> Result<i32, LuaError> {
1164 // C: LoadS ls; ls.s = buff; ls.size = size;
1165 // C: return lua_load(L, getS, &ls, name, mode);
1166 // TODO(phase-b): state.load expects (chunk: &[u8], name, mode) in state_stub; the reader-based loader needs a load_with_reader API match.
1167 let _reader = make_string_reader(buff.to_vec());
1168 let ok = state.load(buff, name, mode)?;
1169 Ok(if ok { 0 } else { 1 })
1170}
1171
1172/// Load a buffer as a Lua chunk (no mode argument).
1173///
1174/// C: `#define luaL_loadbuffer(L,s,sz,n) luaL_loadbufferx(L,s,sz,n,NULL)`
1175pub fn load_buffer(
1176 state: &mut LuaState,
1177 buff: &[u8],
1178 name: &[u8],
1179) -> Result<i32, LuaError> {
1180 load_bufferx(state, buff, name, None)
1181}
1182
1183/// Load a NUL-terminated byte-string as a Lua chunk.
1184///
1185/// C: `LUALIB_API int luaL_loadstring(lua_State *L, const char *s)`
1186pub fn load_string(state: &mut LuaState, s: &[u8]) -> Result<i32, LuaError> {
1187 // C: return luaL_loadbuffer(L, s, strlen(s), s);
1188 load_buffer(state, s, s)
1189}
1190
1191// ── Meta-field and misc helpers ───────────────────────────────────────────────
1192
1193/// Push the metafield `event` of `obj` onto the stack and return its type.
1194/// If there is no metafield, nothing is pushed and `LuaType::Nil` is returned.
1195///
1196/// C: `LUALIB_API int luaL_getmetafield(lua_State *L, int obj, const char *event)`
1197pub fn get_metafield(
1198 state: &mut LuaState,
1199 obj: i32,
1200 event: &[u8],
1201) -> Result<LuaType, LuaError> {
1202 // C: if (!lua_getmetatable(L, obj)) return LUA_TNIL;
1203 if !state.get_metatable(obj)? {
1204 return Ok(LuaType::Nil);
1205 }
1206 // C: lua_pushstring(L, event); tt = lua_rawget(L, -2);
1207 state.push_bytes(event)?;
1208 let tt = state.raw_get(-2)?;
1209 if tt == LuaType::Nil {
1210 // C: lua_pop(L, 2); /* remove metatable and metafield */
1211 state.pop_n(2);
1212 } else {
1213 // C: lua_remove(L, -2); /* remove only metatable */
1214 state.remove(-2)?;
1215 }
1216 Ok(tt)
1217}
1218
1219/// Call the metafield `event` of `obj` with `obj` as argument, pushing one result.
1220/// Returns `true` if the meta-method existed and was called.
1221///
1222/// C: `LUALIB_API int luaL_callmeta(lua_State *L, int obj, const char *event)`
1223pub fn call_meta(state: &mut LuaState, obj: i32, event: &[u8]) -> Result<bool, LuaError> {
1224 let obj = state.abs_index(obj);
1225 // C: if (luaL_getmetafield(L, obj, event) == LUA_TNIL) return 0;
1226 if get_metafield(state, obj, event)? == LuaType::Nil {
1227 return Ok(false);
1228 }
1229 // C: lua_pushvalue(L, obj); lua_call(L, 1, 1);
1230 state.push_value(obj)?;
1231 state.call(1, 1)?;
1232 Ok(true)
1233}
1234
1235/// Return the length of the value at `idx` as a `i64`, raising an error if
1236/// the length is not an integer.
1237///
1238/// C: `LUALIB_API lua_Integer luaL_len(lua_State *L, int idx)`
1239pub fn lua_len(state: &mut LuaState, idx: i32) -> Result<i64, LuaError> {
1240 // C: lua_len(L, idx);
1241 state.len_op(idx)?;
1242 // C: l = lua_tointegerx(L, -1, &isnum);
1243 let l = match state.to_integer_x(-1) {
1244 Some(n) => n,
1245 None => {
1246 return Err(LuaError::runtime(format_args!(
1247 "object length is not an integer"
1248 )));
1249 }
1250 };
1251 // C: lua_pop(L, 1);
1252 state.pop_n(1);
1253 Ok(l)
1254}
1255
1256/// Convert the value at `idx` to a byte-string representation (using `__tostring`
1257/// if available) and push it onto the stack.
1258///
1259/// C: `LUALIB_API const char *luaL_tolstring(lua_State *L, int idx, size_t *len)`
1260pub fn to_lua_string(state: &mut LuaState, idx: i32) -> Result<Vec<u8>, LuaError> {
1261 let idx = state.abs_index(idx);
1262 // C: if (luaL_callmeta(L, idx, "__tostring"))
1263 if call_meta(state, idx, b"__tostring")? {
1264 // C: if (!lua_isstring(L, -1)) luaL_error(...)
1265 if state.type_at(-1) != LuaType::String {
1266 return Err(LuaError::runtime(format_args!(
1267 "'__tostring' must return a string"
1268 )));
1269 }
1270 } else {
1271 match state.type_at(idx) {
1272 LuaType::Number => {
1273 // C: if (lua_isinteger(L, idx)) lua_pushfstring(L, "%I", ...)
1274 if state.is_integer(idx) {
1275 let i = state.to_integer_x(idx).unwrap_or(0);
1276 state.push_fstring(format_args!("{}", i))?;
1277 } else {
1278 let f = state.to_number_x(idx).unwrap_or(0.0);
1279 state.push_fstring(format_args!("{:?}", f))?;
1280 }
1281 }
1282 LuaType::String => {
1283 // C: lua_pushvalue(L, idx);
1284 state.push_value(idx)?;
1285 }
1286 LuaType::Boolean => {
1287 let b = state.to_boolean(idx);
1288 state.push_string(if b { b"true" } else { b"false" })?;
1289 }
1290 LuaType::Nil => {
1291 state.push_string(b"nil")?;
1292 }
1293 _ => {
1294 // C: int tt = luaL_getmetafield(L, idx, "__name");
1295 let tt = get_metafield(state, idx, b"__name")?;
1296 let kind: Vec<u8> = if tt == LuaType::String {
1297 state.peek_bytes(-1).unwrap_or_else(|| b"?".to_vec())
1298 } else {
1299 state.type_name_at(idx).to_vec()
1300 };
1301 // C: lua_pushfstring(L, "%s: %p", kind, lua_topointer(L, idx));
1302 // TODO(port): lua_topointer gives a pointer address; in Rust use
1303 // a hash or allocation address for a stable identifier.
1304 state.push_fstring(format_args!("{}: 0x?", BStr(&kind)))?;
1305 if tt != LuaType::Nil {
1306 // C: lua_remove(L, -2);
1307 state.remove(-2)?;
1308 }
1309 }
1310 }
1311 }
1312 // C: return lua_tolstring(L, -1, len);
1313 Ok(state.peek_bytes(-1).unwrap_or_default())
1314}
1315
1316/// Register the functions in `l` into the table at `-(nup + 1)`, giving each
1317/// closure the `nup` upvalues currently at the top of the stack.
1318///
1319/// C: `LUALIB_API void luaL_setfuncs(lua_State *L, const luaL_Reg *l, int nup)`
1320pub fn set_funcs(
1321 state: &mut LuaState,
1322 l: &[LuaReg],
1323 nup: i32,
1324) -> Result<(), LuaError> {
1325 check_stack(state, nup, Some(b"too many upvalues"))?;
1326 for reg in l {
1327 match reg.func {
1328 None => {
1329 // C: lua_pushboolean(L, 0);
1330 state.push(LuaValue::Bool(false));
1331 }
1332 Some(f) => {
1333 // C: for (i = 0; i < nup; i++) lua_pushvalue(L, -nup);
1334 for _ in 0..nup {
1335 state.push_value(-nup)?;
1336 }
1337 // C: lua_pushcclosure(L, l->func, nup);
1338 state.push_c_closure(f, nup)?;
1339 }
1340 }
1341 // C: lua_setfield(L, -(nup + 2), l->name);
1342 state.set_field(-(nup + 2), reg.name)?;
1343 }
1344 // C: lua_pop(L, nup);
1345 state.pop_n(nup as usize);
1346 Ok(())
1347}
1348
1349/// Ensure `state[idx][fname]` is a table; push it.
1350/// Returns `true` if the table already existed, `false` if newly created.
1351///
1352/// C: `LUALIB_API int luaL_getsubtable(lua_State *L, int idx, const char *fname)`
1353pub fn get_subtable(
1354 state: &mut LuaState,
1355 idx: i32,
1356 fname: &[u8],
1357) -> Result<bool, LuaError> {
1358 if state.get_field(idx, fname)? == LuaType::Table {
1359 return Ok(true);
1360 }
1361 state.pop_n(1);
1362 let idx = state.abs_index(idx);
1363 let new_tbl = state.new_table();
1364 state.push(LuaValue::Table(new_tbl));
1365 state.push_value(-1)?;
1366 state.set_field(idx, fname)?;
1367 Ok(false)
1368}
1369
1370/// Simplified `require`: open module `modname` via `openf`, register it in
1371/// `package.loaded`, and (if `glb`) in the global table.
1372/// Leaves the module on top of the stack.
1373///
1374/// C: `LUALIB_API void luaL_requiref(lua_State *L, const char *modname, lua_CFunction openf, int glb)`
1375pub fn requiref(
1376 state: &mut LuaState,
1377 modname: &[u8],
1378 openf: fn(&mut LuaState) -> Result<usize, LuaError>,
1379 glb: bool,
1380) -> Result<(), LuaError> {
1381 get_subtable(state, LUA_REGISTRYINDEX, LUA_LOADED_TABLE)?;
1382 state.get_field(-1, modname)?;
1383 if !state.to_boolean(-1) {
1384 state.pop_n(1);
1385 state.push_c_function(openf)?;
1386 state.push_bytes(modname)?;
1387 state.call(1, 1)?;
1388 state.push_value(-1)?;
1389 state.set_field(-3, modname)?;
1390 }
1391 state.remove(-2)?;
1392 if glb {
1393 // C: lua_pushvalue(L, -1); lua_setglobal(L, modname);
1394 state.push_value(-1)?;
1395 state.set_global(modname)?;
1396 }
1397 Ok(())
1398}
1399
1400// ── Helper for registry-based metatable lookup ─────────────────────────────────
1401
1402/// Push `registry[tname]` and return its type.
1403///
1404/// C: `#define luaL_getmetatable(L,n) (lua_getfield(L, LUA_REGISTRYINDEX, (n)))`
1405pub fn get_metatable(state: &mut LuaState, tname: &[u8]) -> Result<LuaType, LuaError> {
1406 state.get_field(LUA_REGISTRYINDEX, tname)
1407}
1408
1409// ── State creation and version check ─────────────────────────────────────────
1410
1411/// Create a new `LuaState` with the default allocator, a panic handler, and
1412/// warnings disabled.
1413///
1414/// C: `LUALIB_API lua_State *luaL_newstate(void)`
1415pub fn new_state() -> Result<LuaState, LuaError> {
1416 // C: lua_State *L = lua_newstate(l_alloc, NULL);
1417 // PORT NOTE: Rust's allocator is used implicitly; no l_alloc hook needed.
1418 // TODO(phase-b): LuaState::new() / set_panic_handler / set_warn_fn need a real LuaState constructor in lua-vm. Stub for Phase A.
1419 let _ = default_panic_handler;
1420 let _ = warn_off;
1421 todo!("phase-b: LuaState::new()")
1422}
1423
1424/// Default panic handler: print message to stderr and return to abort.
1425///
1426/// C: `static int panic(lua_State *L)`
1427fn default_panic_handler(state: &mut LuaState) -> Result<usize, LuaError> {
1428 // C: const char *msg = (lua_type(L, -1) == LUA_TSTRING) ? lua_tostring(L, -1) : "...";
1429 let msg = if state.type_at(-1) == LuaType::String {
1430 state.peek_bytes(-1).unwrap_or_else(|| b"?".to_vec())
1431 } else {
1432 b"error object is not a string".to_vec()
1433 };
1434 // C: lua_writestringerror(...)
1435 eprintln!("PANIC: unprotected error in call to Lua API ({})", BStr(&msg));
1436 Ok(0) // return to Lua to abort
1437}
1438
1439/// Warning function: warnings are off.
1440///
1441/// C: `static void warnfoff(void *ud, const char *message, int tocont)`
1442fn warn_off(state: &mut LuaState, message: &[u8], tocont: bool) -> Result<(), LuaError> {
1443 check_control(state, message, tocont)?;
1444 Ok(())
1445}
1446
1447/// Warning function: ready to start a new message.
1448///
1449/// C: `static void warnfon(void *ud, const char *message, int tocont)`
1450fn warn_on(state: &mut LuaState, message: &[u8], tocont: bool) -> Result<(), LuaError> {
1451 if check_control(state, message, tocont)? {
1452 return Ok(());
1453 }
1454 eprint!("Lua warning: ");
1455 warn_cont(state, message, tocont)
1456}
1457
1458/// Warning function: continue writing a previous warning message.
1459///
1460/// C: `static void warnfcont(void *ud, const char *message, int tocont)`
1461fn warn_cont(state: &mut LuaState, message: &[u8], tocont: bool) -> Result<(), LuaError> {
1462 // C: lua_writestringerror("%s", message);
1463 eprint!("{}", BStr(message));
1464 // TODO(phase-b): set_warn_fn expects lua_CFunction in state_stub; warn_cont/warn_on take (msg, tocont). Wire after warn-fn API lands in lua-vm.
1465 if tocont {
1466 let _ = (warn_cont as fn(&mut LuaState, &[u8], bool) -> Result<(), LuaError>,);
1467 } else {
1468 eprintln!();
1469 let _ = (warn_on as fn(&mut LuaState, &[u8], bool) -> Result<(), LuaError>,);
1470 }
1471 Ok(())
1472}
1473
1474/// Handle a warning control message (e.g. `"@on"`, `"@off"`).
1475/// Returns `true` if the message was a recognised control message.
1476///
1477/// C: `static int checkcontrol(lua_State *L, const char *message, int tocont)`
1478fn check_control(
1479 state: &mut LuaState,
1480 message: &[u8],
1481 tocont: bool,
1482) -> Result<bool, LuaError> {
1483 // C: if (tocont || *(message++) != '@') return 0;
1484 if tocont || message.first() != Some(&b'@') {
1485 return Ok(false);
1486 }
1487 let cmd = &message[1..];
1488 // TODO(phase-b): set_warn_fn expects lua_CFunction in state_stub; warn_off/warn_on take (msg, tocont). Wire after warn-fn API lands in lua-vm.
1489 let _ = state;
1490 if cmd == b"off" {
1491 let _ = warn_off as fn(&mut LuaState, &[u8], bool) -> Result<(), LuaError>;
1492 } else if cmd == b"on" {
1493 let _ = warn_on as fn(&mut LuaState, &[u8], bool) -> Result<(), LuaError>;
1494 }
1495 Ok(true)
1496}
1497
1498/// Version-compatibility check: error if numeric type sizes or version mismatch.
1499///
1500/// C: `LUALIB_API void luaL_checkversion_(lua_State *L, lua_Number ver, size_t sz)`
1501pub fn check_version(state: &mut LuaState, ver: f64, sz: usize) -> Result<(), LuaError> {
1502 // C: LUAL_NUMSIZES = sizeof(lua_Integer)*16 + sizeof(lua_Number)
1503 const LUAL_NUMSIZES: usize = std::mem::size_of::<i64>() * 16 + std::mem::size_of::<f64>();
1504 if sz != LUAL_NUMSIZES {
1505 return Err(LuaError::runtime(format_args!(
1506 "core and library have incompatible numeric types"
1507 )));
1508 }
1509 let v = state.lua_version();
1510 if (v - ver).abs() > f64::EPSILON {
1511 return Err(LuaError::runtime(format_args!(
1512 "version mismatch: app. needs {}, Lua core provides {}",
1513 ver, v
1514 )));
1515 }
1516 Ok(())
1517}
1518
1519// ── Internal display helper ────────────────────────────────────────────────────
1520
1521/// Wrapper that implements `Display` for `&[u8]` as a lossy byte string.
1522/// Used to embed byte slices in `format_args!` without allocating a `String`.
1523///
1524/// PORT NOTE: not used for Lua string data; used only for error message
1525/// formatting inside `format_args!` literals.
1526struct BStr<'a>(&'a [u8]);
1527
1528impl<'a> std::fmt::Display for BStr<'a> {
1529 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1530 for &b in self.0 {
1531 if b.is_ascii() {
1532 f.write_char(b as char)?;
1533 } else {
1534 write!(f, "\\x{:02x}", b)?;
1535 }
1536 }
1537 Ok(())
1538 }
1539}
1540
1541// Required for fmt::Display
1542use std::fmt::Write as _;
1543
1544// ── LuaDebug Default ─────────────────────────────────────────────────────────
1545
1546
1547// ──────────────────────────────────────────────────────────────────────────
1548// PORT STATUS
1549// source: src/lauxlib.c (1127 lines, ~50 functions)
1550// target_crate: lua-stdlib
1551// confidence: medium
1552// todos: 10
1553// port_notes: 8
1554// unsafe_blocks: 0
1555// notes: Buffer simplified from stack-based C UBox/box-on-Lua-stack to
1556// plain Vec<u8> (LuaBuffer); UBox/resizebox/boxgc/boxmt/newbox
1557// machinery dropped entirely — Rust Drop handles deallocation.
1558// load_filex now reads via std::fs::read and pushes an error
1559// string on open failure so loadfile/dofile return (nil, err)
1560// per C semantics (stdin loading still TODO).
1561// Warning system uses fn-ptr callbacks matching lua_WarnFunction
1562// type; warnfoff/warnfon/warnfcont translated faithfully.
1563// LuaState / LuaDebug / GcRef are Phase-A stubs; Phase B replaces
1564// with real imports from lua-vm / lua-types.
1565// add_size() is a no-op in Phase A (Vec tracks length implicitly);
1566// direct buffer writes via spare capacity need revisit in Phase B.
1567// int_error() return type changed from `!` to `Result<usize,_>` as
1568// the never type is nightly-only on stable Rust.
1569// ──────────────────────────────────────────────────────────────────────────