From 136cb7d6d68cf77c589411f1e2b6f84d1056dbb7 Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Sun, 21 Apr 2024 20:38:14 +0200 Subject: [PATCH] Mostly port globals to lune-std crate --- Cargo.lock | 5 + crates/lune-std/Cargo.toml | 8 + crates/lune-std/src/global.rs | 92 ++++++ crates/lune-std/src/globals/g_table.rs | 5 + crates/lune-std/src/globals/mod.rs | 5 + crates/lune-std/src/globals/print.rs | 9 + crates/lune-std/src/globals/require/alias.rs | 74 +++++ .../lune-std/src/globals/require/builtin.rs | 14 + .../lune-std/src/globals/require/context.rs | 292 ++++++++++++++++++ crates/lune-std/src/globals/require/mod.rs | 96 ++++++ crates/lune-std/src/globals/require/path.rs | 129 ++++++++ crates/lune-std/src/globals/version.rs | 8 + crates/lune-std/src/globals/warn.rs | 9 + crates/lune-std/src/lib.rs | 5 +- crates/lune-std/src/library.rs | 14 +- 15 files changed, 760 insertions(+), 5 deletions(-) create mode 100644 crates/lune-std/src/global.rs create mode 100644 crates/lune-std/src/globals/g_table.rs create mode 100644 crates/lune-std/src/globals/mod.rs create mode 100644 crates/lune-std/src/globals/print.rs create mode 100644 crates/lune-std/src/globals/require/alias.rs create mode 100644 crates/lune-std/src/globals/require/builtin.rs create mode 100644 crates/lune-std/src/globals/require/context.rs create mode 100644 crates/lune-std/src/globals/require/mod.rs create mode 100644 crates/lune-std/src/globals/require/path.rs create mode 100644 crates/lune-std/src/globals/version.rs create mode 100644 crates/lune-std/src/globals/warn.rs diff --git a/Cargo.lock b/Cargo.lock index d8cac8f..951790c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1551,7 +1551,12 @@ dependencies = [ "lune-std-serde", "lune-std-stdio", "lune-std-task", + "lune-utils", "mlua", + "mlua-luau-scheduler 0.0.1", + "path-clean", + "pathdiff", + "tokio", ] [[package]] diff --git a/crates/lune-std/Cargo.toml b/crates/lune-std/Cargo.toml index 0a33e42..883cdc0 100644 --- a/crates/lune-std/Cargo.toml +++ b/crates/lune-std/Cargo.toml @@ -37,6 +37,14 @@ task = ["dep:lune-std-task"] [dependencies] 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-fs = { optional = true, version = "0.8.3", path = "../lune-std-fs" } diff --git a/crates/lune-std/src/global.rs b/crates/lune-std/src/global.rs new file mode 100644 index 0000000..1c0944f --- /dev/null +++ b/crates/lune-std/src/global.rs @@ -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> { + 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 { + 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::>() + .join(", ") + )) + } + }) + } +} diff --git a/crates/lune-std/src/globals/g_table.rs b/crates/lune-std/src/globals/g_table.rs new file mode 100644 index 0000000..e21fa1e --- /dev/null +++ b/crates/lune-std/src/globals/g_table.rs @@ -0,0 +1,5 @@ +use mlua::prelude::*; + +pub fn create(lua: &Lua) -> LuaResult { + lua.create_table()?.into_lua(lua) +} diff --git a/crates/lune-std/src/globals/mod.rs b/crates/lune-std/src/globals/mod.rs new file mode 100644 index 0000000..b60e9a1 --- /dev/null +++ b/crates/lune-std/src/globals/mod.rs @@ -0,0 +1,5 @@ +pub mod g_table; +pub mod print; +pub mod require; +pub mod version; +pub mod warn; diff --git a/crates/lune-std/src/globals/print.rs b/crates/lune-std/src/globals/print.rs new file mode 100644 index 0000000..45ccdab --- /dev/null +++ b/crates/lune-std/src/globals/print.rs @@ -0,0 +1,9 @@ +use mlua::prelude::*; + +pub fn create(lua: &Lua) -> LuaResult { + let f = lua.create_function(|_, args: LuaMultiValue| { + // TODO: Port this over from the old crate + Ok(()) + })?; + f.into_lua(lua) +} diff --git a/crates/lune-std/src/globals/require/alias.rs b/crates/lune-std/src/globals/require/alias.rs new file mode 100644 index 0000000..0818f88 --- /dev/null +++ b/crates/lune-std/src/globals/require/alias.rs @@ -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> +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::>() + .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 +} diff --git a/crates/lune-std/src/globals/require/builtin.rs b/crates/lune-std/src/globals/require/builtin.rs new file mode 100644 index 0000000..d75ddf5 --- /dev/null +++ b/crates/lune-std/src/globals/require/builtin.rs @@ -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> +where + 'lua: 'ctx, +{ + ctx.load_library(lua, name) +} diff --git a/crates/lune-std/src/globals/require/context.rs b/crates/lune-std/src/globals/require/context.rs new file mode 100644 index 0000000..102b804 --- /dev/null +++ b/crates/lune-std/src/globals/require/context.rs @@ -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>>>, + cache_results: Arc>>>, + cache_pending: Arc>>>, +} + +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, + path: impl AsRef, + ) -> 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) -> LuaResult { + 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) -> LuaResult { + 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, + ) -> LuaResult> { + 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::>(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, + ) -> LuaResult> { + 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, + rel_path: impl AsRef, + ) -> LuaResult { + 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, + rel_path: impl AsRef, + ) -> LuaResult> { + 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::>(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, + ) -> LuaResult> { + 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::>(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 + } +} diff --git a/crates/lune-std/src/globals/require/mod.rs b/crates/lune-std/src/globals/require/mod.rs new file mode 100644 index 0000000..0292797 --- /dev/null +++ b/crates/lune-std/src/globals/require/mod.rs @@ -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 { + 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> { + 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 + } +} diff --git a/crates/lune-std/src/globals/require/path.rs b/crates/lune-std/src/globals/require/path.rs new file mode 100644 index 0000000..7e8084f --- /dev/null +++ b/crates/lune-std/src/globals/require/path.rs @@ -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> +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> +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, + rel_path: impl AsRef, +) -> LuaResult> +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, 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::().is_some() + } else { + false + } +} diff --git a/crates/lune-std/src/globals/version.rs b/crates/lune-std/src/globals/version.rs new file mode 100644 index 0000000..29b4359 --- /dev/null +++ b/crates/lune-std/src/globals/version.rs @@ -0,0 +1,8 @@ +use mlua::prelude::*; + +use lune_utils::get_version_string; + +pub fn create(lua: &Lua) -> LuaResult { + let s = get_version_string().to_string(); + lua.create_string(s)?.into_lua(lua) +} diff --git a/crates/lune-std/src/globals/warn.rs b/crates/lune-std/src/globals/warn.rs new file mode 100644 index 0000000..45ccdab --- /dev/null +++ b/crates/lune-std/src/globals/warn.rs @@ -0,0 +1,9 @@ +use mlua::prelude::*; + +pub fn create(lua: &Lua) -> LuaResult { + let f = lua.create_function(|_, args: LuaMultiValue| { + // TODO: Port this over from the old crate + Ok(()) + })?; + f.into_lua(lua) +} diff --git a/crates/lune-std/src/lib.rs b/crates/lune-std/src/lib.rs index 8e9b1df..be7ff9b 100644 --- a/crates/lune-std/src/lib.rs +++ b/crates/lune-std/src/lib.rs @@ -1,5 +1,8 @@ #![allow(clippy::cargo_common_metadata)] +mod global; +mod globals; mod library; -pub use library::LuneStandardLibrary; +pub use self::global::LuneStandardGlobal; +pub use self::library::LuneStandardLibrary; diff --git a/crates/lune-std/src/library.rs b/crates/lune-std/src/library.rs index b6e7204..dfede4f 100644 --- a/crates/lune-std/src/library.rs +++ b/crates/lune-std/src/library.rs @@ -112,10 +112,16 @@ impl FromStr for LuneStandardLibrary { #[cfg(feature = "stdio")] "stdio" => Self::Stdio, #[cfg(feature = "roblox")] "roblox" => Self::Roblox, - _ => return Err(format!( - "Unknown standard library '{low}'\nValid libraries are: {}", - Self::ALL.iter().map(Self::name).collect::>().join(", ") - )), + _ => { + return Err(format!( + "Unknown standard library '{low}'\nValid libraries are: {}", + Self::ALL + .iter() + .map(Self::name) + .collect::>() + .join(", ") + )) + } }) } }