1use crate::state_stub::{LuaDebug, LuaState, LuaStateStubExt as _};
20use lua_types::{
21 error::LuaError, gc::GcRef, string::LuaString, userdata::LuaUserData, value::LuaValue,
22 LuaStatus, LuaType,
23};
24
25const LEVELS1: i32 = 10;
29
30const LEVELS2: i32 = 11;
32
33pub const LUA_REFNIL: i32 = -1;
35
36pub const LUA_NOREF: i32 = -2;
38
39pub const LUA_ERRFILE: i32 = 6;
41
42pub const LUA_LOADED_TABLE: &[u8] = b"_LOADED";
44
45pub const LUA_PRELOAD_TABLE: &[u8] = b"_PRELOAD";
47
48pub const LUA_GNAME: &[u8] = b"_G";
50
51pub const LUA_FILE_HANDLE: &[u8] = b"FILE*";
53
54const LUA_REGISTRYINDEX: i32 = -1_001_000;
56
57#[expect(
59 dead_code,
60 reason = "ported stdlib helper; not yet wired into the runtime"
61)]
62const LUA_MINSTACK: i32 = 20;
63
64pub struct LuaReg {
72 pub name: &'static [u8],
73 pub func: Option<fn(&mut LuaState) -> Result<usize, LuaError>>,
74}
75
76pub struct LuaBuffer {
83 pub data: Vec<u8>,
84}
85
86pub struct LuaStream {
91 pub f: Option<Box<dyn std::io::Read>>,
93 pub closef: Option<fn(&mut LuaState) -> Result<usize, LuaError>>,
95}
96
97fn find_field(state: &mut LuaState, objidx: i32, level: i32) -> Result<bool, LuaError> {
104 if level == 0 || state.type_at(-1) != LuaType::Table {
105 return Ok(false);
106 }
107 state.push(LuaValue::Nil);
108 while state.table_next(-2)? {
109 if state.type_at(-2) == LuaType::String {
110 if state.raw_equal(objidx, -1)? {
111 state.pop_n(1); return Ok(true);
113 } else if find_field(state, objidx, level - 1)? {
114 state.push_string(b".")?; state.replace(-3)?; state.concat(3)?; return Ok(true);
119 }
120 }
121 state.pop_n(1); }
123 Ok(false)
124}
125
126fn push_global_func_name(state: &mut LuaState, ar: &mut LuaDebug) -> Result<bool, LuaError> {
130 if state.global().lua_version == lua_types::LuaVersion::V51 {
131 return Ok(false);
132 }
133 let top = state.top_count();
134 state.get_info(b"f", ar)?;
135 state.get_field(LUA_REGISTRYINDEX, LUA_LOADED_TABLE)?;
136 check_stack(state, 6, Some(b"not enough stack"))?;
137 if find_field(state, top + 1, 2)? {
138 if state
139 .peek_bytes(-1)
140 .map_or(false, |n| n.starts_with(b"_G."))
141 {
142 let suffix = state
143 .peek_bytes(-1)
144 .map(|n| n[3..].to_vec())
145 .unwrap_or_default();
146 state.push_bytes(&suffix)?;
147 state.remove(-2)?;
148 }
149 state.copy_value(-1, top + 1)?;
150 lua_vm::api::set_top(state, top + 1)?;
151 Ok(true)
152 } else {
153 lua_vm::api::set_top(state, top)?;
154 Ok(false)
155 }
156}
157
158fn push_global_func_name_from_target(
159 state: &mut LuaState,
160 target: &mut LuaState,
161 ar: &mut LuaDebug,
162) -> Result<bool, LuaError> {
163 if state.global().lua_version == lua_types::LuaVersion::V51 {
164 return Ok(false);
165 }
166 let top = state.top_count();
167 target.get_info(b"f", ar)?;
168 let func = target.get_at(target.top_idx() - 1);
169 target.pop_n(1);
170 state.push(func);
171 state.get_field(LUA_REGISTRYINDEX, LUA_LOADED_TABLE)?;
172 check_stack(state, 6, Some(b"not enough stack"))?;
173 if find_field(state, top + 1, 2)? {
174 if state
175 .peek_bytes(-1)
176 .map_or(false, |n| n.starts_with(b"_G."))
177 {
178 let suffix = state
179 .peek_bytes(-1)
180 .map(|n| n[3..].to_vec())
181 .unwrap_or_default();
182 state.push_bytes(&suffix)?;
183 state.remove(-2)?;
184 }
185 state.copy_value(-1, top + 1)?;
186 lua_vm::api::set_top(state, top + 1)?;
187 Ok(true)
188 } else {
189 lua_vm::api::set_top(state, top)?;
190 Ok(false)
191 }
192}
193
194fn push_func_name(
203 state: &mut LuaState,
204 ar: &mut LuaDebug,
205 global_lookup_target: Option<&mut LuaState>,
206) -> Result<(), LuaError> {
207 if state.global().lua_version == lua_types::LuaVersion::V52 {
208 if !ar.namewhat.is_empty() {
209 let name = ar.name.clone().unwrap_or_else(|| b"?".to_vec());
210 state.push_fstring(format_args!("function '{}'", BStr(&name)))?;
211 } else if ar.what == b'm' {
212 state.push_string(b"main chunk")?;
213 } else if ar.what == b'C' {
214 state.push_string(b"?")?;
215 } else {
216 let src = ar.short_src.clone();
217 let line = ar.linedefined;
218 state.push_fstring(format_args!("function <{}:{}>", BStr(&src), line))?;
219 }
220 return Ok(());
221 }
222 let namewhat_first = state.global().lua_version == lua_types::LuaVersion::V55;
227 if namewhat_first && !ar.namewhat.is_empty() {
228 let namewhat = ar.namewhat.clone();
229 let name = ar.name.clone().unwrap_or_else(|| b"?".to_vec());
230 state.push_fstring(format_args!("{} '{}'", BStr(&namewhat), BStr(&name)))?;
231 return Ok(());
232 }
233 let found_global = match global_lookup_target {
234 Some(target) => push_global_func_name_from_target(state, target, ar)?,
235 None => push_global_func_name(state, ar)?,
236 };
237 if found_global {
238 let name = state.peek_bytes(-1).unwrap_or_else(|| b"?".to_vec());
239 state.push_fstring(format_args!("function '{}'", BStr(&name)))?;
240 state.remove(-2)?;
241 } else if !ar.namewhat.is_empty() {
242 let namewhat = ar.namewhat.clone();
243 let name = ar.name.clone().unwrap_or_else(|| b"?".to_vec());
244 state.push_fstring(format_args!("{} '{}'", BStr(&namewhat), BStr(&name)))?;
245 } else if ar.what == b'm' {
246 state.push_string(b"main chunk")?;
247 } else if ar.what != b'C' {
248 let src = ar.short_src.clone();
249 let line = ar.linedefined;
250 state.push_fstring(format_args!("function <{}:{}>", BStr(&src), line))?;
251 } else {
252 state.push_string(b"?")?;
253 }
254 Ok(())
255}
256
257fn last_level(state: &mut LuaState) -> i32 {
260 let mut ar = LuaDebug::default();
261 let mut li: i32 = 1;
262 let mut le: i32 = 1;
263 while state.get_stack(le, &mut ar) {
264 li = le;
265 le *= 2;
266 }
267 while li < le {
269 let m = (li + le) / 2;
270 if state.get_stack(m, &mut ar) {
271 li = m + 1;
272 } else {
273 le = m;
274 }
275 }
276 le - 1
277}
278
279pub fn traceback(
289 state: &mut LuaState,
290 mut other: Option<&mut LuaState>,
291 msg: Option<&[u8]>,
292 level: i32,
293) -> Result<(), LuaError> {
294 if state.global().lua_version == lua_types::LuaVersion::V51 {
295 return traceback_51(state, other, msg, level);
296 }
297
298 let mut b = LuaBuffer::new();
299 let mut ar = LuaDebug::default();
300 let last = match &mut other {
301 Some(o) => last_level(o),
302 None => last_level(state),
303 };
304 let mut limit2show: i32 = if last - level > LEVELS1 + LEVELS2 {
305 LEVELS1
306 } else {
307 -1
308 };
309 buf_init(state, &mut b);
310 if let Some(m) = msg {
311 add_lstring(&mut b, m);
312 add_char(&mut b, b'\n');
313 }
314 add_lstring(&mut b, b"stack traceback:");
315 let mut level = level;
316 loop {
317 let got = match &mut other {
318 Some(o) => o.get_stack(level, &mut ar),
319 None => state.get_stack(level, &mut ar),
320 };
321 if !got {
322 break;
323 }
324 level += 1;
325 if limit2show == 0 {
326 let n = last - level - LEVELS2 + 1;
327 state.push_fstring(format_args!("\n\t...\t(skipping {} levels)", n))?;
328 add_value(state, &mut b)?;
329 level += n;
330 limit2show = LEVELS2;
331 } else {
332 limit2show -= 1;
333 match &mut other {
334 Some(o) => o.get_info(b"Slnt", &mut ar)?,
335 None => state.get_info(b"Slnt", &mut ar)?,
336 }
337 if ar.currentline <= 0 {
338 let src = ar.short_src.clone();
339 state.push_fstring(format_args!("\n\t{}: in ", BStr(&src)))?;
340 } else {
341 let src = ar.short_src.clone();
342 let line = ar.currentline;
343 state.push_fstring(format_args!("\n\t{}:{}: in ", BStr(&src), line))?;
344 }
345 add_value(state, &mut b)?;
346 match &mut other {
347 Some(o) => push_func_name(state, &mut ar, Some(&mut **o))?,
348 None => push_func_name(state, &mut ar, None)?,
349 }
350 add_value(state, &mut b)?;
351 if ar.istailcall {
352 add_lstring(&mut b, b"\n\t(...tail calls...)");
353 }
354 }
355 }
356 push_result(state, &mut b)?;
357 Ok(())
358}
359
360fn traceback_51(
361 state: &mut LuaState,
362 mut other: Option<&mut LuaState>,
363 msg: Option<&[u8]>,
364 level: i32,
365) -> Result<(), LuaError> {
366 const LEVELS1_51: i32 = 12;
367 const LEVELS2_51: i32 = 10;
368
369 let mut b = LuaBuffer::new();
370 let mut ar = LuaDebug::default();
371 let mut firstpart = true;
372 buf_init(state, &mut b);
373 if let Some(m) = msg {
374 add_lstring(&mut b, m);
375 add_char(&mut b, b'\n');
376 }
377 add_lstring(&mut b, b"stack traceback:");
378
379 let mut level = level;
380 loop {
381 let got = match &mut other {
382 Some(o) => o.get_stack(level, &mut ar),
383 None => state.get_stack(level, &mut ar),
384 };
385 if !got {
386 break;
387 }
388 level += 1;
389 if level > LEVELS1_51 && firstpart {
390 let has_tail = match &mut other {
391 Some(o) => o.get_stack(level + LEVELS2_51, &mut ar),
392 None => state.get_stack(level + LEVELS2_51, &mut ar),
393 };
394 if !has_tail {
395 level -= 1;
396 } else {
397 add_lstring(&mut b, b"\n\t...");
398 while match &mut other {
399 Some(o) => o.get_stack(level + LEVELS2_51, &mut ar),
400 None => state.get_stack(level + LEVELS2_51, &mut ar),
401 } {
402 level += 1;
403 }
404 }
405 firstpart = false;
406 continue;
407 }
408
409 match &mut other {
410 Some(o) => o.get_info(b"Snl", &mut ar)?,
411 None => state.get_info(b"Snl", &mut ar)?,
412 }
413 add_lstring(&mut b, b"\n\t");
414 add_lstring(&mut b, &ar.short_src);
415 add_char(&mut b, b':');
416 if ar.currentline > 0 {
417 state.push_fstring(format_args!("{}:", ar.currentline))?;
418 add_value(state, &mut b)?;
419 }
420 if !ar.namewhat.is_empty() {
421 let name = ar.name.clone().unwrap_or_else(|| b"?".to_vec());
422 state.push_fstring(format_args!(" in function '{}'", BStr(&name)))?;
423 add_value(state, &mut b)?;
424 } else if ar.what == b'm' {
425 add_lstring(&mut b, b" in main chunk");
426 } else if ar.what == b'C' || ar.what == b't' {
427 add_lstring(&mut b, b" ?");
428 } else {
429 let src = ar.short_src.clone();
430 let line = ar.linedefined;
431 state.push_fstring(format_args!(" in function <{}:{}>", BStr(&src), line))?;
432 add_value(state, &mut b)?;
433 }
434 }
435
436 push_result(state, &mut b)?;
437 Ok(())
438}
439
440pub fn arg_error(state: &mut LuaState, mut arg: i32, extramsg: &[u8]) -> Result<usize, LuaError> {
447 let mut ar = LuaDebug::default();
448 if !state.get_stack(0, &mut ar) {
449 return Err(LuaError::runtime(format_args!(
450 "bad argument #{} ({})",
451 arg,
452 BStr(extramsg)
453 )));
454 }
455 state.get_info(b"n", &mut ar)?;
456 if ar.namewhat == b"method" {
457 arg -= 1; if arg == 0 {
459 let name = ar.name.clone().unwrap_or_else(|| b"?".to_vec());
460 return Err(LuaError::runtime(format_args!(
461 "calling '{}' on bad self ({})",
462 BStr(&name),
463 BStr(extramsg)
464 )));
465 }
466 }
467 let fname = if ar.name.is_none() {
468 if push_global_func_name(state, &mut ar)? {
469 state.peek_bytes(-1).unwrap_or_else(|| b"?".to_vec())
470 } else {
471 b"?".to_vec()
472 }
473 } else {
474 ar.name.clone().unwrap_or_else(|| b"?".to_vec())
475 };
476 Err(LuaError::runtime(format_args!(
477 "bad argument #{} to '{}' ({})",
478 arg,
479 BStr(&fname),
480 BStr(extramsg)
481 )))
482}
483
484pub fn type_error_arg(state: &mut LuaState, arg: i32, tname: &[u8]) -> Result<usize, LuaError> {
488 let honors_name = state.global().lua_version.honors_name_metafield();
494 let typearg: Vec<u8> = if honors_name && get_metafield(state, arg, b"__name")? == LuaType::String {
495 let bytes = state.peek_bytes(-1).unwrap_or_else(|| b"?".to_vec());
496 state.pop_n(1);
497 bytes
498 } else if state.type_at(arg) == LuaType::LightUserData {
499 b"light userdata".to_vec()
500 } else if state.type_at(arg) == LuaType::None {
501 b"no value".to_vec()
502 } else {
503 state.type_name_at(arg).to_vec()
504 };
505 let msg_owned = format!("{} expected, got {}", BStr(tname), BStr(&typearg));
506 arg_error(state, arg, msg_owned.as_bytes())
507}
508
509fn tag_error(state: &mut LuaState, arg: i32, tag: LuaType) -> Result<(), LuaError> {
512 let name = state.type_name(tag);
513 type_error_arg(state, arg, name)?;
514 Ok(())
515}
516
517pub fn push_where(state: &mut LuaState, level: i32) -> Result<(), LuaError> {
521 let mut ar = LuaDebug::default();
522 if state.get_stack(level, &mut ar) {
523 state.get_info(b"Sl", &mut ar)?;
524 if ar.currentline > 0 {
525 let src = ar.short_src.clone();
526 let line = ar.currentline;
527 state.push_fstring(format_args!("{}:{}: ", BStr(&src), line))?;
528 return Ok(());
529 }
530 }
531 state.push_string(b"")?;
532 Ok(())
533}
534
535pub fn lua_error(state: &mut LuaState, msg: &[u8]) -> Result<usize, LuaError> {
541 push_where(state, 1)?;
542 let where_str = state.pop_bytes();
543 let full = [where_str.as_slice(), msg].concat();
544 Err(LuaError::runtime(format_args!("{}", BStr(&full))))
545}
546
547pub fn file_result(
552 state: &mut LuaState,
553 stat: bool,
554 fname: Option<&[u8]>,
555) -> Result<usize, LuaError> {
556 if stat {
557 state.push(LuaValue::Bool(true));
558 Ok(1)
559 } else {
560 state.push(LuaValue::Nil);
561 let errmsg = b"(errno unavailable in Rust port)".to_vec();
562 if let Some(name) = fname {
563 let full = [name, b": ".as_slice(), &errmsg].concat();
564 state.push_bytes(&full)?;
565 } else {
566 state.push_bytes(&errmsg)?;
567 }
568 state.push(LuaValue::Int(0));
569 Ok(3)
570 }
571}
572
573pub fn exec_result(state: &mut LuaState, stat: i32) -> Result<usize, LuaError> {
576 if stat != 0 {
577 return file_result(state, false, None);
578 }
579 let what = b"exit".as_slice();
580 state.push(LuaValue::Bool(true));
581 state.push_bytes(what)?;
582 state.push(LuaValue::Int(stat as i64));
583 Ok(3)
584}
585
586pub fn new_metatable(state: &mut LuaState, tname: &[u8]) -> Result<bool, LuaError> {
593 if get_metatable(state, tname)? != LuaType::Nil {
594 return Ok(false); }
596 state.pop_n(1);
597 state.create_table(0, 2)?;
598 state.push_bytes(tname)?;
599 state.set_field(-2, b"__name")?;
600 state.push_value(-1)?;
601 state.set_field(LUA_REGISTRYINDEX, tname)?;
602 Ok(true)
603}
604
605pub fn set_metatable(state: &mut LuaState, tname: &[u8]) -> Result<(), LuaError> {
608 get_metatable(state, tname)?;
609 state.set_metatable(-2)?;
610 Ok(())
611}
612
613pub fn test_udata(
617 state: &mut LuaState,
618 ud: i32,
619 tname: &[u8],
620) -> Result<Option<GcRef<LuaUserData>>, LuaError> {
621 let p = state.to_userdata(ud);
622 if let Some(p) = p {
623 if state.get_metatable(ud)? {
624 get_metatable(state, tname)?;
625 let eq = state.raw_equal(-1, -2)?;
626 state.pop_n(2); if eq {
628 return Ok(Some(p));
629 }
630 }
631 }
632 Ok(None)
633}
634
635pub fn check_udata(
638 state: &mut LuaState,
639 ud: i32,
640 tname: &[u8],
641) -> Result<GcRef<LuaUserData>, LuaError> {
642 match test_udata(state, ud, tname)? {
643 Some(p) => Ok(p),
644 None => {
645 type_error_arg(state, ud, tname)?;
646 unreachable!()
647 }
648 }
649}
650
651pub fn check_option(
657 state: &mut LuaState,
658 arg: i32,
659 def: Option<&[u8]>,
660 lst: &[&[u8]],
661) -> Result<usize, LuaError> {
662 let name: Vec<u8> = match def {
663 Some(d) if state.is_none_or_nil(arg) => d.to_vec(),
664 _ => check_lstring(state, arg)?.as_bytes().to_vec(),
665 };
666 for (i, entry) in lst.iter().enumerate() {
667 if *entry == name.as_slice() {
668 return Ok(i);
669 }
670 }
671 Err(LuaError::runtime(format_args!(
672 "invalid option '{}'",
673 BStr(&name)
674 )))
675}
676
677pub fn check_stack(state: &mut LuaState, space: i32, msg: Option<&[u8]>) -> Result<(), LuaError> {
680 if !state.check_stack_space(space) {
681 match msg {
682 Some(m) => {
683 return Err(LuaError::runtime(format_args!(
684 "stack overflow ({})",
685 BStr(m)
686 )));
687 }
688 None => {
689 return Err(LuaError::runtime(format_args!("stack overflow")));
690 }
691 }
692 }
693 Ok(())
694}
695
696pub fn check_type(state: &mut LuaState, arg: i32, t: LuaType) -> Result<(), LuaError> {
699 if state.type_at(arg) != t {
700 tag_error(state, arg, t)?;
701 }
702 Ok(())
703}
704
705pub fn check_any(state: &mut LuaState, arg: i32) -> Result<(), LuaError> {
708 if state.type_at(arg) == LuaType::None {
709 arg_error(state, arg, b"value expected")?;
710 }
711 Ok(())
712}
713
714pub fn check_lstring(state: &mut LuaState, arg: i32) -> Result<GcRef<LuaString>, LuaError> {
717 match state.to_lua_string(arg) {
718 Some(s) => Ok(s),
719 None => {
720 tag_error(state, arg, LuaType::String)?;
721 unreachable!()
722 }
723 }
724}
725
726pub fn opt_lstring(
729 state: &mut LuaState,
730 arg: i32,
731 def: Option<&[u8]>,
732) -> Result<Option<Vec<u8>>, LuaError> {
733 if state.is_none_or_nil(arg) {
734 return Ok(def.map(|d| d.to_vec()));
735 }
736 let s = check_lstring(state, arg)?;
737 Ok(Some(s.as_bytes().to_vec()))
738}
739
740pub fn check_number(state: &mut LuaState, arg: i32) -> Result<f64, LuaError> {
743 match state.to_number_x(arg) {
744 Some(d) => Ok(d),
745 None => {
746 tag_error(state, arg, LuaType::Number)?;
747 unreachable!()
748 }
749 }
750}
751
752pub fn opt_number(state: &mut LuaState, arg: i32, def: f64) -> Result<f64, LuaError> {
755 if state.is_none_or_nil(arg) {
756 Ok(def)
757 } else {
758 check_number(state, arg)
759 }
760}
761
762fn int_error(state: &mut LuaState, arg: i32) -> Result<usize, LuaError> {
768 if state.is_number(arg) {
769 arg_error(state, arg, b"number has no integer representation")
770 } else {
771 tag_error(state, arg, LuaType::Number)?;
772 unreachable!("tag_error always returns Err")
773 }
774}
775
776pub fn check_integer(state: &mut LuaState, arg: i32) -> Result<i64, LuaError> {
779 match state.to_integer_x(arg) {
780 Some(d) => Ok(d),
781 None => {
782 int_error(state, arg)?;
783 unreachable!("int_error always returns Err")
784 }
785 }
786}
787
788pub fn opt_integer(state: &mut LuaState, arg: i32, def: i64) -> Result<i64, LuaError> {
791 if state.is_none_or_nil(arg) {
792 Ok(def)
793 } else {
794 check_integer(state, arg)
795 }
796}
797
798impl LuaBuffer {
801 pub fn new() -> Self {
805 LuaBuffer { data: Vec::new() }
806 }
807
808 pub fn len(&self) -> usize {
810 self.data.len()
811 }
812}
813
814impl Default for LuaBuffer {
815 fn default() -> Self {
816 LuaBuffer::new()
817 }
818}
819
820pub fn buf_init(state: &mut LuaState, buf: &mut LuaBuffer) {
826 *buf = LuaBuffer::new();
827 let _ = state.push(LuaValue::Nil);
828}
829
830pub fn buf_init_size(state: &mut LuaState, buf: &mut LuaBuffer, sz: usize) -> Result<(), LuaError> {
833 buf_init(state, buf);
834 buf.data.reserve(sz);
835 Ok(())
836}
837
838fn new_buff_size(buf: &LuaBuffer, sz: usize) -> Result<usize, LuaError> {
842 if usize::MAX - sz < buf.len() {
843 return Err(LuaError::runtime(format_args!("buffer too large")));
844 }
845 let newsize = (buf.data.capacity() / 2) * 3; if newsize < buf.len() + sz {
847 Ok(buf.len() + sz)
848 } else {
849 Ok(newsize)
850 }
851}
852
853pub fn prep_buff_size(buf: &mut LuaBuffer, sz: usize) -> Result<(), LuaError> {
856 if buf.data.capacity() - buf.data.len() < sz {
857 let newcap = new_buff_size(buf, sz)?;
858 buf.data.reserve(newcap - buf.data.len());
859 }
860 Ok(())
861}
862
863pub fn add_lstring(buf: &mut LuaBuffer, s: &[u8]) {
866 if !s.is_empty() {
867 buf.data.extend_from_slice(s);
868 }
869}
870
871pub fn add_char(buf: &mut LuaBuffer, c: u8) {
874 buf.data.push(c);
875}
876
877pub fn add_size(_buf: &mut LuaBuffer, sz: usize) {
883 let _ = sz;
884}
885
886pub fn add_value(state: &mut LuaState, buf: &mut LuaBuffer) -> Result<(), LuaError> {
889 if let Some(bytes) = state.peek_bytes(-1) {
890 let owned = bytes.to_vec();
891 add_lstring(buf, &owned);
892 }
893 state.pop_n(1);
894 Ok(())
895}
896
897pub fn push_result(state: &mut LuaState, buf: &mut LuaBuffer) -> Result<(), LuaError> {
900 state.push_bytes(&buf.data)?;
901 state.remove(-2)?;
902 Ok(())
903}
904
905pub fn push_result_size(
908 state: &mut LuaState,
909 buf: &mut LuaBuffer,
910 sz: usize,
911) -> Result<(), LuaError> {
912 add_size(buf, sz);
913 push_result(state, buf)
914}
915
916pub fn add_gsub(buf: &mut LuaBuffer, s: &[u8], pat: &[u8], repl: &[u8]) {
920 if pat.is_empty() {
921 add_lstring(buf, s);
922 return;
923 }
924 let mut remaining = s;
925 while let Some(pos) = find_bytes(remaining, pat) {
926 add_lstring(buf, &remaining[..pos]);
927 add_lstring(buf, repl);
928 remaining = &remaining[pos + pat.len()..];
929 }
930 add_lstring(buf, remaining);
931}
932
933pub fn gsub<'a>(
937 state: &'a mut LuaState,
938 s: &[u8],
939 pat: &[u8],
940 repl: &[u8],
941) -> Result<Vec<u8>, LuaError> {
942 let mut b = LuaBuffer::new();
943 buf_init(state, &mut b);
944 add_gsub(&mut b, s, pat, repl);
945 push_result(state, &mut b)?;
946 Ok(state.peek_bytes(-1).unwrap_or_default())
947}
948
949fn find_bytes(haystack: &[u8], needle: &[u8]) -> Option<usize> {
953 if needle.is_empty() {
954 return Some(0);
955 }
956 haystack.windows(needle.len()).position(|w| w == needle)
957}
958
959fn freelist_ref(version: lua_types::LuaVersion) -> i64 {
989 match version {
990 lua_types::LuaVersion::V51 => 0,
991 lua_types::LuaVersion::V52 => 0,
992 lua_types::LuaVersion::V53 => 0,
993 lua_types::LuaVersion::V54 => 3,
994 lua_types::LuaVersion::V55 => 1,
995 _ => 3,
996 }
997}
998
999pub fn lua_ref(state: &mut LuaState, t: i32) -> Result<i32, LuaError> {
1012 if state.type_at(-1) == LuaType::Nil {
1013 state.pop_n(1);
1014 return Ok(LUA_REFNIL);
1015 }
1016 let t = state.abs_index(t);
1017 let freelist = freelist_ref(state.global().lua_version);
1018 let ref_val: i32;
1019 if state.raw_get_i(t, freelist)? != LuaType::Number {
1020 ref_val = 0;
1021 state.push(LuaValue::Int(0));
1022 state.raw_set_i(t, freelist)?;
1023 } else {
1024 ref_val = state.to_integer_x(-1).unwrap_or(0) as i32;
1025 }
1026 state.pop_n(1);
1027 let next_ref: i32;
1028 if ref_val != 0 {
1029 state.raw_get_i(t, ref_val as i64)?;
1030 state.raw_set_i(t, freelist)?;
1031 next_ref = ref_val;
1032 } else {
1033 next_ref = (state.raw_len(t) as i32) + 1;
1034 }
1035 state.raw_set_i(t, next_ref as i64)?;
1036 Ok(next_ref)
1037}
1038
1039pub fn lua_unref(state: &mut LuaState, t: i32, r: i32) -> Result<(), LuaError> {
1042 if r >= 0 {
1043 let t = state.abs_index(t);
1044 let freelist = freelist_ref(state.global().lua_version);
1045 state.raw_get_i(t, freelist)?;
1046 debug_assert!(state.type_at(-1) == LuaType::Number);
1047 state.raw_set_i(t, r as i64)?;
1048 state.push(LuaValue::Int(r as i64));
1049 state.raw_set_i(t, freelist)?;
1050 }
1051 Ok(())
1052}
1053
1054fn make_string_reader(data: Vec<u8>) -> impl FnMut() -> Option<Vec<u8>> {
1059 let mut remaining = Some(data);
1060 move || remaining.take()
1061}
1062
1063fn skip_bom_and_shebang(buf: &[u8]) -> Vec<u8> {
1071 let s = if buf.starts_with(b"\xEF\xBB\xBF") {
1072 &buf[3..]
1073 } else {
1074 buf
1075 };
1076 if s.first() == Some(&b'#') {
1077 let nl = s
1078 .iter()
1079 .position(|&b| b == b'\n')
1080 .map(|p| p + 1)
1081 .unwrap_or(s.len());
1082 let rest = &s[nl..];
1083 if rest.first() == Some(&0x1B) {
1084 rest.to_vec()
1085 } else {
1086 let mut out = Vec::with_capacity(rest.len() + 1);
1087 out.push(b'\n');
1088 out.extend_from_slice(rest);
1089 out
1090 }
1091 } else {
1092 s.to_vec()
1093 }
1094}
1095
1096pub fn load_filex(
1103 state: &mut LuaState,
1104 filename: Option<&[u8]>,
1105 mode: Option<&[u8]>,
1106) -> Result<i32, LuaError> {
1107 let _ = mode;
1108 let fname = match filename {
1109 Some(f) => f,
1110 None => {
1111 state.push_string(b"cannot read stdin: no filename given")?;
1112 return Ok(LUA_ERRFILE);
1113 }
1114 };
1115 let raw = match state.global().file_loader_hook {
1116 Some(load_fn) => load_fn(fname),
1117 None => Err(LuaError::runtime(format_args!(
1118 "no file_loader_hook registered"
1119 ))),
1120 };
1121 let raw = match raw {
1122 Ok(bytes) => bytes,
1123 Err(e) => {
1124 let detail = match e.message_bytes() {
1125 Some(b) => String::from_utf8_lossy(b).into_owned(),
1126 None => format!("{:?}", &e),
1127 };
1128 state.push_fstring(format_args!("cannot open {}: {}", BStr(fname), detail))?;
1129 return Ok(LUA_ERRFILE);
1130 }
1131 };
1132 let payload = skip_bom_and_shebang(&raw);
1133 let mut once = Some(payload);
1134 let boxed: lua_vm::zio::ChunkReader = Box::new(move |_state| Ok(once.take()));
1135 let mut chunkname = b"@".to_vec();
1136 chunkname.extend_from_slice(fname);
1137 let status = lua_vm::api::load(state, boxed, Some(&chunkname), mode)?;
1138 Ok(if status == LuaStatus::Ok {
1139 0
1140 } else {
1141 status as i32
1142 })
1143}
1144
1145pub fn load_bufferx(
1148 state: &mut LuaState,
1149 buff: &[u8],
1150 name: &[u8],
1151 mode: Option<&[u8]>,
1152) -> Result<i32, LuaError> {
1153 let _reader = make_string_reader(buff.to_vec());
1154 let ok = state.load(buff, name, mode)?;
1155 Ok(if ok { 0 } else { 1 })
1156}
1157
1158pub fn load_buffer(state: &mut LuaState, buff: &[u8], name: &[u8]) -> Result<i32, LuaError> {
1161 load_bufferx(state, buff, name, None)
1162}
1163
1164pub fn load_string(state: &mut LuaState, s: &[u8]) -> Result<i32, LuaError> {
1167 load_buffer(state, s, s)
1168}
1169
1170pub fn get_metafield(state: &mut LuaState, obj: i32, event: &[u8]) -> Result<LuaType, LuaError> {
1176 if !state.get_metatable(obj)? {
1177 return Ok(LuaType::Nil);
1178 }
1179 state.push_bytes(event)?;
1180 let tt = state.raw_get(-2)?;
1181 if tt == LuaType::Nil {
1182 state.pop_n(2);
1183 } else {
1184 state.remove(-2)?;
1185 }
1186 Ok(tt)
1187}
1188
1189pub fn call_meta(state: &mut LuaState, obj: i32, event: &[u8]) -> Result<bool, LuaError> {
1193 let obj = state.abs_index(obj);
1194 if get_metafield(state, obj, event)? == LuaType::Nil {
1195 return Ok(false);
1196 }
1197 state.push_value(obj)?;
1198 state.call(1, 1)?;
1199 Ok(true)
1200}
1201
1202pub fn lua_len(state: &mut LuaState, idx: i32) -> Result<i64, LuaError> {
1206 state.len_op(idx)?;
1207 let l = match state.to_integer_x(-1) {
1208 Some(n) => n,
1209 None => {
1210 return Err(LuaError::runtime(format_args!(
1211 "object length is not an integer"
1212 )));
1213 }
1214 };
1215 state.pop_n(1);
1216 Ok(l)
1217}
1218
1219pub fn to_lua_string(state: &mut LuaState, idx: i32) -> Result<Vec<u8>, LuaError> {
1229 state.to_display_string(idx)
1230}
1231
1232pub fn set_funcs(state: &mut LuaState, l: &[LuaReg], nup: i32) -> Result<(), LuaError> {
1236 check_stack(state, nup, Some(b"too many upvalues"))?;
1237 for reg in l {
1238 match reg.func {
1239 None => {
1240 state.push(LuaValue::Bool(false));
1241 }
1242 Some(f) => {
1243 for _ in 0..nup {
1244 state.push_value(-nup)?;
1245 }
1246 state.push_c_closure(f, nup)?;
1247 }
1248 }
1249 state.set_field(-(nup + 2), reg.name)?;
1250 }
1251 state.pop_n(nup as usize);
1252 Ok(())
1253}
1254
1255pub fn get_subtable(state: &mut LuaState, idx: i32, fname: &[u8]) -> Result<bool, LuaError> {
1259 if state.get_field(idx, fname)? == LuaType::Table {
1260 return Ok(true);
1261 }
1262 state.pop_n(1);
1263 let idx = state.abs_index(idx);
1264 let new_tbl = state.new_table();
1265 state.push(LuaValue::Table(new_tbl));
1266 state.push_value(-1)?;
1267 state.set_field(idx, fname)?;
1268 Ok(false)
1269}
1270
1271pub fn requiref(
1276 state: &mut LuaState,
1277 modname: &[u8],
1278 openf: fn(&mut LuaState) -> Result<usize, LuaError>,
1279 glb: bool,
1280) -> Result<(), LuaError> {
1281 get_subtable(state, LUA_REGISTRYINDEX, LUA_LOADED_TABLE)?;
1282 state.get_field(-1, modname)?;
1283 if !state.to_boolean(-1) {
1284 state.pop_n(1);
1285 state.push_c_function(openf)?;
1286 state.push_bytes(modname)?;
1287 state.call(1, 1)?;
1288 state.push_value(-1)?;
1289 state.set_field(-3, modname)?;
1290 }
1291 state.remove(-2)?;
1292 if glb {
1293 state.push_value(-1)?;
1294 state.set_global(modname)?;
1295 }
1296 Ok(())
1297}
1298
1299pub fn get_metatable(state: &mut LuaState, tname: &[u8]) -> Result<LuaType, LuaError> {
1304 state.get_field(LUA_REGISTRYINDEX, tname)
1305}
1306
1307pub fn new_state() -> Result<LuaState, LuaError> {
1314 let _ = default_panic_handler;
1315 let _ = warn_off;
1316 todo!("phase-b: LuaState::new()")
1317}
1318
1319fn default_panic_handler(state: &mut LuaState) -> Result<usize, LuaError> {
1322 let msg = if state.type_at(-1) == LuaType::String {
1323 state.peek_bytes(-1).unwrap_or_else(|| b"?".to_vec())
1324 } else {
1325 b"error object is not a string".to_vec()
1326 };
1327 eprintln!(
1328 "PANIC: unprotected error in call to Lua API ({})",
1329 BStr(&msg)
1330 );
1331 Ok(0) }
1333
1334fn warn_off(state: &mut LuaState, message: &[u8], tocont: bool) -> Result<(), LuaError> {
1337 check_control(state, message, tocont)?;
1338 Ok(())
1339}
1340
1341fn warn_on(state: &mut LuaState, message: &[u8], tocont: bool) -> Result<(), LuaError> {
1344 if check_control(state, message, tocont)? {
1345 return Ok(());
1346 }
1347 eprint!("Lua warning: ");
1348 warn_cont(state, message, tocont)
1349}
1350
1351fn warn_cont(_state: &mut LuaState, message: &[u8], tocont: bool) -> Result<(), LuaError> {
1354 eprint!("{}", BStr(message));
1355 if tocont {
1356 let _ = (warn_cont as fn(&mut LuaState, &[u8], bool) -> Result<(), LuaError>,);
1357 } else {
1358 eprintln!();
1359 let _ = (warn_on as fn(&mut LuaState, &[u8], bool) -> Result<(), LuaError>,);
1360 }
1361 Ok(())
1362}
1363
1364fn check_control(state: &mut LuaState, message: &[u8], tocont: bool) -> Result<bool, LuaError> {
1368 if tocont || message.first() != Some(&b'@') {
1369 return Ok(false);
1370 }
1371 let cmd = &message[1..];
1372 let _ = state;
1373 if cmd == b"off" {
1374 let _ = warn_off as fn(&mut LuaState, &[u8], bool) -> Result<(), LuaError>;
1375 } else if cmd == b"on" {
1376 let _ = warn_on as fn(&mut LuaState, &[u8], bool) -> Result<(), LuaError>;
1377 }
1378 Ok(true)
1379}
1380
1381pub fn check_version(state: &mut LuaState, ver: f64, sz: usize) -> Result<(), LuaError> {
1384 const LUAL_NUMSIZES: usize = std::mem::size_of::<i64>() * 16 + std::mem::size_of::<f64>();
1385 if sz != LUAL_NUMSIZES {
1386 return Err(LuaError::runtime(format_args!(
1387 "core and library have incompatible numeric types"
1388 )));
1389 }
1390 let v = state.lua_version();
1391 if (v - ver).abs() > f64::EPSILON {
1392 return Err(LuaError::runtime(format_args!(
1393 "version mismatch: app. needs {}, Lua core provides {}",
1394 ver, v
1395 )));
1396 }
1397 Ok(())
1398}
1399
1400struct BStr<'a>(&'a [u8]);
1408
1409impl<'a> std::fmt::Display for BStr<'a> {
1410 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1411 for &b in self.0 {
1412 if b.is_ascii() {
1413 f.write_char(b as char)?;
1414 } else {
1415 write!(f, "\\x{:02x}", b)?;
1416 }
1417 }
1418 Ok(())
1419 }
1420}
1421
1422use std::fmt::Write as _;
1424
1425#[cfg(test)]
1428mod tests {
1429 use super::*;
1430 use lua_types::LuaVersion;
1431
1432 #[test]
1439 fn freelist_ref_is_version_exact() {
1440 assert_eq!(freelist_ref(LuaVersion::V51), 0);
1441 assert_eq!(freelist_ref(LuaVersion::V52), 0);
1442 assert_eq!(freelist_ref(LuaVersion::V53), 0);
1443 assert_eq!(freelist_ref(LuaVersion::V54), 3);
1444 assert_eq!(freelist_ref(LuaVersion::V55), 1);
1445 }
1446
1447 #[test]
1453 fn freelist_ref_does_not_collide_with_5_5_mainthread_slot() {
1454 const V55_MAINTHREAD_REGISTRY_SLOT: i64 = 3;
1455 assert_ne!(freelist_ref(LuaVersion::V55), V55_MAINTHREAD_REGISTRY_SLOT);
1456 }
1457}