diff --git a/crates/lune-std/src/globals/require/mod.rs b/crates/lune-std/src/globals/require/mod.rs index c4eee70..7c11b91 100644 --- a/crates/lune-std/src/globals/require/mod.rs +++ b/crates/lune-std/src/globals/require/mod.rs @@ -1,4 +1,4 @@ -use crate::{luaurc::path_to_alias, path::get_parent_path}; +use crate::{luaurc::path_to_alias, path::get_parent_path, LuneStandardLibrary}; use mlua::prelude::*; use std::path::PathBuf; @@ -9,11 +9,8 @@ pub async fn lua_require(lua: &Lua, path: String) -> LuaResult { let require_alias = path_to_alias(&require_path_rel)?; if let Some(require_alias) = require_alias { - if require_alias.alias == "lune" { - Err(LuaError::runtime(format!( - "Tried requiring a lune library '{}'\nbut aliases are not implemented yet.", - require_alias.path, - ))) + if storage::RequireStorage::std_exists(lua, &require_alias.alias)? { + storage::RequireStorage::require_std(lua, require_alias) } else { Err(LuaError::runtime(format!( "Tried requiring a custom alias '{}'\nbut aliases are not implemented yet.", @@ -22,7 +19,7 @@ pub async fn lua_require(lua: &Lua, path: String) -> LuaResult { } } else { let parent_path = get_parent_path(lua)?; - let require_path_abs = parent_path.join(require_path_rel); + let require_path_abs = parent_path.join(&require_path_rel); Err(LuaError::runtime(format!( "Tried requiring '{}'\nbut requires are not implemented yet.", @@ -34,5 +31,11 @@ pub async fn lua_require(lua: &Lua, path: String) -> LuaResult { pub fn create(lua: &Lua) -> LuaResult { let f = lua.create_async_function(lua_require)?; + storage::RequireStorage::init(lua)?; + + for std in LuneStandardLibrary::ALL { + storage::RequireStorage::inject_std(lua, "lune", *std)?; + } + f.into_lua(lua) } diff --git a/crates/lune-std/src/globals/require/storage.rs b/crates/lune-std/src/globals/require/storage.rs index 8c1eda9..777dbdf 100644 --- a/crates/lune-std/src/globals/require/storage.rs +++ b/crates/lune-std/src/globals/require/storage.rs @@ -1,6 +1,92 @@ -use crate::library::StandardLibrary; +use crate::{library::StandardLibrary, luaurc::RequireAlias}; +use mlua::prelude::*; use std::collections::HashMap; -pub struct RequireStorage<'a> { - stds: HashMap<&'a str, Box>, +/// The private struct that's stored in mlua's app data container +#[derive(Debug, Default)] +struct RequireStorageData<'a> { + std: HashMap<&'a str, HashMap<&'a str, Box>>, + std_cache: HashMap, + cache: HashMap<&'a str, LuaRegistryKey>, +} + +#[derive(Debug)] +pub struct RequireStorage {} + +impl RequireStorage { + pub fn init(lua: &Lua) -> LuaResult<()> { + if lua.set_app_data(RequireStorageData::default()).is_some() { + Err(LuaError::runtime("RequireStorage::init got called twice")) + } else { + Ok(()) + } + } + + pub fn std_exists(lua: &Lua, alias: &str) -> LuaResult { + let data_ref = lua + .app_data_ref::() + .ok_or(LuaError::runtime("Couldn't find RequireStorageData in app data container, make sure RequireStorage::init is called on this lua instance"))?; + + Ok(data_ref.std.contains_key(alias)) + } + + pub fn require_std(lua: &Lua, require_alias: RequireAlias) -> LuaResult> { + let data_ref = lua + .app_data_ref::() + .ok_or(LuaError::runtime("Couldn't find RequireStorageData in app data container, make sure RequireStorage::init is called on this lua instance"))?; + + if let Some(cached) = data_ref.std_cache.get(&require_alias) { + return cached.into_lua(lua)?.into_lua_multi(lua); + } + + let libraries = + data_ref + .std + .get(&require_alias.alias.as_str()) + .ok_or(mlua::Error::runtime(format!( + "Alias '{}' does not point to a built-in standard library", + require_alias.alias + )))?; + + let std = libraries + .get(require_alias.path.as_str()) + .ok_or(mlua::Error::runtime(format!( + "Library '{}' does not point to a member of '{}' standard libraries", + require_alias.path, require_alias.alias + )))?; + + let multi = std.module(lua)?; + let mutli_clone = multi.clone(); + let multi_reg = lua.create_registry_value(mutli_clone.into_vec())?; + + let mut data = lua + .app_data_mut::() + .ok_or(LuaError::runtime("Couldn't find RequireStorageData in app data container, make sure RequireStorage::init is called on this lua instance"))?; + + data.std_cache.insert(require_alias, multi_reg); + + Ok(multi) + } + + pub fn inject_std( + lua: &Lua, + alias: &'static str, + std: impl StandardLibrary + 'static, + ) -> LuaResult<()> { + let mut data = lua + .app_data_mut::() + .ok_or(LuaError::runtime("Couldn't find RequireStorageData in app data container, make sure RequireStorage::init is called on this lua instance"))?; + + if let Some(map) = data.std.get_mut(alias) { + map.insert(std.name(), Box::new(std)); + } else { + let mut map: HashMap<&str, Box> = HashMap::new(); + + map.insert(std.name(), Box::new(std)); + + data.std.insert(alias, map); + }; + + Ok(()) + } } diff --git a/crates/lune-std/src/library.rs b/crates/lune-std/src/library.rs index 96961c0..4f7745e 100644 --- a/crates/lune-std/src/library.rs +++ b/crates/lune-std/src/library.rs @@ -1,8 +1,11 @@ -use std::str::FromStr; +use std::{fmt::Debug, str::FromStr}; use mlua::prelude::*; -pub trait StandardLibrary { +pub trait StandardLibrary +where + Self: Debug, +{ fn name(&self) -> &'static str; fn module<'lua>(&self, lua: &'lua Lua) -> LuaResult>; @@ -26,6 +29,25 @@ pub enum LuneStandardLibrary { #[cfg(feature = "roblox")] Roblox, } +impl LuneStandardLibrary { + /** + All available standard libraries. + */ + #[rustfmt::skip] + pub const ALL: &'static [Self] = &[ + #[cfg(feature = "datetime")] Self::DateTime, + #[cfg(feature = "fs")] Self::Fs, + #[cfg(feature = "luau")] Self::Luau, + #[cfg(feature = "net")] Self::Net, + #[cfg(feature = "task")] Self::Task, + #[cfg(feature = "process")] Self::Process, + #[cfg(feature = "regex")] Self::Regex, + #[cfg(feature = "serde")] Self::Serde, + #[cfg(feature = "stdio")] Self::Stdio, + #[cfg(feature = "roblox")] Self::Roblox, + ]; +} + impl StandardLibrary for LuneStandardLibrary { /** Gets the name of the library, such as `datetime` or `fs`. diff --git a/crates/lune-std/src/luaurc.rs b/crates/lune-std/src/luaurc.rs index c4fb655..4a5606a 100644 --- a/crates/lune-std/src/luaurc.rs +++ b/crates/lune-std/src/luaurc.rs @@ -8,10 +8,10 @@ use std::{ }; use tokio::fs; -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct RequireAlias<'a> { - pub alias: &'a str, - pub path: &'a str, +#[derive(Debug, Clone, Eq, Hash, PartialEq)] +pub struct RequireAlias { + pub alias: String, + pub path: String, } #[derive(Debug, Clone, PartialEq, Deserialize)] @@ -26,7 +26,7 @@ pub struct Luaurc { /// `@lune/task` becomes `Some({ alias: "lune", path: "task" })` /// /// `../path/script` becomes `None` -pub fn path_to_alias(path: &Path) -> Result>, mlua::Error> { +pub fn path_to_alias(path: &Path) -> Result, mlua::Error> { if let Some(aliased_path) = path .to_str() .ok_or(mlua::Error::runtime("Couldn't turn path into string"))? @@ -36,7 +36,10 @@ pub fn path_to_alias(path: &Path) -> Result>, mlua::Erro "Require with alias doesn't contain '/'", ))?; - Ok(Some(RequireAlias { alias, path })) + Ok(Some(RequireAlias { + alias: alias.to_string(), + path: path.to_string(), + })) } else { Ok(None) } @@ -55,7 +58,7 @@ async fn parse_luaurc(_: &mlua::Lua, path: &PathBuf) -> Result, m /// until an alias for the provided `RequireAlias` is found pub async fn resolve_require_alias<'lua>( lua: &'lua mlua::Lua, - alias: &'lua RequireAlias<'lua>, + alias: &'lua RequireAlias, ) -> Result { let cwd = current_dir()?; let parent = cwd.join(get_parent_path(lua)?); @@ -65,8 +68,8 @@ pub async fn resolve_require_alias<'lua>( if path.starts_with(&cwd) { if let Some(luaurc) = parse_luaurc(lua, &parent.join(".luaurc")).await? { if let Some(aliases) = luaurc.aliases { - if let Some(alias_path) = aliases.get(alias.alias) { - let resolved = path.join(alias_path.join(alias.path)); + if let Some(alias_path) = aliases.get(&alias.alias) { + let resolved = path.join(alias_path.join(&alias.path)); return Ok(resolved); }