mirror of
https://github.com/lune-org/lune.git
synced 2024-12-12 13:00:37 +00:00
Initial scaffolding to get custom globals and require working
This commit is contained in:
parent
7fe43a969f
commit
7d73601a58
13 changed files with 193 additions and 11 deletions
|
@ -32,7 +32,7 @@ pub async fn show_interface() -> Result<ExitCode> {
|
||||||
let mut prompt_state = PromptState::Regular;
|
let mut prompt_state = PromptState::Regular;
|
||||||
let mut source_code = String::new();
|
let mut source_code = String::new();
|
||||||
|
|
||||||
let lune_instance = Lune::new();
|
let mut lune_instance = Lune::new();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let prompt = match prompt_state {
|
let prompt = match prompt_state {
|
||||||
|
|
18
src/lune/globals/mod.rs
Normal file
18
src/lune/globals/mod.rs
Normal 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(())
|
||||||
|
}
|
13
src/lune/globals/require/absolute.rs
Normal file
13
src/lune/globals/require/absolute.rs
Normal 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}')"
|
||||||
|
)))
|
||||||
|
}
|
14
src/lune/globals/require/alias.rs
Normal file
14
src/lune/globals/require/alias.rs
Normal 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}')"
|
||||||
|
)))
|
||||||
|
}
|
13
src/lune/globals/require/builtin.rs
Normal file
13
src/lune/globals/require/builtin.rs
Normal 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}')"
|
||||||
|
)))
|
||||||
|
}
|
43
src/lune/globals/require/context.rs
Normal file
43
src/lune/globals/require/context.rs
Normal 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")
|
||||||
|
}
|
||||||
|
}
|
41
src/lune/globals/require/mod.rs
Normal file
41
src/lune/globals/require/mod.rs
Normal 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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
13
src/lune/globals/require/relative.rs
Normal file
13
src/lune/globals/require/relative.rs
Normal 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}')"
|
||||||
|
)))
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
use std::process::ExitCode;
|
use std::process::ExitCode;
|
||||||
|
|
||||||
mod error;
|
mod error;
|
||||||
|
mod globals;
|
||||||
mod scheduler;
|
mod scheduler;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
|
@ -12,6 +13,7 @@ use mlua::Lua;
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Lune {
|
pub struct Lune {
|
||||||
lua: &'static Lua,
|
lua: &'static Lua,
|
||||||
|
scheduler: &'static Scheduler<'static, 'static>,
|
||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,8 +23,19 @@ impl Lune {
|
||||||
*/
|
*/
|
||||||
#[allow(clippy::new_without_default)]
|
#[allow(clippy::new_without_default)]
|
||||||
pub fn new() -> Self {
|
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 {
|
Self {
|
||||||
lua: Lua::new().into_static(),
|
lua,
|
||||||
|
scheduler,
|
||||||
args: Vec::new(),
|
args: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,29 +55,29 @@ impl Lune {
|
||||||
Runs a Lune script inside of a new Luau VM.
|
Runs a Lune script inside of a new Luau VM.
|
||||||
*/
|
*/
|
||||||
pub async fn run(
|
pub async fn run(
|
||||||
&self,
|
&mut self,
|
||||||
script_name: impl AsRef<str>,
|
script_name: impl AsRef<str>,
|
||||||
script_contents: impl AsRef<[u8]>,
|
script_contents: impl AsRef<[u8]>,
|
||||||
) -> Result<ExitCode, LuneError> {
|
) -> Result<ExitCode, LuneError> {
|
||||||
let scheduler = Scheduler::new(self.lua);
|
|
||||||
self.lua.set_app_data(scheduler.clone());
|
|
||||||
|
|
||||||
let main = self
|
let main = self
|
||||||
.lua
|
.lua
|
||||||
.load(script_contents.as_ref())
|
.load(script_contents.as_ref())
|
||||||
.set_name(script_name.as_ref());
|
.set_name(script_name.as_ref());
|
||||||
|
|
||||||
scheduler.push_back(main, ())?;
|
self.scheduler.push_back(main, ())?;
|
||||||
Ok(scheduler.run_to_completion().await)
|
Ok(self.scheduler.run_to_completion().await)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Lune {
|
impl Drop for Lune {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
// SAFETY: The scheduler needs the static lifetime reference to lua,
|
// SAFETY: When the Lune struct is dropped, it is guaranteed
|
||||||
// when dropped nothing outside of here has access to the scheduler
|
// 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 {
|
unsafe {
|
||||||
Lua::from_static(self.lua);
|
Lua::from_static(self.lua);
|
||||||
|
Scheduler::from_static(self.scheduler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,8 @@ where
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Schedules the given `thread` to run when the given `fut` completes.
|
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>(
|
pub fn schedule_future_thread<F, FR>(
|
||||||
&'fut self,
|
&'fut self,
|
||||||
|
@ -35,6 +37,7 @@ where
|
||||||
{
|
{
|
||||||
let thread = thread.into_owned_lua_thread(self.lua)?;
|
let thread = thread.into_owned_lua_thread(self.lua)?;
|
||||||
self.schedule_future(async move {
|
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 = fut.await.expect("Failed to receive result");
|
||||||
let rets = rets
|
let rets = rets
|
||||||
.into_lua_multi(self.lua)
|
.into_lua_multi(self.lua)
|
||||||
|
|
|
@ -51,4 +51,14 @@ impl<'lua, 'fut> Scheduler<'lua, 'fut> {
|
||||||
futures: Arc::new(AsyncMutex::new(FuturesUnordered::new())),
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,6 +65,7 @@ where
|
||||||
let async_func = self
|
let async_func = self
|
||||||
.load(ASYNC_IMPL_LUA)
|
.load(ASYNC_IMPL_LUA)
|
||||||
.set_name("async")
|
.set_name("async")
|
||||||
|
.set_environment(async_env)
|
||||||
.into_function()?;
|
.into_function()?;
|
||||||
Ok(async_func)
|
Ok(async_func)
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ macro_rules! create_tests {
|
||||||
// The rest of the test logic can continue as normal
|
// The rest of the test logic can continue as normal
|
||||||
let full_name = format!("tests/{}.luau", $value);
|
let full_name = format!("tests/{}.luau", $value);
|
||||||
let script = read_to_string(&full_name).await?;
|
let script = read_to_string(&full_name).await?;
|
||||||
let lune = Lune::new().with_args(
|
let mut lune = Lune::new().with_args(
|
||||||
ARGS
|
ARGS
|
||||||
.clone()
|
.clone()
|
||||||
.iter()
|
.iter()
|
||||||
|
|
Loading…
Reference in a new issue