From d6dbe5c8611603bc4cbabedf210cc782b85a1a3a Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Sat, 19 Aug 2023 19:18:36 -0500 Subject: [PATCH] Add back luau builtin --- src/lune/builtins/luau/mod.rs | 39 ++++++++++ src/lune/builtins/luau/options.rs | 116 ++++++++++++++++++++++++++++++ src/lune/builtins/mod.rs | 5 ++ 3 files changed, 160 insertions(+) create mode 100644 src/lune/builtins/luau/mod.rs create mode 100644 src/lune/builtins/luau/options.rs diff --git a/src/lune/builtins/luau/mod.rs b/src/lune/builtins/luau/mod.rs new file mode 100644 index 0000000..b8c989b --- /dev/null +++ b/src/lune/builtins/luau/mod.rs @@ -0,0 +1,39 @@ +use mlua::prelude::*; + +use crate::lune::util::TableBuilder; + +mod options; +use options::{LuauCompileOptions, LuauLoadOptions}; + +const BYTECODE_ERROR_BYTE: u8 = 0; + +pub fn create(lua: &Lua) -> LuaResult { + TableBuilder::new(lua)? + .with_function("compile", compile_source)? + .with_function("load", load_source)? + .build_readonly() +} + +fn compile_source<'lua>( + lua: &'lua Lua, + (source, options): (LuaString<'lua>, LuauCompileOptions), +) -> LuaResult> { + let bytecode = options.into_compiler().compile(source); + + match bytecode.first() { + Some(&BYTECODE_ERROR_BYTE) => Err(LuaError::RuntimeError( + String::from_utf8_lossy(&bytecode).into_owned(), + )), + Some(_) => lua.create_string(bytecode), + None => panic!("Compiling resulted in empty bytecode"), + } +} + +fn load_source<'lua>( + lua: &'lua Lua, + (source, options): (LuaString<'lua>, LuauLoadOptions), +) -> LuaResult> { + lua.load(source.as_bytes()) + .set_name(options.debug_name) + .into_function() +} diff --git a/src/lune/builtins/luau/options.rs b/src/lune/builtins/luau/options.rs new file mode 100644 index 0000000..d1febe6 --- /dev/null +++ b/src/lune/builtins/luau/options.rs @@ -0,0 +1,116 @@ +use mlua::prelude::*; +use mlua::Compiler as LuaCompiler; + +const DEFAULT_DEBUG_NAME: &str = "luau.load(...)"; + +pub struct LuauCompileOptions { + pub(crate) optimization_level: u8, + pub(crate) coverage_level: u8, + pub(crate) debug_level: u8, +} + +impl LuauCompileOptions { + pub fn into_compiler(self) -> LuaCompiler { + LuaCompiler::default() + .set_optimization_level(self.optimization_level) + .set_coverage_level(self.coverage_level) + .set_debug_level(self.debug_level) + } +} + +impl Default for LuauCompileOptions { + fn default() -> Self { + // NOTE: This is the same as LuaCompiler::default() values, but they are + // not accessible from outside of mlua so we need to recreate them here. + Self { + optimization_level: 1, + coverage_level: 0, + debug_level: 1, + } + } +} + +impl<'lua> FromLua<'lua> for LuauCompileOptions { + fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult { + Ok(match value { + LuaValue::Nil => Self::default(), + LuaValue::Table(t) => { + let mut options = Self::default(); + + let get_and_check = |name: &'static str| -> LuaResult> { + match t.get(name)? { + Some(n @ (0 | 1 | 2)) => Ok(Some(n)), + Some(n) => Err(LuaError::runtime(format!( + "'{name}' must be one of: 0, 1, or 2 - got {n}" + ))), + None => Ok(None), + } + }; + + if let Some(optimization_level) = get_and_check("optimizationLevel")? { + options.optimization_level = optimization_level; + } + if let Some(coverage_level) = get_and_check("coverageLevel")? { + options.coverage_level = coverage_level; + } + if let Some(debug_level) = get_and_check("debugLevel")? { + options.debug_level = debug_level; + } + + options + } + _ => { + return Err(LuaError::FromLuaConversionError { + from: value.type_name(), + to: "CompileOptions", + message: Some(format!( + "Invalid compile options - expected table, got {}", + value.type_name() + )), + }) + } + }) + } +} + +pub struct LuauLoadOptions { + pub(crate) debug_name: String, +} + +impl Default for LuauLoadOptions { + fn default() -> Self { + Self { + debug_name: DEFAULT_DEBUG_NAME.to_string(), + } + } +} + +impl<'lua> FromLua<'lua> for LuauLoadOptions { + fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult { + Ok(match value { + LuaValue::Nil => Self::default(), + LuaValue::Table(t) => { + let mut options = Self::default(); + + if let Some(debug_name) = t.get("debugName")? { + options.debug_name = debug_name; + } + + options + } + LuaValue::String(s) => Self { + debug_name: s.to_string_lossy().to_string(), + }, + _ => { + return Err(LuaError::FromLuaConversionError { + from: value.type_name(), + to: "LoadOptions", + message: Some(format!( + "Invalid load options - expected string or table, got {}", + value.type_name() + )), + }) + } + }) + } +} diff --git a/src/lune/builtins/mod.rs b/src/lune/builtins/mod.rs index 51cdb55..a813b5f 100644 --- a/src/lune/builtins/mod.rs +++ b/src/lune/builtins/mod.rs @@ -2,11 +2,13 @@ use std::str::FromStr; use mlua::prelude::*; +mod luau; mod stdio; mod task; #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] pub enum LuneBuiltin { + Luau, Task, Stdio, } @@ -17,6 +19,7 @@ where { pub fn name(&self) -> &'static str { match self { + Self::Luau => "luau", Self::Task => "task", Self::Stdio => "stdio", } @@ -24,6 +27,7 @@ where pub fn create(&self, lua: &'lua Lua) -> LuaResult> { let res = match self { + Self::Luau => luau::create(lua), Self::Task => task::create(lua), Self::Stdio => stdio::create(lua), }; @@ -41,6 +45,7 @@ impl FromStr for LuneBuiltin { type Err = String; fn from_str(s: &str) -> Result { match s.trim().to_ascii_lowercase().as_str() { + "luau" => Ok(Self::Luau), "task" => Ok(Self::Task), "stdio" => Ok(Self::Stdio), _ => Err(format!("Unknown builtin library '{s}'")),