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