From 7d73601a58f74e14e9406878e894f0a4a820048f Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Fri, 18 Aug 2023 16:35:33 -0500 Subject: [PATCH] Initial scaffolding to get custom globals and require working --- src/cli/repl.rs | 2 +- src/lune/globals/mod.rs | 18 ++++++++++++ src/lune/globals/require/absolute.rs | 13 +++++++++ src/lune/globals/require/alias.rs | 14 +++++++++ src/lune/globals/require/builtin.rs | 13 +++++++++ src/lune/globals/require/context.rs | 43 ++++++++++++++++++++++++++++ src/lune/globals/require/mod.rs | 41 ++++++++++++++++++++++++++ src/lune/globals/require/relative.rs | 13 +++++++++ src/lune/mod.rs | 31 ++++++++++++++------ src/lune/scheduler/impl_async.rs | 3 ++ src/lune/scheduler/mod.rs | 10 +++++++ src/lune/scheduler/traits.rs | 1 + src/tests.rs | 2 +- 13 files changed, 193 insertions(+), 11 deletions(-) create mode 100644 src/lune/globals/mod.rs create mode 100644 src/lune/globals/require/absolute.rs create mode 100644 src/lune/globals/require/alias.rs create mode 100644 src/lune/globals/require/builtin.rs create mode 100644 src/lune/globals/require/context.rs create mode 100644 src/lune/globals/require/mod.rs create mode 100644 src/lune/globals/require/relative.rs diff --git a/src/cli/repl.rs b/src/cli/repl.rs index 6639424..71b4923 100644 --- a/src/cli/repl.rs +++ b/src/cli/repl.rs @@ -32,7 +32,7 @@ pub async fn show_interface() -> Result { let mut prompt_state = PromptState::Regular; let mut source_code = String::new(); - let lune_instance = Lune::new(); + let mut lune_instance = Lune::new(); loop { let prompt = match prompt_state { diff --git a/src/lune/globals/mod.rs b/src/lune/globals/mod.rs new file mode 100644 index 0000000..fb03d1e --- /dev/null +++ b/src/lune/globals/mod.rs @@ -0,0 +1,18 @@ +use mlua::prelude::*; + +use super::util::TableBuilder; + +mod require; + +pub fn inject_all(lua: &'static Lua) -> LuaResult<()> { + let all = TableBuilder::new(lua)? + .with_value("require", require::create(lua)?)? + .build_readonly()?; + + for res in all.pairs() { + let (key, value): (LuaValue, LuaValue) = res?; + lua.globals().set(key, value)?; + } + + Ok(()) +} diff --git a/src/lune/globals/require/absolute.rs b/src/lune/globals/require/absolute.rs new file mode 100644 index 0000000..08f45ea --- /dev/null +++ b/src/lune/globals/require/absolute.rs @@ -0,0 +1,13 @@ +use mlua::prelude::*; + +use super::context::*; + +pub(super) async fn require<'lua>( + _lua: &'lua Lua, + _ctx: RequireContext, + path: &str, +) -> LuaResult> { + Err(LuaError::runtime(format!( + "TODO: Support require for absolute paths (tried to require '{path}')" + ))) +} diff --git a/src/lune/globals/require/alias.rs b/src/lune/globals/require/alias.rs new file mode 100644 index 0000000..0db3a07 --- /dev/null +++ b/src/lune/globals/require/alias.rs @@ -0,0 +1,14 @@ +use mlua::prelude::*; + +use super::context::*; + +pub(super) async fn require<'lua>( + _lua: &'lua Lua, + _ctx: RequireContext, + alias: &str, + name: &str, +) -> LuaResult> { + Err(LuaError::runtime(format!( + "TODO: Support require for built-in libraries (tried to require '{name}' with alias '{alias}')" + ))) +} diff --git a/src/lune/globals/require/builtin.rs b/src/lune/globals/require/builtin.rs new file mode 100644 index 0000000..377d947 --- /dev/null +++ b/src/lune/globals/require/builtin.rs @@ -0,0 +1,13 @@ +use mlua::prelude::*; + +use super::context::*; + +pub(super) async fn require<'lua>( + _lua: &'lua Lua, + _ctx: RequireContext, + name: &str, +) -> LuaResult> { + Err(LuaError::runtime(format!( + "TODO: Support require for built-in libraries (tried to require '{name}')" + ))) +} diff --git a/src/lune/globals/require/context.rs b/src/lune/globals/require/context.rs new file mode 100644 index 0000000..54fd981 --- /dev/null +++ b/src/lune/globals/require/context.rs @@ -0,0 +1,43 @@ +use mlua::prelude::*; + +const REGISTRY_KEY: &str = "RequireContext"; + +// TODO: Store current file path for each thread in +// this context somehow, as well as built-in libraries +#[derive(Clone)] +pub(super) struct RequireContext { + pub(super) use_absolute_paths: bool, +} + +impl RequireContext { + pub fn new() -> Self { + Self { + // TODO: Set to false by default, load some kind of config + // or env var to check if we should be using absolute paths + use_absolute_paths: true, + } + } + + pub fn from_registry(lua: &Lua) -> Self { + lua.named_registry_value(REGISTRY_KEY) + .expect("Missing require context in lua registry") + } + + pub fn insert_into_registry(self, lua: &Lua) { + lua.set_named_registry_value(REGISTRY_KEY, self) + .expect("Failed to insert RequireContext into registry"); + } +} + +impl LuaUserData for RequireContext {} + +impl<'lua> FromLua<'lua> for RequireContext { + fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult { + if let LuaValue::UserData(ud) = value { + if let Ok(ctx) = ud.borrow::() { + return Ok(ctx.clone()); + } + } + unreachable!("RequireContext should only be used from registry") + } +} diff --git a/src/lune/globals/require/mod.rs b/src/lune/globals/require/mod.rs new file mode 100644 index 0000000..8315687 --- /dev/null +++ b/src/lune/globals/require/mod.rs @@ -0,0 +1,41 @@ +use mlua::prelude::*; + +use crate::lune::scheduler::LuaSchedulerExt; + +mod context; +use context::RequireContext; + +mod absolute; +mod alias; +mod builtin; +mod relative; + +pub fn create(lua: &'static Lua) -> LuaResult> { + RequireContext::new().insert_into_registry(lua); + + lua.create_async_function(|lua, path: LuaString| async move { + let context = RequireContext::from_registry(lua); + + let path = path + .to_str() + .into_lua_err() + .context("Failed to parse require path as string")? + .to_string(); + + 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, name) = aliased_path.split_once('/').ok_or(LuaError::runtime( + "Require with custom alias must contain '/' delimiter", + ))?; + alias::require(lua, context, alias, name).await + } else if context.use_absolute_paths { + absolute::require(lua, context, &path).await + } else { + relative::require(lua, context, &path).await + } + }) +} diff --git a/src/lune/globals/require/relative.rs b/src/lune/globals/require/relative.rs new file mode 100644 index 0000000..08f45ea --- /dev/null +++ b/src/lune/globals/require/relative.rs @@ -0,0 +1,13 @@ +use mlua::prelude::*; + +use super::context::*; + +pub(super) async fn require<'lua>( + _lua: &'lua Lua, + _ctx: RequireContext, + path: &str, +) -> LuaResult> { + Err(LuaError::runtime(format!( + "TODO: Support require for absolute paths (tried to require '{path}')" + ))) +} diff --git a/src/lune/mod.rs b/src/lune/mod.rs index bf41a4b..1b5a602 100644 --- a/src/lune/mod.rs +++ b/src/lune/mod.rs @@ -1,6 +1,7 @@ use std::process::ExitCode; mod error; +mod globals; mod scheduler; mod util; @@ -12,6 +13,7 @@ use mlua::Lua; #[derive(Debug, Clone)] pub struct Lune { lua: &'static Lua, + scheduler: &'static Scheduler<'static, 'static>, args: Vec, } @@ -21,8 +23,19 @@ impl Lune { */ #[allow(clippy::new_without_default)] pub fn new() -> Self { + // FIXME: Leaking these and using a manual drop implementation + // does not feel great... is there any way for us to create a + // scheduler, store it in app data, and guarantee it has + // the same lifetime as Lua without using any unsafe? + let lua = Lua::new().into_static(); + let scheduler = Scheduler::new(lua).into_static(); + + lua.set_app_data(scheduler); + globals::inject_all(lua).expect("Failed to inject lua globals"); + Self { - lua: Lua::new().into_static(), + lua, + scheduler, args: Vec::new(), } } @@ -42,29 +55,29 @@ impl Lune { Runs a Lune script inside of a new Luau VM. */ pub async fn run( - &self, + &mut self, script_name: impl AsRef, script_contents: impl AsRef<[u8]>, ) -> Result { - let scheduler = Scheduler::new(self.lua); - self.lua.set_app_data(scheduler.clone()); - let main = self .lua .load(script_contents.as_ref()) .set_name(script_name.as_ref()); - scheduler.push_back(main, ())?; - Ok(scheduler.run_to_completion().await) + self.scheduler.push_back(main, ())?; + Ok(self.scheduler.run_to_completion().await) } } impl Drop for Lune { fn drop(&mut self) { - // SAFETY: The scheduler needs the static lifetime reference to lua, - // when dropped nothing outside of here has access to the scheduler + // SAFETY: When the Lune struct is dropped, it is guaranteed + // that the Lua and Scheduler structs are no longer being used, + // since all the methods that reference them (eg. `run`) + // take an exclusive / mutable reference unsafe { Lua::from_static(self.lua); + Scheduler::from_static(self.scheduler); } } } diff --git a/src/lune/scheduler/impl_async.rs b/src/lune/scheduler/impl_async.rs index 9ae53a5..fa3bf99 100644 --- a/src/lune/scheduler/impl_async.rs +++ b/src/lune/scheduler/impl_async.rs @@ -23,6 +23,8 @@ where /** Schedules the given `thread` to run when the given `fut` completes. + + If the given future returns a [`LuaError`], that error will be passed to the given `thread`. */ pub fn schedule_future_thread( &'fut self, @@ -35,6 +37,7 @@ where { let thread = thread.into_owned_lua_thread(self.lua)?; self.schedule_future(async move { + // TODO: Throw any error back to lua instead of panicking here let rets = fut.await.expect("Failed to receive result"); let rets = rets .into_lua_multi(self.lua) diff --git a/src/lune/scheduler/mod.rs b/src/lune/scheduler/mod.rs index 2bbb373..8b970a0 100644 --- a/src/lune/scheduler/mod.rs +++ b/src/lune/scheduler/mod.rs @@ -51,4 +51,14 @@ impl<'lua, 'fut> Scheduler<'lua, 'fut> { futures: Arc::new(AsyncMutex::new(FuturesUnordered::new())), } } + + #[doc(hidden)] + pub fn into_static(self) -> &'static Self { + Box::leak(Box::new(self)) + } + + #[doc(hidden)] + pub unsafe fn from_static(lua: &'static Scheduler) -> Self { + *Box::from_raw(lua as *const Scheduler as *mut Scheduler) + } } diff --git a/src/lune/scheduler/traits.rs b/src/lune/scheduler/traits.rs index 3531c7b..9731608 100644 --- a/src/lune/scheduler/traits.rs +++ b/src/lune/scheduler/traits.rs @@ -65,6 +65,7 @@ where let async_func = self .load(ASYNC_IMPL_LUA) .set_name("async") + .set_environment(async_env) .into_function()?; Ok(async_func) } diff --git a/src/tests.rs b/src/tests.rs index d88c047..af56722 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -20,7 +20,7 @@ macro_rules! create_tests { // The rest of the test logic can continue as normal let full_name = format!("tests/{}.luau", $value); let script = read_to_string(&full_name).await?; - let lune = Lune::new().with_args( + let mut lune = Lune::new().with_args( ARGS .clone() .iter()