2023-01-24 20:27:38 +00:00
|
|
|
use std::{
|
2023-03-20 12:52:18 +00:00
|
|
|
cell::RefCell,
|
|
|
|
collections::HashMap,
|
|
|
|
env::current_dir,
|
2023-02-22 22:21:37 +00:00
|
|
|
path::{self, PathBuf},
|
2023-01-24 20:27:38 +00:00
|
|
|
};
|
2023-01-24 07:05:54 +00:00
|
|
|
|
2023-02-22 22:21:37 +00:00
|
|
|
use dunce::canonicalize;
|
2023-01-24 07:05:54 +00:00
|
|
|
use mlua::prelude::*;
|
2023-03-20 12:52:18 +00:00
|
|
|
use tokio::{fs, sync::oneshot};
|
2023-01-24 07:05:54 +00:00
|
|
|
|
2023-03-20 12:52:18 +00:00
|
|
|
use crate::lua::{
|
|
|
|
table::TableBuilder,
|
|
|
|
task::{TaskScheduler, TaskSchedulerScheduleExt},
|
|
|
|
};
|
2023-02-10 11:14:28 +00:00
|
|
|
|
2023-02-20 12:02:22 +00:00
|
|
|
const REQUIRE_IMPL_LUA: &str = r#"
|
|
|
|
local source = info(1, "s")
|
|
|
|
if source == '[string "require"]' then
|
|
|
|
source = info(2, "s")
|
|
|
|
end
|
2023-03-20 12:52:18 +00:00
|
|
|
local absolute, relative = importer:paths(source, ...)
|
|
|
|
return importer:load(thread(), absolute, relative)
|
2023-02-20 12:02:22 +00:00
|
|
|
"#;
|
|
|
|
|
2023-03-20 12:52:18 +00:00
|
|
|
#[derive(Debug, Clone, Default)]
|
|
|
|
struct Importer<'lua> {
|
|
|
|
builtins: HashMap<String, LuaMultiValue<'lua>>,
|
|
|
|
cached: RefCell<HashMap<String, LuaResult<LuaMultiValue<'lua>>>>,
|
|
|
|
pwd: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'lua> Importer<'lua> {
|
|
|
|
pub fn new() -> Self {
|
|
|
|
let mut pwd = current_dir()
|
|
|
|
.expect("Failed to access current working directory")
|
|
|
|
.to_string_lossy()
|
|
|
|
.to_string();
|
|
|
|
if !pwd.ends_with(path::MAIN_SEPARATOR) {
|
|
|
|
pwd = format!("{pwd}{}", path::MAIN_SEPARATOR)
|
|
|
|
}
|
|
|
|
Self {
|
|
|
|
pwd,
|
|
|
|
..Default::default()
|
|
|
|
}
|
2023-02-17 18:20:17 +00:00
|
|
|
}
|
2023-03-20 12:52:18 +00:00
|
|
|
|
|
|
|
fn paths(&self, require_source: String, require_path: String) -> LuaResult<(String, String)> {
|
|
|
|
if require_path.starts_with('@') {
|
|
|
|
return Ok((require_path.clone(), require_path));
|
|
|
|
}
|
|
|
|
let path_relative_to_pwd = PathBuf::from(
|
|
|
|
&require_source
|
|
|
|
.trim_start_matches("[string \"")
|
|
|
|
.trim_end_matches("\"]"),
|
|
|
|
)
|
|
|
|
.parent()
|
|
|
|
.unwrap()
|
|
|
|
.join(&require_path);
|
|
|
|
// Try to normalize and resolve relative path segments such as './' and '../'
|
|
|
|
let file_path = match (
|
|
|
|
canonicalize(path_relative_to_pwd.with_extension("luau")),
|
|
|
|
canonicalize(path_relative_to_pwd.with_extension("lua")),
|
|
|
|
) {
|
|
|
|
(Ok(luau), _) => luau,
|
|
|
|
(_, Ok(lua)) => lua,
|
|
|
|
_ => {
|
|
|
|
return Err(LuaError::RuntimeError(format!(
|
|
|
|
"File does not exist at path '{require_path}'"
|
|
|
|
)))
|
2023-03-20 08:43:01 +00:00
|
|
|
}
|
2023-03-20 12:52:18 +00:00
|
|
|
};
|
|
|
|
let absolute = file_path.to_string_lossy().to_string();
|
|
|
|
let relative = absolute.trim_start_matches(&self.pwd).to_string();
|
|
|
|
Ok((absolute, relative))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn load_builtin(&self, module_name: &str) -> LuaResult<LuaMultiValue> {
|
|
|
|
match self.builtins.get(module_name) {
|
|
|
|
Some(module) => Ok(module.clone()),
|
|
|
|
None => Err(LuaError::RuntimeError(format!(
|
|
|
|
"No builtin module exists with the name '{}'",
|
|
|
|
module_name
|
|
|
|
))),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn load_file(
|
|
|
|
&self,
|
|
|
|
lua: &'lua Lua,
|
|
|
|
absolute_path: String,
|
|
|
|
relative_path: String,
|
|
|
|
) -> LuaResult<LuaMultiValue> {
|
|
|
|
let cached = { self.cached.borrow().get(&absolute_path).cloned() };
|
|
|
|
match cached {
|
|
|
|
Some(cached) => cached,
|
|
|
|
None => {
|
|
|
|
// Try to read the wanted file, note that we use bytes instead of reading
|
|
|
|
// to a string since lua scripts are not necessarily valid utf-8 strings
|
|
|
|
let contents = fs::read(&absolute_path).await.map_err(LuaError::external)?;
|
|
|
|
// Use a name without extensions for loading the chunk, some
|
|
|
|
// other code assumes the require path is without extensions
|
|
|
|
let path_relative_no_extension = relative_path
|
|
|
|
.trim_end_matches(".lua")
|
|
|
|
.trim_end_matches(".luau");
|
|
|
|
// Load the file into a thread
|
|
|
|
let loaded_func = lua
|
2023-02-17 18:20:17 +00:00
|
|
|
.load(&contents)
|
|
|
|
.set_name(path_relative_no_extension)?
|
2023-03-20 12:52:18 +00:00
|
|
|
.into_function()?;
|
|
|
|
let loaded_thread = lua.create_thread(loaded_func)?;
|
|
|
|
// Run the thread and provide a channel that will
|
|
|
|
// then get its result received when it finishes
|
|
|
|
let (tx, rx) = oneshot::channel();
|
|
|
|
{
|
|
|
|
let sched = lua.app_data_ref::<&TaskScheduler>().unwrap();
|
|
|
|
let task = sched.schedule_blocking(loaded_thread, LuaMultiValue::new())?;
|
|
|
|
sched.set_task_result_sender(task, tx);
|
|
|
|
}
|
|
|
|
// Wait for the thread to finish running, cache + return our result
|
|
|
|
let rets = rx.await.expect("Sender was dropped during require");
|
|
|
|
self.cached.borrow_mut().insert(absolute_path, rets.clone());
|
|
|
|
rets
|
2023-02-17 18:20:17 +00:00
|
|
|
}
|
2023-03-20 12:52:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn load(
|
|
|
|
&self,
|
|
|
|
lua: &'lua Lua,
|
|
|
|
absolute_path: String,
|
|
|
|
relative_path: String,
|
|
|
|
) -> LuaResult<LuaMultiValue> {
|
|
|
|
if absolute_path == relative_path && absolute_path.starts_with('@') {
|
|
|
|
if let Some(module_name) = absolute_path.strip_prefix("@lune/") {
|
|
|
|
self.load_builtin(module_name)
|
|
|
|
} else {
|
|
|
|
Err(LuaError::RuntimeError(
|
|
|
|
"Require paths prefixed by '@' are not yet supported".to_string(),
|
|
|
|
))
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
self.load_file(lua, absolute_path, relative_path).await
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'i> LuaUserData for Importer<'i> {
|
|
|
|
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
|
|
|
methods.add_method(
|
|
|
|
"paths",
|
|
|
|
|_, this, (require_source, require_path): (String, String)| {
|
|
|
|
this.paths(require_source, require_path)
|
|
|
|
},
|
|
|
|
);
|
|
|
|
methods.add_method(
|
|
|
|
"load",
|
|
|
|
|lua, this, (thread, absolute_path, relative_path): (LuaThread, String, String)| {
|
|
|
|
// TODO: Make this work
|
|
|
|
// this.load(lua, absolute_path, relative_path)
|
|
|
|
Ok(())
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2023-01-24 20:27:38 +00:00
|
|
|
|
2023-03-20 12:52:18 +00:00
|
|
|
pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> {
|
|
|
|
let require_importer = Importer::new();
|
|
|
|
let require_thread: LuaFunction = lua.named_registry_value("co.thread")?;
|
|
|
|
let require_info: LuaFunction = lua.named_registry_value("dbg.info")?;
|
2023-02-17 14:03:13 +00:00
|
|
|
let require_env = TableBuilder::new(lua)?
|
2023-03-20 12:52:18 +00:00
|
|
|
.with_value("importer", require_importer)?
|
|
|
|
.with_value("thread", require_thread)?
|
2023-02-17 14:03:13 +00:00
|
|
|
.with_value("info", require_info)?
|
|
|
|
.build_readonly()?;
|
2023-03-20 12:52:18 +00:00
|
|
|
|
2023-02-17 14:03:13 +00:00
|
|
|
let require_fn_lua = lua
|
2023-02-20 12:02:22 +00:00
|
|
|
.load(REQUIRE_IMPL_LUA)
|
2023-02-17 14:03:13 +00:00
|
|
|
.set_name("require")?
|
|
|
|
.set_environment(require_env)?
|
|
|
|
.into_function()?;
|
2023-03-20 12:52:18 +00:00
|
|
|
|
2023-02-10 11:14:28 +00:00
|
|
|
TableBuilder::new(lua)?
|
2023-02-17 14:03:13 +00:00
|
|
|
.with_value("require", require_fn_lua)?
|
2023-02-10 11:14:28 +00:00
|
|
|
.build_readonly()
|
2023-01-24 07:05:54 +00:00
|
|
|
}
|