1use crate::state_stub::{lua_CFunction, LuaState, LuaStateStubExt as _};
32use lua_types::{LuaError, LuaType, LuaValue};
33use lua_vm::state::{DynLibId, DynamicSymbol};
34
35const LUA_POF: &[u8] = b"luaopen_";
38
39const LUA_OFSEP: &[u8] = b"_";
40
41const CLIBS: &[u8] = b"_CLIBS";
42
43const LIB_FAIL_ABSENT: &[u8] = b"absent";
48
49const LUA_PATH_SEP: u8 = b';';
50
51const LUA_PATH_MARK: u8 = b'?';
52
53const LUA_IGMARK: u8 = b'-';
54
55#[cfg(target_os = "windows")]
56const LUA_DIRSEP: u8 = b'\\';
57#[cfg(not(target_os = "windows"))]
58const LUA_DIRSEP: u8 = b'/';
59
60const LUA_CSUBSEP: u8 = LUA_DIRSEP;
62const LUA_LSUBSEP: u8 = LUA_DIRSEP;
63
64const DLMSG: &[u8] = b"dynamic libraries not enabled; check your Lua installation";
71
72const C_ABI_UNSUPPORTED_MSG: &[u8] =
77 b"dynamic library loaded, but Lua C ABI modules are not supported by this build";
78
79const LUA_PATH_VAR: &[u8] = b"LUA_PATH";
80const LUA_CPATH_VAR: &[u8] = b"LUA_CPATH";
81
82fn package_config(version: lua_types::LuaVersion) -> Vec<u8> {
90 let mut config = vec![
91 LUA_DIRSEP,
92 b'\n',
93 LUA_PATH_SEP,
94 b'\n',
95 LUA_PATH_MARK,
96 b'\n',
97 b'!',
98 b'\n',
99 LUA_IGMARK,
100 ];
101 if !matches!(version, lua_types::LuaVersion::V51) {
102 config.push(b'\n');
103 }
104 config
105}
106
107fn lua_vdir(version: lua_types::LuaVersion) -> &'static [u8] {
131 match version {
132 lua_types::LuaVersion::V51 => b"5.1",
133 lua_types::LuaVersion::V52 => b"5.2",
134 lua_types::LuaVersion::V53 => b"5.3",
135 lua_types::LuaVersion::V54 => b"5.4",
136 lua_types::LuaVersion::V55 => b"5.5",
137 _ => b"5.4",
138 }
139}
140
141fn lua_versuffix(version: lua_types::LuaVersion) -> &'static [u8] {
146 match version {
147 lua_types::LuaVersion::V51 => b"_5_1",
148 lua_types::LuaVersion::V52 => b"_5_2",
149 lua_types::LuaVersion::V53 => b"_5_3",
150 lua_types::LuaVersion::V54 => b"_5_4",
151 lua_types::LuaVersion::V55 => b"_5_5",
152 _ => b"_5_4",
153 }
154}
155
156fn has_versioned_env_vars(version: lua_types::LuaVersion) -> bool {
162 !matches!(version, lua_types::LuaVersion::V51)
163}
164
165fn lua_ldir(version: lua_types::LuaVersion) -> Vec<u8> {
168 let mut dir = b"/usr/local/share/lua/".to_vec();
169 dir.extend_from_slice(lua_vdir(version));
170 dir.push(b'/');
171 dir
172}
173
174fn lua_cdir(version: lua_types::LuaVersion) -> Vec<u8> {
177 let mut dir = b"/usr/local/lib/lua/".to_vec();
178 dir.extend_from_slice(lua_vdir(version));
179 dir.push(b'/');
180 dir
181}
182
183fn unix_path_default(version: lua_types::LuaVersion) -> Vec<u8> {
193 let ldir = lua_ldir(version);
194 let cdir = lua_cdir(version);
195 let mut path = Vec::new();
196 match version {
197 lua_types::LuaVersion::V51 => {
198 path.extend_from_slice(b"./?.lua;");
199 path.extend_from_slice(&ldir);
200 path.extend_from_slice(b"?.lua;");
201 path.extend_from_slice(&ldir);
202 path.extend_from_slice(b"?/init.lua;");
203 path.extend_from_slice(&cdir);
204 path.extend_from_slice(b"?.lua;");
205 path.extend_from_slice(&cdir);
206 path.extend_from_slice(b"?/init.lua");
207 }
208 lua_types::LuaVersion::V52 => {
209 path.extend_from_slice(&ldir);
210 path.extend_from_slice(b"?.lua;");
211 path.extend_from_slice(&ldir);
212 path.extend_from_slice(b"?/init.lua;");
213 path.extend_from_slice(&cdir);
214 path.extend_from_slice(b"?.lua;");
215 path.extend_from_slice(&cdir);
216 path.extend_from_slice(b"?/init.lua;./?.lua");
217 }
218 _ => {
219 path.extend_from_slice(&ldir);
220 path.extend_from_slice(b"?.lua;");
221 path.extend_from_slice(&ldir);
222 path.extend_from_slice(b"?/init.lua;");
223 path.extend_from_slice(&cdir);
224 path.extend_from_slice(b"?.lua;");
225 path.extend_from_slice(&cdir);
226 path.extend_from_slice(b"?/init.lua;./?.lua;./?/init.lua");
227 }
228 }
229 path
230}
231
232fn windows_path_default(version: lua_types::LuaVersion) -> Vec<u8> {
244 match version {
245 lua_types::LuaVersion::V51 => {
246 b".\\?.lua;!\\lua\\?.lua;!\\lua\\?\\init.lua;!\\?.lua;!\\?\\init.lua".to_vec()
247 }
248 lua_types::LuaVersion::V52 => {
249 b"!\\lua\\?.lua;!\\lua\\?\\init.lua;!\\?.lua;!\\?\\init.lua;.\\?.lua".to_vec()
250 }
251 _ => {
252 let vdir = lua_vdir(version);
253 let mut path = Vec::new();
254 path.extend_from_slice(b"!\\lua\\?.lua;!\\lua\\?\\init.lua;");
255 path.extend_from_slice(b"!\\?.lua;!\\?\\init.lua;");
256 path.extend_from_slice(b"!\\..\\share\\lua\\");
257 path.extend_from_slice(vdir);
258 path.extend_from_slice(b"\\?.lua;");
259 path.extend_from_slice(b"!\\..\\share\\lua\\");
260 path.extend_from_slice(vdir);
261 path.extend_from_slice(b"\\?\\init.lua;");
262 path.extend_from_slice(b".\\?.lua;.\\?\\init.lua");
263 path
264 }
265 }
266}
267
268fn lua_path_default(version: lua_types::LuaVersion) -> Vec<u8> {
270 if cfg!(target_os = "windows") {
271 windows_path_default(version)
272 } else {
273 unix_path_default(version)
274 }
275}
276
277fn unix_cpath_default(version: lua_types::LuaVersion) -> Vec<u8> {
282 let cdir = lua_cdir(version);
283 let mut path = Vec::new();
284 match version {
285 lua_types::LuaVersion::V51 => {
286 path.extend_from_slice(b"./?.so;");
287 path.extend_from_slice(&cdir);
288 path.extend_from_slice(b"?.so;");
289 path.extend_from_slice(&cdir);
290 path.extend_from_slice(b"loadall.so");
291 }
292 _ => {
293 path.extend_from_slice(&cdir);
294 path.extend_from_slice(b"?.so;");
295 path.extend_from_slice(&cdir);
296 path.extend_from_slice(b"loadall.so;./?.so");
297 }
298 }
299 path
300}
301
302fn windows_cpath_default(version: lua_types::LuaVersion) -> Vec<u8> {
309 match version {
310 lua_types::LuaVersion::V51 => b".\\?.dll;!\\?.dll;!\\loadall.dll".to_vec(),
311 lua_types::LuaVersion::V52 => b"!\\?.dll;!\\loadall.dll;.\\?.dll".to_vec(),
312 _ => {
313 let vdir = lua_vdir(version);
314 let mut path = Vec::new();
315 path.extend_from_slice(b"!\\?.dll;!\\..\\lib\\lua\\");
316 path.extend_from_slice(vdir);
317 path.extend_from_slice(b"\\?.dll;");
318 path.extend_from_slice(b"!\\loadall.dll;.\\?.dll");
319 path
320 }
321 }
322}
323
324fn lua_cpath_default(version: lua_types::LuaVersion) -> Vec<u8> {
326 if cfg!(target_os = "windows") {
327 windows_cpath_default(version)
328 } else {
329 unix_cpath_default(version)
330 }
331}
332
333fn getenv_bytes(state: &LuaState, name: &[u8]) -> Option<Vec<u8>> {
334 if let Some(env_fn) = state.global().env_hook {
335 return env_fn(name);
336 }
337
338 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
339 {
340 None
341 }
342
343 #[cfg(all(unix, not(all(target_arch = "wasm32", target_os = "unknown"))))]
344 {
345 use std::ffi::OsStr;
346 use std::os::unix::ffi::{OsStrExt, OsStringExt};
347
348 let os_name = OsStr::from_bytes(name);
349 std::env::var_os(os_name).map(|v| v.into_vec())
350 }
351
352 #[cfg(all(not(unix), not(all(target_arch = "wasm32", target_os = "unknown"))))]
353 {
354 std::str::from_utf8(name)
355 .ok()
356 .and_then(|name_str| std::env::var(name_str).ok())
357 .map(|s| s.into_bytes())
358 }
359}
360
361fn gsub_append(buf: &mut Vec<u8>, s: &[u8], pattern: &[u8], replacement: &[u8]) {
376 if pattern.is_empty() {
377 buf.extend_from_slice(s);
378 return;
379 }
380 let mut pos = 0;
381 while pos < s.len() {
382 if s[pos..].starts_with(pattern) {
383 buf.extend_from_slice(replacement);
384 pos += pattern.len();
385 } else {
386 buf.push(s[pos]);
387 pos += 1;
388 }
389 }
390}
391
392fn gsub_bytes(s: &[u8], pattern: &[u8], replacement: &[u8]) -> Vec<u8> {
395 let mut out = Vec::new();
396 gsub_append(&mut out, s, pattern, replacement);
397 out
398}
399
400fn find_subslice(haystack: &[u8], needle: &[u8]) -> Option<usize> {
402 if needle.is_empty() {
403 return Some(0);
404 }
405 haystack.windows(needle.len()).position(|w| w == needle)
406}
407
408fn lsys_unloadlib(state: &mut LuaState, lib: DynLibId) {
419 if let Some(hook) = state.global().dynlib_unload_hook {
420 hook(lib);
421 }
422}
423
424fn lsys_load(
434 state: &mut LuaState,
435 path: &[u8],
436 see_glb: bool,
437) -> (Option<DynLibId>, &'static [u8]) {
438 let hook = state.global().dynlib_load_hook;
439 let Some(load_fn) = hook else {
440 let s = match state.intern_str(DLMSG) {
441 Ok(s) => s,
442 Err(_) => return (None, LIB_FAIL_ABSENT),
443 };
444 state.push(LuaValue::Str(s));
445 return (None, LIB_FAIL_ABSENT);
446 };
447 match load_fn(state, path, see_glb) {
448 Ok(id) => (Some(id), b"open"),
449 Err(LuaError::File) => {
455 let mut msg = b"cannot find library '".to_vec();
456 msg.extend_from_slice(path);
457 msg.push(b'\'');
458 let s = match state.intern_str(&msg) {
459 Ok(s) => s,
460 Err(_) => return (None, LIB_FAIL_ABSENT),
461 };
462 state.push(LuaValue::Str(s));
463 (None, LIB_FAIL_ABSENT)
464 }
465 Err(err) => {
466 let msg = error_to_bytes(&err);
467 let s = match state.intern_str(&msg) {
468 Ok(s) => s,
469 Err(_) => return (None, b"open"),
470 };
471 state.push(LuaValue::Str(s));
472 (None, b"open")
473 }
474 }
475}
476
477fn lsys_sym(state: &mut LuaState, lib: DynLibId, sym: &[u8]) -> SymOutcome {
484 let hook = state.global().dynlib_symbol_hook;
485 let Some(sym_fn) = hook else {
486 let s = match state.intern_str(DLMSG) {
487 Ok(s) => s,
488 Err(_) => return SymOutcome::Missing,
489 };
490 state.push(LuaValue::Str(s));
491 return SymOutcome::Missing;
492 };
493 match sym_fn(state, lib, sym) {
494 Ok(DynamicSymbol::RustNative(f)) => SymOutcome::Found(f),
495 Ok(DynamicSymbol::LuaCAbi(_)) => {
496 let s = match state.intern_str(C_ABI_UNSUPPORTED_MSG) {
497 Ok(s) => s,
498 Err(_) => return SymOutcome::Missing,
499 };
500 state.push(LuaValue::Str(s));
501 SymOutcome::Missing
502 }
503 Ok(DynamicSymbol::Unsupported { reason }) => {
504 let s = match state.intern_str(&reason) {
505 Ok(s) => s,
506 Err(_) => return SymOutcome::Missing,
507 };
508 state.push(LuaValue::Str(s));
509 SymOutcome::Missing
510 }
511 Err(err) => {
512 let msg = error_to_bytes(&err);
513 let s = match state.intern_str(&msg) {
514 Ok(s) => s,
515 Err(_) => return SymOutcome::Missing,
516 };
517 state.push(LuaValue::Str(s));
518 SymOutcome::Missing
519 }
520 }
521}
522
523enum SymOutcome {
530 Found(lua_CFunction),
532 Missing,
534}
535
536fn error_to_bytes(e: &LuaError) -> Vec<u8> {
539 match e.message_bytes() {
540 Some(b) => b.to_vec(),
541 None => format!("{:?}", e).into_bytes(),
542 }
543}
544
545fn encode_dynlib_id(id: DynLibId) -> *mut std::ffi::c_void {
549 id.0 as usize as *mut std::ffi::c_void
550}
551
552fn decode_dynlib_id(p: *mut std::ffi::c_void) -> DynLibId {
554 DynLibId(p as usize as u64)
555}
556
557fn noenv(state: &mut LuaState) -> bool {
562 let _ = state.get_field_registry(b"LUA_NOENV");
563 let b = state.to_boolean(-1);
564 state.pop_n(1);
565 b
566}
567
568fn setpath(
581 state: &mut LuaState,
582 fieldname: &[u8],
583 envname: &[u8],
584 dft: &[u8],
585) -> Result<(), LuaError> {
586 let version = state.global().lua_version;
587
588 let path_opt = if noenv(state) {
589 None
590 } else if has_versioned_env_vars(version) {
591 let mut nver = envname.to_vec();
592 nver.extend_from_slice(lua_versuffix(version));
593 getenv_bytes(state, &nver).or_else(|| getenv_bytes(state, envname))
594 } else {
595 getenv_bytes(state, envname)
596 };
597
598 let final_path: Vec<u8> = match path_opt {
599 None => dft.to_vec(),
600 Some(path) if double_semicolon_splice_is_legacy(version) => {
601 legacy_double_semicolon_splice(&path, dft)
602 }
603 Some(path) => modern_double_semicolon_splice(&path, dft),
604 };
605
606 let final_path = setprogdir(final_path)?;
607 let s = state.intern_str(&final_path)?;
608 state.push(LuaValue::Str(s));
609 state.set_field(-2, fieldname)?;
610
611 Ok(())
612}
613
614fn double_semicolon_splice_is_legacy(version: lua_types::LuaVersion) -> bool {
625 matches!(
626 version,
627 lua_types::LuaVersion::V51 | lua_types::LuaVersion::V52 | lua_types::LuaVersion::V53
628 )
629}
630
631fn legacy_double_semicolon_splice(path: &[u8], dft: &[u8]) -> Vec<u8> {
639 let double_sep = [LUA_PATH_SEP, LUA_PATH_SEP];
640 if find_subslice(path, &double_sep).is_none() {
641 return path.to_vec();
642 }
643 let mut replacement = vec![LUA_PATH_SEP];
644 replacement.extend_from_slice(dft);
645 replacement.push(LUA_PATH_SEP);
646 gsub_bytes(path, &double_sep, &replacement)
647}
648
649fn modern_double_semicolon_splice(path: &[u8], dft: &[u8]) -> Vec<u8> {
654 let double_sep = [LUA_PATH_SEP, LUA_PATH_SEP];
655 let Some(dftmark_pos) = find_subslice(path, &double_sep) else {
656 return path.to_vec();
657 };
658 let mut buf = Vec::new();
659 if dftmark_pos > 0 {
660 buf.extend_from_slice(&path[..dftmark_pos]);
661 buf.push(LUA_PATH_SEP);
662 }
663 buf.extend_from_slice(dft);
664 let after = dftmark_pos + 2;
665 if after < path.len() {
666 buf.push(LUA_PATH_SEP);
667 buf.extend_from_slice(&path[after..]);
668 }
669 buf
670}
671
672#[cfg(target_os = "windows")]
681fn setprogdir(path: Vec<u8>) -> Result<Vec<u8>, LuaError> {
682 let exe = std::env::current_exe()
683 .map_err(|_| LuaError::runtime(format_args!("unable to get ModuleFileName")))?;
684 let Some(dir) = exe.parent() else {
685 return Err(LuaError::runtime(format_args!(
686 "unable to get ModuleFileName"
687 )));
688 };
689 let dir = dir.to_string_lossy();
690 Ok(gsub_bytes(&path, b"!", dir.as_bytes()))
691}
692
693#[cfg(not(target_os = "windows"))]
696fn setprogdir(path: Vec<u8>) -> Result<Vec<u8>, LuaError> {
697 Ok(path)
698}
699
700fn checkclib(state: &mut LuaState, path: &[u8]) -> Option<DynLibId> {
705 let _ = state.get_field_registry(CLIBS);
706 let _ = state.get_field(-1, path);
707 let handle = state.to_light_userdata(-1).map(decode_dynlib_id);
708 state.pop_n(2);
709 handle
710}
711
712fn addtoclib(state: &mut LuaState, path: &[u8], plib: DynLibId) -> Result<(), LuaError> {
715 state.get_field_registry(CLIBS)?;
716 state.push(LuaValue::LightUserData(encode_dynlib_id(plib)));
717 state.push_value(-1)?;
718 state.set_field(-3, path)?;
719 let n = state.len_at(-2);
720 state.raw_seti(-2, n + 1)?;
721 state.pop_n(1);
722 Ok(())
723}
724
725fn gctm(state: &mut LuaState) -> Result<usize, LuaError> {
729 let n = state.len_at(1);
730 let mut i = n;
731 while i >= 1 {
732 state.raw_geti(1, i)?;
733 if let Some(handle) = state.to_light_userdata(-1).map(decode_dynlib_id) {
734 lsys_unloadlib(state, handle);
735 }
736 state.pop_n(1);
737 i -= 1;
738 }
739 Ok(0)
740}
741
742enum LookForFuncStatus {
754 Ok,
756 ErrLib(&'static [u8]),
758 ErrFunc,
760}
761
762fn lookforfunc(
763 state: &mut LuaState,
764 path: &[u8],
765 sym: &[u8],
766) -> Result<LookForFuncStatus, LuaError> {
767 let reg = match checkclib(state, path) {
768 Some(handle) => handle,
769 None => {
770 let (loaded, tag) = lsys_load(state, path, sym.first() == Some(&b'*'));
771 match loaded {
772 Some(handle) => {
773 addtoclib(state, path, handle)?;
774 handle
775 }
776 None => return Ok(LookForFuncStatus::ErrLib(tag)),
777 }
778 }
779 };
780 if sym.first() == Some(&b'*') {
781 state.push(LuaValue::Bool(true));
782 return Ok(LookForFuncStatus::Ok);
783 }
784 match lsys_sym(state, reg, sym) {
785 SymOutcome::Found(func) => {
786 state.push_c_function(func)?;
787 Ok(LookForFuncStatus::Ok)
788 }
789 SymOutcome::Missing => Ok(LookForFuncStatus::ErrFunc),
790 }
791}
792
793pub fn ll_loadlib(state: &mut LuaState) -> Result<usize, LuaError> {
802 let path = state.check_arg_string(1)?.to_vec();
803 let init = state.check_arg_string(2)?.to_vec();
804 let stat = lookforfunc(state, &path, &init)?;
805 let where_bytes: &[u8] = match stat {
806 LookForFuncStatus::Ok => return Ok(1),
807 LookForFuncStatus::ErrLib(tag) => tag,
808 LookForFuncStatus::ErrFunc => b"init",
809 };
810 state.push(LuaValue::Nil);
817 state.insert(-2)?;
818 let where_s = state.intern_str(where_bytes)?;
819 state.push(LuaValue::Str(where_s));
820 Ok(3)
821}
822
823fn readable(state: &LuaState, filename: &[u8]) -> bool {
832 match state.global().file_loader_hook {
833 Some(hook) => hook(filename).is_ok(),
834 None => false,
835 }
836}
837
838struct PathComponents<'a> {
844 remaining: &'a [u8],
845}
846
847impl<'a> PathComponents<'a> {
848 fn new(path: &'a [u8]) -> Self {
849 PathComponents { remaining: path }
850 }
851}
852
853impl<'a> Iterator for PathComponents<'a> {
854 type Item = &'a [u8];
855
856 fn next(&mut self) -> Option<Self::Item> {
857 if self.remaining.is_empty() {
858 return None;
859 }
860 let component = match self.remaining.iter().position(|&b| b == LUA_PATH_SEP) {
861 Some(sep_pos) => {
862 let c = &self.remaining[..sep_pos];
863 self.remaining = &self.remaining[sep_pos + 1..];
864 c
865 }
866 None => {
867 let c = self.remaining;
868 self.remaining = &[];
869 c
870 }
871 };
872 Some(component)
873 }
874}
875
876fn pusherrornotfound(state: &mut LuaState, path: &[u8]) -> Result<(), LuaError> {
883 let mut buf: Vec<u8> = Vec::new();
884 buf.extend_from_slice(b"no file '");
885 gsub_append(&mut buf, path, &[LUA_PATH_SEP], b"'\n\tno file '");
886 buf.push(b'\'');
887 let s = state.intern_str(&buf)?;
888 state.push(LuaValue::Str(s));
889 Ok(())
890}
891
892fn searchpath(
901 state: &mut LuaState,
902 name: &[u8],
903 path: &[u8],
904 sep: &[u8],
905 dirsep: &[u8],
906) -> Result<Option<Vec<u8>>, LuaError> {
907 let name_buf: Vec<u8> = if !sep.is_empty() && name.contains(&sep[0]) {
908 gsub_bytes(name, sep, dirsep)
909 } else {
910 name.to_vec()
911 };
912
913 let pathname: Vec<u8> = gsub_bytes(path, &[LUA_PATH_MARK], &name_buf);
914
915 for filename in PathComponents::new(&pathname) {
916 if readable(state, filename) {
917 let s = state.intern_str(filename)?;
918 state.push(LuaValue::Str(s));
919 return Ok(Some(filename.to_vec()));
920 }
921 }
922
923 pusherrornotfound(state, &pathname)?;
924 Ok(None)
925}
926
927pub fn ll_searchpath(state: &mut LuaState) -> Result<usize, LuaError> {
934 let name = state.check_arg_string(1)?.to_vec();
935 let path = state.check_arg_string(2)?.to_vec();
936 let sep = state.opt_arg_string(3, b".")?;
937 let dirsep_default = [LUA_DIRSEP];
938 let dirsep = state.opt_arg_string(4, &dirsep_default)?;
939
940 let found = searchpath(state, &name, &path, &sep, &dirsep)?;
941 if found.is_some() {
942 return Ok(1);
943 }
944 if searchpath_error_has_leading_separator(state.global().lua_version) {
945 prepend_searchpath_separator(state)?;
946 }
947 state.push(LuaValue::Nil);
948 state.insert(-2)?;
949 Ok(2)
950}
951
952fn searchpath_error_has_leading_separator(version: lua_types::LuaVersion) -> bool {
963 matches!(version, lua_types::LuaVersion::V52 | lua_types::LuaVersion::V53)
964}
965
966fn prepend_searchpath_separator(state: &mut LuaState) -> Result<(), LuaError> {
970 let Some(bare) = state.to_bytes(-1) else {
971 return Ok(());
972 };
973 state.pop_n(1);
974 let mut prefixed = b"\n\t".to_vec();
975 prefixed.extend_from_slice(&bare);
976 let s = state.intern_str(&prefixed)?;
977 state.push(LuaValue::Str(s));
978 Ok(())
979}
980
981fn findfile(
985 state: &mut LuaState,
986 name: &[u8],
987 pname: &[u8],
988 dirsep: u8,
989) -> Result<Option<Vec<u8>>, LuaError> {
990 let uv = state.upvalue_index(1);
991 let _ = state.get_field(uv, pname);
992 let path_opt: Option<Vec<u8>> = state.to_bytes(-1);
993 let Some(path) = path_opt else {
994 state.pop_n(1);
995 return Err(LuaError::runtime(format_args!(
996 "'package.{}' must be a string",
997 String::from_utf8_lossy(pname)
998 )));
999 };
1000 state.pop_n(1);
1001 searchpath(state, name, &path, b".", &[dirsep])
1002}
1003
1004fn checkload(state: &mut LuaState, stat: bool, filename: &[u8]) -> Result<usize, LuaError> {
1008 if stat {
1009 let s = state.intern_str(filename)?;
1010 state.push(LuaValue::Str(s));
1011 Ok(2)
1012 } else {
1013 let modname = state.to_bytes(1).unwrap_or_else(|| b"?".to_vec());
1017 let loader_err = state.to_bytes(-1).unwrap_or_else(|| b"?".to_vec());
1018
1019 let mut msg = b"error loading module '".to_vec();
1020 msg.extend_from_slice(&modname);
1021 msg.extend_from_slice(b"' from file '");
1022 msg.extend_from_slice(filename);
1023 msg.extend_from_slice(b"':\n\t");
1024 msg.extend_from_slice(&loader_err);
1025
1026 let s = state.intern_str(&msg)?;
1027 return Err(LuaError::from_value(LuaValue::Str(s)));
1028 }
1029}
1030
1031fn searcher_lua(state: &mut LuaState) -> Result<usize, LuaError> {
1039 let name = state.check_arg_string(1)?.to_vec();
1040 let filename = findfile(state, &name, b"path", LUA_LSUBSEP)?;
1041 if filename.is_none() {
1042 return Ok(1);
1043 }
1044 let filename = filename.unwrap();
1045 let chunk = match state.global().file_loader_hook {
1051 Some(hook) => hook(&filename),
1052 None => Err(LuaError::runtime(format_args!(
1053 "no file_loader_hook registered; cannot read '{}'",
1054 String::from_utf8_lossy(&filename)
1055 ))),
1056 };
1057 let load_ok = match chunk {
1058 Ok(bytes) => {
1059 let mut chunkname = b"@".to_vec();
1061 chunkname.extend_from_slice(&filename);
1062 match state.load(&bytes, &chunkname, None) {
1063 Ok(true) => true,
1064 Ok(false) => false,
1065 Err(e) => {
1066 let msg = match e.message_bytes() {
1067 Some(b) => b.to_vec(),
1068 None => format!("{:?}", &e).into_bytes(),
1069 };
1070 let s = state.intern_str(&msg)?;
1071 state.push(LuaValue::Str(s));
1072 false
1073 }
1074 }
1075 }
1076 Err(e) => {
1077 let msg = match e.message_bytes() {
1078 Some(b) => b.to_vec(),
1079 None => format!("{:?}", &e).into_bytes(),
1080 };
1081 let s = state.intern_str(&msg)?;
1082 state.push(LuaValue::Str(s));
1083 false
1084 }
1085 };
1086 checkload(state, load_ok, &filename)
1087}
1088
1089fn loadfunc(
1095 state: &mut LuaState,
1096 filename: &[u8],
1097 modname: &[u8],
1098) -> Result<LookForFuncStatus, LuaError> {
1099 let modname: Vec<u8> = gsub_bytes(modname, b".", LUA_OFSEP);
1100
1101 if let Some(mark_pos) = modname.iter().position(|&b| b == LUA_IGMARK) {
1102 let prefix = &modname[..mark_pos];
1103 let mut openfunc = LUA_POF.to_vec();
1104 openfunc.extend_from_slice(prefix);
1105 let stat = lookforfunc(state, filename, &openfunc)?;
1106 if !matches!(stat, LookForFuncStatus::ErrFunc) {
1107 return Ok(stat);
1108 }
1109 let tail = &modname[mark_pos + 1..];
1110 let mut openfunc2 = LUA_POF.to_vec();
1111 openfunc2.extend_from_slice(tail);
1112 return lookforfunc(state, filename, &openfunc2);
1113 }
1114
1115 let mut openfunc = LUA_POF.to_vec();
1116 openfunc.extend_from_slice(&modname);
1117 lookforfunc(state, filename, &openfunc)
1118}
1119
1120fn searcher_c(state: &mut LuaState) -> Result<usize, LuaError> {
1123 let name = state.check_arg_string(1)?.to_vec();
1124 let filename = findfile(state, &name, b"cpath", LUA_CSUBSEP)?;
1125 if filename.is_none() {
1126 return Ok(1);
1127 }
1128 let filename = filename.unwrap();
1129 let stat = loadfunc(state, &filename, &name)?;
1130 let ok = matches!(stat, LookForFuncStatus::Ok);
1131 checkload(state, ok, &filename)
1132}
1133
1134fn searcher_croot(state: &mut LuaState) -> Result<usize, LuaError> {
1138 let name = state.check_arg_string(1)?.to_vec();
1139 let dot_pos = name.iter().position(|&b| b == b'.');
1140 if dot_pos.is_none() {
1141 return Ok(0);
1142 }
1143 let dot_pos = dot_pos.unwrap();
1144
1145 let root = &name[..dot_pos];
1146
1147 let filename = findfile(state, root, b"cpath", LUA_CSUBSEP)?;
1148
1149 if filename.is_none() {
1150 return Ok(1);
1151 }
1152 let filename = filename.unwrap();
1153
1154 let stat = loadfunc(state, &filename, &name)?;
1155 match stat {
1156 LookForFuncStatus::Ok => {}
1157 LookForFuncStatus::ErrFunc => {
1158 let mut msg = b"no module '".to_vec();
1159 msg.extend_from_slice(&name);
1160 msg.extend_from_slice(b"' in file '");
1161 msg.extend_from_slice(&filename);
1162 msg.push(b'\'');
1163 let s = state.intern_str(&msg)?;
1164 state.push(LuaValue::Str(s));
1165 return Ok(1);
1166 }
1167 LookForFuncStatus::ErrLib(_) => {
1168 return checkload(state, false, &filename);
1169 }
1170 }
1171
1172 let s = state.intern_str(&filename)?;
1173 state.push(LuaValue::Str(s));
1174 Ok(2)
1175}
1176
1177fn searcher_preload(state: &mut LuaState) -> Result<usize, LuaError> {
1183 let name = state.check_arg_string(1)?.to_vec();
1184 state.get_field_registry(b"_PRELOAD")?;
1185 let ty = state.get_field(-1, &name)?;
1186 if ty == LuaType::Nil {
1187 let mut msg = b"no field package.preload['".to_vec();
1188 msg.extend_from_slice(&name);
1189 msg.push(b'\'');
1190 msg.push(b']');
1191 let s = state.intern_str(&msg)?;
1192 state.push(LuaValue::Str(s));
1193 return Ok(1);
1194 }
1195 if !require_returns_loader_data(state.global().lua_version) {
1196 return Ok(1);
1197 }
1198 let tag = state.intern_str(b":preload:")?;
1199 state.push(LuaValue::Str(tag));
1200 Ok(2)
1201}
1202
1203fn findloader(state: &mut LuaState, name: &[u8]) -> Result<(), LuaError> {
1215 let uv = state.upvalue_index(1);
1216 let field: &[u8] = if matches!(state.global().lua_version, lua_types::LuaVersion::V51) {
1220 b"loaders"
1221 } else {
1222 b"searchers"
1223 };
1224 let ty = state.get_field(uv, field)?;
1225 if ty != LuaType::Table {
1226 return Err(LuaError::runtime(format_args!(
1227 "'package.searchers' must be a table"
1228 )));
1229 }
1230
1231 let mut msg_buf: Vec<u8> = Vec::new();
1232
1233 let mut i: i64 = 1;
1234 loop {
1235 msg_buf.extend_from_slice(b"\n\t");
1236
1237 let item_ty = state.raw_geti(-1, i)?;
1238 if item_ty == LuaType::Nil {
1239 state.pop_n(1);
1240 let len = msg_buf.len();
1241 if len >= 2 {
1242 msg_buf.truncate(len - 2);
1243 }
1244 let mut err = b"module '".to_vec();
1246 err.extend_from_slice(name);
1247 err.extend_from_slice(b"' not found:");
1248 err.extend_from_slice(&msg_buf);
1249 let err_s = state.intern_str(&err)?;
1250 return Err(LuaError::from_value(LuaValue::Str(err_s)));
1251 }
1252
1253 let name_s = state.intern_str(name)?;
1254 state.push(LuaValue::Str(name_s));
1255
1256 state.call(1, 2)?;
1257
1258 if state.type_at(-2) == LuaType::Function {
1260 return Ok(());
1262 }
1263
1264 if state.type_at(-2) == LuaType::String {
1265 state.pop_n(1);
1267 if let Some(bytes) = state.to_bytes(-1) {
1268 msg_buf.extend_from_slice(&bytes);
1269 }
1270 state.pop_n(1);
1271 } else {
1272 state.pop_n(2);
1273 let len = msg_buf.len();
1274 if len >= 2 {
1275 msg_buf.truncate(len - 2);
1276 }
1277 }
1278
1279 i += 1;
1280 }
1281}
1282
1283pub fn ll_require(state: &mut LuaState) -> Result<usize, LuaError> {
1289 let name = state.check_arg_string(1)?.to_vec();
1290 let version = state.global().lua_version;
1291
1292 lua_vm::api::set_top(state, 1)?;
1296
1297 state.get_field_registry(b"_LOADED")?;
1298
1299 state.get_field(2, &name)?;
1300
1301 if state.to_boolean(-1) {
1302 return Ok(1);
1303 }
1304
1305 state.pop_n(1);
1306
1307 findloader(state, &name)?;
1309
1310 if require_passes_loader_data(version) {
1311 state.rotate(-2, 1)?;
1316 state.push_value(1)?;
1317 state.push_value(-3)?;
1318 state.call(2, 1)?;
1319 } else {
1320 state.pop_n(1);
1322 state.push_value(1)?;
1323 state.call(1, 1)?;
1324 }
1325
1326 if state.type_at(-1) != LuaType::Nil {
1327 state.set_field(2, &name)?;
1328 } else {
1329 state.pop_n(1);
1330 }
1331
1332 let ty = state.get_field(2, &name)?;
1333 if ty == LuaType::Nil {
1334 state.push(LuaValue::Bool(true));
1335 state.copy_value(-1, -2)?;
1336 state.set_field(2, &name)?;
1337 }
1338
1339 if require_returns_loader_data(version) {
1340 state.rotate(-2, 1)?;
1343 Ok(2)
1344 } else {
1345 if require_passes_loader_data(version) {
1349 state.remove(-2)?;
1350 }
1351 Ok(1)
1352 }
1353}
1354
1355fn require_passes_loader_data(version: lua_types::LuaVersion) -> bool {
1362 !matches!(version, lua_types::LuaVersion::V51)
1363}
1364
1365fn require_returns_loader_data(version: lua_types::LuaVersion) -> bool {
1373 matches!(version, lua_types::LuaVersion::V54 | lua_types::LuaVersion::V55)
1374}
1375
1376fn createsearcherstable(state: &mut LuaState) -> Result<(), LuaError> {
1382 let searchers: &[fn(&mut LuaState) -> Result<usize, LuaError>] =
1383 &[searcher_preload, searcher_lua, searcher_c, searcher_croot];
1384
1385 state.create_table(searchers.len() as i32, 0)?;
1386
1387 for (i, &f) in searchers.iter().enumerate() {
1388 state.push_value(-2)?;
1392 state.push_c_closure(f, 1)?;
1393 state.raw_seti(-2, (i + 1) as i64)?;
1394 }
1395 let version = state.global().lua_version;
1403 let has_loaders = matches!(
1404 version,
1405 lua_types::LuaVersion::V51 | lua_types::LuaVersion::V52
1406 );
1407 let has_searchers = !matches!(version, lua_types::LuaVersion::V51);
1408 if has_loaders {
1409 state.push_value(-1)?;
1410 state.set_field(-3, b"loaders")?;
1411 }
1412 if has_searchers {
1413 state.set_field(-2, b"searchers")?;
1414 } else {
1415 state.pop_n(1);
1417 }
1418 Ok(())
1419}
1420
1421fn createclibstable(state: &mut LuaState) -> Result<(), LuaError> {
1425 state.get_subtable_registry(CLIBS)?;
1426 state.create_table(0, 1)?;
1427 state.push_c_function(gctm)?;
1428 state.set_field(-2, b"__gc")?;
1429 state.set_metatable(-2)?;
1430 Ok(())
1431}
1432
1433fn ll_seeall(state: &mut LuaState) -> Result<usize, LuaError> {
1447 state.check_arg_type(1, LuaType::Table)?;
1448 if !state.get_metatable(1)? {
1449 state.create_table(0, 1)?;
1450 state.push_value(-1)?;
1451 state.set_metatable(1)?;
1452 }
1453 state.push_globals()?;
1454 state.set_field(-2, b"__index")?;
1455 Ok(0)
1456}
1457
1458fn findtable(state: &mut LuaState, table_idx: i32, name: &[u8]) -> Result<(), LuaError> {
1463 state.push_value_at(table_idx)?;
1465 for part in name.split(|&b| b == b'.') {
1466 let ty = state.get_field(-1, part)?;
1468 if ty == LuaType::Nil {
1469 state.pop_n(1); state.create_table(0, 1)?; state.push_value(-1)?; state.set_field(-3, part)?; state.remove(-2)?;
1475 } else if ty == LuaType::Table {
1476 state.remove(-2)?;
1478 } else {
1479 return Err(LuaError::runtime(format_args!(
1480 "name conflict for module '{}'",
1481 String::from_utf8_lossy(name)
1482 )));
1483 }
1484 }
1485 Ok(())
1486}
1487
1488fn ll_module(state: &mut LuaState) -> Result<usize, LuaError> {
1495 let modname: Vec<u8> = state.check_arg_string(1)?;
1496 let n_opts = state.top() as i32;
1497
1498 state.get_field_registry(b"_LOADED")?;
1500 let loaded_idx = state.top() as i32;
1501 state.get_field(loaded_idx, &modname)?;
1502 if state.type_at(-1) != LuaType::Table {
1503 state.pop_n(1); state.push_globals()?;
1506 let g_idx = state.top() as i32;
1507 findtable(state, g_idx, &modname)?;
1508 state.remove(g_idx)?; state.push_value(-1)?;
1510 state.set_field(loaded_idx, &modname)?; }
1512
1513 let has_name = state.get_field(-1, b"_NAME")? != LuaType::Nil;
1515 state.pop_n(1);
1516 if !has_name {
1517 state.push_value(-1)?;
1519 state.set_field(-2, b"_M")?;
1520 state.push_string(&modname)?;
1522 state.set_field(-2, b"_NAME")?;
1523 let pkg: &[u8] = match modname.iter().rposition(|&b| b == b'.') {
1525 Some(dot) => &modname[..=dot],
1526 None => b"",
1527 };
1528 state.push_string(pkg)?;
1529 state.set_field(-2, b"_PACKAGE")?;
1530 }
1531
1532 let module_tbl = state.value_at(-1);
1535 crate::base::set_func_env_at_level(state, 1, module_tbl)?;
1536
1537 let mut i = 2;
1539 while i <= n_opts {
1540 state.push_value_at(i)?; state.push_value(-2)?; state.call(1, 0)?;
1543 i += 1;
1544 }
1545 Ok(0)
1546}
1547
1548pub fn luaopen_package(state: &mut LuaState) -> Result<usize, LuaError> {
1551 createclibstable(state)?;
1552
1553 state.new_lib(&[(
1558 b"loadlib" as &[u8],
1559 ll_loadlib as fn(&mut LuaState) -> Result<usize, LuaError>,
1560 )])?;
1561
1562 if !matches!(state.global().lua_version, lua_types::LuaVersion::V51) {
1563 state.push_c_function(ll_searchpath)?;
1564 state.set_field(-2, b"searchpath")?;
1565 }
1566
1567 createsearcherstable(state)?;
1568
1569 let path_default = lua_path_default(state.global().lua_version);
1570 setpath(state, b"path", LUA_PATH_VAR, &path_default)?;
1571
1572 let cpath_default = lua_cpath_default(state.global().lua_version);
1573 setpath(state, b"cpath", LUA_CPATH_VAR, &cpath_default)?;
1574
1575 let config = package_config(state.global().lua_version);
1576 let config_s = state.intern_str(&config)?;
1577 state.push(LuaValue::Str(config_s));
1578
1579 state.set_field(-2, b"config")?;
1580
1581 state.get_subtable_registry(b"_LOADED")?;
1582 state.set_field(-2, b"loaded")?;
1583
1584 state.get_subtable_registry(b"_PRELOAD")?;
1585 state.set_field(-2, b"preload")?;
1586
1587 state.push_globals()?;
1588 state.push_value(-2)?;
1589 state.set_funcs_with_upvalues(
1590 &[(
1591 b"require" as &[u8],
1592 ll_require as fn(&mut LuaState) -> Result<usize, LuaError>,
1593 )],
1594 1,
1595 )?;
1596 state.pop_n(1);
1597
1598 if matches!(
1603 state.global().lua_version,
1604 lua_types::LuaVersion::V51 | lua_types::LuaVersion::V52
1605 ) {
1606 state.push_c_function(ll_seeall)?;
1608 state.set_field(-2, b"seeall")?;
1609 state.push_c_function(ll_module)?;
1611 state.set_global(b"module")?;
1612 }
1613
1614 Ok(1)
1615}
1616
1617#[cfg(test)]
1618mod tests {
1619 use super::*;
1620 use lua_types::LuaVersion;
1621
1622 const ALL: [LuaVersion; 5] =
1623 [LuaVersion::V51, LuaVersion::V52, LuaVersion::V53, LuaVersion::V54, LuaVersion::V55];
1624
1625 #[test]
1629 fn vdir_is_version_exact() {
1630 assert_eq!(lua_vdir(LuaVersion::V51), b"5.1");
1631 assert_eq!(lua_vdir(LuaVersion::V52), b"5.2");
1632 assert_eq!(lua_vdir(LuaVersion::V53), b"5.3");
1633 assert_eq!(lua_vdir(LuaVersion::V54), b"5.4");
1634 assert_eq!(lua_vdir(LuaVersion::V55), b"5.5");
1635 }
1636
1637 #[test]
1638 fn versuffix_is_version_exact() {
1639 assert_eq!(lua_versuffix(LuaVersion::V51), b"_5_1");
1640 assert_eq!(lua_versuffix(LuaVersion::V52), b"_5_2");
1641 assert_eq!(lua_versuffix(LuaVersion::V53), b"_5_3");
1642 assert_eq!(lua_versuffix(LuaVersion::V54), b"_5_4");
1643 assert_eq!(lua_versuffix(LuaVersion::V55), b"_5_5");
1644 }
1645
1646 #[test]
1650 fn only_5_1_lacks_versioned_env_vars() {
1651 assert!(!has_versioned_env_vars(LuaVersion::V51));
1652 for v in [LuaVersion::V52, LuaVersion::V53, LuaVersion::V54, LuaVersion::V55] {
1653 assert!(has_versioned_env_vars(v), "{v:?}");
1654 }
1655 }
1656
1657 #[test]
1663 fn path_default_is_version_exact() {
1664 assert_eq!(
1665 unix_path_default(LuaVersion::V51),
1666 b"./?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;\
1667 /usr/local/lib/lua/5.1/?.lua;/usr/local/lib/lua/5.1/?/init.lua"
1668 .to_vec()
1669 );
1670 assert_eq!(
1671 unix_path_default(LuaVersion::V52),
1672 b"/usr/local/share/lua/5.2/?.lua;/usr/local/share/lua/5.2/?/init.lua;\
1673 /usr/local/lib/lua/5.2/?.lua;/usr/local/lib/lua/5.2/?/init.lua;./?.lua"
1674 .to_vec()
1675 );
1676 for v in [LuaVersion::V53, LuaVersion::V54, LuaVersion::V55] {
1677 let vdir = lua_vdir(v);
1678 let mut expected = Vec::new();
1679 expected.extend_from_slice(b"/usr/local/share/lua/");
1680 expected.extend_from_slice(vdir);
1681 expected.extend_from_slice(b"/?.lua;/usr/local/share/lua/");
1682 expected.extend_from_slice(vdir);
1683 expected.extend_from_slice(b"/?/init.lua;/usr/local/lib/lua/");
1684 expected.extend_from_slice(vdir);
1685 expected.extend_from_slice(b"/?.lua;/usr/local/lib/lua/");
1686 expected.extend_from_slice(vdir);
1687 expected.extend_from_slice(b"/?/init.lua;./?.lua;./?/init.lua");
1688 assert_eq!(unix_path_default(v), expected, "{v:?}");
1689 }
1690 }
1691
1692 #[test]
1693 fn cpath_default_is_version_exact() {
1694 assert_eq!(
1695 unix_cpath_default(LuaVersion::V51),
1696 b"./?.so;/usr/local/lib/lua/5.1/?.so;/usr/local/lib/lua/5.1/loadall.so".to_vec()
1697 );
1698 for v in [LuaVersion::V52, LuaVersion::V53, LuaVersion::V54, LuaVersion::V55] {
1699 let vdir = lua_vdir(v);
1700 let mut expected = Vec::new();
1701 expected.extend_from_slice(b"/usr/local/lib/lua/");
1702 expected.extend_from_slice(vdir);
1703 expected.extend_from_slice(b"/?.so;/usr/local/lib/lua/");
1704 expected.extend_from_slice(vdir);
1705 expected.extend_from_slice(b"/loadall.so;./?.so");
1706 assert_eq!(unix_cpath_default(v), expected, "{v:?}");
1707 }
1708 }
1709
1710 #[test]
1715 fn windows_path_default_is_version_exact() {
1716 assert_eq!(
1717 windows_path_default(LuaVersion::V51),
1718 b".\\?.lua;!\\lua\\?.lua;!\\lua\\?\\init.lua;!\\?.lua;!\\?\\init.lua".to_vec()
1719 );
1720 assert_eq!(
1721 windows_path_default(LuaVersion::V52),
1722 b"!\\lua\\?.lua;!\\lua\\?\\init.lua;!\\?.lua;!\\?\\init.lua;.\\?.lua".to_vec()
1723 );
1724 for v in [LuaVersion::V53, LuaVersion::V54, LuaVersion::V55] {
1725 let vdir = lua_vdir(v);
1726 let mut expected = Vec::new();
1727 expected.extend_from_slice(b"!\\lua\\?.lua;!\\lua\\?\\init.lua;");
1728 expected.extend_from_slice(b"!\\?.lua;!\\?\\init.lua;");
1729 expected.extend_from_slice(b"!\\..\\share\\lua\\");
1730 expected.extend_from_slice(vdir);
1731 expected.extend_from_slice(b"\\?.lua;!\\..\\share\\lua\\");
1732 expected.extend_from_slice(vdir);
1733 expected.extend_from_slice(b"\\?\\init.lua;.\\?.lua;.\\?\\init.lua");
1734 assert_eq!(windows_path_default(v), expected, "{v:?}");
1735 }
1736 }
1737
1738 #[test]
1741 fn windows_cpath_default_is_version_exact() {
1742 assert_eq!(
1743 windows_cpath_default(LuaVersion::V51),
1744 b".\\?.dll;!\\?.dll;!\\loadall.dll".to_vec()
1745 );
1746 assert_eq!(
1747 windows_cpath_default(LuaVersion::V52),
1748 b"!\\?.dll;!\\loadall.dll;.\\?.dll".to_vec()
1749 );
1750 for v in [LuaVersion::V53, LuaVersion::V54, LuaVersion::V55] {
1751 let vdir = lua_vdir(v);
1752 let mut expected = b"!\\?.dll;!\\..\\lib\\lua\\".to_vec();
1753 expected.extend_from_slice(vdir);
1754 expected.extend_from_slice(b"\\?.dll;!\\loadall.dll;.\\?.dll");
1755 assert_eq!(windows_cpath_default(v), expected, "{v:?}");
1756 }
1757 }
1758
1759 #[test]
1763 fn exec_dir_substitution_replaces_every_mark() {
1764 assert_eq!(
1765 gsub_bytes(
1766 &windows_cpath_default(LuaVersion::V54),
1767 b"!",
1768 b"C:\\Lua\\bin"
1769 ),
1770 b"C:\\Lua\\bin\\?.dll;C:\\Lua\\bin\\..\\lib\\lua\\5.4\\?.dll;\
1771 C:\\Lua\\bin\\loadall.dll;.\\?.dll"
1772 .to_vec()
1773 );
1774 assert_eq!(
1775 gsub_bytes(b".\\?.lua;.\\?\\init.lua", b"!", b"C:\\Lua\\bin"),
1776 b".\\?.lua;.\\?\\init.lua".to_vec()
1777 );
1778 }
1779
1780 #[test]
1785 fn every_version_has_a_distinct_path_default() {
1786 for (i, a) in ALL.iter().enumerate() {
1787 for b in &ALL[i + 1..] {
1788 assert_ne!(unix_path_default(*a), unix_path_default(*b), "{a:?} vs {b:?}");
1789 assert_ne!(
1790 unix_cpath_default(*a),
1791 unix_cpath_default(*b),
1792 "{a:?} vs {b:?}"
1793 );
1794 assert_ne!(
1795 windows_path_default(*a),
1796 windows_path_default(*b),
1797 "{a:?} vs {b:?}"
1798 );
1799 assert_ne!(
1800 windows_cpath_default(*a),
1801 windows_cpath_default(*b),
1802 "{a:?} vs {b:?}"
1803 );
1804 }
1805 }
1806 }
1807
1808 #[test]
1811 fn double_semicolon_splice_is_legacy_matrix() {
1812 for v in [LuaVersion::V51, LuaVersion::V52, LuaVersion::V53] {
1813 assert!(double_semicolon_splice_is_legacy(v), "{v:?}");
1814 }
1815 for v in [LuaVersion::V54, LuaVersion::V55] {
1816 assert!(!double_semicolon_splice_is_legacy(v), "{v:?}");
1817 }
1818 }
1819
1820 #[test]
1824 fn legacy_splice_replaces_every_occurrence_both_sides() {
1825 assert_eq!(
1826 legacy_double_semicolon_splice(b"/a/?.lua;;;;/b/?.lua", b"DEFAULT"),
1827 b"/a/?.lua;DEFAULT;;DEFAULT;/b/?.lua".to_vec()
1828 );
1829 assert_eq!(
1830 legacy_double_semicolon_splice(b";;/b/?.lua", b"DEFAULT"),
1831 b";DEFAULT;/b/?.lua".to_vec()
1832 );
1833 assert_eq!(
1834 legacy_double_semicolon_splice(b"/a/?.lua;;", b"DEFAULT"),
1835 b"/a/?.lua;DEFAULT;".to_vec()
1836 );
1837 assert_eq!(
1838 legacy_double_semicolon_splice(b"/a/?.lua", b"DEFAULT"),
1839 b"/a/?.lua".to_vec()
1840 );
1841 }
1842
1843 #[test]
1847 fn modern_splice_replaces_first_occurrence_and_omits_boundary_separators() {
1848 assert_eq!(
1849 modern_double_semicolon_splice(b"/a/?.lua;;;;/b/?.lua", b"DEFAULT"),
1850 b"/a/?.lua;DEFAULT;;;/b/?.lua".to_vec()
1851 );
1852 assert_eq!(
1853 modern_double_semicolon_splice(b";;/b/?.lua", b"DEFAULT"),
1854 b"DEFAULT;/b/?.lua".to_vec()
1855 );
1856 assert_eq!(
1857 modern_double_semicolon_splice(b"/a/?.lua;;", b"DEFAULT"),
1858 b"/a/?.lua;DEFAULT".to_vec()
1859 );
1860 assert_eq!(
1861 modern_double_semicolon_splice(b"/a/?.lua", b"DEFAULT"),
1862 b"/a/?.lua".to_vec()
1863 );
1864 }
1865}