Skip to main content

luna_core/vm/
lib_package.rs

1//! package library: `require` and the searchers it runs, `package.path` /
2//! `cpath` / `config` / `loaded` / `preload`, `package.searchpath`,
3//! `package.loadlib`, and 5.1/5.2's `module` / `package.seeall`. Shaped per
4//! dialect after loadlib.c 5.1–5.5 (5.2 with LUA_COMPAT_MODULE and
5//! LUA_COMPAT_LOADERS, as its default build has them).
6//!
7//! luna links no dynamic loader: `package.loadlib` and the C searchers fail
8//! the way a PUC build without dynamic-library support does.
9
10use crate::runtime::{Gc, Table, UserdataPayload, Value};
11use crate::version::LuaVersion;
12use crate::vm::argcheck::{self, Args};
13use crate::vm::builtins::raise_str;
14use crate::vm::error::LuaError;
15use crate::vm::exec::Vm;
16use crate::vm::lib_io::{c_str, os_path};
17
18/// Default search paths. PUC compiles in its install prefix; luna has
19/// none, so only the current-directory templates remain.
20const LUA_PATH_DEFAULT: &[u8] = b"./?.lua;./?/init.lua";
21const LUA_CPATH_DEFAULT: &[u8] = b"";
22
23/// PUC's message and `loadlib` "where" when built without dynamic libraries.
24const DLMSG: &[u8] = b"dynamic libraries not enabled; check your Lua installation";
25
26pub(crate) fn open_package(vm: &mut Vm) {
27    let v = vm.version();
28    let pkg = vm.heap.new_table();
29    let loaded = registry_table(vm, "_LOADED").expect("stdlib registration");
30    for name in [
31        "_G",
32        "package",
33        "coroutine",
34        "table",
35        "io",
36        "os",
37        "string",
38        "bit32",
39        "math",
40        "utf8",
41        "debug",
42    ] {
43        let val = match name {
44            "package" => Value::Table(pkg),
45            _ => {
46                let k = Value::Str(vm.heap.intern(name.as_bytes()));
47                vm.globals().get(k)
48            }
49        };
50        if !val.is_nil() {
51            raw_set(vm, loaded, name, val);
52        }
53    }
54    raw_set(vm, pkg, "loaded", Value::Table(loaded));
55    // 5.1 keeps preload in the package table only; 5.2 moved it to the
56    // registry
57    let preload = if v == LuaVersion::Lua51 {
58        vm.heap.new_table()
59    } else {
60        registry_table(vm, "_PRELOAD").expect("stdlib registration")
61    };
62    raw_set(vm, pkg, "preload", Value::Table(preload));
63
64    let searchers = vm.heap.new_table();
65    let lf = vm.native(crate::vm::lib_os_io::nat_loadfile);
66    let lua_searcher = vm.native_with(searcher_lua, Box::new([Value::Table(pkg), lf]));
67    let fns = [
68        vm.native_with(searcher_preload, Box::new([Value::Table(pkg)])),
69        lua_searcher,
70        vm.native_with(searcher_c, Box::new([Value::Table(pkg)])),
71        vm.native_with(searcher_croot, Box::new([Value::Table(pkg)])),
72    ];
73    for (i, f) in fns.into_iter().enumerate() {
74        // SAFETY: Gc<T> is NonNull<T> over the GC heap; the heap is single-threaded and the pointer is live as long as it is reachable from active roots (see heap.rs:5-7).
75        unsafe { searchers.as_mut() }
76            .set(&mut vm.heap, Value::Int(i as i64 + 1), f)
77            .expect("valid key");
78    }
79    vm.barrier_back_table(searchers);
80    match v {
81        LuaVersion::Lua51 => raw_set(vm, pkg, "loaders", Value::Table(searchers)),
82        LuaVersion::Lua52 => {
83            raw_set(vm, pkg, "searchers", Value::Table(searchers));
84            raw_set(vm, pkg, "loaders", Value::Table(searchers));
85        }
86        _ => raw_set(vm, pkg, "searchers", Value::Table(searchers)),
87    }
88
89    // 5.2 on: `-E` (the registry's LUA_NOENV) keeps the defaults
90    let noenv = vm.ignore_env && v >= LuaVersion::Lua52;
91    let path = env_path(v, noenv, "LUA_PATH", LUA_PATH_DEFAULT);
92    let path = Value::Str(vm.heap.intern(&path));
93    raw_set(vm, pkg, "path", path);
94    let cpath = env_path(v, noenv, "LUA_CPATH", LUA_CPATH_DEFAULT);
95    let cpath = Value::Str(vm.heap.intern(&cpath));
96    raw_set(vm, pkg, "cpath", cpath);
97    // dir separator, path separator, template mark, executable-dir mark,
98    // ignore mark; 5.2 added the final newline
99    let config: &[u8] = if v == LuaVersion::Lua51 {
100        b"/\n;\n?\n!\n-"
101    } else {
102        b"/\n;\n?\n!\n-\n"
103    };
104    let config = Value::Str(vm.heap.intern(config));
105    raw_set(vm, pkg, "config", config);
106
107    let f = vm.native(ll_loadlib);
108    raw_set(vm, pkg, "loadlib", f);
109    if v >= LuaVersion::Lua52 {
110        let f = vm.native(ll_searchpath);
111        raw_set(vm, pkg, "searchpath", f);
112    }
113    // 5.1 marks a module being loaded with a sentinel, to catch loops
114    let sentinel = Value::Userdata(vm.heap.new_userdata(UserdataPayload::Empty, false));
115    let req = vm.native_with(
116        ll_require,
117        Box::new([Value::Table(pkg), Value::Table(loaded), sentinel]),
118    );
119    vm.set_global("require", req).expect("stdlib registration");
120    if v <= LuaVersion::Lua52 {
121        let f = vm.native(ll_seeall);
122        raw_set(vm, pkg, "seeall", f);
123        let m = vm.native_with(ll_module, Box::new([Value::Table(loaded)]));
124        vm.set_global("module", m).expect("stdlib registration");
125    }
126    vm.set_global("package", Value::Table(pkg))
127        .expect("stdlib registration");
128    vm.barrier_back_table(pkg);
129    vm.barrier_back_table(loaded);
130}
131
132impl Vm {
133    /// lua.c's `-E`: the package library opened after this takes its
134    /// default `path` and `cpath` instead of reading `LUA_PATH` /
135    /// `LUA_CPATH` (5.2 on; 5.1 has no such switch), and a registry the
136    /// debug library makes after it holds `LUA_NOENV = true`, as lua.c sets
137    /// it for the libraries.
138    pub fn set_ignore_env(&mut self, ignore: bool) {
139        self.ignore_env = ignore;
140    }
141}
142
143fn raw_set(vm: &mut Vm, t: Gc<Table>, k: &str, v: Value) {
144    let k = Value::Str(vm.heap.intern(k.as_bytes()));
145    // SAFETY: Gc<T> is NonNull<T> over the GC heap; the heap is single-threaded and the pointer is live as long as it is reachable from active roots (see heap.rs:5-7).
146    unsafe { t.as_mut() }
147        .set(&mut vm.heap, k, v)
148        .expect("valid key");
149}
150
151/// A raw store into a table a script can reach and fill: a full hash
152/// part raises "table overflow" as any other store does.
153fn raw_set_checked(vm: &mut Vm, t: Gc<Table>, k: Value, v: Value) -> Result<(), LuaError> {
154    // SAFETY: Gc<T> is NonNull<T> over the GC heap; the heap is single-threaded and the pointer is live as long as it is reachable from active roots (see heap.rs:5-7).
155    if unsafe { t.as_mut() }.set(&mut vm.heap, k, v).is_err() {
156        return Err(vm.rt_err("table overflow"));
157    }
158    vm.barrier_back_table(t);
159    Ok(())
160}
161
162/// `luaL_getsubtable(L, LUA_REGISTRYINDEX, name)`: the registry's table
163/// `name`, created on first use. Without a registry (the debug library
164/// makes it) the table is simply not reachable from there.
165fn registry_table(vm: &mut Vm, name: &str) -> Result<Gc<Table>, LuaError> {
166    let Some(reg) = vm.registry else {
167        return Ok(vm.heap.new_table());
168    };
169    let k = Value::Str(vm.heap.intern(name.as_bytes()));
170    if let Value::Table(t) = reg.get(k) {
171        return Ok(t);
172    }
173    let t = vm.heap.new_table();
174    raw_set_checked(vm, reg, k, Value::Table(t))?;
175    Ok(t)
176}
177
178/// `setpath`: unless `noenv`, the environment's path (5.2+ try `NAME_5_x`
179/// first) with ";;" replaced by the default, else the default. 5.1–5.3
180/// replace every ";;"; 5.4 replaces the first and drops a separator left
181/// dangling.
182fn env_path(v: LuaVersion, noenv: bool, var: &str, dft: &[u8]) -> Vec<u8> {
183    if noenv {
184        return dft.to_vec();
185    }
186    let suffix = match v {
187        LuaVersion::Lua52 => "_5_2",
188        LuaVersion::Lua53 => "_5_3",
189        LuaVersion::Lua54 => "_5_4",
190        LuaVersion::Lua55 => "_5_5",
191        _ => "",
192    };
193    let versioned = format!("{var}{suffix}");
194    let found = if suffix.is_empty() {
195        std::env::var_os(var)
196    } else {
197        std::env::var_os(&versioned).or_else(|| std::env::var_os(var))
198    };
199    let Some(path) = found else {
200        return dft.to_vec();
201    };
202    let path = os_bytes(&path);
203    if v <= LuaVersion::Lua53 {
204        // ";;" -> ";\1;" -> the default in place of "\1"
205        let marked = replace(&path, b";;", b";\x01;");
206        return replace(&marked, b"\x01", dft);
207    }
208    let Some(at) = path.windows(2).position(|w| w == b";;") else {
209        return path;
210    };
211    let mut out = Vec::new();
212    if at > 0 {
213        out.extend_from_slice(&path[..at]);
214        out.push(b';');
215    }
216    out.extend_from_slice(dft);
217    if at + 2 < path.len() {
218        out.push(b';');
219        out.extend_from_slice(&path[at + 2..]);
220    }
221    out
222}
223
224fn os_bytes(s: &std::ffi::OsStr) -> Vec<u8> {
225    #[cfg(unix)]
226    {
227        use std::os::unix::ffi::OsStrExt;
228        s.as_bytes().to_vec()
229    }
230    #[cfg(not(unix))]
231    {
232        s.to_string_lossy().into_owned().into_bytes()
233    }
234}
235
236/// `luaL_gsub`: replace every `from` in `src` with `to`.
237fn replace(src: &[u8], from: &[u8], to: &[u8]) -> Vec<u8> {
238    let mut out = Vec::with_capacity(src.len());
239    let mut i = 0;
240    while i < src.len() {
241        if src[i..].starts_with(from) {
242            out.extend_from_slice(to);
243            i += from.len();
244        } else {
245            out.push(src[i]);
246            i += 1;
247        }
248    }
249    out
250}
251
252// ---- searching the paths ----
253
254/// `readable`: whether `fopen(name, "r")` succeeds.
255fn readable(name: &[u8]) -> bool {
256    std::fs::File::open(os_path(name)).is_ok()
257}
258
259/// `searchpath`: the first readable file among `path`'s templates with
260/// `name` (its `sep`s turned into `dirsep`) in place of each '?', or the
261/// "no file" message listing every candidate. ≤5.3 skip empty templates
262/// and start each entry with "\n\t"; 5.4 keeps empty ones and joins them.
263fn search_path(
264    v: LuaVersion,
265    name: &[u8],
266    path: &[u8],
267    sep: &[u8],
268    dirsep: &[u8],
269) -> Result<Vec<u8>, Vec<u8>> {
270    let name = if sep.is_empty() {
271        name.to_vec()
272    } else {
273        replace(name, sep, dirsep)
274    };
275    let mut err = Vec::new();
276    if v <= LuaVersion::Lua53 {
277        for tpl in path.split(|&b| b == b';').filter(|t| !t.is_empty()) {
278            let file = replace(tpl, b"?", &name);
279            if readable(&file) {
280                return Ok(file);
281            }
282            err.extend_from_slice(b"\n\tno file '");
283            err.extend_from_slice(&file);
284            err.push(b'\'');
285        }
286        return Err(err);
287    }
288    let expanded = replace(path, b"?", &name);
289    for file in expanded.split(|&b| b == b';') {
290        if readable(file) {
291            return Ok(file.to_vec());
292        }
293    }
294    err.extend_from_slice(b"no file '");
295    err.extend_from_slice(&replace(&expanded, b";", b"'\n\tno file '"));
296    err.push(b'\'');
297    Err(err)
298}
299
300fn ll_searchpath(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
301    let a = Args::new(fs, nargs);
302    let name = argcheck::check_string(vm, a, 0)?.as_bytes().to_vec();
303    let path = argcheck::check_string(vm, a, 1)?.as_bytes().to_vec();
304    let sep = match argcheck::opt_string(vm, a, 2)? {
305        Some(s) => s.as_bytes().to_vec(),
306        None => b".".to_vec(),
307    };
308    let dirsep = match argcheck::opt_string(vm, a, 3)? {
309        Some(s) => s.as_bytes().to_vec(),
310        None => b"/".to_vec(),
311    };
312    let r = search_path(
313        vm.version(),
314        c_str(&name),
315        c_str(&path),
316        c_str(&sep),
317        c_str(&dirsep),
318    );
319    Ok(match r {
320        Ok(file) => {
321            let f = Value::Str(vm.heap.intern(&file));
322            vm.nat_return(fs, &[f])
323        }
324        Err(msg) => {
325            let m = Value::Str(vm.heap.intern(&msg));
326            vm.nat_return(fs, &[Value::Nil, m])
327        }
328    })
329}
330
331/// `findfile`: search `package[pname]` (which must be a string) for `name`.
332fn find_file(
333    vm: &mut Vm,
334    pkg: Gc<Table>,
335    name: &[u8],
336    pname: &str,
337) -> Result<Result<Vec<u8>, Vec<u8>>, LuaError> {
338    let k = Value::Str(vm.heap.intern(pname.as_bytes()));
339    let path = vm.index_value(Value::Table(pkg), k)?;
340    let Some(path) = argcheck::to_str_bytes(vm, path) else {
341        return Err(raise_str(
342            vm,
343            &format!("'package.{pname}' must be a string"),
344        ));
345    };
346    // 5.1 turns every '.' of the name into the directory separator itself
347    Ok(search_path(
348        vm.version(),
349        c_str(name),
350        c_str(&path),
351        b".",
352        b"/",
353    ))
354}
355
356/// `loaderror` / `checkload` for a module file that failed to load.
357fn load_error(vm: &mut Vm, name: &[u8], file: &[u8], msg: &[u8]) -> LuaError {
358    let text = format!(
359        "error loading module '{}' from file '{}':\n\t{}",
360        String::from_utf8_lossy(c_str(name)),
361        String::from_utf8_lossy(file),
362        String::from_utf8_lossy(msg)
363    );
364    raise_str(vm, &text)
365}
366
367fn upval_table(vm: &Vm, fs: u32, i: usize) -> Gc<Table> {
368    match vm.nat_upval(fs, i) {
369        Value::Table(t) => t,
370        _ => unreachable!("package natives keep tables in their upvalues"),
371    }
372}
373
374/// Whether `a` is the sentinel userdata `b`.
375fn same(a: Value, b: Value) -> bool {
376    matches!((a, b), (Value::Userdata(x), Value::Userdata(y)) if x.ptr_eq(y))
377}
378
379fn str_value(vm: &mut Vm, b: &[u8]) -> Value {
380    Value::Str(vm.heap.intern(b))
381}
382
383// ---- the searchers ----
384
385fn searcher_preload(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
386    let v = vm.version();
387    let name = argcheck::check_string(vm, Args::new(fs, nargs), 0)?;
388    let preload = if v == LuaVersion::Lua51 {
389        let pkg = upval_table(vm, fs, 0);
390        let k = Value::Str(vm.heap.intern(b"preload"));
391        match vm.index_value(Value::Table(pkg), k)? {
392            Value::Table(t) => t,
393            _ => return Err(raise_str(vm, "'package.preload' must be a table")),
394        }
395    } else {
396        registry_table(vm, "_PRELOAD")?
397    };
398    let loader = vm.index_value(Value::Table(preload), Value::Str(name))?;
399    if loader.is_nil() {
400        let lead = if v >= LuaVersion::Lua54 { "" } else { "\n\t" };
401        let msg = format!(
402            "{lead}no field package.preload['{}']",
403            String::from_utf8_lossy(c_str(name.as_bytes()))
404        );
405        let m = str_value(vm, msg.as_bytes());
406        return Ok(vm.nat_return(fs, &[m]));
407    }
408    if v >= LuaVersion::Lua54 {
409        let data = str_value(vm, b":preload:");
410        return Ok(vm.nat_return(fs, &[loader, data]));
411    }
412    Ok(vm.nat_return(fs, &[loader]))
413}
414
415fn searcher_lua(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
416    let name = argcheck::check_string(vm, Args::new(fs, nargs), 0)?
417        .as_bytes()
418        .to_vec();
419    let pkg = upval_table(vm, fs, 0);
420    let file = match find_file(vm, pkg, &name, "path")? {
421        Ok(f) => f,
422        Err(msg) => {
423            let m = str_value(vm, &msg);
424            return Ok(vm.nat_return(fs, &[m]));
425        }
426    };
427    let loadfile = vm.nat_upval(fs, 1);
428    let fname = str_value(vm, &file);
429    let r = vm.call_value(loadfile, &[fname])?;
430    match r.first() {
431        Some(f @ Value::Closure(_)) => {
432            let f = *f;
433            // 5.1's loader gets only the name; 5.2+ also the file name
434            if vm.version() == LuaVersion::Lua51 {
435                return Ok(vm.nat_return(fs, &[f]));
436            }
437            Ok(vm.nat_return(fs, &[f, fname]))
438        }
439        _ => {
440            let msg = match r.get(1) {
441                Some(&m) => argcheck::to_str_bytes(vm, m).unwrap_or_default(),
442                None => Vec::new(),
443            };
444            Err(load_error(vm, &name, &file, &msg))
445        }
446    }
447}
448
449/// The C searchers: a file found on `cpath` cannot be opened without a
450/// dynamic loader, which is a load error.
451fn searcher_c(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
452    let name = argcheck::check_string(vm, Args::new(fs, nargs), 0)?
453        .as_bytes()
454        .to_vec();
455    let pkg = upval_table(vm, fs, 0);
456    match find_file(vm, pkg, &name, "cpath")? {
457        Ok(file) => Err(load_error(vm, &name, &file, DLMSG)),
458        Err(msg) => {
459            let m = str_value(vm, &msg);
460            Ok(vm.nat_return(fs, &[m]))
461        }
462    }
463}
464
465/// The all-in-one C searcher: look for the root of a dotted name on
466/// `cpath`; a name without a dot is not its business.
467fn searcher_croot(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
468    let name = argcheck::check_string(vm, Args::new(fs, nargs), 0)?
469        .as_bytes()
470        .to_vec();
471    let Some(dot) = c_str(&name).iter().position(|&b| b == b'.') else {
472        return Ok(vm.nat_return(fs, &[]));
473    };
474    let pkg = upval_table(vm, fs, 0);
475    match find_file(vm, pkg, &name[..dot], "cpath")? {
476        Ok(file) => Err(load_error(vm, &name, &file, DLMSG)),
477        Err(msg) => {
478            let m = str_value(vm, &msg);
479            Ok(vm.nat_return(fs, &[m]))
480        }
481    }
482}
483
484fn ll_loadlib(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
485    let a = Args::new(fs, nargs);
486    argcheck::check_string(vm, a, 0)?;
487    argcheck::check_string(vm, a, 1)?;
488    let msg = str_value(vm, DLMSG);
489    let place = str_value(vm, b"absent");
490    Ok(vm.nat_return(fs, &[Value::Nil, msg, place]))
491}
492
493// ---- require ----
494
495/// `findloader`: ask each searcher in turn; the first one to return a
496/// function supplies the loader (and its extra value). The misses are
497/// collected into the "not found" message.
498fn find_loader(vm: &mut Vm, pkg: Gc<Table>, name: Value) -> Result<(Value, Value), LuaError> {
499    let v = vm.version();
500    let field = if v == LuaVersion::Lua51 {
501        "loaders"
502    } else {
503        "searchers"
504    };
505    let k = Value::Str(vm.heap.intern(field.as_bytes()));
506    let Value::Table(searchers) = vm.index_value(Value::Table(pkg), k)? else {
507        return Err(raise_str(vm, &format!("'package.{field}' must be a table")));
508    };
509    let mut msg = Vec::new();
510    for i in 1.. {
511        let s = searchers.get(Value::Int(i));
512        if s.is_nil() {
513            break;
514        }
515        let r = vm.call_value(s, &[name])?;
516        let first = r.first().copied().unwrap_or(Value::Nil);
517        let second = r.get(1).copied().unwrap_or(Value::Nil);
518        if matches!(first, Value::Closure(_) | Value::Native(_)) {
519            return Ok((first, second));
520        }
521        if let Some(text) = argcheck::to_str_bytes(vm, first) {
522            // 5.4 puts the separator between messages itself
523            if v >= LuaVersion::Lua54 {
524                msg.extend_from_slice(b"\n\t");
525            }
526            msg.extend_from_slice(&text);
527        }
528    }
529    let Value::Str(n) = name else {
530        unreachable!("require passes the name as a string");
531    };
532    let text = format!(
533        "module '{}' not found:{}",
534        String::from_utf8_lossy(c_str(n.as_bytes())),
535        String::from_utf8_lossy(&msg)
536    );
537    Err(raise_str(vm, &text))
538}
539
540fn ll_require(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
541    let v = vm.version();
542    let name_s = argcheck::check_string(vm, Args::new(fs, nargs), 0)?;
543    let name = Value::Str(name_s);
544    let pkg = upval_table(vm, fs, 0);
545    let loaded = upval_table(vm, fs, 1);
546    let sentinel = vm.nat_upval(fs, 2);
547    let cur = vm.index_value(Value::Table(loaded), name)?;
548    if cur.truthy() {
549        if v == LuaVersion::Lua51 && same(cur, sentinel) {
550            let text = format!(
551                "loop or previous error loading module '{}'",
552                String::from_utf8_lossy(c_str(name_s.as_bytes()))
553            );
554            return Err(raise_str(vm, &text));
555        }
556        return Ok(vm.nat_return(fs, &[cur]));
557    }
558    let (loader, data) = find_loader(vm, pkg, name)?;
559    if v == LuaVersion::Lua51 {
560        vm.newindex_value(Value::Table(loaded), name, sentinel)?;
561    }
562    // 5.1 hands the loader only the name
563    let args: &[Value] = if v == LuaVersion::Lua51 {
564        &[name]
565    } else {
566        &[name, data]
567    };
568    let r = vm.call_value(loader, args)?;
569    let res = r.first().copied().unwrap_or(Value::Nil);
570    if !res.is_nil() {
571        vm.newindex_value(Value::Table(loaded), name, res)?;
572    }
573    let mut value = vm.index_value(Value::Table(loaded), name)?;
574    let unset = if v == LuaVersion::Lua51 {
575        same(value, sentinel)
576    } else {
577        value.is_nil()
578    };
579    if unset {
580        value = Value::Bool(true);
581        vm.newindex_value(Value::Table(loaded), name, value)?;
582    }
583    // 5.4 also returns the loader data
584    if v >= LuaVersion::Lua54 {
585        return Ok(vm.nat_return(fs, &[value, data]));
586    }
587    Ok(vm.nat_return(fs, &[value]))
588}
589
590// ---- module / seeall (5.1, 5.2) ----
591
592/// `luaL_findtable` on the globals: walk (raw) the dotted `name`, creating
593/// missing tables; `None` when a part is a non-table value.
594fn find_table(vm: &mut Vm, name: &[u8]) -> Result<Option<Gc<Table>>, LuaError> {
595    let mut t = vm.globals();
596    for part in name.split(|&b| b == b'.') {
597        let k = Value::Str(vm.heap.intern(part));
598        t = match t.get(k) {
599            Value::Table(next) => next,
600            Value::Nil => {
601                let next = vm.heap.new_table();
602                raw_set_checked(vm, t, k, Value::Table(next))?;
603                next
604            }
605            _ => return Ok(None),
606        };
607    }
608    Ok(Some(t))
609}
610
611fn ll_module(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
612    let v = vm.version();
613    let a = Args::new(fs, nargs);
614    let name_s = argcheck::check_string(vm, a, 0)?;
615    let name = c_str(name_s.as_bytes()).to_vec();
616    let loaded = upval_table(vm, fs, 0);
617    let module = match loaded.get(Value::Str(name_s)) {
618        Value::Table(t) => t,
619        _ => {
620            let Some(t) = find_table(vm, &name)? else {
621                let text = format!(
622                    "name conflict for module '{}'",
623                    String::from_utf8_lossy(&name)
624                );
625                return Err(raise_str(vm, &text));
626            };
627            raw_set_checked(vm, loaded, Value::Str(name_s), Value::Table(t))?;
628            t
629        }
630    };
631    let mv = Value::Table(module);
632    let nk = str_value(vm, b"_NAME");
633    if vm.index_value(mv, nk)?.is_nil() {
634        // modinit: _M, _NAME, and _PACKAGE (the name up to its last dot)
635        let mk = str_value(vm, b"_M");
636        vm.newindex_value(mv, mk, mv)?;
637        let nv = str_value(vm, &name);
638        vm.newindex_value(mv, nk, nv)?;
639        let cut = name.iter().rposition(|&b| b == b'.').map_or(0, |i| i + 1);
640        let pk = str_value(vm, b"_PACKAGE");
641        let pv = str_value(vm, &name[..cut]);
642        vm.newindex_value(mv, pk, pv)?;
643    }
644    set_caller_env(vm, mv)?;
645    // options: 5.1 calls every extra argument, 5.2 only the functions
646    for i in 1..nargs {
647        let opt = a.get(vm, i);
648        if v == LuaVersion::Lua52 && !matches!(opt, Value::Closure(_) | Value::Native(_)) {
649            continue;
650        }
651        vm.call_value(opt, &[mv])?;
652    }
653    if v == LuaVersion::Lua51 {
654        return Ok(vm.nat_return(fs, &[]));
655    }
656    Ok(vm.nat_return(fs, &[mv]))
657}
658
659/// Make `env` the environment of the Lua function that called `module`:
660/// 5.1's `setfenv` rewrites its (per-closure) `_ENV` cell, 5.2's
661/// `lua_setupvalue(f, 1)` its first upvalue.
662fn set_caller_env(vm: &mut Vm, env: Value) -> Result<(), LuaError> {
663    let Some(cl) = lua_caller(vm) else {
664        return Err(raise_str(vm, "'module' not called from a Lua function"));
665    };
666    let idx = if vm.version() == LuaVersion::Lua51 {
667        cl.proto.upvals.iter().position(|d| &*d.name == "_ENV")
668    } else {
669        (!cl.upvals().is_empty()).then_some(0)
670    };
671    if let Some(i) = idx {
672        vm.upvalue_set_value(cl, i, env);
673    }
674    Ok(())
675}
676
677/// The Lua function that called the running native (PUC: level 1 of the
678/// stack is a Lua activation), if it was one.
679fn lua_caller(vm: &Vm) -> Option<Gc<crate::runtime::LuaClosure>> {
680    vm.lua_closure_at_level(1)
681}
682
683fn ll_seeall(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
684    let t = argcheck::check_table(vm, Args::new(fs, nargs), 0)?;
685    let mt = match t.metatable() {
686        Some(mt) => mt,
687        None => {
688            let mt = vm.heap.new_table();
689            // SAFETY: Gc<T> is NonNull<T> over the GC heap; the heap is single-threaded and the pointer is live as long as it is reachable from active roots (see heap.rs:5-7).
690            unsafe { t.as_mut() }.set_metatable(Some(mt));
691            mt
692        }
693    };
694    let g = Value::Table(vm.globals());
695    let k = Value::Str(vm.heap.intern(b"__index"));
696    raw_set_checked(vm, mt, k, g)?;
697    Ok(vm.nat_return(fs, &[]))
698}