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 fs;
|
||||||
|
pub mod luau;
|
||||||
pub mod net;
|
pub mod net;
|
||||||
pub mod process;
|
pub mod process;
|
||||||
pub mod serde;
|
pub mod serde;
|
||||||
|
|
|
@ -14,6 +14,7 @@ pub fn create(lua: &'static Lua, args: Vec<String>) -> LuaResult<()> {
|
||||||
("serde", builtins::serde::create(lua)?),
|
("serde", builtins::serde::create(lua)?),
|
||||||
("stdio", builtins::stdio::create(lua)?),
|
("stdio", builtins::stdio::create(lua)?),
|
||||||
("task", builtins::task::create(lua)?),
|
("task", builtins::task::create(lua)?),
|
||||||
|
("luau", builtins::luau::create(lua)?),
|
||||||
#[cfg(feature = "roblox")]
|
#[cfg(feature = "roblox")]
|
||||||
("roblox", builtins::roblox::create(lua)?),
|
("roblox", builtins::roblox::create(lua)?),
|
||||||
];
|
];
|
||||||
|
|
|
@ -101,6 +101,9 @@ create_tests! {
|
||||||
task_delay: "task/delay",
|
task_delay: "task/delay",
|
||||||
task_spawn: "task/spawn",
|
task_spawn: "task/spawn",
|
||||||
task_wait: "task/wait",
|
task_wait: "task/wait",
|
||||||
|
|
||||||
|
luau_compile: "luau/compile",
|
||||||
|
luau_load: "luau/load",
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "roblox")]
|
#[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