mirror of
https://github.com/lune-org/lune.git
synced 2024-12-12 13:00:37 +00:00
Implement new luau
built-in library. (#82)
This commit is contained in:
parent
e57b8c3b15
commit
0a8773dc04
7 changed files with 305 additions and 0 deletions
134
src/lune/builtins/luau.rs
Normal file
134
src/lune/builtins/luau.rs
Normal file
|
@ -0,0 +1,134 @@
|
|||
use mlua::prelude::*;
|
||||
use mlua::Compiler as LuaCompiler;
|
||||
|
||||
use crate::lune::lua::table::TableBuilder;
|
||||
|
||||
const DEFAULT_DEBUG_NAME: &str = "luau.load(...)";
|
||||
const BYTECODE_ERROR_BYTE: u8 = 0;
|
||||
|
||||
struct CompileOptions {
|
||||
pub optimization_level: u8,
|
||||
pub coverage_level: u8,
|
||||
pub debug_level: u8,
|
||||
}
|
||||
|
||||
impl Default for CompileOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
optimization_level: 1,
|
||||
coverage_level: 0,
|
||||
debug_level: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'lua> FromLua<'lua> for CompileOptions {
|
||||
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
|
||||
Ok(match value {
|
||||
LuaValue::Nil => Self {
|
||||
optimization_level: 1,
|
||||
coverage_level: 0,
|
||||
debug_level: 1,
|
||||
},
|
||||
LuaValue::Table(t) => {
|
||||
let optimization_level: Option<u8> = t.get("optimizationLevel")?;
|
||||
let coverage_level: Option<u8> = t.get("coverageLevel")?;
|
||||
let debug_level: Option<u8> = t.get("debugLevel")?;
|
||||
|
||||
Self {
|
||||
optimization_level: optimization_level.unwrap_or(1),
|
||||
coverage_level: coverage_level.unwrap_or(0),
|
||||
debug_level: debug_level.unwrap_or(1),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(LuaError::FromLuaConversionError {
|
||||
from: value.type_name(),
|
||||
to: "CompileOptions",
|
||||
message: Some(format!(
|
||||
"Invalid compile options - expected table, got {}",
|
||||
value.type_name()
|
||||
)),
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct LoadOptions {
|
||||
pub debug_name: String,
|
||||
}
|
||||
|
||||
impl Default for LoadOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
debug_name: DEFAULT_DEBUG_NAME.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'lua> FromLua<'lua> for LoadOptions {
|
||||
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
|
||||
Ok(match value {
|
||||
LuaValue::Nil => Self {
|
||||
debug_name: DEFAULT_DEBUG_NAME.to_string(),
|
||||
},
|
||||
LuaValue::Table(t) => {
|
||||
let debug_name: Option<String> = t.get("debugName")?;
|
||||
|
||||
Self {
|
||||
debug_name: debug_name.unwrap_or(DEFAULT_DEBUG_NAME.to_string()),
|
||||
}
|
||||
}
|
||||
LuaValue::String(s) => Self {
|
||||
debug_name: s.to_str()?.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()
|
||||
)),
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> {
|
||||
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>, CompileOptions),
|
||||
) -> LuaResult<LuaString<'lua>> {
|
||||
let source_bytecode_bytes = LuaCompiler::default()
|
||||
.set_optimization_level(options.optimization_level)
|
||||
.set_coverage_level(options.coverage_level)
|
||||
.set_debug_level(options.debug_level)
|
||||
.compile(source);
|
||||
|
||||
let first_byte = source_bytecode_bytes.first().unwrap();
|
||||
|
||||
match *first_byte {
|
||||
BYTECODE_ERROR_BYTE => Err(LuaError::RuntimeError(
|
||||
String::from_utf8(source_bytecode_bytes).unwrap(),
|
||||
)),
|
||||
_ => lua.create_string(source_bytecode_bytes),
|
||||
}
|
||||
}
|
||||
|
||||
fn load_source<'a>(
|
||||
lua: &'static Lua,
|
||||
(source, options): (LuaString<'a>, LoadOptions),
|
||||
) -> LuaResult<LuaFunction<'a>> {
|
||||
lua.load(source.to_str()?.trim_start())
|
||||
.set_name(options.debug_name)
|
||||
.into_function()
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
pub mod fs;
|
||||
pub mod luau;
|
||||
pub mod net;
|
||||
pub mod process;
|
||||
pub mod serde;
|
||||
|
|
|
@ -14,6 +14,7 @@ pub fn create(lua: &'static Lua, args: Vec<String>) -> LuaResult<()> {
|
|||
("serde", builtins::serde::create(lua)?),
|
||||
("stdio", builtins::stdio::create(lua)?),
|
||||
("task", builtins::task::create(lua)?),
|
||||
("luau", builtins::luau::create(lua)?),
|
||||
#[cfg(feature = "roblox")]
|
||||
("roblox", builtins::roblox::create(lua)?),
|
||||
];
|
||||
|
|
|
@ -101,6 +101,9 @@ create_tests! {
|
|||
task_delay: "task/delay",
|
||||
task_spawn: "task/spawn",
|
||||
task_wait: "task/wait",
|
||||
|
||||
luau_compile: "luau/compile",
|
||||
luau_load: "luau/load",
|
||||
}
|
||||
|
||||
#[cfg(feature = "roblox")]
|
||||
|
|
17
tests/luau/compile.luau
Normal file
17
tests/luau/compile.luau
Normal file
|
@ -0,0 +1,17 @@
|
|||
local luau = require("@lune/luau")
|
||||
|
||||
local EMPTY_LUAU_CODE_BLOCK = "do end"
|
||||
local BROKEN_LUAU_CODE_BLOCK = "do"
|
||||
|
||||
assert(type(luau.compile) == "function", "expected `luau.compile` to be a function")
|
||||
|
||||
assert(
|
||||
type(luau.compile(EMPTY_LUAU_CODE_BLOCK)) == "string",
|
||||
"expected `luau.compile` to return bytecode string"
|
||||
)
|
||||
|
||||
local success = pcall(function()
|
||||
luau.compile(BROKEN_LUAU_CODE_BLOCK)
|
||||
end)
|
||||
|
||||
assert(success == false, "expected 'BROKEN_LUAU_CODE_BLOCK' to fail to compile into bytecode.")
|
33
tests/luau/load.luau
Normal file
33
tests/luau/load.luau
Normal file
|
@ -0,0 +1,33 @@
|
|||
local luau = require("@lune/luau")
|
||||
|
||||
local RETURN_VALUE = 1
|
||||
|
||||
local EMPTY_LUAU_CODE_BLOCK = "do end"
|
||||
local RETURN_LUAU_CODE_BLOCK = "return " .. tostring(RETURN_VALUE)
|
||||
|
||||
local CUSTOM_SOURCE_BLOCK_NAME = "test"
|
||||
|
||||
assert(type(luau.load) == "function", "expected `luau.compile` to be a function")
|
||||
|
||||
assert(
|
||||
type(luau.load(EMPTY_LUAU_CODE_BLOCK)) == "function",
|
||||
"expected 'luau.load' to return a function"
|
||||
)
|
||||
assert(
|
||||
luau.load(RETURN_LUAU_CODE_BLOCK)() == RETURN_VALUE,
|
||||
"expected 'luau.load' to return a value"
|
||||
)
|
||||
|
||||
local sourceFunction = luau.load(EMPTY_LUAU_CODE_BLOCK, { debugName = CUSTOM_SOURCE_BLOCK_NAME })
|
||||
local sourceFunctionDebugName = debug.info(sourceFunction, "s")
|
||||
|
||||
assert(
|
||||
string.find(sourceFunctionDebugName, CUSTOM_SOURCE_BLOCK_NAME),
|
||||
"expected source block name for 'luau.load' to return a custom debug name"
|
||||
)
|
||||
|
||||
local success = pcall(function()
|
||||
luau.load(luau.compile(RETURN_LUAU_CODE_BLOCK))
|
||||
end)
|
||||
|
||||
assert(success, "expected `luau.load` to be able to process the result of `luau.compile`")
|
116
types/Luau.luau
Normal file
116
types/Luau.luau
Normal file
|
@ -0,0 +1,116 @@
|
|||
--[=[
|
||||
@interface CompileOptions
|
||||
@within Luau
|
||||
|
||||
The Luau compiler options used in generating luau bytecode
|
||||
|
||||
This is a dictionary that may contain one or more of the following values:
|
||||
|
||||
* `optimizationLevel` - Sets the compiler option "optimizationLevel". Defaults to `1`
|
||||
* `coverageLevel` - Sets the compiler option "coverageLevel". Defaults to `0`
|
||||
* `debugLevel` - Sets the compiler option "debugLevel". Defaults to `1`
|
||||
|
||||
Documentation regarding what these values represent can be found here;
|
||||
* https://github.com/Roblox/luau/blob/bd229816c0a82a8590395416c81c333087f541fd/Compiler/include/luacode.h#L13
|
||||
]=]
|
||||
export type CompileOptions = {
|
||||
optimizationLevel: number,
|
||||
coverageLevel: number,
|
||||
debugLevel: number,
|
||||
}
|
||||
|
||||
--[=[
|
||||
@interface LoadOptions
|
||||
@within Luau
|
||||
|
||||
The Luau load options are used for generating a lua function from either bytecode or sourcecode
|
||||
|
||||
This is a dictionary that may contain one or more of the following values:
|
||||
|
||||
* `debugName` - The debug name of the closure. Defaults to `string ["..."]`
|
||||
]=]
|
||||
export type LoadOptions = {
|
||||
debugName: string,
|
||||
}
|
||||
|
||||
--[=[
|
||||
@class Luau
|
||||
|
||||
Built-in library for generating luau bytecode & functions.
|
||||
|
||||
### Example usage
|
||||
|
||||
```lua
|
||||
local luau = require("@lune/luau")
|
||||
|
||||
local bytecode = luau.compile("print('Hello, World!')")
|
||||
local callableFn = luau.load(bytecode)
|
||||
|
||||
-- Additionally, we can skip the bytecode generation and load a callable function directly from the code itself.
|
||||
-- local callableFn = luau.load("print('Hello, World!')")
|
||||
|
||||
callableFn()
|
||||
```
|
||||
]=]
|
||||
local luau = {}
|
||||
|
||||
--[=[
|
||||
@within Luau
|
||||
|
||||
Compiles sourcecode into Luau bytecode
|
||||
|
||||
An error will be thrown if the sourcecode given isn't valid Luau code.
|
||||
|
||||
### Example usage
|
||||
|
||||
```lua
|
||||
local luau = require("@lune/luau")
|
||||
|
||||
local bytecode = luau.compile("print('Hello, World!')", {
|
||||
optimizationLevel: 1,
|
||||
coverageLevel: 0,
|
||||
debugLevel: 1,
|
||||
})
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
@param source The string that'll be compiled into bytecode
|
||||
@param CompileOptions The luau compiler options used when compiling the source string
|
||||
|
||||
@return luau bytecode
|
||||
]=]
|
||||
function luau.compile(source: string, CompileOptions: CompileOptions): string
|
||||
return nil :: any
|
||||
end
|
||||
|
||||
--[=[
|
||||
@within Luau
|
||||
|
||||
Generates a function from either bytecode or sourcecode
|
||||
|
||||
An error will be thrown if the sourcecode given isn't valid luau code.
|
||||
|
||||
### Example usage
|
||||
|
||||
```lua
|
||||
local luau = require("@lune/luau")
|
||||
|
||||
local bytecode = luau.compile("print('Hello, World!')")
|
||||
local callableFn = luau.load(bytecode, {
|
||||
debugName = "'Hello, World'"
|
||||
})
|
||||
|
||||
callableFn()
|
||||
```
|
||||
|
||||
@param source Either bytecode or sourcecode
|
||||
@param loadOptions The load options used when creating a callbable function
|
||||
|
||||
@return luau function
|
||||
]=]
|
||||
function luau.load(source: string, loadOptions: LoadOptions): string
|
||||
return nil :: any
|
||||
end
|
||||
|
||||
return luau
|
Loading…
Reference in a new issue