luallaby 0.1.0

**Work in progress** A pure-Rust Lua interpreter/compiler
Documentation
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
            )),
        )?
        // Loaded defined in StdLib::new()
        .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()
}