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