mirror of
https://github.com/lune-org/lune.git
synced 2025-04-10 21:40:54 +01:00
Mostly port globals to lune-std crate
This commit is contained in:
parent
0bb978640d
commit
136cb7d6d6
15 changed files with 760 additions and 5 deletions
5
Cargo.lock
generated
5
Cargo.lock
generated
|
@ -1551,7 +1551,12 @@ dependencies = [
|
||||||
"lune-std-serde",
|
"lune-std-serde",
|
||||||
"lune-std-stdio",
|
"lune-std-stdio",
|
||||||
"lune-std-task",
|
"lune-std-task",
|
||||||
|
"lune-utils",
|
||||||
"mlua",
|
"mlua",
|
||||||
|
"mlua-luau-scheduler 0.0.1",
|
||||||
|
"path-clean",
|
||||||
|
"pathdiff",
|
||||||
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -37,6 +37,14 @@ task = ["dep:lune-std-task"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
mlua = "0.9.7"
|
mlua = "0.9.7"
|
||||||
|
mlua-luau-scheduler = "0.0.1"
|
||||||
|
|
||||||
|
path-clean = "1.0"
|
||||||
|
pathdiff = "0.2"
|
||||||
|
|
||||||
|
tokio = { version = "1", default-features = false, features = ["fs"] }
|
||||||
|
|
||||||
|
lune-utils = { version = "0.8.3", path = "../lune-utils" }
|
||||||
|
|
||||||
lune-std-datetime = { optional = true, version = "0.8.3", path = "../lune-std-datetime" }
|
lune-std-datetime = { optional = true, version = "0.8.3", path = "../lune-std-datetime" }
|
||||||
lune-std-fs = { optional = true, version = "0.8.3", path = "../lune-std-fs" }
|
lune-std-fs = { optional = true, version = "0.8.3", path = "../lune-std-fs" }
|
||||||
|
|
92
crates/lune-std/src/global.rs
Normal file
92
crates/lune-std/src/global.rs
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use mlua::prelude::*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
A standard global provided by Lune.
|
||||||
|
*/
|
||||||
|
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||||
|
pub enum LuneStandardGlobal {
|
||||||
|
GTable,
|
||||||
|
Print,
|
||||||
|
Require,
|
||||||
|
Version,
|
||||||
|
Warn,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuneStandardGlobal {
|
||||||
|
/**
|
||||||
|
All available standard globals.
|
||||||
|
*/
|
||||||
|
pub const ALL: &'static [Self] = &[
|
||||||
|
Self::GTable,
|
||||||
|
Self::Print,
|
||||||
|
Self::Require,
|
||||||
|
Self::Version,
|
||||||
|
Self::Warn,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
Gets the name of the global, such as `_G` or `require`.
|
||||||
|
*/
|
||||||
|
#[must_use]
|
||||||
|
pub fn name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::GTable => "_G",
|
||||||
|
Self::Print => "print",
|
||||||
|
Self::Require => "require",
|
||||||
|
Self::Version => "_VERSION",
|
||||||
|
Self::Warn => "warn",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Creates the Lua value for the global.
|
||||||
|
|
||||||
|
# Errors
|
||||||
|
|
||||||
|
If the global could not be created.
|
||||||
|
*/
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[allow(unreachable_patterns)]
|
||||||
|
pub fn create<'lua>(&self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
|
||||||
|
let res = match self {
|
||||||
|
Self::GTable => crate::globals::g_table::create(lua),
|
||||||
|
Self::Print => crate::globals::print::create(lua),
|
||||||
|
Self::Require => crate::globals::require::create(lua),
|
||||||
|
Self::Version => crate::globals::version::create(lua),
|
||||||
|
Self::Warn => crate::globals::warn::create(lua),
|
||||||
|
};
|
||||||
|
match res {
|
||||||
|
Ok(v) => Ok(v),
|
||||||
|
Err(e) => Err(e.context(format!(
|
||||||
|
"Failed to create standard global '{}'",
|
||||||
|
self.name()
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for LuneStandardGlobal {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let low = s.trim().to_ascii_lowercase();
|
||||||
|
Ok(match low.as_str() {
|
||||||
|
"_g" => Self::GTable,
|
||||||
|
"print" => Self::Print,
|
||||||
|
"require" => Self::Require,
|
||||||
|
"_version" => Self::Version,
|
||||||
|
"warn" => Self::Warn,
|
||||||
|
_ => {
|
||||||
|
return Err(format!(
|
||||||
|
"Unknown standard global '{low}'\nValid globals are: {}",
|
||||||
|
Self::ALL
|
||||||
|
.iter()
|
||||||
|
.map(Self::name)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ")
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
5
crates/lune-std/src/globals/g_table.rs
Normal file
5
crates/lune-std/src/globals/g_table.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
use mlua::prelude::*;
|
||||||
|
|
||||||
|
pub fn create(lua: &Lua) -> LuaResult<LuaValue> {
|
||||||
|
lua.create_table()?.into_lua(lua)
|
||||||
|
}
|
5
crates/lune-std/src/globals/mod.rs
Normal file
5
crates/lune-std/src/globals/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
pub mod g_table;
|
||||||
|
pub mod print;
|
||||||
|
pub mod require;
|
||||||
|
pub mod version;
|
||||||
|
pub mod warn;
|
9
crates/lune-std/src/globals/print.rs
Normal file
9
crates/lune-std/src/globals/print.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
use mlua::prelude::*;
|
||||||
|
|
||||||
|
pub fn create(lua: &Lua) -> LuaResult<LuaValue> {
|
||||||
|
let f = lua.create_function(|_, args: LuaMultiValue| {
|
||||||
|
// TODO: Port this over from the old crate
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
f.into_lua(lua)
|
||||||
|
}
|
74
crates/lune-std/src/globals/require/alias.rs
Normal file
74
crates/lune-std/src/globals/require/alias.rs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
use mlua::prelude::*;
|
||||||
|
|
||||||
|
use lune_utils::{
|
||||||
|
luaurc::LuauRc,
|
||||||
|
paths::{make_absolute_and_clean, CWD},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::context::*;
|
||||||
|
|
||||||
|
pub(super) async fn require<'lua, 'ctx>(
|
||||||
|
lua: &'lua Lua,
|
||||||
|
ctx: &'ctx RequireContext,
|
||||||
|
source: &str,
|
||||||
|
alias: &str,
|
||||||
|
path: &str,
|
||||||
|
) -> LuaResult<LuaMultiValue<'lua>>
|
||||||
|
where
|
||||||
|
'lua: 'ctx,
|
||||||
|
{
|
||||||
|
let alias = alias.to_ascii_lowercase();
|
||||||
|
|
||||||
|
let parent = make_absolute_and_clean(source)
|
||||||
|
.parent()
|
||||||
|
.expect("how did a root path end up here..")
|
||||||
|
.to_path_buf();
|
||||||
|
|
||||||
|
// Try to gather the first luaurc and / or error we
|
||||||
|
// encounter to display better error messages to users
|
||||||
|
let mut first_luaurc = None;
|
||||||
|
let mut first_error = None;
|
||||||
|
let predicate = |rc: &LuauRc| {
|
||||||
|
if first_luaurc.is_none() {
|
||||||
|
first_luaurc.replace(rc.clone());
|
||||||
|
}
|
||||||
|
if let Err(e) = rc.validate() {
|
||||||
|
if first_error.is_none() {
|
||||||
|
first_error.replace(e);
|
||||||
|
}
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
rc.find_alias(&alias).is_some()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Try to find a luaurc that contains the alias we're searching for
|
||||||
|
let luaurc = LuauRc::read_recursive(parent, predicate)
|
||||||
|
.await
|
||||||
|
.ok_or_else(|| {
|
||||||
|
if let Some(error) = first_error {
|
||||||
|
LuaError::runtime(format!("error while parsing .luaurc file: {error}"))
|
||||||
|
} else if let Some(luaurc) = first_luaurc {
|
||||||
|
LuaError::runtime(format!(
|
||||||
|
"failed to find alias '{alias}' - known aliases:\n{}",
|
||||||
|
luaurc
|
||||||
|
.aliases()
|
||||||
|
.iter()
|
||||||
|
.map(|(name, path)| format!(" {name} > {path}"))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n")
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
LuaError::runtime(format!("failed to find alias '{alias}' (no .luaurc)"))
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// We now have our aliased path, our path require function just needs it
|
||||||
|
// in a slightly different format with both absolute + relative to cwd
|
||||||
|
let abs_path = luaurc.find_alias(&alias).unwrap().join(path);
|
||||||
|
let rel_path = pathdiff::diff_paths(&abs_path, CWD.as_path()).ok_or_else(|| {
|
||||||
|
LuaError::runtime(format!("failed to find relative path for alias '{alias}'"))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
super::path::require_abs_rel(lua, ctx, abs_path, rel_path).await
|
||||||
|
}
|
14
crates/lune-std/src/globals/require/builtin.rs
Normal file
14
crates/lune-std/src/globals/require/builtin.rs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
use mlua::prelude::*;
|
||||||
|
|
||||||
|
use super::context::*;
|
||||||
|
|
||||||
|
pub(super) async fn require<'lua, 'ctx>(
|
||||||
|
lua: &'lua Lua,
|
||||||
|
ctx: &'ctx RequireContext,
|
||||||
|
name: &str,
|
||||||
|
) -> LuaResult<LuaMultiValue<'lua>>
|
||||||
|
where
|
||||||
|
'lua: 'ctx,
|
||||||
|
{
|
||||||
|
ctx.load_library(lua, name)
|
||||||
|
}
|
292
crates/lune-std/src/globals/require/context.rs
Normal file
292
crates/lune-std/src/globals/require/context.rs
Normal file
|
@ -0,0 +1,292 @@
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
use mlua::prelude::*;
|
||||||
|
use mlua_luau_scheduler::LuaSchedulerExt;
|
||||||
|
|
||||||
|
use tokio::{
|
||||||
|
fs::read,
|
||||||
|
sync::{
|
||||||
|
broadcast::{self, Sender},
|
||||||
|
Mutex as AsyncMutex,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::library::LuneStandardLibrary;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Context containing cached results for all `require` operations.
|
||||||
|
|
||||||
|
The cache uses absolute paths, so any given relative
|
||||||
|
path will first be transformed into an absolute path.
|
||||||
|
*/
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(super) struct RequireContext {
|
||||||
|
cache_libraries: Arc<AsyncMutex<HashMap<LuneStandardLibrary, LuaResult<LuaRegistryKey>>>>,
|
||||||
|
cache_results: Arc<AsyncMutex<HashMap<PathBuf, LuaResult<LuaRegistryKey>>>>,
|
||||||
|
cache_pending: Arc<AsyncMutex<HashMap<PathBuf, Sender<()>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RequireContext {
|
||||||
|
/**
|
||||||
|
Creates a new require context for the given [`Lua`] struct.
|
||||||
|
|
||||||
|
Note that this require context is global and only one require
|
||||||
|
context should be created per [`Lua`] struct, creating more
|
||||||
|
than one context may lead to undefined require-behavior.
|
||||||
|
*/
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
cache_libraries: Arc::new(AsyncMutex::new(HashMap::new())),
|
||||||
|
cache_results: Arc::new(AsyncMutex::new(HashMap::new())),
|
||||||
|
cache_pending: Arc::new(AsyncMutex::new(HashMap::new())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Resolves the given `source` and `path` into require paths
|
||||||
|
to use, based on the current require context settings.
|
||||||
|
|
||||||
|
This will resolve path segments such as `./`, `../`, ..., and
|
||||||
|
if the resolved path is not an absolute path, will create an
|
||||||
|
absolute path by prepending the current working directory.
|
||||||
|
*/
|
||||||
|
pub fn resolve_paths(
|
||||||
|
&self,
|
||||||
|
source: impl AsRef<str>,
|
||||||
|
path: impl AsRef<str>,
|
||||||
|
) -> LuaResult<(PathBuf, PathBuf)> {
|
||||||
|
let path = PathBuf::from(source.as_ref())
|
||||||
|
.parent()
|
||||||
|
.ok_or_else(|| LuaError::runtime("Failed to get parent path of source"))?
|
||||||
|
.join(path.as_ref());
|
||||||
|
|
||||||
|
let rel_path = path_clean::clean(path);
|
||||||
|
let abs_path = if rel_path.is_absolute() {
|
||||||
|
rel_path.to_path_buf()
|
||||||
|
} else {
|
||||||
|
CWD.join(&rel_path)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((abs_path, rel_path))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Checks if the given path has a cached require result.
|
||||||
|
*/
|
||||||
|
pub fn is_cached(&self, abs_path: impl AsRef<Path>) -> LuaResult<bool> {
|
||||||
|
let is_cached = self
|
||||||
|
.cache_results
|
||||||
|
.try_lock()
|
||||||
|
.expect("RequireContext may not be used from multiple threads")
|
||||||
|
.contains_key(abs_path.as_ref());
|
||||||
|
Ok(is_cached)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Checks if the given path is currently being used in `require`.
|
||||||
|
*/
|
||||||
|
pub fn is_pending(&self, abs_path: impl AsRef<Path>) -> LuaResult<bool> {
|
||||||
|
let is_pending = self
|
||||||
|
.cache_pending
|
||||||
|
.try_lock()
|
||||||
|
.expect("RequireContext may not be used from multiple threads")
|
||||||
|
.contains_key(abs_path.as_ref());
|
||||||
|
Ok(is_pending)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Gets the resulting value from the require cache.
|
||||||
|
|
||||||
|
Will panic if the path has not been cached, use [`is_cached`] first.
|
||||||
|
*/
|
||||||
|
pub fn get_from_cache<'lua>(
|
||||||
|
&self,
|
||||||
|
lua: &'lua Lua,
|
||||||
|
abs_path: impl AsRef<Path>,
|
||||||
|
) -> LuaResult<LuaMultiValue<'lua>> {
|
||||||
|
let results = self
|
||||||
|
.cache_results
|
||||||
|
.try_lock()
|
||||||
|
.expect("RequireContext may not be used from multiple threads");
|
||||||
|
|
||||||
|
let cached = results
|
||||||
|
.get(abs_path.as_ref())
|
||||||
|
.expect("Path does not exist in results cache");
|
||||||
|
match cached {
|
||||||
|
Err(e) => Err(e.clone()),
|
||||||
|
Ok(k) => {
|
||||||
|
let multi_vec = lua
|
||||||
|
.registry_value::<Vec<LuaValue>>(k)
|
||||||
|
.expect("Missing require result in lua registry");
|
||||||
|
Ok(LuaMultiValue::from_vec(multi_vec))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Waits for the resulting value from the require cache.
|
||||||
|
|
||||||
|
Will panic if the path has not been cached, use [`is_cached`] first.
|
||||||
|
*/
|
||||||
|
pub async fn wait_for_cache<'lua>(
|
||||||
|
&self,
|
||||||
|
lua: &'lua Lua,
|
||||||
|
abs_path: impl AsRef<Path>,
|
||||||
|
) -> LuaResult<LuaMultiValue<'lua>> {
|
||||||
|
let mut thread_recv = {
|
||||||
|
let pending = self
|
||||||
|
.cache_pending
|
||||||
|
.try_lock()
|
||||||
|
.expect("RequireContext may not be used from multiple threads");
|
||||||
|
let thread_id = pending
|
||||||
|
.get(abs_path.as_ref())
|
||||||
|
.expect("Path is not currently pending require");
|
||||||
|
thread_id.subscribe()
|
||||||
|
};
|
||||||
|
|
||||||
|
thread_recv.recv().await.into_lua_err()?;
|
||||||
|
|
||||||
|
self.get_from_cache(lua, abs_path.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn load<'lua>(
|
||||||
|
&self,
|
||||||
|
lua: &'lua Lua,
|
||||||
|
abs_path: impl AsRef<Path>,
|
||||||
|
rel_path: impl AsRef<Path>,
|
||||||
|
) -> LuaResult<LuaRegistryKey> {
|
||||||
|
let abs_path = abs_path.as_ref();
|
||||||
|
let rel_path = rel_path.as_ref();
|
||||||
|
|
||||||
|
// Read the file at the given path, try to parse and
|
||||||
|
// load it into a new lua thread that we can schedule
|
||||||
|
let file_contents = read(&abs_path).await?;
|
||||||
|
let file_thread = lua
|
||||||
|
.load(file_contents)
|
||||||
|
.set_name(rel_path.to_string_lossy().to_string());
|
||||||
|
|
||||||
|
// Schedule the thread to run, wait for it to finish running
|
||||||
|
let thread_id = lua.push_thread_back(file_thread, ())?;
|
||||||
|
lua.track_thread(thread_id);
|
||||||
|
lua.wait_for_thread(thread_id).await;
|
||||||
|
let thread_res = lua.get_thread_result(thread_id).unwrap();
|
||||||
|
|
||||||
|
// Return the result of the thread, storing any lua value(s) in the registry
|
||||||
|
match thread_res {
|
||||||
|
Err(e) => Err(e),
|
||||||
|
Ok(v) => {
|
||||||
|
let multi_vec = v.into_vec();
|
||||||
|
let multi_key = lua
|
||||||
|
.create_registry_value(multi_vec)
|
||||||
|
.expect("Failed to store require result in registry - out of memory");
|
||||||
|
Ok(multi_key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Loads (requires) the file at the given path.
|
||||||
|
*/
|
||||||
|
pub async fn load_with_caching<'lua>(
|
||||||
|
&self,
|
||||||
|
lua: &'lua Lua,
|
||||||
|
abs_path: impl AsRef<Path>,
|
||||||
|
rel_path: impl AsRef<Path>,
|
||||||
|
) -> LuaResult<LuaMultiValue<'lua>> {
|
||||||
|
let abs_path = abs_path.as_ref();
|
||||||
|
let rel_path = rel_path.as_ref();
|
||||||
|
|
||||||
|
// Set this abs path as currently pending
|
||||||
|
let (broadcast_tx, _) = broadcast::channel(1);
|
||||||
|
self.cache_pending
|
||||||
|
.try_lock()
|
||||||
|
.expect("RequireContext may not be used from multiple threads")
|
||||||
|
.insert(abs_path.to_path_buf(), broadcast_tx);
|
||||||
|
|
||||||
|
// Try to load at this abs path
|
||||||
|
let load_res = self.load(lua, abs_path, rel_path).await;
|
||||||
|
let load_val = match &load_res {
|
||||||
|
Err(e) => Err(e.clone()),
|
||||||
|
Ok(k) => {
|
||||||
|
let multi_vec = lua
|
||||||
|
.registry_value::<Vec<LuaValue>>(k)
|
||||||
|
.expect("Failed to fetch require result from registry");
|
||||||
|
Ok(LuaMultiValue::from_vec(multi_vec))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// NOTE: We use the async lock and not try_lock here because
|
||||||
|
// some other thread may be wanting to insert into the require
|
||||||
|
// cache at the same time, and that's not an actual error case
|
||||||
|
self.cache_results
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.insert(abs_path.to_path_buf(), load_res);
|
||||||
|
|
||||||
|
// Remove the pending thread id from the require context,
|
||||||
|
// broadcast a message to let any listeners know that this
|
||||||
|
// path has now finished the require process and is cached
|
||||||
|
let broadcast_tx = self
|
||||||
|
.cache_pending
|
||||||
|
.try_lock()
|
||||||
|
.expect("RequireContext may not be used from multiple threads")
|
||||||
|
.remove(abs_path)
|
||||||
|
.expect("Pending require broadcaster was unexpectedly removed");
|
||||||
|
broadcast_tx.send(()).ok();
|
||||||
|
|
||||||
|
load_val
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Loads (requires) the library with the given name.
|
||||||
|
*/
|
||||||
|
pub fn load_library<'lua>(
|
||||||
|
&self,
|
||||||
|
lua: &'lua Lua,
|
||||||
|
name: impl AsRef<str>,
|
||||||
|
) -> LuaResult<LuaMultiValue<'lua>> {
|
||||||
|
let library: LuneStandardLibrary = match name.as_ref().parse() {
|
||||||
|
Err(e) => return Err(LuaError::runtime(e)),
|
||||||
|
Ok(b) => b,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut cache = self
|
||||||
|
.cache_libraries
|
||||||
|
.try_lock()
|
||||||
|
.expect("RequireContext may not be used from multiple threads");
|
||||||
|
|
||||||
|
if let Some(res) = cache.get(&library) {
|
||||||
|
return match res {
|
||||||
|
Err(e) => return Err(e.clone()),
|
||||||
|
Ok(key) => {
|
||||||
|
let multi_vec = lua
|
||||||
|
.registry_value::<Vec<LuaValue>>(key)
|
||||||
|
.expect("Missing library result in lua registry");
|
||||||
|
Ok(LuaMultiValue::from_vec(multi_vec))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = library.module(lua);
|
||||||
|
|
||||||
|
cache.insert(
|
||||||
|
library,
|
||||||
|
match result.clone() {
|
||||||
|
Err(e) => Err(e),
|
||||||
|
Ok(multi) => {
|
||||||
|
let multi_vec = multi.into_vec();
|
||||||
|
let multi_key = lua
|
||||||
|
.create_registry_value(multi_vec)
|
||||||
|
.expect("Failed to store require result in registry - out of memory");
|
||||||
|
Ok(multi_key)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
96
crates/lune-std/src/globals/require/mod.rs
Normal file
96
crates/lune-std/src/globals/require/mod.rs
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
use mlua::prelude::*;
|
||||||
|
|
||||||
|
use lune_utils::TableBuilder;
|
||||||
|
|
||||||
|
mod context;
|
||||||
|
use context::RequireContext;
|
||||||
|
|
||||||
|
mod alias;
|
||||||
|
mod builtin;
|
||||||
|
mod path;
|
||||||
|
|
||||||
|
const REQUIRE_IMPL: &str = r"
|
||||||
|
return require(source(), ...)
|
||||||
|
";
|
||||||
|
|
||||||
|
pub fn create(lua: &Lua) -> LuaResult<LuaValue> {
|
||||||
|
lua.set_app_data(RequireContext::new());
|
||||||
|
|
||||||
|
/*
|
||||||
|
Require implementation needs a few workarounds:
|
||||||
|
|
||||||
|
- Async functions run outside of the lua resumption cycle,
|
||||||
|
so the current lua thread, as well as its stack/debug info
|
||||||
|
is not available, meaning we have to use a normal function
|
||||||
|
|
||||||
|
- Using the async require function directly in another lua function
|
||||||
|
would mean yielding across the metamethod/c-call boundary, meaning
|
||||||
|
we have to first load our two functions into a normal lua chunk
|
||||||
|
and then load that new chunk into our final require function
|
||||||
|
|
||||||
|
Also note that we inspect the stack at level 2:
|
||||||
|
|
||||||
|
1. The current c / rust function
|
||||||
|
2. The wrapper lua chunk defined above
|
||||||
|
3. The lua chunk we are require-ing from
|
||||||
|
*/
|
||||||
|
|
||||||
|
let require_fn = lua.create_async_function(require)?;
|
||||||
|
let get_source_fn = lua.create_function(move |lua, _: ()| match lua.inspect_stack(2) {
|
||||||
|
None => Err(LuaError::runtime(
|
||||||
|
"Failed to get stack info for require source",
|
||||||
|
)),
|
||||||
|
Some(info) => match info.source().source {
|
||||||
|
None => Err(LuaError::runtime(
|
||||||
|
"Stack info is missing source for require",
|
||||||
|
)),
|
||||||
|
Some(source) => lua.create_string(source.as_bytes()),
|
||||||
|
},
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let require_env = TableBuilder::new(lua)?
|
||||||
|
.with_value("source", get_source_fn)?
|
||||||
|
.with_value("require", require_fn)?
|
||||||
|
.build_readonly()?;
|
||||||
|
|
||||||
|
lua.load(REQUIRE_IMPL)
|
||||||
|
.set_name("require")
|
||||||
|
.set_environment(require_env)
|
||||||
|
.into_function()?
|
||||||
|
.into_lua(lua)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn require<'lua>(
|
||||||
|
lua: &'lua Lua,
|
||||||
|
(source, path): (LuaString<'lua>, LuaString<'lua>),
|
||||||
|
) -> LuaResult<LuaMultiValue<'lua>> {
|
||||||
|
let source = source
|
||||||
|
.to_str()
|
||||||
|
.into_lua_err()
|
||||||
|
.context("Failed to parse require source as string")?
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let path = path
|
||||||
|
.to_str()
|
||||||
|
.into_lua_err()
|
||||||
|
.context("Failed to parse require path as string")?
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let context = lua
|
||||||
|
.app_data_ref()
|
||||||
|
.expect("Failed to get RequireContext from app data");
|
||||||
|
|
||||||
|
if let Some(builtin_name) = path
|
||||||
|
.strip_prefix("@lune/")
|
||||||
|
.map(|name| name.to_ascii_lowercase())
|
||||||
|
{
|
||||||
|
builtin::require(lua, &context, &builtin_name).await
|
||||||
|
} else if let Some(aliased_path) = path.strip_prefix('@') {
|
||||||
|
let (alias, path) = aliased_path.split_once('/').ok_or(LuaError::runtime(
|
||||||
|
"Require with custom alias must contain '/' delimiter",
|
||||||
|
))?;
|
||||||
|
alias::require(lua, &context, &source, alias, path).await
|
||||||
|
} else {
|
||||||
|
path::require(lua, &context, &source, &path).await
|
||||||
|
}
|
||||||
|
}
|
129
crates/lune-std/src/globals/require/path.rs
Normal file
129
crates/lune-std/src/globals/require/path.rs
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use mlua::prelude::*;
|
||||||
|
use mlua::Error::ExternalError;
|
||||||
|
|
||||||
|
use super::context::*;
|
||||||
|
|
||||||
|
pub(super) async fn require<'lua, 'ctx>(
|
||||||
|
lua: &'lua Lua,
|
||||||
|
ctx: &'ctx RequireContext,
|
||||||
|
source: &str,
|
||||||
|
path: &str,
|
||||||
|
) -> LuaResult<LuaMultiValue<'lua>>
|
||||||
|
where
|
||||||
|
'lua: 'ctx,
|
||||||
|
{
|
||||||
|
let (abs_path, rel_path) = ctx.resolve_paths(source, path)?;
|
||||||
|
require_abs_rel(lua, ctx, abs_path, rel_path).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn require_abs_rel<'lua, 'ctx>(
|
||||||
|
lua: &'lua Lua,
|
||||||
|
ctx: &'ctx RequireContext,
|
||||||
|
abs_path: PathBuf, // Absolute to filesystem
|
||||||
|
rel_path: PathBuf, // Relative to CWD (for displaying)
|
||||||
|
) -> LuaResult<LuaMultiValue<'lua>>
|
||||||
|
where
|
||||||
|
'lua: 'ctx,
|
||||||
|
{
|
||||||
|
// 1. Try to require the exact path
|
||||||
|
match require_inner(lua, ctx, &abs_path, &rel_path).await {
|
||||||
|
Ok(res) => return Ok(res),
|
||||||
|
Err(err) => {
|
||||||
|
if !is_file_not_found_error(&err) {
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Try to require the path with an added "luau" extension
|
||||||
|
// 3. Try to require the path with an added "lua" extension
|
||||||
|
for extension in ["luau", "lua"] {
|
||||||
|
match require_inner(
|
||||||
|
lua,
|
||||||
|
ctx,
|
||||||
|
&append_extension(&abs_path, extension),
|
||||||
|
&append_extension(&rel_path, extension),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(res) => return Ok(res),
|
||||||
|
Err(err) => {
|
||||||
|
if !is_file_not_found_error(&err) {
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We didn't find any direct file paths, look
|
||||||
|
// for directories with "init" files in them...
|
||||||
|
let abs_init = abs_path.join("init");
|
||||||
|
let rel_init = rel_path.join("init");
|
||||||
|
|
||||||
|
// 4. Try to require the init path with an added "luau" extension
|
||||||
|
// 5. Try to require the init path with an added "lua" extension
|
||||||
|
for extension in ["luau", "lua"] {
|
||||||
|
match require_inner(
|
||||||
|
lua,
|
||||||
|
ctx,
|
||||||
|
&append_extension(&abs_init, extension),
|
||||||
|
&append_extension(&rel_init, extension),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(res) => return Ok(res),
|
||||||
|
Err(err) => {
|
||||||
|
if !is_file_not_found_error(&err) {
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nothing left to try, throw an error
|
||||||
|
Err(LuaError::runtime(format!(
|
||||||
|
"No file exists at the path '{}'",
|
||||||
|
rel_path.display()
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn require_inner<'lua, 'ctx>(
|
||||||
|
lua: &'lua Lua,
|
||||||
|
ctx: &'ctx RequireContext,
|
||||||
|
abs_path: impl AsRef<Path>,
|
||||||
|
rel_path: impl AsRef<Path>,
|
||||||
|
) -> LuaResult<LuaMultiValue<'lua>>
|
||||||
|
where
|
||||||
|
'lua: 'ctx,
|
||||||
|
{
|
||||||
|
let abs_path = abs_path.as_ref();
|
||||||
|
let rel_path = rel_path.as_ref();
|
||||||
|
|
||||||
|
if ctx.is_cached(abs_path)? {
|
||||||
|
ctx.get_from_cache(lua, abs_path)
|
||||||
|
} else if ctx.is_pending(abs_path)? {
|
||||||
|
ctx.wait_for_cache(lua, &abs_path).await
|
||||||
|
} else {
|
||||||
|
ctx.load_with_caching(lua, &abs_path, &rel_path).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_extension(path: impl Into<PathBuf>, ext: &'static str) -> PathBuf {
|
||||||
|
let mut new = path.into();
|
||||||
|
match new.extension() {
|
||||||
|
// FUTURE: There's probably a better way to do this than converting to a lossy string
|
||||||
|
Some(e) => new.set_extension(format!("{}.{ext}", e.to_string_lossy())),
|
||||||
|
None => new.set_extension(ext),
|
||||||
|
};
|
||||||
|
new
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_file_not_found_error(err: &LuaError) -> bool {
|
||||||
|
if let ExternalError(err) = err {
|
||||||
|
err.as_ref().downcast_ref::<std::io::Error>().is_some()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
8
crates/lune-std/src/globals/version.rs
Normal file
8
crates/lune-std/src/globals/version.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
use mlua::prelude::*;
|
||||||
|
|
||||||
|
use lune_utils::get_version_string;
|
||||||
|
|
||||||
|
pub fn create(lua: &Lua) -> LuaResult<LuaValue> {
|
||||||
|
let s = get_version_string().to_string();
|
||||||
|
lua.create_string(s)?.into_lua(lua)
|
||||||
|
}
|
9
crates/lune-std/src/globals/warn.rs
Normal file
9
crates/lune-std/src/globals/warn.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
use mlua::prelude::*;
|
||||||
|
|
||||||
|
pub fn create(lua: &Lua) -> LuaResult<LuaValue> {
|
||||||
|
let f = lua.create_function(|_, args: LuaMultiValue| {
|
||||||
|
// TODO: Port this over from the old crate
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
f.into_lua(lua)
|
||||||
|
}
|
|
@ -1,5 +1,8 @@
|
||||||
#![allow(clippy::cargo_common_metadata)]
|
#![allow(clippy::cargo_common_metadata)]
|
||||||
|
|
||||||
|
mod global;
|
||||||
|
mod globals;
|
||||||
mod library;
|
mod library;
|
||||||
|
|
||||||
pub use library::LuneStandardLibrary;
|
pub use self::global::LuneStandardGlobal;
|
||||||
|
pub use self::library::LuneStandardLibrary;
|
||||||
|
|
|
@ -112,10 +112,16 @@ impl FromStr for LuneStandardLibrary {
|
||||||
#[cfg(feature = "stdio")] "stdio" => Self::Stdio,
|
#[cfg(feature = "stdio")] "stdio" => Self::Stdio,
|
||||||
#[cfg(feature = "roblox")] "roblox" => Self::Roblox,
|
#[cfg(feature = "roblox")] "roblox" => Self::Roblox,
|
||||||
|
|
||||||
_ => return Err(format!(
|
_ => {
|
||||||
"Unknown standard library '{low}'\nValid libraries are: {}",
|
return Err(format!(
|
||||||
Self::ALL.iter().map(Self::name).collect::<Vec<_>>().join(", ")
|
"Unknown standard library '{low}'\nValid libraries are: {}",
|
||||||
)),
|
Self::ALL
|
||||||
|
.iter()
|
||||||
|
.map(Self::name)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ")
|
||||||
|
))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue