1#![allow(non_snake_case, non_camel_case_types)]
2use 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
121pub 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
136pub 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
157pub 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#[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#[derive(Debug, Clone)]
186pub struct LuaApiCall {
187 pub function: String,
188 pub args: Vec<String>,
189}
190
191#[derive(Debug, Clone)]
193pub struct LuaModuleTrace {
194 pub module: String,
195 pub api_calls: Vec<LuaApiCall>,
196}
197
198#[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
300impl 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, }
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
403fn 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 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 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 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 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 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
532fn register_lua_closure(
534 func: &LuaFunction,
535 state: &Rc<RefCell<Box<lua_State>>>,
536 vm: &VM,
537) -> Result<Value, String> {
538 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 let mut lua_args = Vec::new();
548 for arg in args {
549 lua_args.push(value_to_lua(arg, vm));
550 }
551
552 if let LuaValue::Function(f) = &lua_func {
555 if let Some(handle) = f.lust_handle {
556 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 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 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 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 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
977pub 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 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
1235pub 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#[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 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 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 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 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 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 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 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 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 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 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 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(¤t, seg);
2557 }
2558 }
2559 let leaf = segments.last().unwrap().to_string();
2560 let leaf_handle = ensure_child_table(¤t, &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 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 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 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 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);
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 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 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 }
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}