lust/lua_compat/
mod.rs

1#![allow(non_snake_case, non_camel_case_types)]
2//! Lua 5.1 C API compatibility scaffolding.
3//! This module will host the runtime bridge and tracing that drive extern stub generation.
4
5use crate::bytecode::{Value, ValueKey};
6use crate::number::{LustFloat, LustInt};
7use crate::vm::{NativeCallResult, VM};
8use alloc::rc::Rc;
9use alloc::string::String;
10use alloc::vec::Vec;
11use core::cell::RefCell;
12use core::ffi::{c_char, c_int, c_void};
13use core::hash::{Hash, Hasher};
14use core::ptr;
15use core::sync::atomic::{AtomicUsize, Ordering};
16use hashbrown::{hash_map::Entry, HashMap};
17#[cfg(all(feature = "packages", not(target_arch = "wasm32")))]
18use libloading::Library;
19use std::cell::RefCell as StdRefCell;
20use std::ffi::{CStr, CString};
21use std::path::PathBuf;
22use std::sync::OnceLock;
23
24thread_local! {
25    static LUST_FN_REGISTRY: StdRefCell<HashMap<usize, Value>> = StdRefCell::new(HashMap::new());
26}
27
28#[cfg(feature = "lua_transpile")]
29pub mod transpile;
30
31static NEXT_LUA_FUNCTION_ID: AtomicUsize = AtomicUsize::new(1);
32
33#[derive(Clone, Debug)]
34struct LuaTraceConfig {
35    enabled: bool,
36    stack: bool,
37    cfunc: bool,
38    filter: Option<String>,
39}
40
41static LUA_TRACE_CONFIG: OnceLock<LuaTraceConfig> = OnceLock::new();
42
43fn env_flag(name: &str) -> bool {
44    match std::env::var(name) {
45        Ok(value) => {
46            let value = value.trim();
47            !(value.is_empty() || value == "0" || value.eq_ignore_ascii_case("false"))
48        }
49        Err(_) => false,
50    }
51}
52
53fn lua_trace_config() -> &'static LuaTraceConfig {
54    LUA_TRACE_CONFIG.get_or_init(|| LuaTraceConfig {
55        enabled: env_flag("LUST_LUA_TRACE"),
56        stack: env_flag("LUST_LUA_TRACE_STACK"),
57        cfunc: env_flag("LUST_LUA_TRACE_CFUNC"),
58        filter: std::env::var("LUST_LUA_TRACE_FILTER")
59            .ok()
60            .and_then(|v| (!v.trim().is_empty()).then_some(v)),
61    })
62}
63
64fn format_lua_value_brief(value: &LuaValue) -> String {
65    match value {
66        LuaValue::Nil => "nil".to_string(),
67        LuaValue::Bool(b) => format!("bool({})", b),
68        LuaValue::Int(i) => format!("int({})", i),
69        LuaValue::Float(f) => format!("num({})", f),
70        LuaValue::String(s) => {
71            const MAX: usize = 40;
72            if s.len() > MAX {
73                format!("str({:?}…)", &s[..MAX])
74            } else {
75                format!("str({:?})", s)
76            }
77        }
78        LuaValue::Table(handle) => format!("table({:p})", Rc::as_ptr(handle)),
79        LuaValue::Function(f) => match &f.name {
80            Some(name) => format!("func#{}({})", f.id, name),
81            None => format!("func#{}", f.id),
82        },
83        LuaValue::Userdata(u) => format!("ud#{}", u.id),
84        LuaValue::Thread(t) => format!("thread#{}", t.id),
85        LuaValue::LightUserdata(ptr) => format!("lud({:#x})", ptr),
86    }
87}
88
89fn format_lua_stack_brief(stack: &[LuaValue]) -> String {
90    const MAX: usize = 8;
91    if stack.is_empty() {
92        return "[]".to_string();
93    }
94    let mut parts = Vec::new();
95    let start = stack.len().saturating_sub(MAX);
96    for value in &stack[start..] {
97        parts.push(format_lua_value_brief(value));
98    }
99    if start > 0 {
100        format!("[… {}]", parts.join(", "))
101    } else {
102        format!("[{}]", parts.join(", "))
103    }
104}
105
106fn next_lua_function_id() -> usize {
107    NEXT_LUA_FUNCTION_ID.fetch_add(1, Ordering::SeqCst)
108}
109
110pub(crate) fn register_lust_function(value: Value) -> usize {
111    let id = next_lua_function_id();
112    LUST_FN_REGISTRY.with(|registry| {
113        registry.borrow_mut().insert(id, value);
114    });
115    id
116}
117
118pub(crate) fn lookup_lust_function(id: usize) -> Option<Value> {
119    LUST_FN_REGISTRY.with(|registry| registry.borrow().get(&id).cloned())
120}
121
122/// Lua 5.1 type codes.
123pub const LUA_TNONE: c_int = -1;
124pub const LUA_TNIL: c_int = 0;
125pub const LUA_TBOOLEAN: c_int = 1;
126pub const LUA_TLIGHTUSERDATA: c_int = 2;
127pub const LUA_TNUMBER: c_int = 3;
128pub const LUA_TSTRING: c_int = 4;
129pub const LUA_TTABLE: c_int = 5;
130pub const LUA_TFUNCTION: c_int = 6;
131pub const LUA_TUSERDATA: c_int = 7;
132pub const LUA_TTHREAD: c_int = 8;
133pub const LUA_REGISTRYINDEX: c_int = -10000;
134pub const LUA_ENVIRONINDEX: c_int = -10001;
135pub const LUA_GLOBALSINDEX: c_int = -10002;
136
137/// Lua 5.1 error codes.
138pub const LUA_ERRRUN: c_int = 2;
139pub const LUA_ERRSYNTAX: c_int = 3;
140pub const LUA_ERRMEM: c_int = 4;
141pub const LUA_ERRERR: c_int = 5;
142
143pub type lua_Number = LustFloat;
144pub type lua_Integer = LustInt;
145pub type lua_CFunction = Option<unsafe extern "C" fn(*mut lua_State) -> c_int>;
146
147#[repr(C)]
148pub struct lua_State {
149    pub state: LuaState,
150}
151
152#[repr(C)]
153pub struct luaL_Reg {
154    pub name: *const c_char,
155    pub func: lua_CFunction,
156}
157
158/// Simplified mirror of the Lua 5.1 buffer helper type.
159pub const LUAL_BUFFERSIZE: usize = 8192;
160
161#[repr(C)]
162pub struct luaL_Buffer {
163    pub p: *mut c_char,
164    pub lvl: c_int,
165    pub L: *mut lua_State,
166    pub buffer: [c_char; LUAL_BUFFERSIZE],
167}
168
169/// Metadata about a Lua C library that should be loaded through the compatibility layer.
170#[derive(Debug, Clone)]
171pub struct LuaModuleSpec {
172    pub library_path: PathBuf,
173    pub entrypoints: Vec<String>,
174}
175
176impl LuaModuleSpec {
177    pub fn new(library_path: PathBuf, entrypoints: Vec<String>) -> Self {
178        Self {
179            library_path,
180            entrypoints,
181        }
182    }
183}
184
185/// Describes a traced call into the Lua 5.1 API while evaluating `luaopen_*`.
186#[derive(Debug, Clone)]
187pub struct LuaApiCall {
188    pub function: String,
189    pub args: Vec<String>,
190}
191
192/// Placeholder for a traced module export that can later be turned into a Lust extern stub.
193#[derive(Debug, Clone)]
194pub struct LuaModuleTrace {
195    pub module: String,
196    pub api_calls: Vec<LuaApiCall>,
197}
198
199/// Result of running a `luaopen_*` entrypoint through the compat shim.
200#[derive(Clone)]
201pub struct LuaOpenResult {
202    pub module: String,
203    pub trace: Vec<LuaApiCall>,
204    pub returns: Vec<LuaValue>,
205    pub state: Option<Rc<RefCell<Box<lua_State>>>>,
206}
207
208impl core::fmt::Debug for LuaOpenResult {
209    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
210        f.debug_struct("LuaOpenResult")
211            .field("module", &self.module)
212            .field("trace", &self.trace)
213            .field("returns", &self.returns)
214            .finish_non_exhaustive()
215    }
216}
217
218#[derive(Clone, Debug)]
219pub enum LuaValue {
220    Nil,
221    Bool(bool),
222    Int(LustInt),
223    Float(LustFloat),
224    String(String),
225    Table(LuaTableHandle),
226    Function(LuaFunction),
227    Userdata(LuaUserdata),
228    Thread(LuaThread),
229    LightUserdata(usize),
230}
231
232#[derive(Clone, Debug)]
233pub struct LuaFunction {
234    pub id: usize,
235    pub name: Option<String>,
236    pub cfunc: lua_CFunction,
237    pub lust_handle: Option<usize>,
238    pub upvalues: Vec<LuaValue>,
239}
240
241#[derive(Clone, Debug)]
242pub struct LuaUserdata {
243    pub id: usize,
244    pub data: *mut c_void,
245    pub state: *mut lua_State,
246}
247
248#[derive(Clone, Debug)]
249pub struct LuaThread {
250    pub id: usize,
251}
252
253#[derive(Clone, Debug, Default)]
254pub struct LuaTable {
255    pub entries: HashMap<LuaValue, LuaValue>,
256    pub metamethods: HashMap<String, LuaValue>,
257    pub metatable: Option<LuaTableHandle>,
258}
259
260pub type LuaTableHandle = Rc<RefCell<LuaTable>>;
261
262impl PartialEq for LuaValue {
263    fn eq(&self, other: &Self) -> bool {
264        use LuaValue::*;
265        match (self, other) {
266            (Nil, Nil) => true,
267            (Bool(a), Bool(b)) => a == b,
268            (Int(a), Int(b)) => a == b,
269            (Float(a), Float(b)) => a == b,
270            (String(a), String(b)) => a == b,
271            (LightUserdata(a), LightUserdata(b)) => a == b,
272            (Table(a), Table(b)) => Rc::ptr_eq(a, b),
273            (Function(a), Function(b)) => a.id == b.id,
274            (Userdata(a), Userdata(b)) => a.id == b.id,
275            (Thread(a), Thread(b)) => a.id == b.id,
276            _ => false,
277        }
278    }
279}
280
281impl Eq for LuaValue {}
282
283impl Hash for LuaValue {
284    fn hash<H: Hasher>(&self, state: &mut H) {
285        core::mem::discriminant(self).hash(state);
286        match self {
287            LuaValue::Nil => {}
288            LuaValue::Bool(b) => b.hash(state),
289            LuaValue::Int(i) => i.hash(state),
290            LuaValue::Float(f) => f.to_bits().hash(state),
291            LuaValue::String(s) => s.hash(state),
292            LuaValue::LightUserdata(ptr) => ptr.hash(state),
293            LuaValue::Table(handle) => (Rc::as_ptr(handle) as usize).hash(state),
294            LuaValue::Function(f) => f.id.hash(state),
295            LuaValue::Userdata(u) => u.id.hash(state),
296            LuaValue::Thread(t) => t.id.hash(state),
297        }
298    }
299}
300
301// Arithmetic operators for LuaValue
302impl std::ops::Add for LuaValue {
303    type Output = LuaValue;
304    fn add(self, other: Self) -> Self::Output {
305        use LuaValue::*;
306        match (self, other) {
307            (Int(a), Int(b)) => Int(a + b),
308            (Float(a), Float(b)) => Float(a + b),
309            (Int(a), Float(b)) => Float(a as LustFloat + b),
310            (Float(a), Int(b)) => Float(a + b as LustFloat),
311            _ => Nil, // Lua would throw error, but return Nil for now
312        }
313    }
314}
315
316impl std::ops::Sub for LuaValue {
317    type Output = LuaValue;
318    fn sub(self, other: Self) -> Self::Output {
319        use LuaValue::*;
320        match (self, other) {
321            (Int(a), Int(b)) => Int(a - b),
322            (Float(a), Float(b)) => Float(a - b),
323            (Int(a), Float(b)) => Float(a as LustFloat - b),
324            (Float(a), Int(b)) => Float(a - b as LustFloat),
325            _ => Nil,
326        }
327    }
328}
329
330impl std::ops::Mul for LuaValue {
331    type Output = LuaValue;
332    fn mul(self, other: Self) -> Self::Output {
333        use LuaValue::*;
334        match (self, other) {
335            (Int(a), Int(b)) => Int(a * b),
336            (Float(a), Float(b)) => Float(a * b),
337            (Int(a), Float(b)) => Float(a as LustFloat * b),
338            (Float(a), Int(b)) => Float(a * b as LustFloat),
339            _ => Nil,
340        }
341    }
342}
343
344impl std::ops::Div for LuaValue {
345    type Output = LuaValue;
346    fn div(self, other: Self) -> Self::Output {
347        use LuaValue::*;
348        match (self, other) {
349            (Int(a), Int(b)) => Float(a as LustFloat / b as LustFloat),
350            (Float(a), Float(b)) => Float(a / b),
351            (Int(a), Float(b)) => Float(a as LustFloat / b),
352            (Float(a), Int(b)) => Float(a / b as LustFloat),
353            _ => Nil,
354        }
355    }
356}
357
358impl std::ops::Rem for LuaValue {
359    type Output = LuaValue;
360    fn rem(self, other: Self) -> Self::Output {
361        use LuaValue::*;
362        match (self, other) {
363            (Int(a), Int(b)) => Int(a % b),
364            (Float(a), Float(b)) => Float(a % b),
365            (Int(a), Float(b)) => Float(a as LustFloat % b),
366            (Float(a), Int(b)) => Float(a % b as LustFloat),
367            _ => Nil,
368        }
369    }
370}
371
372impl std::ops::Neg for LuaValue {
373    type Output = LuaValue;
374    fn neg(self) -> Self::Output {
375        use LuaValue::*;
376        match self {
377            Int(a) => Int(-a),
378            Float(a) => Float(-a),
379            _ => Nil,
380        }
381    }
382}
383
384impl PartialOrd for LuaValue {
385    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
386        use LuaValue::*;
387        match (self, other) {
388            (Int(a), Int(b)) => a.partial_cmp(b),
389            (Float(a), Float(b)) => a.partial_cmp(b),
390            (Int(a), Float(b)) => (*a as LustFloat).partial_cmp(b),
391            (Float(a), Int(b)) => a.partial_cmp(&(*b as LustFloat)),
392            (String(a), String(b)) => a.partial_cmp(b),
393            _ => None,
394        }
395    }
396}
397
398impl LuaTable {
399    pub fn new() -> Self {
400        Self::default()
401    }
402}
403
404/// Convert a LuaValue into a Lust `Value` that matches the builtin LuaValue enum/structs.
405fn register_c_function(
406    func: &LuaFunction,
407    state: &Rc<RefCell<Box<lua_State>>>,
408    vm: &VM,
409) -> Result<Value, String> {
410    let cfunc = func
411        .cfunc
412        .ok_or_else(|| "missing cfunc pointer".to_string())?;
413    let shared_state = state.clone();
414    let cfunc_name = func.name.clone();
415    let cfunc_upvalues = func.upvalues.clone();
416    let native = Value::NativeFunction(Rc::new(move |args: &[Value]| {
417        let state_cell = shared_state.clone();
418        VM::with_current(|vm| {
419            let cfg = lua_trace_config();
420            let saved_upvalues: Vec<LuaValue>;
421            let mut saved_stack: Vec<LuaValue>;
422            let mut saved_pending_error: Option<LuaValue>;
423            let ptr: *mut lua_State = {
424                let mut guard = state_cell.borrow_mut();
425                // Simulate a fresh Lua call frame without clobbering an existing frame.
426                // This matters for nested calls (Lua -> Lust -> Lua) where the outer C function
427                // still expects its own stack arguments to be intact.
428                saved_stack = core::mem::take(&mut guard.state.stack);
429                saved_pending_error = guard.state.pending_error.take();
430                saved_upvalues =
431                    core::mem::replace(&mut guard.state.current_upvalues, cfunc_upvalues.clone());
432
433                // Lua multiple-return semantics: if the last argument is a multi-return list, expand it.
434                // Our Lua-compat layer represents multi-returns as `Value::Array` (typically `Array<LuaValue>`).
435                // - For the last arg: expand all elements onto the Lua stack.
436                // - For non-last args: only pass the first element (Lua would truncate).
437                for (idx, arg) in args.iter().enumerate() {
438                    let is_last = idx + 1 == args.len();
439                    if let Value::Array(arr) = arg {
440                        let arr = arr.borrow();
441                        if is_last {
442                            for elem in arr.iter() {
443                                guard.state.push(value_to_lua(elem, vm));
444                            }
445                        } else if let Some(first) = arr.first() {
446                            guard.state.push(value_to_lua(first, vm));
447                        } else {
448                            guard.state.push(LuaValue::Nil);
449                        }
450                    } else {
451                        let lua_arg = value_to_lua(arg, vm);
452                        guard.state.push(lua_arg);
453                    }
454                }
455
456                if cfg.enabled || cfg.cfunc {
457                    let label = cfunc_name.as_deref().unwrap_or("<anonymous>");
458                    eprintln!(
459                        "[lua-cfunc] -> {} {:#x} nargs={} top={} stack={}",
460                        label,
461                        cfunc as usize,
462                        args.len(),
463                        guard.state.len(),
464                        format_lua_stack_brief(&guard.state.stack),
465                    );
466                }
467
468                &mut **guard as *mut lua_State
469            };
470
471            // Drop the RefCell borrow while the C function runs so nested cfunc calls can borrow too.
472            let ret_count = unsafe { cfunc(ptr) };
473
474            let results = {
475                let mut guard = state_cell.borrow_mut();
476                if cfg.enabled || cfg.cfunc {
477                    let label = cfunc_name.as_deref().unwrap_or("<anonymous>");
478                    eprintln!(
479                        "[lua-cfunc] <- {} {:#x} nret={} top={} stack={}",
480                        label,
481                        cfunc as usize,
482                        ret_count,
483                        guard.state.len(),
484                        format_lua_stack_brief(&guard.state.stack),
485                    );
486                }
487                let pending_error = guard.state.pending_error.take();
488                guard.state.current_upvalues = saved_upvalues;
489                if let Some(err) = pending_error {
490                    // Restore the outer call frame before returning the error.
491                    guard.state.stack = core::mem::take(&mut saved_stack);
492                    guard.state.pending_error = saved_pending_error.take();
493                    return Err(lua_error_message(&err));
494                }
495                if ret_count < 0 {
496                    guard.state.stack = core::mem::take(&mut saved_stack);
497                    guard.state.pending_error = saved_pending_error.take();
498                    return Err("lua_error".to_string());
499                }
500
501                let mut results = Vec::new();
502                if ret_count > 0 {
503                    for _ in 0..ret_count {
504                        results.push(guard.state.pop().unwrap_or(LuaValue::Nil));
505                    }
506                    results.reverse();
507                }
508
509                // Discard the callee call frame and restore the outer one.
510                guard.state.stack = core::mem::take(&mut saved_stack);
511                guard.state.pending_error = saved_pending_error.take();
512                results
513            };
514
515            let mut converted = Vec::new();
516            for value in &results {
517                converted.push(lua_to_lust(value, vm, Some(state_cell.clone()))?);
518            }
519            let return_value = Value::array(converted);
520            Ok(NativeCallResult::Return(return_value))
521        })
522    }));
523    let handle = register_lust_function(native.clone());
524    let instance = vm
525        .instantiate_struct(
526            "LuaFunction",
527            vec![(Rc::new("handle".to_string()), Value::Int(handle as i64))],
528        )
529        .map_err(|e| e.to_string())?;
530    Ok(Value::enum_variant("LuaValue", "Function", vec![instance]))
531}
532
533/// Register a Lua closure (non-C function) so it can be called from Lust code
534fn register_lua_closure(
535    func: &LuaFunction,
536    state: &Rc<RefCell<Box<lua_State>>>,
537    vm: &VM,
538) -> Result<Value, String> {
539    // Store the entire function value so we can call it later
540    let lua_func = LuaValue::Function(func.clone());
541    let shared_state = state.clone();
542
543    let native = Value::NativeFunction(Rc::new(move |args: &[Value]| {
544        VM::with_current(|vm| {
545            let state_cell = shared_state.clone();
546
547            // Build argument list as Lua values
548            let mut lua_args = Vec::new();
549            for arg in args {
550                lua_args.push(value_to_lua(arg, vm));
551            }
552
553            // Try to call the Lua function
554            // If it has a lust_handle, it's a Lust function wrapped as Lua
555            if let LuaValue::Function(f) = &lua_func {
556                if let Some(handle) = f.lust_handle {
557                    // Call through the Lust function registry
558                    if let Some(lust_func) = lookup_lust_function(handle) {
559                        let lust_args: Result<Vec<Value>, String> = lua_args
560                            .iter()
561                            .map(|v| lua_to_lust(v, vm, Some(state_cell.clone())))
562                            .collect();
563
564                        return match lust_args {
565                            Ok(converted_args) => {
566                                match vm.call_value(&lust_func, converted_args) {
567                                    Ok(ret_val) => {
568                                        let results = if let Value::Tuple(vals) = ret_val {
569                                            vals.iter().map(|v| value_to_lua(v, vm)).collect()
570                                        } else {
571                                            vec![value_to_lua(&ret_val, vm)]
572                                        };
573
574                                        // Convert back to Lust values
575                                        let lust_results: Result<Vec<Value>, String> = results
576                                            .iter()
577                                            .map(|v| lua_to_lust(v, vm, Some(state_cell.clone())))
578                                            .collect();
579
580                                        match lust_results {
581                                            Ok(vals) if vals.len() == 1 => Ok(
582                                                crate::bytecode::value::NativeCallResult::Return(
583                                                    vals[0].clone(),
584                                                ),
585                                            ),
586                                            Ok(vals) if vals.is_empty() => Ok(
587                                                crate::bytecode::value::NativeCallResult::Return(
588                                                    Value::Nil,
589                                                ),
590                                            ),
591                                            Ok(vals) => {
592                                                let arr = Rc::new(RefCell::new(vals));
593                                                Ok(crate::bytecode::value::NativeCallResult::Return(Value::Array(arr)))
594                                            }
595                                            Err(e) => Err(e),
596                                        }
597                                    }
598                                    Err(e) => Err(e.to_string()),
599                                }
600                            }
601                            Err(e) => Err(e),
602                        };
603                    }
604                }
605            }
606
607            // Pure Lua closures without bytecode interpreter support
608            Err("Cannot call pure Lua closures (no Lua bytecode interpreter available)".to_string())
609        })
610    }));
611
612    let handle = register_lust_function(native.clone());
613    let instance = vm
614        .instantiate_struct(
615            "LuaFunction",
616            vec![(Rc::new("handle".to_string()), Value::Int(handle as i64))],
617        )
618        .map_err(|e| e.to_string())?;
619    Ok(Value::enum_variant("LuaValue", "Function", vec![instance]))
620}
621
622fn lua_error_message(err: &LuaValue) -> String {
623    match err {
624        LuaValue::String(s) => s.clone(),
625        LuaValue::Table(handle) => {
626            let table = handle.borrow();
627            for key in [
628                LuaValue::Int(2),
629                LuaValue::String("message".to_string()),
630                LuaValue::String("msg".to_string()),
631                LuaValue::Int(1),
632            ] {
633                if let Some(LuaValue::String(s)) = table.entries.get(&key) {
634                    return s.clone();
635                }
636            }
637            format_lua_value_brief(err)
638        }
639        _ => format_lua_value_brief(err),
640    }
641}
642
643pub fn lua_to_lust(
644    value: &LuaValue,
645    vm: &VM,
646    state: Option<Rc<RefCell<Box<lua_State>>>>,
647) -> Result<Value, String> {
648    let mut table_cache: HashMap<usize, Value> = HashMap::new();
649    lua_to_lust_cached(value, vm, state, &mut table_cache)
650}
651
652fn lua_to_lust_cached(
653    value: &LuaValue,
654    vm: &VM,
655    state: Option<Rc<RefCell<Box<lua_State>>>>,
656    table_cache: &mut HashMap<usize, Value>,
657) -> Result<Value, String> {
658    match value {
659        LuaValue::Nil => Ok(Value::enum_unit("LuaValue", "Nil")),
660        LuaValue::Bool(b) => Ok(Value::enum_variant(
661            "LuaValue",
662            "Bool",
663            vec![Value::Bool(*b)],
664        )),
665        LuaValue::Int(i) => Ok(Value::enum_variant("LuaValue", "Int", vec![Value::Int(*i)])),
666        LuaValue::Float(f) => Ok(Value::enum_variant(
667            "LuaValue",
668            "Float",
669            vec![Value::Float(*f)],
670        )),
671        LuaValue::String(s) => Ok(Value::enum_variant(
672            "LuaValue",
673            "String",
674            vec![Value::string(s.clone())],
675        )),
676        LuaValue::LightUserdata(ptr) => Ok(Value::enum_variant(
677            "LuaValue",
678            "LightUserdata",
679            vec![Value::Int(*ptr as i64)],
680        )),
681        LuaValue::Function(func) => {
682            if let Some(handle) = func.lust_handle {
683                if let Some(inner) = lookup_lust_function(handle) {
684                    return Ok(inner);
685                }
686            }
687            if func.cfunc.is_some() {
688                if let Some(state) = &state {
689                    return register_c_function(func, state, vm);
690                }
691            }
692            // For Lua closures without cfunc, create a wrapper that can call them
693            if let Some(state_rc) = &state {
694                return register_lua_closure(func, state_rc, vm);
695            }
696            let instance = vm
697                .instantiate_struct(
698                    "LuaFunction",
699                    vec![(Rc::new("handle".to_string()), Value::Int(func.id as i64))],
700                )
701                .map_err(|e| e.to_string())?;
702            Ok(Value::enum_variant("LuaValue", "Function", vec![instance]))
703        }
704        LuaValue::Userdata(data) => {
705            let metamethods_value = vm.new_map_value();
706            let userdata_meta = state.as_ref().and_then(|cell| {
707                cell.borrow()
708                    .state
709                    .userdata_metamethods
710                    .get(&data.id)
711                    .cloned()
712            });
713            if let Some(meta) = userdata_meta {
714                for (name, meta_value) in meta {
715                    let converted =
716                        lua_to_lust_cached(&meta_value, vm, state.clone(), table_cache)?;
717                    metamethods_value
718                        .map_set(ValueKey::string(name), converted)
719                        .map_err(|e| e.to_string())?;
720                }
721            }
722
723            let instance = vm
724                .instantiate_struct(
725                    "LuaUserdata",
726                    vec![
727                        (Rc::new("handle".to_string()), Value::Int(data.id as i64)),
728                        (
729                            Rc::new("ptr".to_string()),
730                            Value::Int(data.data as usize as i64),
731                        ),
732                        (
733                            Rc::new("state".to_string()),
734                            Value::Int(data.state as usize as i64),
735                        ),
736                        (Rc::new("metamethods".to_string()), metamethods_value),
737                    ],
738                )
739                .map_err(|e| e.to_string())?;
740            Ok(Value::enum_variant("LuaValue", "Userdata", vec![instance]))
741        }
742        LuaValue::Thread(thread) => {
743            let instance = vm
744                .instantiate_struct(
745                    "LuaThread",
746                    vec![(Rc::new("handle".to_string()), Value::Int(thread.id as i64))],
747                )
748                .map_err(|e| e.to_string())?;
749            Ok(Value::enum_variant("LuaValue", "Thread", vec![instance]))
750        }
751        LuaValue::Table(handle) => lua_table_to_struct_cached(handle, vm, state, table_cache),
752    }
753}
754
755fn unwrap_lua_value_for_key(value: Value) -> Value {
756    if let Value::Enum {
757        enum_name,
758        variant,
759        values,
760    } = &value
761    {
762        if enum_name == "LuaValue" {
763            return match variant.as_str() {
764                "Nil" => Value::Nil,
765                "Bool" | "Int" | "Float" | "String" | "Table" | "Function" | "LightUserdata"
766                | "Userdata" | "Thread" => values
767                    .as_ref()
768                    .and_then(|v| v.get(0))
769                    .cloned()
770                    .unwrap_or(Value::Nil),
771                _ => value,
772            };
773        }
774    }
775    value
776}
777
778fn lua_table_to_struct_cached(
779    handle: &LuaTableHandle,
780    vm: &VM,
781    state: Option<Rc<RefCell<Box<lua_State>>>>,
782    table_cache: &mut HashMap<usize, Value>,
783) -> Result<Value, String> {
784    let cache_key = Rc::as_ptr(handle) as usize;
785    if let Some(existing) = table_cache.get(&cache_key) {
786        return Ok(existing.clone());
787    }
788
789    let table_value = vm.new_map_value();
790    let metamethods_value = vm.new_map_value();
791    let struct_value = vm
792        .instantiate_struct(
793            "LuaTable",
794            vec![
795                (Rc::new("table".to_string()), table_value.clone()),
796                (
797                    Rc::new("metamethods".to_string()),
798                    metamethods_value.clone(),
799                ),
800            ],
801        )
802        .map_err(|e| e.to_string())?;
803    let wrapped = Value::enum_variant("LuaValue", "Table", vec![struct_value]);
804    table_cache.insert(cache_key, wrapped.clone());
805
806    {
807        let borrowed = handle.borrow();
808        for (key, value) in &borrowed.entries {
809            // Table keys should behave like Lua keys (string/number/bool, etc.). Using the LuaValue
810            // wrapper enum directly as a key is problematic because enum keys currently compare by
811            // payload-pointer identity. Unwrap keys into their raw underlying values instead.
812            let key_val =
813                unwrap_lua_value_for_key(lua_to_lust_cached(key, vm, state.clone(), table_cache)?);
814            let value_val = lua_to_lust_cached(value, vm, state.clone(), table_cache)?;
815            table_value
816                .map_set(ValueKey::from(key_val), value_val)
817                .map_err(|e| e.to_string())?;
818        }
819        for (name, meta) in &borrowed.metamethods {
820            let meta_val = lua_to_lust_cached(meta, vm, state.clone(), table_cache)?;
821            metamethods_value
822                .map_set(ValueKey::string(name.clone()), meta_val)
823                .map_err(|e| e.to_string())?;
824        }
825    }
826
827    Ok(wrapped)
828}
829
830fn lua_function_from_handle(handle: usize) -> LuaFunction {
831    LuaFunction {
832        id: handle,
833        name: None,
834        cfunc: None,
835        lust_handle: Some(handle),
836        upvalues: Vec::new(),
837    }
838}
839
840pub(crate) fn value_to_lua(value: &Value, vm: &VM) -> LuaValue {
841    match value {
842        Value::Nil => LuaValue::Nil,
843        Value::Bool(b) => LuaValue::Bool(*b),
844        Value::Int(i) => LuaValue::Int(*i),
845        Value::Float(f) => LuaValue::Float(*f),
846        Value::String(s) => LuaValue::String((**s).clone()),
847        Value::Enum {
848            enum_name,
849            variant,
850            values,
851        } if enum_name == "LuaValue" => match variant.as_str() {
852            "Nil" => LuaValue::Nil,
853            "Bool" => values
854                .as_ref()
855                .and_then(|v| v.get(0))
856                .and_then(|v| {
857                    if let Value::Bool(b) = v {
858                        Some(*b)
859                    } else {
860                        None
861                    }
862                })
863                .map(LuaValue::Bool)
864                .unwrap_or(LuaValue::Nil),
865            "Int" => values
866                .as_ref()
867                .and_then(|v| v.get(0))
868                .and_then(|v| v.as_int())
869                .map(LuaValue::Int)
870                .unwrap_or(LuaValue::Nil),
871            "Float" => values
872                .as_ref()
873                .and_then(|v| v.get(0))
874                .and_then(|v| v.as_float())
875                .map(LuaValue::Float)
876                .unwrap_or(LuaValue::Nil),
877            "String" => values
878                .as_ref()
879                .and_then(|v| v.get(0))
880                .and_then(|v| v.as_string_rc())
881                .map(|s| LuaValue::String((*s).clone()))
882                .unwrap_or(LuaValue::Nil),
883            "LightUserdata" => values
884                .as_ref()
885                .and_then(|v| v.get(0))
886                .and_then(|v| v.as_int())
887                .map(|i| LuaValue::LightUserdata(i as usize))
888                .unwrap_or(LuaValue::LightUserdata(0)),
889            "Function" => {
890                let handle = values
891                    .as_ref()
892                    .and_then(|vals| vals.get(0))
893                    .and_then(|v| v.struct_get_field("handle"))
894                    .and_then(|v| v.as_int())
895                    .unwrap_or(0) as usize;
896                LuaValue::Function(lua_function_from_handle(handle))
897            }
898            "Userdata" => {
899                let handle = values
900                    .as_ref()
901                    .and_then(|vals| vals.get(0))
902                    .and_then(|v| v.struct_get_field("handle"))
903                    .and_then(|v| v.as_int())
904                    .unwrap_or(0) as usize;
905                let ptr = values
906                    .as_ref()
907                    .and_then(|vals| vals.get(0))
908                    .and_then(|v| v.struct_get_field("ptr"))
909                    .and_then(|v| v.as_int())
910                    .unwrap_or(0) as usize;
911                let state_ptr = values
912                    .as_ref()
913                    .and_then(|vals| vals.get(0))
914                    .and_then(|v| v.struct_get_field("state"))
915                    .and_then(|v| v.as_int())
916                    .unwrap_or(0) as usize;
917                LuaValue::Userdata(LuaUserdata {
918                    id: handle,
919                    data: ptr as *mut c_void,
920                    state: state_ptr as *mut lua_State,
921                })
922            }
923            "Thread" => {
924                let handle = values
925                    .as_ref()
926                    .and_then(|vals| vals.get(0))
927                    .and_then(|v| v.struct_get_field("handle"))
928                    .and_then(|v| v.as_int())
929                    .unwrap_or(0) as usize;
930                LuaValue::Thread(LuaThread { id: handle })
931            }
932            "Table" => {
933                if let Some(values) = values {
934                    if let Some(table_struct) = values.get(0) {
935                        let table_field = table_struct.struct_get_field("table");
936                        let meta_field = table_struct.struct_get_field("metamethods");
937                        let mut lua_table = LuaTable::new();
938                        if let Some(Value::Map(map)) = table_field {
939                            for (k, v) in map.borrow().iter() {
940                                lua_table
941                                    .entries
942                                    .insert(value_to_lua(&k.to_value(), vm), value_to_lua(v, vm));
943                            }
944                        }
945                        if let Some(Value::Map(meta)) = meta_field {
946                            for (k, v) in meta.borrow().iter() {
947                                if let Some(key) = k.to_value().as_string() {
948                                    lua_table
949                                        .metamethods
950                                        .insert(key.to_string(), value_to_lua(v, vm));
951                                }
952                            }
953                        }
954                        return LuaValue::Table(Rc::new(RefCell::new(lua_table)));
955                    }
956                }
957                LuaValue::Nil
958            }
959            _ => LuaValue::Nil,
960        },
961        Value::Map(map) => {
962            let mut table = LuaTable::new();
963            for (k, v) in map.borrow().iter() {
964                table
965                    .entries
966                    .insert(value_to_lua(&k.to_value(), vm), value_to_lua(v, vm));
967            }
968            LuaValue::Table(Rc::new(RefCell::new(table)))
969        }
970        Value::Function(_) | Value::Closure { .. } | Value::NativeFunction(_) => {
971            let handle = register_lust_function(value.clone());
972            LuaValue::Function(lua_function_from_handle(handle))
973        }
974        _ => LuaValue::LightUserdata(value.type_of() as usize),
975    }
976}
977
978/// Lightweight state used while running `luaopen_*` entrypoints via the compat layer.
979/// This is not a full Lua VM; it is just enough structure to drive API tracing and
980/// convert between Rust-side LuaValues and Lust-visible LuaValue enums.
981pub struct LuaState {
982    pub stack: Vec<LuaValue>,
983    trace: Vec<LuaApiCall>,
984    globals: HashMap<String, LuaValue>,
985    pending_error: Option<LuaValue>,
986    next_userdata_id: usize,
987    next_reference: i32,
988    references: HashMap<i32, LuaValue>,
989    userdata_storage: HashMap<usize, (Box<[usize]>, usize)>,
990    userdata_metamethods: HashMap<usize, HashMap<String, LuaValue>>,
991    pub userdata_metatables: HashMap<usize, LuaTableHandle>,
992    string_cache: Vec<std::ffi::CString>,
993    pub current_upvalues: Vec<LuaValue>,
994    #[cfg(all(feature = "packages", not(target_arch = "wasm32")))]
995    libraries: Vec<Rc<Library>>,
996    registry: LuaTableHandle,
997    globals_table: LuaTableHandle,
998}
999
1000impl Default for LuaState {
1001    fn default() -> Self {
1002        let globals_table = Rc::new(RefCell::new(LuaTable::new()));
1003        let registry = Rc::new(RefCell::new(LuaTable::new()));
1004        let loaded = Rc::new(RefCell::new(LuaTable::new()));
1005
1006        registry.borrow_mut().entries.insert(
1007            LuaValue::String("_LOADED".to_string()),
1008            LuaValue::Table(loaded.clone()),
1009        );
1010
1011        {
1012            let mut globals_guard = globals_table.borrow_mut();
1013            globals_guard.entries.insert(
1014                LuaValue::String("_G".to_string()),
1015                LuaValue::Table(globals_table.clone()),
1016            );
1017            let package = Rc::new(RefCell::new(LuaTable::new()));
1018            package.borrow_mut().entries.insert(
1019                LuaValue::String("loaded".to_string()),
1020                LuaValue::Table(loaded),
1021            );
1022            globals_guard.entries.insert(
1023                LuaValue::String("package".to_string()),
1024                LuaValue::Table(package),
1025            );
1026        }
1027
1028        Self {
1029            stack: Vec::new(),
1030            trace: Vec::new(),
1031            globals: HashMap::new(),
1032            pending_error: None,
1033            next_userdata_id: 0,
1034            next_reference: 0,
1035            references: HashMap::new(),
1036            userdata_storage: HashMap::new(),
1037            userdata_metamethods: HashMap::new(),
1038            userdata_metatables: HashMap::new(),
1039            string_cache: Vec::new(),
1040            current_upvalues: Vec::new(),
1041            #[cfg(all(feature = "packages", not(target_arch = "wasm32")))]
1042            libraries: Vec::new(),
1043            registry,
1044            globals_table,
1045        }
1046    }
1047}
1048
1049impl LuaState {
1050    pub fn new() -> Self {
1051        Self::default()
1052    }
1053
1054    pub fn push(&mut self, value: LuaValue) {
1055        self.stack.push(value);
1056    }
1057
1058    pub fn pop(&mut self) -> Option<LuaValue> {
1059        self.stack.pop()
1060    }
1061
1062    pub fn len(&self) -> usize {
1063        self.stack.len()
1064    }
1065
1066    pub fn record_call(&mut self, function: impl Into<String>, args: Vec<String>) {
1067        let function = function.into();
1068        let cfg = lua_trace_config();
1069        if cfg.enabled
1070            && cfg
1071                .filter
1072                .as_ref()
1073                .map(|filter| function.contains(filter))
1074                .unwrap_or(true)
1075        {
1076            if cfg.stack {
1077                eprintln!(
1078                    "[lua-api] {}({}) top={} stack={}",
1079                    function,
1080                    args.join(", "),
1081                    self.len(),
1082                    format_lua_stack_brief(&self.stack)
1083                );
1084            } else {
1085                eprintln!(
1086                    "[lua-api] {}({}) top={}",
1087                    function,
1088                    args.join(", "),
1089                    self.len()
1090                );
1091            }
1092        }
1093        self.trace.push(LuaApiCall { function, args });
1094    }
1095
1096    pub fn take_trace(&mut self) -> Vec<LuaApiCall> {
1097        core::mem::take(&mut self.trace)
1098    }
1099
1100    pub fn stack_snapshot(&self) -> Vec<LuaValue> {
1101        self.stack.clone()
1102    }
1103
1104    fn next_func_id(&mut self) -> usize {
1105        next_lua_function_id()
1106    }
1107
1108    fn next_userdata_id(&mut self) -> usize {
1109        let id = self.next_userdata_id;
1110        self.next_userdata_id += 1;
1111        id
1112    }
1113
1114    fn next_ref(&mut self) -> i32 {
1115        self.next_reference += 1;
1116        self.next_reference
1117    }
1118}
1119
1120fn state_from_ptr<'a>(ptr: *mut lua_State) -> Option<&'a mut LuaState> {
1121    unsafe { ptr.as_mut().map(|raw| &mut raw.state) }
1122}
1123
1124fn translate_index(len: usize, idx: c_int) -> Option<usize> {
1125    if idx == 0 {
1126        return None;
1127    }
1128    if idx > 0 {
1129        let idx = idx as usize;
1130        if idx == 0 || idx > len {
1131            None
1132        } else {
1133            Some(idx - 1)
1134        }
1135    } else {
1136        let adj = len as isize + idx as isize;
1137        if adj < 0 {
1138            None
1139        } else {
1140            Some(adj as usize)
1141        }
1142    }
1143}
1144
1145fn pseudo_table(state: &mut LuaState, idx: c_int) -> Option<LuaTableHandle> {
1146    match idx {
1147        LUA_REGISTRYINDEX => Some(state.registry.clone()),
1148        LUA_ENVIRONINDEX | LUA_GLOBALSINDEX => Some(state.globals_table.clone()),
1149        _ => None,
1150    }
1151}
1152
1153fn value_at(state: &mut LuaState, idx: c_int) -> Option<LuaValue> {
1154    if let Some(table) = pseudo_table(state, idx) {
1155        return Some(LuaValue::Table(table));
1156    }
1157    // Lua 5.1 C-closure upvalues are addressed via `lua_upvalueindex(i)`, which expands to
1158    // `LUA_GLOBALSINDEX - i`.
1159    if idx < LUA_GLOBALSINDEX {
1160        let upvalue = (LUA_GLOBALSINDEX - idx) as isize;
1161        if upvalue > 0 {
1162            let slot = (upvalue - 1) as usize;
1163            return state.current_upvalues.get(slot).cloned();
1164        }
1165    }
1166    translate_index(state.stack.len(), idx).and_then(|slot| state.stack.get(slot).cloned())
1167}
1168
1169fn ensure_table_at(state: &mut LuaState, idx: c_int) -> Option<LuaTableHandle> {
1170    if let Some(handle) = pseudo_table(state, idx) {
1171        return Some(handle);
1172    }
1173    let slot = translate_index(state.stack.len(), idx)?;
1174    match state.stack.get(slot) {
1175        Some(LuaValue::Table(handle)) => Some(handle.clone()),
1176        _ => None,
1177    }
1178}
1179
1180fn ensure_child_table(parent: &LuaTableHandle, key: &str) -> LuaTableHandle {
1181    let mut guard = parent.borrow_mut();
1182    match guard.entries.entry(LuaValue::String(key.to_string())) {
1183        Entry::Occupied(mut entry) => match entry.get() {
1184            LuaValue::Table(handle) => handle.clone(),
1185            _ => {
1186                let table = Rc::new(RefCell::new(LuaTable::new()));
1187                entry.insert(LuaValue::Table(table.clone()));
1188                table
1189            }
1190        },
1191        Entry::Vacant(entry) => {
1192            let table = Rc::new(RefCell::new(LuaTable::new()));
1193            entry.insert(LuaValue::Table(table.clone()));
1194            table
1195        }
1196    }
1197}
1198
1199fn cache_cstring<'a>(state: &'a mut LuaState, s: String) -> *const c_char {
1200    let owned = CString::new(s).unwrap_or_else(|_| CString::new("").unwrap());
1201    state.string_cache.push(owned);
1202    state
1203        .string_cache
1204        .last()
1205        .map(|c| c.as_ptr())
1206        .unwrap_or(core::ptr::null())
1207}
1208
1209fn value_typecode(value: &LuaValue) -> c_int {
1210    match value {
1211        LuaValue::Nil => LUA_TNIL,
1212        LuaValue::Bool(_) => LUA_TBOOLEAN,
1213        LuaValue::LightUserdata(_) => LUA_TLIGHTUSERDATA,
1214        LuaValue::Int(_) | LuaValue::Float(_) => LUA_TNUMBER,
1215        LuaValue::String(_) => LUA_TSTRING,
1216        LuaValue::Table(_) => LUA_TTABLE,
1217        LuaValue::Function(_) => LUA_TFUNCTION,
1218        LuaValue::Userdata(_) => LUA_TUSERDATA,
1219        LuaValue::Thread(_) => LUA_TTHREAD,
1220    }
1221}
1222
1223fn buffer_len(buf: &luaL_Buffer) -> usize {
1224    if buf.p.is_null() {
1225        return 0;
1226    }
1227    let start = buf.buffer.as_ptr();
1228    let diff = unsafe { buf.p.offset_from(start) };
1229    if diff < 0 {
1230        0
1231    } else {
1232        diff as usize
1233    }
1234}
1235
1236/// Render a Lust extern stub for a Lua table value returned by a `luaopen_*` call.
1237pub fn render_table_stub(module_name: &str, handle: &LuaTableHandle) -> String {
1238    let mut functions = Vec::new();
1239    let mut values = Vec::new();
1240    for (key, value) in handle.borrow().entries.iter() {
1241        if let LuaValue::String(name) = key {
1242            match value {
1243                LuaValue::Function(_) => functions.push(name.clone()),
1244                _ => values.push(name.clone()),
1245            }
1246        }
1247    }
1248    if std::env::var("LUST_DEBUG_LUAOPEN").is_ok() {
1249        let mut keys: Vec<String> = handle
1250            .borrow()
1251            .entries
1252            .keys()
1253            .map(|k| format!("{:?}", k))
1254            .collect();
1255        keys.sort();
1256        eprintln!("luaopen '{}' table keys: {}", module_name, keys.join(", "));
1257    }
1258    functions.sort();
1259    values.sort();
1260
1261    let mut out = String::new();
1262    out.push_str(&format!(
1263        "-- Auto-generated stub for Lua module '{}'\n\npub extern\n",
1264        module_name
1265    ));
1266    for func in functions {
1267        out.push_str(&format!(
1268            "    function {}.{}(LuaValue): LuaValue\n",
1269            module_name, func
1270        ));
1271    }
1272    for value in values {
1273        out.push_str(&format!("    const {}.{}: LuaValue\n", module_name, value));
1274    }
1275    out.push_str("end\n");
1276    out
1277}
1278
1279#[cfg(all(feature = "packages", not(target_arch = "wasm32")))]
1280fn luaopen_symbol_name(name: &str) -> String {
1281    if name.ends_with('\0') {
1282        name.to_string()
1283    } else {
1284        format!("{name}\0")
1285    }
1286}
1287
1288#[cfg(all(feature = "packages", not(target_arch = "wasm32")))]
1289fn luaopen_module_name(name: &str) -> String {
1290    name.strip_prefix("luaopen_")
1291        .unwrap_or(name)
1292        .replace('_', ".")
1293}
1294
1295#[cfg(all(feature = "packages", not(target_arch = "wasm32")))]
1296pub fn trace_luaopen(spec: &LuaModuleSpec) -> Result<Vec<LuaOpenResult>, String> {
1297    let library = Rc::new(unsafe { Library::new(&spec.library_path) }.map_err(|e| {
1298        format!(
1299            "failed to load Lua library '{}': {e}",
1300            spec.library_path.display()
1301        )
1302    })?);
1303    let mut results = Vec::new();
1304
1305    for entry in &spec.entrypoints {
1306        let symbol_name = luaopen_symbol_name(entry);
1307        unsafe {
1308            let func = library
1309                .get::<unsafe extern "C" fn(*mut lua_State) -> c_int>(symbol_name.as_bytes())
1310                .map_err(|e| {
1311                    format!(
1312                        "missing symbol '{}' in {}: {e}",
1313                        entry,
1314                        spec.library_path.display()
1315                    )
1316                })?;
1317            let state_ptr = luaL_newstate();
1318            let ret_count = func(state_ptr);
1319            let shared_state: Rc<RefCell<Box<lua_State>>> =
1320                Rc::new(RefCell::new(Box::from_raw(state_ptr)));
1321            let mut boxed = shared_state.borrow_mut();
1322            boxed.state.libraries.push(library.clone());
1323            let mut returns = Vec::new();
1324            let count = if ret_count < 0 { 0 } else { ret_count as usize };
1325            for _ in 0..count {
1326                returns.push(boxed.state.pop().unwrap_or(LuaValue::Nil));
1327            }
1328            returns.reverse();
1329            if returns
1330                .iter()
1331                .all(|value| !matches!(value, LuaValue::Table(_)))
1332            {
1333                if let Some(table) =
1334                    boxed
1335                        .state
1336                        .stack_snapshot()
1337                        .into_iter()
1338                        .find_map(|value| match value {
1339                            LuaValue::Table(handle) => Some(handle),
1340                            _ => None,
1341                        })
1342                {
1343                    returns.insert(0, LuaValue::Table(table));
1344                }
1345            }
1346            if returns.is_empty() {
1347                if let Some(LuaValue::Table(handle)) = boxed.state.stack.last().cloned() {
1348                    returns.push(LuaValue::Table(handle));
1349                }
1350            }
1351            if returns.is_empty() {
1352                let module_name = luaopen_module_name(entry);
1353                let mut keys = vec![module_name.clone()];
1354                if let Some(first) = module_name.split('.').next() {
1355                    keys.push(first.to_string());
1356                }
1357                if let Some(loaded) = boxed
1358                    .state
1359                    .registry
1360                    .borrow()
1361                    .entries
1362                    .get(&LuaValue::String("_LOADED".to_string()))
1363                {
1364                    if let LuaValue::Table(tbl) = loaded {
1365                        for key in &keys {
1366                            let lookup = LuaValue::String(key.clone());
1367                            if let Some(val) = tbl.borrow().entries.get(&lookup).cloned() {
1368                                returns.push(val);
1369                                break;
1370                            }
1371                        }
1372                    }
1373                }
1374            }
1375            if returns.is_empty() {
1376                let module_name = luaopen_module_name(entry);
1377                let mut keys = vec![module_name.clone()];
1378                if let Some(first) = module_name.split('.').next() {
1379                    keys.push(first.to_string());
1380                }
1381                for key in keys {
1382                    let lookup = LuaValue::String(key);
1383                    if let Some(value) = boxed
1384                        .state
1385                        .globals_table
1386                        .borrow()
1387                        .entries
1388                        .get(&lookup)
1389                        .cloned()
1390                    {
1391                        returns.push(value);
1392                        break;
1393                    }
1394                }
1395            }
1396            if returns.is_empty() && std::env::var("LUST_DEBUG_LUAOPEN").is_ok() {
1397                eprintln!(
1398                    "luaopen '{}' returned no table; stack snapshot: {:?}, trace: {:?}",
1399                    entry,
1400                    boxed.state.stack_snapshot(),
1401                    boxed.state.trace
1402                );
1403            }
1404            if std::env::var("LUST_DEBUG_LUAOPEN").is_ok() {
1405                eprintln!("luaopen '{}' final returns: {:?}", entry, returns);
1406                eprintln!("luaopen '{}' trace: {:?}", entry, boxed.state.trace);
1407            }
1408            let trace = boxed.state.take_trace();
1409            drop(boxed);
1410            if std::env::var("LUST_DEBUG_LUAOPEN").is_ok() {
1411                let set_calls: Vec<_> = trace
1412                    .iter()
1413                    .filter(|c| c.function == "lua_setfield")
1414                    .collect();
1415                eprintln!("luaopen '{}' setfield calls: {:?}", entry, set_calls);
1416            }
1417            results.push(LuaOpenResult {
1418                module: luaopen_module_name(entry),
1419                trace,
1420                returns,
1421                state: Some(shared_state.clone()),
1422            });
1423        }
1424    }
1425
1426    Ok(results)
1427}
1428
1429/// --- C ABI shims ---
1430
1431#[no_mangle]
1432pub unsafe extern "C" fn luaL_newstate() -> *mut lua_State {
1433    Box::into_raw(Box::new(lua_State {
1434        state: LuaState::new(),
1435    }))
1436}
1437
1438#[no_mangle]
1439pub unsafe extern "C" fn lua_newstate(
1440    _alloc: Option<unsafe extern "C" fn(*mut c_void, *mut c_void, usize, usize) -> *mut c_void>,
1441    _ud: *mut c_void,
1442) -> *mut lua_State {
1443    luaL_newstate()
1444}
1445
1446#[no_mangle]
1447pub unsafe extern "C" fn lua_close(L: *mut lua_State) {
1448    if !L.is_null() {
1449        drop(Box::from_raw(L));
1450    }
1451}
1452
1453#[no_mangle]
1454pub unsafe extern "C" fn lua_gettop(L: *mut lua_State) -> c_int {
1455    if let Some(state) = state_from_ptr(L) {
1456        let top = state.len() as c_int;
1457        state.record_call("lua_gettop", vec![]);
1458        top
1459    } else {
1460        0
1461    }
1462}
1463
1464#[no_mangle]
1465pub unsafe extern "C" fn lua_settop(L: *mut lua_State, idx: c_int) {
1466    if let Some(state) = state_from_ptr(L) {
1467        let new_len = if idx >= 0 {
1468            idx as usize
1469        } else {
1470            let base = state.len() as isize + idx as isize + 1;
1471            if base < 0 {
1472                0
1473            } else {
1474                base as usize
1475            }
1476        };
1477        if new_len < state.stack.len() {
1478            state.stack.truncate(new_len);
1479        } else {
1480            while state.stack.len() < new_len {
1481                state.stack.push(LuaValue::Nil);
1482            }
1483        }
1484        state.record_call("lua_settop", vec![idx.to_string()]);
1485    }
1486}
1487
1488#[no_mangle]
1489pub unsafe extern "C" fn lua_pushvalue(L: *mut lua_State, idx: c_int) {
1490    if let Some(state) = state_from_ptr(L) {
1491        if let Some(val) = value_at(state, idx) {
1492            state.push(val);
1493        }
1494        state.record_call("lua_pushvalue", vec![idx.to_string()]);
1495    }
1496}
1497
1498#[no_mangle]
1499pub unsafe extern "C" fn lua_remove(L: *mut lua_State, idx: c_int) {
1500    if let Some(state) = state_from_ptr(L) {
1501        if pseudo_table(state, idx).is_some() {
1502            state.record_call("lua_remove", vec![idx.to_string()]);
1503            return;
1504        }
1505        if let Some(slot) = translate_index(state.stack.len(), idx) {
1506            state.stack.remove(slot);
1507        }
1508        state.record_call("lua_remove", vec![idx.to_string()]);
1509    }
1510}
1511
1512#[no_mangle]
1513pub unsafe extern "C" fn lua_insert(L: *mut lua_State, idx: c_int) {
1514    if let Some(state) = state_from_ptr(L) {
1515        if let Some(slot) = translate_index(state.stack.len(), idx) {
1516            if let Some(val) = state.pop() {
1517                state.stack.insert(slot, val);
1518            }
1519        }
1520        state.record_call("lua_insert", vec![idx.to_string()]);
1521    }
1522}
1523
1524#[no_mangle]
1525pub unsafe extern "C" fn lua_replace(L: *mut lua_State, idx: c_int) {
1526    if let Some(state) = state_from_ptr(L) {
1527        // Lua semantics: `lua_replace(L, idx)` is equivalent to `lua_copy(L, -1, idx); lua_pop(L, 1);`
1528        // The index is relative to the stack *before* the value is popped.
1529        let len_before_pop = state.stack.len();
1530        let slot = translate_index(len_before_pop, idx);
1531        let value = state.pop();
1532        if let (Some(slot), Some(value)) = (slot, value) {
1533            // After popping, the stack is 1 shorter. If `idx` referred to the old top (-1),
1534            // it no longer exists, and the operation is effectively just a pop.
1535            if slot < state.stack.len() {
1536                state.stack[slot] = value;
1537            }
1538        }
1539        state.record_call("lua_replace", vec![idx.to_string()]);
1540    }
1541}
1542
1543#[no_mangle]
1544pub unsafe extern "C" fn lua_checkstack(L: *mut lua_State, _sz: c_int) -> c_int {
1545    if let Some(state) = state_from_ptr(L) {
1546        state.record_call("lua_checkstack", vec![_sz.to_string()]);
1547    }
1548    1
1549}
1550
1551#[no_mangle]
1552pub unsafe extern "C" fn lua_type(L: *mut lua_State, idx: c_int) -> c_int {
1553    if let Some(state) = state_from_ptr(L) {
1554        let code = value_at(state, idx)
1555            .map(|value| value_typecode(&value))
1556            .unwrap_or(LUA_TNONE);
1557        state.record_call("lua_type", vec![idx.to_string()]);
1558        code
1559    } else {
1560        LUA_TNONE
1561    }
1562}
1563
1564#[no_mangle]
1565pub unsafe extern "C" fn lua_typename(L: *mut lua_State, tp: c_int) -> *const c_char {
1566    const TYPE_NAMES: [&[u8]; 10] = [
1567        b"no value\0",
1568        b"nil\0",
1569        b"boolean\0",
1570        b"light userdata\0",
1571        b"number\0",
1572        b"string\0",
1573        b"table\0",
1574        b"function\0",
1575        b"userdata\0",
1576        b"thread\0",
1577    ];
1578    let raw = match tp {
1579        LUA_TNONE => TYPE_NAMES[0],
1580        LUA_TNIL => TYPE_NAMES[1],
1581        LUA_TBOOLEAN => TYPE_NAMES[2],
1582        LUA_TLIGHTUSERDATA => TYPE_NAMES[3],
1583        LUA_TNUMBER => TYPE_NAMES[4],
1584        LUA_TSTRING => TYPE_NAMES[5],
1585        LUA_TTABLE => TYPE_NAMES[6],
1586        LUA_TFUNCTION => TYPE_NAMES[7],
1587        LUA_TUSERDATA => TYPE_NAMES[8],
1588        LUA_TTHREAD => TYPE_NAMES[9],
1589        _ => b"<invalid>\0",
1590    };
1591    if let Some(state) = state_from_ptr(L) {
1592        state.record_call("lua_typename", vec![tp.to_string()]);
1593        return cache_cstring(state, String::from_utf8_lossy(raw).to_string());
1594    }
1595    raw.as_ptr() as *const c_char
1596}
1597
1598#[no_mangle]
1599pub unsafe extern "C" fn lua_isstring(L: *mut lua_State, idx: c_int) -> c_int {
1600    matches!(lua_type(L, idx), LUA_TSTRING | LUA_TNUMBER) as c_int
1601}
1602
1603fn parse_lua_number(text: &str) -> Option<lua_Number> {
1604    let trimmed = text.trim();
1605    if trimmed.is_empty() {
1606        return None;
1607    }
1608
1609    let (sign, rest) = match trimmed.as_bytes().first() {
1610        Some(b'+') => (1.0, &trimmed[1..]),
1611        Some(b'-') => (-1.0, &trimmed[1..]),
1612        _ => (1.0, trimmed),
1613    };
1614
1615    if let Some(hex) = rest.strip_prefix("0x").or_else(|| rest.strip_prefix("0X")) {
1616        let hex = hex.trim();
1617        if hex.is_empty() {
1618            return None;
1619        }
1620        if !hex.chars().all(|c| c.is_ascii_hexdigit()) {
1621            return None;
1622        }
1623        let parsed = u64::from_str_radix(hex, 16).ok()? as lua_Number;
1624        return Some(parsed * sign);
1625    }
1626
1627    rest.parse::<lua_Number>().ok().map(|v| v * sign)
1628}
1629
1630#[no_mangle]
1631pub unsafe extern "C" fn lua_isnumber(L: *mut lua_State, idx: c_int) -> c_int {
1632    if let Some(state) = state_from_ptr(L) {
1633        state.record_call("lua_isnumber", vec![idx.to_string()]);
1634        match value_at(state, idx) {
1635            Some(LuaValue::Int(_)) | Some(LuaValue::Float(_)) => return 1,
1636            Some(LuaValue::String(s)) => return parse_lua_number(&s).is_some() as c_int,
1637            _ => return 0,
1638        }
1639    }
1640    0
1641}
1642
1643#[no_mangle]
1644pub unsafe extern "C" fn lua_iscfunction(L: *mut lua_State, idx: c_int) -> c_int {
1645    matches!(lua_type(L, idx), LUA_TFUNCTION) as c_int
1646}
1647
1648#[no_mangle]
1649pub unsafe extern "C" fn lua_istable(L: *mut lua_State, idx: c_int) -> c_int {
1650    (lua_type(L, idx) == LUA_TTABLE) as c_int
1651}
1652
1653#[no_mangle]
1654pub unsafe extern "C" fn lua_isuserdata(L: *mut lua_State, idx: c_int) -> c_int {
1655    matches!(lua_type(L, idx), LUA_TUSERDATA | LUA_TLIGHTUSERDATA) as c_int
1656}
1657
1658#[no_mangle]
1659pub unsafe extern "C" fn lua_toboolean(L: *mut lua_State, idx: c_int) -> c_int {
1660    if let Some(state) = state_from_ptr(L) {
1661        state.record_call("lua_toboolean", vec![idx.to_string()]);
1662    }
1663    match lua_type(L, idx) {
1664        LUA_TNIL | LUA_TNONE => 0,
1665        LUA_TBOOLEAN => {
1666            if let Some(state) = state_from_ptr(L) {
1667                if let Some(LuaValue::Bool(b)) = value_at(state, idx) {
1668                    return b as c_int;
1669                }
1670            }
1671            0
1672        }
1673        _ => 1,
1674    }
1675}
1676
1677#[no_mangle]
1678pub unsafe extern "C" fn lua_tonumber(L: *mut lua_State, idx: c_int) -> lua_Number {
1679    if let Some(state) = state_from_ptr(L) {
1680        state.record_call("lua_tonumber", vec![idx.to_string()]);
1681        match value_at(state, idx) {
1682            Some(LuaValue::Float(f)) => f,
1683            Some(LuaValue::Int(i)) => i as lua_Number,
1684            Some(LuaValue::String(s)) => parse_lua_number(&s).unwrap_or(0.0),
1685            _ => 0.0,
1686        }
1687    } else {
1688        0.0
1689    }
1690}
1691
1692#[no_mangle]
1693pub unsafe extern "C" fn lua_tointeger(L: *mut lua_State, idx: c_int) -> lua_Integer {
1694    if let Some(state) = state_from_ptr(L) {
1695        state.record_call("lua_tointeger", vec![idx.to_string()]);
1696        match value_at(state, idx) {
1697            Some(LuaValue::Int(i)) => i,
1698            Some(LuaValue::Float(f)) => f as lua_Integer,
1699            Some(LuaValue::String(s)) => {
1700                parse_lua_number(&s).map(|v| v as lua_Integer).unwrap_or(0)
1701            }
1702            _ => 0,
1703        }
1704    } else {
1705        0
1706    }
1707}
1708
1709#[no_mangle]
1710pub unsafe extern "C" fn lua_tolstring(
1711    L: *mut lua_State,
1712    idx: c_int,
1713    len: *mut usize,
1714) -> *const c_char {
1715    let mut ptr = core::ptr::null();
1716    if let Some(state) = state_from_ptr(L) {
1717        state.record_call("lua_tolstring", vec![idx.to_string()]);
1718        if let Some(value) = value_at(state, idx) {
1719            let s = match value {
1720                LuaValue::String(s) => s,
1721                LuaValue::Int(i) => i.to_string(),
1722                LuaValue::Float(f) => f.to_string(),
1723                LuaValue::Bool(b) => b.to_string(),
1724                _ => String::new(),
1725            };
1726            ptr = cache_cstring(state, s.clone());
1727            if !len.is_null() {
1728                *len = s.len();
1729            }
1730        }
1731    }
1732    ptr
1733}
1734
1735#[no_mangle]
1736pub unsafe extern "C" fn lua_objlen(L: *mut lua_State, idx: c_int) -> usize {
1737    let mut length = 0usize;
1738    if let Some(state) = state_from_ptr(L) {
1739        state.record_call("lua_objlen", vec![idx.to_string()]);
1740        if let Some(value) = value_at(state, idx) {
1741            match value {
1742                LuaValue::String(s) => length = s.len(),
1743                LuaValue::Table(handle) => {
1744                    // Lua 5.1 length operator is sequence-like for tables.
1745                    let table = handle.borrow();
1746                    let mut i: lua_Integer = 1;
1747                    loop {
1748                        if table.entries.contains_key(&LuaValue::Int(i)) {
1749                            i += 1;
1750                        } else {
1751                            break;
1752                        }
1753                    }
1754                    length = (i - 1).max(0) as usize;
1755                }
1756                LuaValue::Userdata(userdata) => {
1757                    if let Some((_blob, size)) = state.userdata_storage.get(&userdata.id) {
1758                        length = *size;
1759                    }
1760                }
1761                _ => {}
1762            }
1763        }
1764    }
1765    length
1766}
1767
1768#[no_mangle]
1769pub unsafe extern "C" fn lua_equal(L: *mut lua_State, idx1: c_int, idx2: c_int) -> c_int {
1770    let t1 = lua_type(L, idx1);
1771    let t2 = lua_type(L, idx2);
1772    if t1 == LUA_TNONE || t2 == LUA_TNONE {
1773        return 0;
1774    }
1775    if let Some(state) = state_from_ptr(L) {
1776        if let (Some(v1), Some(v2)) = (value_at(state, idx1), value_at(state, idx2)) {
1777            return (v1 == v2) as c_int;
1778        }
1779    }
1780    0
1781}
1782
1783#[no_mangle]
1784pub unsafe extern "C" fn lua_rawequal(L: *mut lua_State, idx1: c_int, idx2: c_int) -> c_int {
1785    lua_equal(L, idx1, idx2)
1786}
1787
1788#[no_mangle]
1789pub unsafe extern "C" fn lua_lessthan(L: *mut lua_State, _idx1: c_int, _idx2: c_int) -> c_int {
1790    // Minimal stub: ordering not tracked.
1791    if let Some(state) = state_from_ptr(L) {
1792        state.record_call("lua_lessthan", vec![]);
1793    }
1794    0
1795}
1796
1797#[no_mangle]
1798pub unsafe extern "C" fn lua_pushnil(L: *mut lua_State) {
1799    if let Some(state) = state_from_ptr(L) {
1800        state.push(LuaValue::Nil);
1801        state.record_call("lua_pushnil", vec![]);
1802    }
1803}
1804
1805#[no_mangle]
1806pub unsafe extern "C" fn lua_pushnumber(L: *mut lua_State, n: lua_Number) {
1807    if let Some(state) = state_from_ptr(L) {
1808        state.push(LuaValue::Float(n));
1809        state.record_call("lua_pushnumber", vec![n.to_string()]);
1810    }
1811}
1812
1813#[no_mangle]
1814pub unsafe extern "C" fn lua_pushinteger(L: *mut lua_State, n: lua_Integer) {
1815    if let Some(state) = state_from_ptr(L) {
1816        state.push(LuaValue::Int(n));
1817        state.record_call("lua_pushinteger", vec![n.to_string()]);
1818    }
1819}
1820
1821#[no_mangle]
1822pub unsafe extern "C" fn lua_pushlstring(L: *mut lua_State, s: *const c_char, len: usize) {
1823    if let Some(state) = state_from_ptr(L) {
1824        let string = if s.is_null() || len == 0 {
1825            String::new()
1826        } else {
1827            let slice = core::slice::from_raw_parts(s as *const u8, len);
1828            String::from_utf8_lossy(slice).to_string()
1829        };
1830        state.push(LuaValue::String(string.clone()));
1831        state.record_call(
1832            "lua_pushlstring",
1833            vec![format!("len={}", len), format!("text={:?}", string)],
1834        );
1835    }
1836}
1837
1838#[no_mangle]
1839pub unsafe extern "C" fn lua_pushstring(L: *mut lua_State, s: *const c_char) {
1840    if let Some(state) = state_from_ptr(L) {
1841        let text = if s.is_null() {
1842            String::new()
1843        } else {
1844            CStr::from_ptr(s).to_string_lossy().to_string()
1845        };
1846        state.push(LuaValue::String(text.clone()));
1847        state.record_call("lua_pushstring", vec![format!("text={:?}", text)]);
1848    }
1849}
1850
1851#[no_mangle]
1852pub unsafe extern "C" fn lua_pushfstring(L: *mut lua_State, fmt: *const c_char) -> *const c_char {
1853    let mut text = String::new();
1854    if !fmt.is_null() {
1855        text = CStr::from_ptr(fmt).to_string_lossy().to_string();
1856    }
1857    if let Some(state) = state_from_ptr(L) {
1858        state.push(LuaValue::String(text.clone()));
1859        let ptr = cache_cstring(state, text);
1860        state.record_call("lua_pushfstring", vec![]);
1861        return ptr;
1862    }
1863    core::ptr::null()
1864}
1865
1866#[no_mangle]
1867pub unsafe extern "C" fn lua_pushboolean(L: *mut lua_State, b: c_int) {
1868    if let Some(state) = state_from_ptr(L) {
1869        state.push(LuaValue::Bool(b != 0));
1870        state.record_call("lua_pushboolean", vec![b.to_string()]);
1871    }
1872}
1873
1874#[no_mangle]
1875pub unsafe extern "C" fn lua_pushlightuserdata(L: *mut lua_State, p: *mut c_void) {
1876    if let Some(state) = state_from_ptr(L) {
1877        state.push(LuaValue::LightUserdata(p as usize));
1878        state.record_call("lua_pushlightuserdata", vec![format!("{p:p}")]);
1879    }
1880}
1881
1882#[no_mangle]
1883pub unsafe extern "C" fn lua_pushcclosure(L: *mut lua_State, f: lua_CFunction, _n: c_int) {
1884    if let Some(state) = state_from_ptr(L) {
1885        let n = _n.max(0) as usize;
1886        let len = state.stack.len();
1887        let upvalues = if n > 0 && n <= len {
1888            state.stack.split_off(len - n)
1889        } else {
1890            Vec::new()
1891        };
1892        let id = state.next_func_id();
1893        state.push(LuaValue::Function(LuaFunction {
1894            id,
1895            name: None,
1896            cfunc: f,
1897            lust_handle: None,
1898            upvalues,
1899        }));
1900        let addr = f.map(|func| func as usize).unwrap_or(0);
1901        state.record_call(
1902            "lua_pushcclosure",
1903            vec![format!("n={}", _n), format!("f={:#x}", addr)],
1904        );
1905    }
1906}
1907
1908#[no_mangle]
1909pub unsafe extern "C" fn lua_pushcfunction(L: *mut lua_State, f: lua_CFunction) {
1910    lua_pushcclosure(L, f, 0);
1911}
1912
1913#[no_mangle]
1914pub unsafe extern "C" fn lua_newtable(L: *mut lua_State) {
1915    if let Some(state) = state_from_ptr(L) {
1916        state.push(LuaValue::Table(Rc::new(RefCell::new(LuaTable::new()))));
1917        state.record_call("lua_newtable", vec![]);
1918    }
1919}
1920
1921#[no_mangle]
1922pub unsafe extern "C" fn lua_createtable(L: *mut lua_State, _narr: c_int, _nrec: c_int) {
1923    lua_newtable(L);
1924}
1925
1926#[no_mangle]
1927pub unsafe extern "C" fn lua_gettable(L: *mut lua_State, idx: c_int) {
1928    if let Some(state) = state_from_ptr(L) {
1929        // If `idx` is negative, it is relative to the stack *before* the key is popped.
1930        let handle = ensure_table_at(state, idx);
1931        if let Some(key) = state.pop() {
1932            if let Some(handle) = handle {
1933                let value = handle
1934                    .borrow()
1935                    .entries
1936                    .get(&key)
1937                    .cloned()
1938                    .unwrap_or(LuaValue::Nil);
1939                state.push(value);
1940            } else {
1941                state.push(LuaValue::Nil);
1942            }
1943        }
1944        state.record_call("lua_gettable", vec![idx.to_string()]);
1945    }
1946}
1947
1948#[no_mangle]
1949pub unsafe extern "C" fn lua_settable(L: *mut lua_State, idx: c_int) {
1950    if let Some(state) = state_from_ptr(L) {
1951        // If `idx` is negative, it is relative to the stack *before* the key/value are popped.
1952        let handle = ensure_table_at(state, idx);
1953        let value = state.pop();
1954        let key = state.pop();
1955        if let (Some(k), Some(v)) = (key, value) {
1956            if let Some(handle) = handle {
1957                let v = match (&k, v) {
1958                    (LuaValue::String(name), LuaValue::Function(mut func)) => {
1959                        if func.name.is_none() {
1960                            func.name = Some(name.clone());
1961                        }
1962                        LuaValue::Function(func)
1963                    }
1964                    (_, other) => other,
1965                };
1966                handle.borrow_mut().entries.insert(k, v);
1967            }
1968        }
1969        state.record_call("lua_settable", vec![idx.to_string()]);
1970    }
1971}
1972
1973#[no_mangle]
1974pub unsafe extern "C" fn lua_getfield(L: *mut lua_State, idx: c_int, k: *const c_char) {
1975    if let Some(state) = state_from_ptr(L) {
1976        let key = if k.is_null() {
1977            String::new()
1978        } else {
1979            CStr::from_ptr(k).to_string_lossy().to_string()
1980        };
1981        if let Some(handle) = ensure_table_at(state, idx) {
1982            let value = handle
1983                .borrow()
1984                .entries
1985                .get(&LuaValue::String(key.clone()))
1986                .cloned()
1987                .unwrap_or(LuaValue::Nil);
1988            state.push(value);
1989        } else {
1990            state.push(LuaValue::Nil);
1991        }
1992        state.record_call("lua_getfield", vec![idx.to_string(), key]);
1993    }
1994}
1995
1996#[no_mangle]
1997pub unsafe extern "C" fn lua_setfield(L: *mut lua_State, idx: c_int, k: *const c_char) {
1998    if let Some(state) = state_from_ptr(L) {
1999        // If `idx` is negative, it is relative to the stack *before* the value is popped.
2000        let handle = ensure_table_at(state, idx);
2001        let value = state.pop();
2002        let key = if k.is_null() {
2003            String::new()
2004        } else {
2005            CStr::from_ptr(k).to_string_lossy().to_string()
2006        };
2007        if let Some(v) = value {
2008            if let Some(handle) = handle {
2009                let v = match v {
2010                    LuaValue::Function(mut func) => {
2011                        if func.name.is_none() && !key.is_empty() {
2012                            func.name = Some(key.clone());
2013                        }
2014                        LuaValue::Function(func)
2015                    }
2016                    other => other,
2017                };
2018                handle
2019                    .borrow_mut()
2020                    .entries
2021                    .insert(LuaValue::String(key.clone()), v);
2022            }
2023        }
2024        state.record_call("lua_setfield", vec![idx.to_string(), key]);
2025    }
2026}
2027
2028#[no_mangle]
2029pub unsafe extern "C" fn lua_next(L: *mut lua_State, _idx: c_int) -> c_int {
2030    if let Some(state) = state_from_ptr(L) {
2031        let handle = ensure_table_at(state, _idx);
2032        let key = state.pop().unwrap_or(LuaValue::Nil);
2033        if let Some(handle) = handle {
2034            let table = handle.borrow();
2035            let keys: Vec<LuaValue> = table.entries.keys().cloned().collect();
2036            let mut next_key: Option<LuaValue> = None;
2037
2038            if matches!(key, LuaValue::Nil) {
2039                next_key = keys.get(0).cloned();
2040            } else {
2041                let mut seen_current = false;
2042                for k in &keys {
2043                    if !seen_current {
2044                        if *k == key {
2045                            seen_current = true;
2046                        }
2047                        continue;
2048                    }
2049                    next_key = Some(k.clone());
2050                    break;
2051                }
2052            }
2053
2054            if let Some(k) = next_key {
2055                let v = table.entries.get(&k).cloned().unwrap_or(LuaValue::Nil);
2056                state.push(k);
2057                state.push(v);
2058                state.record_call("lua_next", vec![_idx.to_string()]);
2059                return 1;
2060            }
2061        }
2062
2063        state.record_call("lua_next", vec![_idx.to_string()]);
2064    }
2065    0
2066}
2067
2068#[no_mangle]
2069pub unsafe extern "C" fn lua_rawget(L: *mut lua_State, idx: c_int) {
2070    lua_gettable(L, idx)
2071}
2072
2073#[no_mangle]
2074pub unsafe extern "C" fn lua_rawgeti(L: *mut lua_State, idx: c_int, n: c_int) {
2075    if let Some(state) = state_from_ptr(L) {
2076        if let Some(handle) = ensure_table_at(state, idx) {
2077            let value = handle
2078                .borrow()
2079                .entries
2080                .get(&LuaValue::Int(n as lua_Integer))
2081                .cloned()
2082                .unwrap_or(LuaValue::Nil);
2083            state.push(value);
2084        } else {
2085            state.push(LuaValue::Nil);
2086        }
2087        state.record_call("lua_rawgeti", vec![idx.to_string(), n.to_string()]);
2088    }
2089}
2090
2091#[no_mangle]
2092pub unsafe extern "C" fn lua_rawset(L: *mut lua_State, idx: c_int) {
2093    lua_settable(L, idx)
2094}
2095
2096#[no_mangle]
2097pub unsafe extern "C" fn lua_rawseti(L: *mut lua_State, idx: c_int, n: c_int) {
2098    if let Some(state) = state_from_ptr(L) {
2099        // If `idx` is negative, it is relative to the stack *before* the value is popped.
2100        let handle = ensure_table_at(state, idx);
2101        let value = state.pop().unwrap_or(LuaValue::Nil);
2102        if let Some(handle) = handle {
2103            handle
2104                .borrow_mut()
2105                .entries
2106                .insert(LuaValue::Int(n as lua_Integer), value);
2107        }
2108        state.record_call("lua_rawseti", vec![idx.to_string(), n.to_string()]);
2109    }
2110}
2111
2112#[no_mangle]
2113pub unsafe extern "C" fn lua_concat(L: *mut lua_State, n: c_int) {
2114    if let Some(state) = state_from_ptr(L) {
2115        let mut parts: Vec<String> = Vec::new();
2116        for _ in 0..n {
2117            if let Some(val) = state.pop() {
2118                match val {
2119                    LuaValue::String(s) => parts.push(s),
2120                    LuaValue::Int(i) => parts.push(i.to_string()),
2121                    LuaValue::Float(f) => parts.push(f.to_string()),
2122                    LuaValue::Bool(b) => parts.push(b.to_string()),
2123                    _ => parts.push(String::new()),
2124                }
2125            }
2126        }
2127        parts.reverse();
2128        state.push(LuaValue::String(parts.join("")));
2129        state.record_call("lua_concat", vec![n.to_string()]);
2130    }
2131}
2132
2133#[no_mangle]
2134pub unsafe extern "C" fn lua_setmetatable(L: *mut lua_State, objindex: c_int) -> c_int {
2135    if let Some(state) = state_from_ptr(L) {
2136        let len_before_pop = state.stack.len();
2137        let meta = state.pop();
2138        if let Some(LuaValue::Table(meta_handle)) = meta {
2139            let index_value = meta_handle
2140                .borrow()
2141                .entries
2142                .get(&LuaValue::String("__index".to_string()))
2143                .cloned()
2144                .unwrap_or_else(|| LuaValue::Table(meta_handle.clone()));
2145
2146            if let Some(handle) = pseudo_table(state, objindex) {
2147                handle
2148                    .borrow_mut()
2149                    .metamethods
2150                    .insert("__index".to_string(), index_value.clone());
2151                state.record_call("lua_setmetatable", vec![objindex.to_string()]);
2152                return 1;
2153            }
2154
2155            if let Some(slot) = translate_index(len_before_pop, objindex) {
2156                match state.stack.get(slot) {
2157                    Some(LuaValue::Table(handle)) => {
2158                        handle.borrow_mut().metatable = Some(meta_handle.clone());
2159                        handle
2160                            .borrow_mut()
2161                            .metamethods
2162                            .insert("__index".to_string(), index_value.clone());
2163                        state.record_call("lua_setmetatable", vec![objindex.to_string()]);
2164                        return 1;
2165                    }
2166                    Some(LuaValue::Userdata(userdata)) => {
2167                        state
2168                            .userdata_metatables
2169                            .insert(userdata.id, meta_handle.clone());
2170                        state
2171                            .userdata_metamethods
2172                            .entry(userdata.id)
2173                            .or_default()
2174                            .insert("__index".to_string(), index_value.clone());
2175                        state.record_call("lua_setmetatable", vec![objindex.to_string()]);
2176                        return 1;
2177                    }
2178                    _ => {}
2179                }
2180            }
2181        } else if matches!(meta, None | Some(LuaValue::Nil)) {
2182            if let Some(slot) = translate_index(len_before_pop, objindex) {
2183                match state.stack.get(slot) {
2184                    Some(LuaValue::Table(handle)) => {
2185                        handle.borrow_mut().metatable = None;
2186                        handle.borrow_mut().metamethods.remove("__index");
2187                        state.record_call("lua_setmetatable", vec![objindex.to_string()]);
2188                        return 1;
2189                    }
2190                    Some(LuaValue::Userdata(userdata)) => {
2191                        state.userdata_metatables.remove(&userdata.id);
2192                        state.userdata_metamethods.remove(&userdata.id);
2193                        state.record_call("lua_setmetatable", vec![objindex.to_string()]);
2194                        return 1;
2195                    }
2196                    _ => {}
2197                }
2198            }
2199        }
2200    }
2201    0
2202}
2203
2204#[no_mangle]
2205pub unsafe extern "C" fn lua_getmetatable(L: *mut lua_State, objindex: c_int) -> c_int {
2206    if let Some(state) = state_from_ptr(L) {
2207        if let Some(handle) = ensure_table_at(state, objindex) {
2208            if let Some(meta) = handle.borrow().metatable.clone() {
2209                state.push(LuaValue::Table(meta));
2210                state.record_call("lua_getmetatable", vec![objindex.to_string()]);
2211                return 1;
2212            }
2213        }
2214        if let Some(LuaValue::Userdata(userdata)) = value_at(state, objindex) {
2215            if let Some(meta) = state.userdata_metatables.get(&userdata.id).cloned() {
2216                state.push(LuaValue::Table(meta));
2217                state.record_call("lua_getmetatable", vec![objindex.to_string()]);
2218                return 1;
2219            }
2220        }
2221    }
2222    0
2223}
2224
2225fn push_lua_results(state: &mut LuaState, mut results: Vec<LuaValue>, nresults: c_int) {
2226    if nresults >= 0 {
2227        let target = nresults as usize;
2228        if results.len() > target {
2229            results.truncate(target);
2230        } else {
2231            while results.len() < target {
2232                results.push(LuaValue::Nil);
2233            }
2234        }
2235    }
2236    for value in results {
2237        state.push(value);
2238    }
2239}
2240
2241#[no_mangle]
2242pub unsafe extern "C" fn lua_call(L: *mut lua_State, nargs: c_int, nresults: c_int) {
2243    if let Some(state) = state_from_ptr(L) {
2244        let mut args: Vec<LuaValue> = Vec::new();
2245        for _ in 0..nargs {
2246            args.push(state.pop().unwrap_or(LuaValue::Nil));
2247        }
2248        let func = state.pop();
2249        let base_len = state.stack.len();
2250        let mut results: Vec<LuaValue> = Vec::new();
2251        args.reverse();
2252        if let Some(LuaValue::Function(f)) = func {
2253            if let Some(handle) = f.lust_handle {
2254                match VM::with_current(|vm| {
2255                    let mut converted = Vec::new();
2256                    for arg in &args {
2257                        converted.push(lua_to_lust(arg, vm, None)?);
2258                    }
2259                    let func_value = lookup_lust_function(handle).ok_or_else(|| {
2260                        format!("Missing Lust function for LuaValue handle {}", handle)
2261                    })?;
2262                    vm.call_value(&func_value, converted)
2263                        .map_err(|e| e.to_string())
2264                }) {
2265                    Ok(ret) => {
2266                        let tuple_values: Vec<Value> = if let Value::Tuple(values) = &ret {
2267                            values.iter().cloned().collect()
2268                        } else {
2269                            vec![ret]
2270                        };
2271                        for value in tuple_values {
2272                            if let Ok(lua_ret) = VM::with_current(|vm| Ok(value_to_lua(&value, vm)))
2273                            {
2274                                results.push(lua_ret);
2275                            } else {
2276                                results.push(LuaValue::Nil);
2277                            }
2278                        }
2279                    }
2280                    Err(err) => {
2281                        eprintln!("lua_call Lust dispatch failed: {}", err);
2282                        results.push(LuaValue::Nil);
2283                    }
2284                }
2285            } else if let Some(cfunc) = f.cfunc {
2286                // C functions assume a fresh call frame where their arguments start at stack index 1.
2287                // Our shim stores the stack as a flat Vec, so we temporarily swap in an isolated frame.
2288                let caller_stack = core::mem::take(&mut state.stack);
2289                for arg in args {
2290                    state.push(arg);
2291                }
2292                let cfg = lua_trace_config();
2293                if cfg.enabled || cfg.cfunc {
2294                    let label = f.name.as_deref().unwrap_or("<anonymous>");
2295                    eprintln!(
2296                        "[lua-cfunc] lua_call -> {} {:#x} nargs={} top={} stack={}",
2297                        label,
2298                        cfunc as usize,
2299                        nargs,
2300                        state.len(),
2301                        format_lua_stack_brief(&state.stack)
2302                    );
2303                }
2304                let ret_count = cfunc(L);
2305                if cfg.enabled || cfg.cfunc {
2306                    let label = f.name.as_deref().unwrap_or("<anonymous>");
2307                    eprintln!(
2308                        "[lua-cfunc] lua_call <- {} {:#x} nret={} top={} stack={}",
2309                        label,
2310                        cfunc as usize,
2311                        ret_count,
2312                        state.len(),
2313                        format_lua_stack_brief(&state.stack)
2314                    );
2315                }
2316                if state.pending_error.is_none() && ret_count > 0 {
2317                    for _ in 0..ret_count {
2318                        results.push(state.pop().unwrap_or(LuaValue::Nil));
2319                    }
2320                    results.reverse();
2321                }
2322                state.stack = caller_stack;
2323            }
2324        }
2325        state.stack.truncate(base_len);
2326        state.record_call("lua_call", vec![nargs.to_string(), nresults.to_string()]);
2327        push_lua_results(state, results, nresults);
2328    }
2329}
2330
2331#[no_mangle]
2332pub unsafe extern "C" fn lua_pcall(
2333    L: *mut lua_State,
2334    nargs: c_int,
2335    nresults: c_int,
2336    _errfunc: c_int,
2337) -> c_int {
2338    if let Some(state) = state_from_ptr(L) {
2339        let mut args: Vec<LuaValue> = Vec::new();
2340        for _ in 0..nargs {
2341            args.push(state.pop().unwrap_or(LuaValue::Nil));
2342        }
2343        let func = state.pop();
2344        let base_len = state.stack.len();
2345        args.reverse();
2346
2347        let mut results: Vec<LuaValue> = Vec::new();
2348        let mut error_obj: Option<LuaValue> = None;
2349
2350        match func {
2351            Some(LuaValue::Function(f)) => {
2352                if let Some(handle) = f.lust_handle {
2353                    match VM::with_current(|vm| {
2354                        let mut converted = Vec::new();
2355                        for arg in &args {
2356                            converted.push(lua_to_lust(arg, vm, None)?);
2357                        }
2358                        let func_value = lookup_lust_function(handle).ok_or_else(|| {
2359                            format!("Missing Lust function for LuaValue handle {}", handle)
2360                        })?;
2361                        vm.call_value(&func_value, converted)
2362                            .map_err(|e| e.to_string())
2363                    }) {
2364                        Ok(ret) => {
2365                            let tuple_values: Vec<Value> = if let Value::Tuple(values) = &ret {
2366                                values.iter().cloned().collect()
2367                            } else {
2368                                vec![ret]
2369                            };
2370                            for value in tuple_values {
2371                                if let Ok(lua_ret) =
2372                                    VM::with_current(|vm| Ok(value_to_lua(&value, vm)))
2373                                {
2374                                    results.push(lua_ret);
2375                                } else {
2376                                    results.push(LuaValue::Nil);
2377                                }
2378                            }
2379                        }
2380                        Err(err) => {
2381                            error_obj = Some(LuaValue::String(err));
2382                        }
2383                    }
2384                } else if let Some(cfunc) = f.cfunc {
2385                    // C functions assume a fresh call frame where their arguments start at stack index 1.
2386                    let caller_stack = core::mem::take(&mut state.stack);
2387                    for arg in args {
2388                        state.push(arg);
2389                    }
2390                    let ret_count = cfunc(L);
2391                    if let Some(err) = state.pending_error.take() {
2392                        error_obj = Some(err);
2393                    } else if ret_count < 0 {
2394                        error_obj = Some(LuaValue::String("lua_error".to_string()));
2395                    } else if ret_count > 0 {
2396                        for _ in 0..ret_count {
2397                            results.push(state.pop().unwrap_or(LuaValue::Nil));
2398                        }
2399                        results.reverse();
2400                    }
2401                    state.stack = caller_stack;
2402                } else {
2403                    error_obj = Some(LuaValue::String(
2404                        "attempt to call a non-callable function value".to_string(),
2405                    ));
2406                }
2407            }
2408            Some(other) => {
2409                error_obj = Some(LuaValue::String(format!(
2410                    "attempt to call {}",
2411                    format_lua_value_brief(&other)
2412                )));
2413            }
2414            None => {
2415                error_obj = Some(LuaValue::String("attempt to call nil".to_string()));
2416            }
2417        }
2418
2419        state.stack.truncate(base_len);
2420        state.record_call("lua_pcall", vec![nargs.to_string(), nresults.to_string()]);
2421
2422        if let Some(err) = error_obj {
2423            state.push(err);
2424            return LUA_ERRRUN;
2425        }
2426
2427        push_lua_results(state, results, nresults);
2428    }
2429    0
2430}
2431
2432#[no_mangle]
2433pub unsafe extern "C" fn lua_error(L: *mut lua_State) -> c_int {
2434    if let Some(state) = state_from_ptr(L) {
2435        let err = state.pop().unwrap_or(LuaValue::Nil);
2436        state.pending_error = Some(err);
2437        state.record_call("lua_error", vec![]);
2438    }
2439    -1
2440}
2441
2442#[no_mangle]
2443pub unsafe extern "C" fn luaL_argerror(
2444    L: *mut lua_State,
2445    narg: c_int,
2446    extramsg: *const c_char,
2447) -> c_int {
2448    if let Some(state) = state_from_ptr(L) {
2449        let msg = if extramsg.is_null() {
2450            "<unknown>".to_string()
2451        } else {
2452            CStr::from_ptr(extramsg).to_string_lossy().to_string()
2453        };
2454        state.pending_error = Some(LuaValue::String(msg.clone()));
2455        state.record_call("luaL_argerror", vec![narg.to_string(), msg]);
2456    }
2457    -1
2458}
2459
2460#[no_mangle]
2461pub unsafe extern "C" fn luaL_checkstack(
2462    L: *mut lua_State,
2463    sz: c_int,
2464    msg: *const c_char,
2465) -> c_int {
2466    if let Some(state) = state_from_ptr(L) {
2467        let text = if msg.is_null() {
2468            String::new()
2469        } else {
2470            CStr::from_ptr(msg).to_string_lossy().to_string()
2471        };
2472        state.record_call("luaL_checkstack", vec![sz.to_string(), text]);
2473    }
2474    lua_checkstack(L, sz)
2475}
2476
2477#[no_mangle]
2478pub unsafe extern "C" fn luaL_checktype(L: *mut lua_State, narg: c_int, t: c_int) {
2479    if let Some(state) = state_from_ptr(L) {
2480        state.record_call("luaL_checktype", vec![narg.to_string(), t.to_string()]);
2481    }
2482    let current = lua_type(L, narg);
2483    if current == t || t == LUA_TNONE {
2484        return;
2485    }
2486    let _ = luaL_argerror(L, narg, core::ptr::null());
2487}
2488
2489#[no_mangle]
2490pub unsafe extern "C" fn luaL_checkoption(
2491    L: *mut lua_State,
2492    idx: c_int,
2493    def: *const c_char,
2494    lst: *const *const c_char,
2495) -> c_int {
2496    let chosen = if lua_type(L, idx) == LUA_TNONE && !def.is_null() {
2497        CStr::from_ptr(def).to_string_lossy().to_string()
2498    } else {
2499        let mut len: usize = 0;
2500        let ptr = lua_tolstring(L, idx, &mut len as *mut usize);
2501        if ptr.is_null() {
2502            String::new()
2503        } else {
2504            let slice = core::slice::from_raw_parts(ptr as *const u8, len);
2505            String::from_utf8_lossy(slice).to_string()
2506        }
2507    };
2508
2509    let mut i: c_int = 0;
2510    let mut iter = lst;
2511    while !iter.is_null() && !(*iter).is_null() {
2512        let option = CStr::from_ptr(*iter).to_string_lossy().to_string();
2513        if option == chosen {
2514            if let Some(state) = state_from_ptr(L) {
2515                state.record_call("luaL_checkoption", vec![idx.to_string(), option]);
2516            }
2517            return i;
2518        }
2519        i += 1;
2520        iter = iter.add(1);
2521    }
2522
2523    luaL_argerror(L, idx, core::ptr::null());
2524    0
2525}
2526
2527#[no_mangle]
2528pub unsafe extern "C" fn luaL_openlib(
2529    L: *mut lua_State,
2530    libname: *const c_char,
2531    regs: *const luaL_Reg,
2532    _nup: c_int,
2533) {
2534    luaL_register(L, libname, regs);
2535}
2536
2537#[no_mangle]
2538pub unsafe extern "C" fn luaL_register(
2539    L: *mut lua_State,
2540    libname: *const c_char,
2541    regs: *const luaL_Reg,
2542) {
2543    if let Some(state) = state_from_ptr(L) {
2544        let name =
2545            (!libname.is_null()).then(|| CStr::from_ptr(libname).to_string_lossy().to_string());
2546
2547        // Ensure table is on the stack, creating nested globals for dotted names.
2548        let target_handle = if let Some(module) = &name {
2549            let segments: Vec<&str> = module.split('.').collect();
2550            if segments.is_empty() {
2551                lua_newtable(L);
2552                ensure_table_at(state, -1)
2553            } else {
2554                let mut current = state.globals_table.clone();
2555                if segments.len() > 1 {
2556                    for seg in &segments[..segments.len() - 1] {
2557                        current = ensure_child_table(&current, seg);
2558                    }
2559                }
2560                let leaf = segments.last().unwrap().to_string();
2561                let leaf_handle = ensure_child_table(&current, &leaf);
2562                state.push(LuaValue::Table(leaf_handle.clone()));
2563                Some(leaf_handle)
2564            }
2565        } else {
2566            if !matches!(state.stack.last(), Some(LuaValue::Table(_))) {
2567                lua_newtable(L);
2568            }
2569            ensure_table_at(state, -1)
2570        };
2571
2572        if let Some(handle) = target_handle {
2573            let mut iter = regs;
2574            while !iter.is_null() && !(*iter).name.is_null() {
2575                let entry = &*iter;
2576                let key = CStr::from_ptr(entry.name).to_string_lossy().to_string();
2577                let id = state.next_func_id();
2578                handle.borrow_mut().entries.insert(
2579                    LuaValue::String(key.clone()),
2580                    LuaValue::Function(LuaFunction {
2581                        id,
2582                        name: Some(key.clone()),
2583                        cfunc: entry.func,
2584                        lust_handle: None,
2585                        upvalues: Vec::new(),
2586                    }),
2587                );
2588                iter = iter.add(1);
2589            }
2590            if let Some(module) = name {
2591                state
2592                    .globals
2593                    .insert(module.clone(), LuaValue::Table(handle.clone()));
2594                state.globals_table.borrow_mut().entries.insert(
2595                    LuaValue::String(module.clone()),
2596                    LuaValue::Table(handle.clone()),
2597                );
2598                if let Some(loaded) = state
2599                    .registry
2600                    .borrow_mut()
2601                    .entries
2602                    .get_mut(&LuaValue::String("_LOADED".to_string()))
2603                {
2604                    if let LuaValue::Table(tbl) = loaded {
2605                        tbl.borrow_mut().entries.insert(
2606                            LuaValue::String(module.clone()),
2607                            LuaValue::Table(handle.clone()),
2608                        );
2609                    }
2610                }
2611                state.record_call("luaL_register", vec![module]);
2612            } else {
2613                state.record_call("luaL_register", vec!["<anonymous>".to_string()]);
2614            }
2615        }
2616    }
2617}
2618
2619#[no_mangle]
2620pub unsafe extern "C" fn luaL_newmetatable(L: *mut lua_State, tname: *const c_char) -> c_int {
2621    let mut created = 0;
2622    if let Some(state) = state_from_ptr(L) {
2623        let name = if tname.is_null() {
2624            "<unknown>".to_string()
2625        } else {
2626            CStr::from_ptr(tname).to_string_lossy().to_string()
2627        };
2628        let key = LuaValue::String(name.clone());
2629        if !state.registry.borrow().entries.contains_key(&key) {
2630            lua_newtable(L);
2631            if let Some(meta) = state.stack.last().cloned() {
2632                state
2633                    .registry
2634                    .borrow_mut()
2635                    .entries
2636                    .insert(key.clone(), meta);
2637            }
2638            created = 1;
2639        } else {
2640            let meta = state
2641                .registry
2642                .borrow()
2643                .entries
2644                .get(&key)
2645                .cloned()
2646                .unwrap_or_else(|| LuaValue::Table(Rc::new(RefCell::new(LuaTable::new()))));
2647            state.push(meta);
2648        }
2649        state.record_call("luaL_newmetatable", vec![name]);
2650    }
2651    created
2652}
2653
2654#[no_mangle]
2655pub unsafe extern "C" fn luaL_getmetatable(L: *mut lua_State, tname: *const c_char) {
2656    if let Some(state) = state_from_ptr(L) {
2657        let name = if tname.is_null() {
2658            "<unknown>".to_string()
2659        } else {
2660            CStr::from_ptr(tname).to_string_lossy().to_string()
2661        };
2662        let key = LuaValue::String(name.clone());
2663        let value = state
2664            .registry
2665            .borrow()
2666            .entries
2667            .get(&key)
2668            .cloned()
2669            .unwrap_or(LuaValue::Nil);
2670        state.push(value);
2671        state.record_call("luaL_getmetatable", vec![name]);
2672    }
2673}
2674
2675#[no_mangle]
2676pub unsafe extern "C" fn luaL_checklstring(
2677    L: *mut lua_State,
2678    idx: c_int,
2679    len: *mut usize,
2680) -> *const c_char {
2681    let ptr = lua_tolstring(L, idx, len);
2682    if let Some(state) = state_from_ptr(L) {
2683        state.record_call("luaL_checklstring", vec![idx.to_string()]);
2684    }
2685    ptr
2686}
2687
2688#[no_mangle]
2689pub unsafe extern "C" fn luaL_checkstring(L: *mut lua_State, idx: c_int) -> *const c_char {
2690    luaL_checklstring(L, idx, core::ptr::null_mut())
2691}
2692
2693#[no_mangle]
2694pub unsafe extern "C" fn luaL_optlstring(
2695    L: *mut lua_State,
2696    idx: c_int,
2697    def: *const c_char,
2698    len: *mut usize,
2699) -> *const c_char {
2700    let ty = lua_type(L, idx);
2701    if ty == LUA_TNONE || ty == LUA_TNIL {
2702        if let Some(state) = state_from_ptr(L) {
2703            if def.is_null() {
2704                if !len.is_null() {
2705                    *len = 0;
2706                }
2707                state.record_call("luaL_optlstring", vec![idx.to_string()]);
2708                return core::ptr::null();
2709            }
2710            let default = CStr::from_ptr(def).to_string_lossy().to_string();
2711            let ptr = cache_cstring(state, default.clone());
2712            if !len.is_null() {
2713                *len = default.len();
2714            }
2715            state.record_call("luaL_optlstring", vec![idx.to_string()]);
2716            return ptr;
2717        }
2718    }
2719    lua_tolstring(L, idx, len)
2720}
2721
2722#[no_mangle]
2723pub unsafe extern "C" fn luaL_checknumber(L: *mut lua_State, idx: c_int) -> lua_Number {
2724    let n = lua_tonumber(L, idx);
2725    if let Some(state) = state_from_ptr(L) {
2726        state.record_call("luaL_checknumber", vec![idx.to_string()]);
2727    }
2728    n
2729}
2730
2731#[no_mangle]
2732pub unsafe extern "C" fn luaL_checkinteger(L: *mut lua_State, idx: c_int) -> lua_Integer {
2733    let n = lua_tointeger(L, idx);
2734    if let Some(state) = state_from_ptr(L) {
2735        state.record_call("luaL_checkinteger", vec![idx.to_string()]);
2736    }
2737    n
2738}
2739
2740#[no_mangle]
2741pub unsafe extern "C" fn luaL_optnumber(
2742    L: *mut lua_State,
2743    idx: c_int,
2744    def: lua_Number,
2745) -> lua_Number {
2746    if lua_type(L, idx) == LUA_TNONE {
2747        def
2748    } else {
2749        lua_tonumber(L, idx)
2750    }
2751}
2752
2753#[no_mangle]
2754pub unsafe extern "C" fn luaL_optinteger(
2755    L: *mut lua_State,
2756    idx: c_int,
2757    def: lua_Integer,
2758) -> lua_Integer {
2759    if lua_type(L, idx) == LUA_TNONE {
2760        def
2761    } else {
2762        lua_tointeger(L, idx)
2763    }
2764}
2765
2766#[no_mangle]
2767pub unsafe extern "C" fn luaL_ref(L: *mut lua_State, _t: c_int) -> c_int {
2768    if let Some(state) = state_from_ptr(L) {
2769        let value = state.pop().unwrap_or(LuaValue::Nil);
2770        let reference = state.next_ref();
2771        state.references.insert(reference, value);
2772        state.record_call("luaL_ref", vec![reference.to_string()]);
2773        return reference;
2774    }
2775    -1
2776}
2777
2778#[no_mangle]
2779pub unsafe extern "C" fn luaL_unref(L: *mut lua_State, _t: c_int, r: c_int) {
2780    if let Some(state) = state_from_ptr(L) {
2781        state.references.remove(&r);
2782        state.record_call("luaL_unref", vec![r.to_string()]);
2783    }
2784}
2785
2786#[no_mangle]
2787pub unsafe extern "C" fn luaL_error(L: *mut lua_State, s: *const c_char) -> c_int {
2788    if let Some(state) = state_from_ptr(L) {
2789        let msg = if s.is_null() {
2790            "<unknown>".to_string()
2791        } else {
2792            CStr::from_ptr(s).to_string_lossy().to_string()
2793        };
2794        state.pending_error = Some(LuaValue::String(msg.clone()));
2795        state.record_call("luaL_error", vec![msg]);
2796    }
2797    -1
2798}
2799
2800#[no_mangle]
2801pub unsafe extern "C" fn luaL_loadbuffer(
2802    L: *mut lua_State,
2803    _buff: *const c_char,
2804    _size: usize,
2805    _name: *const c_char,
2806) -> c_int {
2807    if let Some(state) = state_from_ptr(L) {
2808        state.record_call("luaL_loadbuffer", vec![]);
2809    }
2810    // Stub: push a dummy function.
2811    lua_pushcfunction(L, None);
2812    0
2813}
2814
2815#[no_mangle]
2816pub unsafe extern "C" fn luaL_loadstring(L: *mut lua_State, s: *const c_char) -> c_int {
2817    if let Some(state) = state_from_ptr(L) {
2818        let name = if s.is_null() {
2819            "<null>".to_string()
2820        } else {
2821            CStr::from_ptr(s).to_string_lossy().to_string()
2822        };
2823        state.record_call("luaL_loadstring", vec![name]);
2824    }
2825    lua_pushcfunction(L, None);
2826    0
2827}
2828
2829#[no_mangle]
2830pub unsafe extern "C" fn luaL_checkudata(
2831    L: *mut lua_State,
2832    idx: c_int,
2833    _tname: *const c_char,
2834) -> *mut c_void {
2835    lua_touserdata(L, idx)
2836}
2837
2838#[no_mangle]
2839pub unsafe extern "C" fn luaL_buffinit(L: *mut lua_State, B: *mut luaL_Buffer) {
2840    if B.is_null() {
2841        return;
2842    }
2843    (*B).L = L;
2844    (*B).p = (*B).buffer.as_mut_ptr();
2845    (*B).lvl = 0;
2846    if let Some(state) = state_from_ptr(L) {
2847        state.record_call("luaL_buffinit", vec![]);
2848    }
2849}
2850
2851#[no_mangle]
2852pub unsafe extern "C" fn luaL_prepbuffer(B: *mut luaL_Buffer) -> *mut c_char {
2853    if B.is_null() {
2854        return core::ptr::null_mut();
2855    }
2856    let buf = &mut *B;
2857    let start = buf.buffer.as_mut_ptr();
2858    let current_len = buffer_len(buf);
2859    if current_len > 0 {
2860        // Lua 5.1 semantics: flush pending data onto the Lua stack and reset `p`.
2861        let l = buf.L;
2862        if !l.is_null() {
2863            lua_pushlstring(l, buf.buffer.as_ptr(), current_len);
2864            buf.lvl += 1;
2865        }
2866    }
2867    buf.p = start;
2868    if let Some(state) = state_from_ptr(buf.L) {
2869        state.record_call("luaL_prepbuffer", vec![]);
2870    }
2871    buf.buffer.as_mut_ptr()
2872}
2873
2874#[no_mangle]
2875pub unsafe extern "C" fn luaL_addlstring(B: *mut luaL_Buffer, s: *const c_char, len: usize) {
2876    if B.is_null() {
2877        return;
2878    }
2879    let buf = &mut *B;
2880    let start = buf.buffer.as_mut_ptr();
2881    let mut remaining = len;
2882    let mut src = s as *const u8;
2883
2884    while remaining > 0 {
2885        let current_len = buffer_len(buf);
2886        if current_len >= LUAL_BUFFERSIZE {
2887            // Flush and continue.
2888            let L = buf.L;
2889            if !L.is_null() {
2890                lua_pushlstring(L, buf.buffer.as_ptr(), current_len);
2891                buf.lvl += 1;
2892            }
2893            buf.p = start;
2894            continue;
2895        }
2896
2897        let available = LUAL_BUFFERSIZE - current_len;
2898        let copy_len = remaining.min(available);
2899        if copy_len > 0 && !src.is_null() {
2900            ptr::copy_nonoverlapping(src, start.add(current_len) as *mut u8, copy_len);
2901            buf.p = start.add(current_len + copy_len);
2902            src = src.add(copy_len);
2903            remaining -= copy_len;
2904        } else {
2905            break;
2906        }
2907    }
2908
2909    if let Some(state) = state_from_ptr(buf.L) {
2910        state.record_call("luaL_addlstring", vec![len.to_string()]);
2911    }
2912}
2913
2914#[no_mangle]
2915pub unsafe extern "C" fn luaL_pushresult(B: *mut luaL_Buffer) {
2916    if B.is_null() {
2917        return;
2918    }
2919    let buf = &mut *B;
2920    let L = buf.L;
2921    if !L.is_null() {
2922        let len = buffer_len(buf);
2923        if len > 0 {
2924            // Equivalent to Lua 5.1 `emptybuffer(B)`.
2925            lua_pushlstring(L, buf.buffer.as_ptr(), len);
2926            buf.p = buf.buffer.as_mut_ptr();
2927            buf.lvl += 1;
2928        }
2929        if buf.lvl > 1 {
2930            lua_concat(L, buf.lvl);
2931        } else if buf.lvl == 0 {
2932            // `lua_concat(L, 0)` pushes the empty string.
2933            lua_concat(L, 0);
2934        }
2935        if let Some(state) = state_from_ptr(L) {
2936            state.record_call("luaL_pushresult", vec![len.to_string()]);
2937        }
2938    }
2939    buf.lvl = 0;
2940    buf.p = buf.buffer.as_mut_ptr();
2941}
2942
2943#[no_mangle]
2944pub unsafe extern "C" fn luaL_addstring(B: *mut luaL_Buffer, s: *const c_char) {
2945    if B.is_null() || s.is_null() {
2946        return;
2947    }
2948    let len = CStr::from_ptr(s).to_bytes().len();
2949    luaL_addlstring(B, s, len);
2950    if let Some(buf) = B.as_ref() {
2951        if let Some(state) = state_from_ptr(buf.L) {
2952            state.record_call("luaL_addstring", vec![len.to_string()]);
2953        }
2954    }
2955}
2956
2957#[no_mangle]
2958pub unsafe extern "C" fn luaL_addvalue(B: *mut luaL_Buffer) {
2959    if B.is_null() {
2960        return;
2961    }
2962    let buf = &mut *B;
2963    let L = buf.L;
2964    if L.is_null() {
2965        return;
2966    }
2967    let mut len: usize = 0;
2968    let ptr = lua_tolstring(L, -1, &mut len as *mut usize);
2969    if !ptr.is_null() && len > 0 {
2970        luaL_addlstring(B, ptr, len);
2971    }
2972    // Pop the value we just consumed.
2973    lua_settop(L, -2);
2974    if let Some(state) = state_from_ptr(L) {
2975        state.record_call("luaL_addvalue", vec![len.to_string()]);
2976    }
2977}
2978
2979#[cfg(test)]
2980mod tests {
2981    use super::*;
2982
2983    #[test]
2984    fn lua_replace_negative_one_pops() {
2985        let L = unsafe { luaL_newstate() };
2986        assert!(!L.is_null());
2987        unsafe {
2988            lua_pushinteger(L, 1);
2989            lua_pushinteger(L, 2);
2990            lua_pushinteger(L, 3);
2991            assert_eq!(lua_gettop(L), 3);
2992            lua_replace(L, -1);
2993            assert_eq!(lua_gettop(L), 2);
2994            let state = state_from_ptr(L).unwrap();
2995            assert!(matches!(
2996                state.stack.as_slice(),
2997                [LuaValue::Int(1), LuaValue::Int(2)]
2998            ));
2999            lua_close(L);
3000        }
3001    }
3002
3003    #[test]
3004    fn lua_replace_replaces_and_pops() {
3005        let L = unsafe { luaL_newstate() };
3006        assert!(!L.is_null());
3007        unsafe {
3008            lua_pushinteger(L, 1);
3009            lua_pushinteger(L, 2);
3010            lua_pushinteger(L, 3);
3011            lua_replace(L, 1);
3012            assert_eq!(lua_gettop(L), 2);
3013            let state = state_from_ptr(L).unwrap();
3014            assert!(matches!(
3015                state.stack.as_slice(),
3016                [LuaValue::Int(3), LuaValue::Int(2)]
3017            ));
3018            lua_close(L);
3019        }
3020    }
3021
3022    #[test]
3023    fn lualib_buffer_concatenates_pieces() {
3024        let L = unsafe { luaL_newstate() };
3025        assert!(!L.is_null());
3026        unsafe {
3027            let mut b: luaL_Buffer = core::mem::zeroed();
3028            luaL_buffinit(L, &mut b as *mut luaL_Buffer);
3029            let prefix = b"HTTP/\0";
3030            luaL_addlstring(
3031                &mut b as *mut luaL_Buffer,
3032                prefix.as_ptr() as *const c_char,
3033                5,
3034            );
3035
3036            let p = luaL_prepbuffer(&mut b as *mut luaL_Buffer) as *mut u8;
3037            assert!(!p.is_null());
3038            let rest = b"1.1 200 OK\r";
3039            core::ptr::copy_nonoverlapping(rest.as_ptr(), p, rest.len());
3040            // Simulate `luaL_addsize`.
3041            b.p = (p as *mut c_char).add(rest.len());
3042
3043            luaL_pushresult(&mut b as *mut luaL_Buffer);
3044            let state = state_from_ptr(L).unwrap();
3045            assert!(matches!(
3046                state.stack.last(),
3047                Some(LuaValue::String(s)) if s == "HTTP/1.1 200 OK\r"
3048            ));
3049            lua_close(L);
3050        }
3051    }
3052
3053    #[test]
3054    fn lua_isnumber_only_accepts_numeric_strings() {
3055        let L = unsafe { luaL_newstate() };
3056        assert!(!L.is_null());
3057        unsafe {
3058            lua_pushstring(L, b"*l\0".as_ptr() as *const c_char);
3059            assert_eq!(lua_isnumber(L, -1), 0);
3060            lua_settop(L, 0);
3061            lua_pushstring(L, b"123\0".as_ptr() as *const c_char);
3062            assert_eq!(lua_isnumber(L, -1), 1);
3063            lua_close(L);
3064        }
3065    }
3066}
3067
3068#[no_mangle]
3069pub unsafe extern "C" fn luaL_openlibs(_L: *mut lua_State) {
3070    // No-op stub for compatibility; libraries will be installed manually as needed.
3071}
3072
3073#[no_mangle]
3074pub unsafe extern "C" fn lua_newuserdata(L: *mut lua_State, sz: usize) -> *mut c_void {
3075    if let Some(state) = state_from_ptr(L) {
3076        let id = state.next_userdata_id();
3077        let word_size = core::mem::size_of::<usize>();
3078        let words = (sz + word_size - 1) / word_size;
3079        let mut blob: Box<[usize]> = vec![0usize; words].into_boxed_slice();
3080        let ptr = blob.as_mut_ptr() as *mut c_void;
3081        state.userdata_storage.insert(id, (blob, sz));
3082        state.push(LuaValue::Userdata(LuaUserdata {
3083            id,
3084            data: ptr,
3085            state: L,
3086        }));
3087        state.record_call("lua_newuserdata", vec![sz.to_string()]);
3088        return ptr;
3089    }
3090    core::ptr::null_mut()
3091}
3092
3093#[no_mangle]
3094pub unsafe extern "C" fn lua_touserdata(L: *mut lua_State, idx: c_int) -> *mut c_void {
3095    if let Some(state) = state_from_ptr(L) {
3096        state.record_call("lua_touserdata", vec![idx.to_string()]);
3097        if let Some(LuaValue::Userdata(data)) = value_at(state, idx) {
3098            return data.data;
3099        }
3100    }
3101    core::ptr::null_mut()
3102}
3103
3104#[no_mangle]
3105pub unsafe extern "C" fn lua_tocfunction(L: *mut lua_State, idx: c_int) -> lua_CFunction {
3106    if let Some(state) = state_from_ptr(L) {
3107        state.record_call("lua_tocfunction", vec![idx.to_string()]);
3108        if let Some(LuaValue::Function(func)) = value_at(state, idx) {
3109            return func.cfunc;
3110        }
3111    }
3112    None
3113}
3114
3115#[no_mangle]
3116pub unsafe extern "C" fn lua_topointer(L: *mut lua_State, idx: c_int) -> *const c_void {
3117    if let Some(state) = state_from_ptr(L) {
3118        state.record_call("lua_topointer", vec![idx.to_string()]);
3119        if let Some(value) = value_at(state, idx) {
3120            let ptr_val = match value {
3121                LuaValue::Table(handle) => Rc::as_ptr(&handle) as *const c_void,
3122                LuaValue::Function(f) => &f as *const _ as *const c_void,
3123                LuaValue::Userdata(u) => u.data,
3124                LuaValue::Thread(t) => &t as *const _ as *const c_void,
3125                LuaValue::LightUserdata(p) => p as *const c_void,
3126                _ => core::ptr::null(),
3127            };
3128            return ptr_val;
3129        }
3130    }
3131    core::ptr::null()
3132}