use std::cell::RefCell;
use std::fs::OpenOptions;
use std::rc::Rc;
use super::StdLib;
use crate::error::Result;
use crate::vm::{FuncBuiltin, FuncDef, Table, Value, ValueType, VM};
use crate::{LuaError, LUA_VERSION};
const DIR_MARK: &str = ".";
const DIR_SEP: u8 = std::path::MAIN_SEPARATOR as u8;
const PATH_MARK: &str = "?";
const PATH_SEP: u8 = b';';
pub(super) fn module(stdlib: &mut StdLib) -> Result<()> {
stdlib.root().func("require", require)?;
stdlib
.module("package")
.cons(
"config",
Value::str_bytes(
format!("{}\n{}\n?\n!\n-\n", DIR_SEP as char, PATH_SEP as char).into_bytes(),
),
)?
.cons(
"cpath",
Value::string(format!(
"/usr/local/lib/lua/{}.{}/?.so;./?.so",
LUA_VERSION.0, LUA_VERSION.1
)),
)?
.func("loadlib", loadlib)?
.cons("path", Value::str("./?.lua;./?/init.lua"))?
.cons(
"preload",
Value::Table(Rc::new(RefCell::new(Table::default()))),
)?
.cons(
"searchers",
Value::Table(Rc::new(RefCell::new({
let mut searchers = Table::default();
searchers.set(
Value::int(1),
Value::Func(Rc::new(FuncDef::Builtin(FuncBuiltin {
module: "package",
name: "searcher_preload",
func: Rc::new(searcher_preload),
}))),
)?;
searchers.set(
Value::int(2),
Value::Func(Rc::new(FuncDef::Builtin(FuncBuiltin {
module: "package",
name: "searcher_lua",
func: Rc::new(searcher_lua),
}))),
)?;
searchers.set(
Value::int(3),
Value::Func(Rc::new(FuncDef::Builtin(FuncBuiltin {
module: "package",
name: "searcher_c",
func: Rc::new(searcher_c),
}))),
)?;
searchers
}))),
)?
.func("searchpath", searchpath)?;
Ok(())
}
impl VM {
pub(in crate::vm) fn module_load(&mut self, modname: Vec<u8>) -> Result<Value> {
let loaded = self
.registry
.borrow()
.get(&Value::str("_LOADED"))
.to_table()?;
let module = loaded.borrow().get(&Value::str_bytes(modname.clone()));
if module.is_truthy() {
Ok(module)
} else {
let package = self
.registry
.borrow()
.get(&Value::str("_PACKAGE"))
.to_table()?;
let searchers = match package.borrow().get(&Value::str("searchers")).to_table() {
Ok(tbl) => tbl,
Err(_) => {
return err!(LuaError::InvalidType("package.searchers", ValueType::Table))
}
};
let mut i = 0;
let mut error = Vec::new();
let loader = loop {
i += 1;
match searchers.borrow().get(&Value::int(i)) {
Value::Nil => break Value::Nil,
v => {
let res = self.call_recursive(
v.to_func()?,
vec![Value::str_bytes(modname.clone())],
)?;
match res.to_single() {
Value::Nil => {
let msg = res.into_vec().get(1).cloned().unwrap_or(Value::Nil);
error.extend_from_slice("\n\t".as_bytes());
error.extend_from_slice(&msg.to_string_coerce()?);
}
_ => break res,
}
}
}
};
let vec = loader.into_vec();
let func = match vec.get(0).cloned().unwrap_or(Value::Nil) {
Value::Nil => {
return err!(LuaError::CustomString(format!(
"module '{}' not found:{}",
String::from_utf8_lossy(&modname),
String::from_utf8_lossy(&error),
)))
}
val => val.to_func()?,
};
let data = vec.get(1).cloned().unwrap_or(Value::Nil);
let func = self
.call_recursive(func, vec![Value::str_bytes(modname.clone()), data.clone()])?
.into_single();
match &func {
Value::Nil => {
let mut loaded = loaded.borrow_mut();
if let Value::Nil = loaded.get(&Value::str_bytes(modname.clone())) {
loaded.set(Value::str_bytes(modname.clone()), Value::Bool(true))?;
}
}
val => {
loaded
.borrow_mut()
.set(Value::str_bytes(modname.clone()), val.clone())?;
}
}
Ok(Value::Mult(vec![
loaded.borrow().get(&Value::str_bytes(modname)),
data,
]))
}
}
}
pub(crate) fn require(vm: &mut VM) -> Result<Value> {
let modname = vm.arg_string_coerce(0)?;
vm.module_load(modname)
}
fn loadlib(_: &mut VM) -> Result<Value> {
Ok(Value::Mult(vec![
Value::Nil,
Value::str("package.loadlib() not supported"),
Value::str("absent"),
]))
}
fn searcher_preload(vm: &mut VM) -> Result<Value> {
let modname = vm.arg(0)?;
let preload = vm
.registry
.borrow()
.get(&Value::str("_PRELOAD"))
.to_table()?;
let preload = preload.borrow();
match preload.get(&Value::str_bytes(modname.to_string_coerce()?)) {
Value::Nil => Ok(Value::Mult(vec![
Value::Nil,
Value::string(format!(
"no field package.preload['{}']",
String::from_utf8_lossy(&modname.to_string_coerce()?)
)),
])),
val => Ok(Value::Mult(vec![val, Value::str(":preload:")])),
}
}
fn searcher_lua(vm: &mut VM) -> Result<Value> {
let modname = vm.arg(0)?;
let package = vm
.registry
.borrow()
.get(&Value::str("_PACKAGE"))
.to_table()?;
let path = package.borrow().get(&Value::str("path"));
if path.to_string_coerce().is_err() {
return err!(LuaError::InvalidType("package.path", ValueType::String));
}
vm.frames.last_mut().unwrap().varargs = vec![modname.clone(), path];
let path = searchpath(vm)?;
if let Value::Nil = path.to_single() {
Ok(path)
} else {
vm.frames.last_mut().unwrap().varargs = vec![path.clone()];
let mut loaded = super::basic::loadfile(vm)?.into_vec();
match loaded.remove(0) {
Value::Nil => err!(LuaError::CustomString(format!(
"error loading module '{}' from file '{}':\n\t{}",
String::from_utf8_lossy(&modname.to_string_coerce()?),
String::from_utf8_lossy(&path.to_string_coerce()?),
String::from_utf8_lossy(&loaded.remove(0).to_string_coerce()?),
))),
func => Ok(Value::Mult(vec![func, path])),
}
}
}
fn searcher_c(vm: &mut VM) -> Result<Value> {
let modname = vm.arg(0)?;
let package = vm
.registry
.borrow()
.get(&Value::str("_PACKAGE"))
.to_table()?;
let path = package.borrow().get(&Value::str("cpath"));
if path.to_string_coerce().is_err() {
return err!(LuaError::InvalidType("package.cpath", ValueType::String));
}
vm.frames.last_mut().unwrap().varargs = vec![modname, path];
let path = searchpath(vm)?;
if let Value::Nil = path.to_single() {
Ok(path)
} else {
Ok(Value::Mult(vec![
Value::Func(Rc::new(FuncDef::Builtin(FuncBuiltin {
module: "",
name: "load_c",
func: Rc::new(|_| Ok(Value::Nil)),
}))),
path,
]))
}
}
fn searchpath(vm: &mut VM) -> Result<Value> {
let name = vm.arg_string_coerce(0)?;
let path = vm.arg_string_coerce(1)?;
let sep = match vm.arg_opt(2) {
Some(val) => val.to_string_coerce()?,
None => DIR_MARK.into(),
};
let rep = match vm.arg_opt(3) {
Some(val) => val.to_string_coerce()?,
None => vec![DIR_SEP],
};
let name = replace(&name, &sep, &rep);
let path = replace(&path, PATH_MARK.as_bytes(), &name);
for path in path.split(|c| c == &PATH_SEP) {
let readable = OpenOptions::new()
.read(true)
.open(String::from_utf8_lossy(path).as_ref())
.is_ok();
if readable {
return Ok(Value::str_bytes(path.to_vec()));
}
}
let mut err = Vec::new();
for path in path.split(|c| c == &PATH_SEP) {
if !err.is_empty() {
err.extend_from_slice("\n\t".as_bytes());
}
err.extend_from_slice("no file '".as_bytes());
err.extend_from_slice(path);
err.push(b'\'');
}
Ok(Value::Mult(vec![Value::Nil, Value::str_bytes(err)]))
}
fn replace(str: &[u8], mark: &[u8], rep: &[u8]) -> Vec<u8> {
let mark = String::from_utf8_lossy(mark);
let rep = String::from_utf8_lossy(rep);
String::from_utf8_lossy(str)
.as_ref()
.replace(mark.as_ref(), rep.as_ref())
.into_bytes()
}