Initial scaffolding to get custom globals and require working

This commit is contained in:
Filip Tibell 2023-08-18 16:35:33 -05:00
parent 7fe43a969f
commit 7d73601a58
13 changed files with 193 additions and 11 deletions

View file

@ -32,7 +32,7 @@ pub async fn show_interface() -> Result<ExitCode> {
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 {

18
src/lune/globals/mod.rs Normal file
View file

@ -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(())
}

View file

@ -0,0 +1,13 @@
use mlua::prelude::*;
use super::context::*;
pub(super) async fn require<'lua>(
_lua: &'lua Lua,
_ctx: RequireContext,
path: &str,
) -> LuaResult<LuaValue<'lua>> {
Err(LuaError::runtime(format!(
"TODO: Support require for absolute paths (tried to require '{path}')"
)))
}

View file

@ -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<LuaValue<'lua>> {
Err(LuaError::runtime(format!(
"TODO: Support require for built-in libraries (tried to require '{name}' with alias '{alias}')"
)))
}

View file

@ -0,0 +1,13 @@
use mlua::prelude::*;
use super::context::*;
pub(super) async fn require<'lua>(
_lua: &'lua Lua,
_ctx: RequireContext,
name: &str,
) -> LuaResult<LuaValue<'lua>> {
Err(LuaError::runtime(format!(
"TODO: Support require for built-in libraries (tried to require '{name}')"
)))
}

View file

@ -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<Self> {
if let LuaValue::UserData(ud) = value {
if let Ok(ctx) = ud.borrow::<RequireContext>() {
return Ok(ctx.clone());
}
}
unreachable!("RequireContext should only be used from registry")
}
}

View file

@ -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<impl IntoLua<'_>> {
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
}
})
}

View file

@ -0,0 +1,13 @@
use mlua::prelude::*;
use super::context::*;
pub(super) async fn require<'lua>(
_lua: &'lua Lua,
_ctx: RequireContext,
path: &str,
) -> LuaResult<LuaValue<'lua>> {
Err(LuaError::runtime(format!(
"TODO: Support require for absolute paths (tried to require '{path}')"
)))
}

View file

@ -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<String>,
}
@ -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<str>,
script_contents: impl AsRef<[u8]>,
) -> Result<ExitCode, LuneError> {
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);
}
}
}

View file

@ -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<F, FR>(
&'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)

View file

@ -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)
}
}

View file

@ -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)
}

View file

@ -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()