glua
A library to embed Lua in Gleam programs.
Gleam wrapper around Luerl.
Types
Represents an action that can be run within a Lua state and potentially mutates that state.
Actions are how we interact with a Lua VM and thus many functions in this library
returns an Action when invoked. It is important to note that the execution of any Action is defered until
you pass it to glua.run:
// no Lua code has been evaluated or even parsed,
// we're just creating an `Action`
let action = glua.eval("return 1")
glua.run(glua.new(), action) // now our Lua code is evaluated
Actions can fail and can mutate the Lua state. When calling multiple Actions in sequence,
you need to make sure each one is executed within the Lua state returned by the previous one since
executing an Action using outdated state could lead to unexpected behaviour.
let state = glua.new()
let result = {
use #(new_state, _) <- result.try(
glua.exec(state, glua.set(keys: ["a_number"], value: glua.int(36)))
)
use #(new_state, ret) <- result.try(
glua.exec(state, glua.eval("return math.sqrt(a_number)"))
)
// we know that `math.sqrt` only returns one value
let assert [ref] = ret
glua.run(new_state, glua.dereference(ref:, using: decode.float))
}
result
// -> Ok(6.0)
However, glua provides function to compose Actionss toghether without having to pass
the state explicitly. The most common of such functions is glua.then, which allows us to take
an existing Action and use its return value to construct another Action.
glua.then will automatically pass the state returned by the first action to the second one
and it will halt the chain as soon as any Action fails (like result.try).
This is equivalent to the above example:
let state = glua.new()
let action = {
use _ <- glua.then(glua.set(keys: ["a_number"], value: glua.int(36)))
use ret <- glua.then(glua.eval(code: "return math.sqrt(a_number)"))
// we know that `math.sqrt` only returns one value
let assert [ref] = ret
glua.dereference(ref:, using: decode.float)
}
glua.run(state, action)
// -> Ok(6.0)
An Action takes two types parameters, return is the type of the value that the Action
would return in case it succeeds, and error is the type of custom errors that
the Action could return.
pub opaque type Action(return, error)
Represents the errors than can happend during the parsing and execution of Lua code
pub type Error(error) {
LuaCompileFailure(errors: List(LuaCompileError))
LuaRuntimeException(
exception: LuaRuntimeExceptionKind,
state: Lua,
)
KeyNotFound(key: List(String))
FileNotFound(path: String)
UnexpectedResultType(List(decode.DecodeError))
CustomError(error: error)
UnknownError(error: dynamic.Dynamic)
}
Constructors
-
LuaCompileFailure(errors: List(LuaCompileError))The compilation process of the Lua code failed because of the presence of one or more compile errors.
-
LuaRuntimeException( exception: LuaRuntimeExceptionKind, state: Lua, )The Lua environment threw an exception during code execution.
-
KeyNotFound(key: List(String))A certain key was not found in the Lua environment.
-
FileNotFound(path: String)A Lua source file was not found
-
UnexpectedResultType(List(decode.DecodeError))The value returned by the Lua environment could not be decoded using the provided decoder.
-
CustomError(error: error)An app-defined error
-
UnknownError(error: dynamic.Dynamic)An error that could not be identified.
Represents a Lua compilation error
pub type LuaCompileError {
LuaCompileError(
line: Int,
kind: LuaCompileErrorKind,
message: String,
)
}
Constructors
-
LuaCompileError( line: Int, kind: LuaCompileErrorKind, message: String, )
Represents the kind of a Lua compilation error
pub type LuaCompileErrorKind {
Parse
Tokenize
}
Constructors
-
Parse -
Tokenize
Represents the kind of exceptions that can happen at runtime during Lua code execution.
pub type LuaRuntimeExceptionKind {
IllegalIndex(index: String, value: String)
ErrorCall(message: String, level: option.Option(Int))
UndefinedFunction(value: String)
UndefinedMethod(object: String, method: String)
BadArith(operator: String, args: List(String))
Badarg(function: String, args: List(dynamic.Dynamic))
AssertError(message: String)
UnknownException
}
Constructors
-
IllegalIndex(index: String, value: String)The exception that happens when trying to access an index that does not exists on a table (also happens when indexing non-table values).
-
ErrorCall(message: String, level: option.Option(Int))The exception that happens when the
errorfunction is called. -
UndefinedFunction(value: String)The exception that happens when trying to call a function that is not defined.
-
UndefinedMethod(object: String, method: String)The exception that happens when trying to call a method that is not defined for an object.
-
BadArith(operator: String, args: List(String))The exception that happens when an invalid arithmetic operation is performed.
-
Badarg(function: String, args: List(dynamic.Dynamic))The exception that happens when a function is called with incorrect arguments.
-
AssertError(message: String)The exception that happens when a call to assert is made passing a value that evalues to
falseas the first argument. -
UnknownExceptionAn exception that could not be identified
This type is used to represent a value that can never happen. What does that mean exactly?
- A
Boolis a type that has two values:TrueandFalse. Nilis a type that has one value:Nil.Neveris a type that has zero values: it’s impossible to construct!
This library uses this type to make glua.failure impossible to construct in glua.functions
to encourage using glua.error instead since glua.failure wouldn’t make sense in that case.
pub type Never
Values
pub fn call_function(
ref fun: Value,
args args: List(Value),
) -> Action(List(Value), e)
Calls a Lua function by reference.
Examples
glua.run(glua.new(), {
use fun <- glua.then(
glua.eval(code: "return math.sqrt") |> glua.try(list.first)
)
glua.call_function(
ref: fun,
args: [glua.int(81)],
)
|> glua.returning_multi(using: decode.float)
})
// -> Ok([9.0])
let code = "function fib(n)
if n <= 1 then
return n
else
return fib(n - 1) + fib(n - 2)
end
end
return fib
"
glua.run(glua.new(), {
use fun <- glua.then(glua.eval(code:) |> glua.try(list.first))
glua.call_function(
ref: fun,
args: [glua.int(10)],
)
|> glua.returning_multi(using: decode.int)
})
// -> Ok([55])
pub fn call_function_by_name(
keys keys: List(String),
args args: List(Value),
) -> Action(List(Value), e)
Gets a reference to the function at keys, then inmediatly calls it with the provided args.
This is a shorthand for glua.get followed by glua.call_function.
Examples
glua.call_function_by_name(
keys: ["string", "upper"],
args: [glua.string("hello from Gleam!")]
))
|> glua.returning_multi(using: decode.string)
|> glua.run(glua.new(), _)
// -> Ok("HELLO FROM GLEAM!")
pub const default_sandbox: List(List(String))
List of Lua modules and functions that will be sandboxed by default
pub fn delete_private(state lua: Lua, key key: String) -> Lua
Remove a private value that is not exposed to the Lua runtime.
Examples
let lua = glua.set_private(glua.new(), "my_value", "will_be_removed")
assert glua.get(lua, "my_value", decode.string) == Ok("will_be_removed")
assert glua.delete_private(lua, "my_value")
|> glua.get("my_value", decode.string)
== Error(glua.KeyNotFound(["my_value"]))
pub fn dereference(
ref ref: Value,
using decoder: decode.Decoder(a),
) -> Action(a, e)
Converts a reference to a Lua value into type-safe Gleam data using the provided decoder.
Examples
glua.run(glua.new(), {
use ref <- glua.then(
glua.eval(code: "return 'Hello from Lua!'")
|> glua.try(list.first)
)
glua.dereference(ref:, using: decode.string)
})
// -> Ok("Hello from Lua!")
let assert Ok(#(state, [ref1, ref2])) = glua.exec(
glua.new(),
glua.eval(code: "return 1, true")
)
let assert Ok(1) =
glua.run(state, glua.dereference(ref: ref1, using: decode.int))
let assert Ok(True) =
glua.run(state, glua.dereference(ref: ref2, using: decode.bool))
pub fn error(message: String) -> Action(a, e)
Invokes the Lua error function with the provided message.
pub fn error_with_level(
message: String,
level: Int,
) -> Action(a, e)
Invokes the Lua error function with the provided message and level.
pub fn eval(code code: String) -> Action(List(Value), e)
Evaluates a string of Lua code.
Examples
glua.eval(code: "return 1 + 2")
|> glua.returning_multi(using: decode.int)
|> glua.run(glua.new(), _)
// -> Ok([3])
let assert Ok(#(state, [ref1, ref2])) = glua.exec(glua.new(), glua.eval(
code: "return 'hello, world!', 10",
))
let assert Ok("hello world") =
glua.run(state, glua.dereference(ref: ref1, using: decode.string))
let assert Ok(10) =
glua.run(state, glua.dereference(ref: ref2, using: decode.int))
glua.run(glua.new(), glua.eval(code: "return 1 * "))
// -> Error(glua.LuaCompileFailure(
[glua.LuaCompileError(1, Parse, "syntax error before: ")]
))
Note: If you are evaluating the same piece of code multiple times, instead of calling
glua.evalrepeatly it is recommended to first convert the code to a chunk by passing it toglua.load, and then evaluate that chunk usingglua.eval_chunk.
pub fn eval_chunk(chunk chunk: Chunk) -> Action(List(Value), e)
Evaluates a compiled chunk of Lua code.
Examples
glua.load(code: "return 'hello, world!'")
|> glua.then(glua.eval_chunk)
|> glua.returning_multi(using: decode.string)
|> glua.run(glua.new(), _)
// -> Ok(["hello, world!"])
pub fn eval_file(path path: String) -> Action(List(Value), e)
Evaluates a Lua source file.
Examples
glua.eval_file(
path: "path/to/hello.lua",
)
|> glua.returning_multi(using: decode.string)
|> glua.run(glua.new(), _)
// -> Ok(["hello, world!"])
glua.run(glua.new(), glua.eval_file(
path: "path/to/non/existent/file",
))
// -> Error(glua.FileNotFound(["path/to/non/existent/file"]))
pub fn exec(
state lua: Lua,
action action: Action(return, error),
) -> Result(#(Lua, return), Error(error))
Runs an Action within a Lua environment and returns both the result
and the updated Lua state in case of no errors.
Examples
let state = glua.new()
let assert Ok(#(new_state, Nil)) =
glua.exec(state, glua.set(["my_value"], glua.string("Hello!")))
glua.exec(
state:,
action: glua.eval(code: "return my_value") |> glua.returning_multi(decode.string)
)
// -> Ok(#(_state, ["Hello!"]))
pub fn failure(error: e) -> Action(a, e)
Creates an Action that always fails with glua.CustomError(error).
Examples
glua.run(
glua.new(),
glua.failure("incorrect number of return values")
)
// -> Error(glua.CustomError("incorrect number of return values"))
pub fn fold(
over list: List(a),
with fun: fn(a) -> Action(b, e),
) -> Action(List(b), e)
Maps a list of elements into a list of Actions by calling a function in each element and then flattens
all the Actions into a single one.
Examples
let numbers = [9, 16, 25]
let keys = ["math", "sqrt"]
glua.run(glua.new(), glua.fold(numbers, fn(n) {
glua.call_function_by_name(keys:, args: [glua.int(n)])
|> glua.try(list.first)
|> glua.returning(using: decode.float)
}))
// -> Ok([3.0, 4.0, 5.0])
pub fn format_error(error: Error(e)) -> String
Turns a glua.Error value into a human-readable string
Examples
let assert Error(e) = glua.run(glua.new(), glua.eval(
code: "if true end",
))
glua.format_error(e)
// -> "Lua compile error: \n\nFailed to parse: error on line 1: syntax error before: 'end'"
let assert Error(e) = glua.run(glua.new(), glua.eval(
code: "local a = 1; local b = true; return a + b",
))
glua.format_error(e)
// -> "Lua runtime exception: Bad arithmetic expression: 1 + true"
let assert Error(e) = glua.run(glua.new(), glua.get(
keys: ["a_value"],
))
glua.format_error(e)
// -> "Key \"a_value\" not found"
let assert Error(e) = glua.run(glua.new(), glua.eval_file(
path: "my_lua_file.lua",
))
glua.format_error(e)
// -> "Lua source file \"my_lua_file.lua\" not found"
let assert Error(e) = glua.run(glua.new(), {
use ret <- glua.then(glua.eval(
code: "return 1 + 1",
))
use ref <- glua.try(list.first(ret))
glua.dereference(ref:, using: decode.string)
})
glua.format_error(e)
// -> "Expected String, but found Int"
pub fn function(
f: fn(List(Value)) -> Action(List(Value), Never),
) -> Value
Encodes a Gleam function into a Lua function.
Note: The function to be encoded has to return an
Actionwith aNevertype as theerrorparameter, meaning that the function cannot invokeglua.failurein its body. If you want to return an error inside that function, you should useglua.errororglua.error_with_code, both of which will call the Luaerrorfunction.
pub fn function_decoder() -> decode.Decoder(
fn(List(Value)) -> Action(List(Value), e),
)
pub fn get(keys keys: List(String)) -> Action(Value, e)
Gets a value in the Lua environment.
Examples
glua.get(keys: ["_VERSION"])
|> glua.returning(using: decode.string)
|> glua.run(glua.new(), _)
// -> Ok("Lua 5.3")
glua.run(glua.new(), {
use _ <- glua.then(glua.set(
keys: ["my_table", "my_value"],
value: glua.bool(True)
))
glua.get(keys: ["my_table", "my_value"]))
|> glua.returning(using: decode.bool)
})
// -> Ok(True)
glua.run(glua.new(), glua.get(keys: ["non_existent"]))
// -> Error(glua.KeyNotFound(["non_existent"]))
pub fn get_private(
state lua: Lua,
key key: String,
using decoder: decode.Decoder(a),
) -> Result(a, Error(e))
Gets a private value that is not exposed to the Lua runtime.
Examples
assert glua.new()
|> glua.set_private("private_value", "secret_value")
|> glua.get_private("private_value", decode.string)
== Ok("secret_value")
pub fn guard(
when requirement: Bool,
return consequence: e,
otherwise alternative: fn() -> Action(a, e),
) -> Action(a, e)
Runs a callback function if the given bool is False, otherwise return a failing Action
using the provided value.
Examples
glua.run(glua.new(), {
use ret <- glua.then(glua.eval(code: "local a = 1"))
use <- glua.guard(when: ret == [], return: "expected at least one value from Lua")
glua.fold(ret, glua.dereference(_, using: decode.int))
})
// -> Error(glua.CustomError("expected at least one value from Lua"))
pub fn index(
table ref: Value,
key key: Value,
) -> Action(Value, e)
Gets the value at keys under the provided table.
This might trigger the __index metamethod.
Examples
glua.run(glua.new(), {
let fun = fn(_) {
glua.success([glua.string("fixed value")])
}
use tbl <- glua.then(glua.table([]))
use mt <- glua.then(glua.table([#(glua.string("__index"), glua.function(fun))]))
use _ <- glua.then(glua.call_function(lib.set_metatable(), [tbl, mt]))
glua.index(tbl, glua.string("a_key")) |> glua.returning(decode.string)
})
// -> Ok("fixed value")
pub fn load(code code: String) -> Action(Chunk, e)
Parses a string of Lua code and returns it as a compiled chunk.
To eval the returned chunk, use glua.eval_chunk.
pub fn load_file(path path: String) -> Action(Chunk, e)
Parses a Lua source file and returns it as a compiled chunk.
To eval the returned chunk, use glua.eval_chunk.
pub fn map(
over action: Action(a, e),
with fun: fn(a) -> b,
) -> Action(b, e)
Transforms the return value of an Action with the provided function.
If the Action returns an Error when executed then the function is not called and the
error is returned.
Examples
glua.get(keys: ["_VERSION"])
|> glua.returning(using: decode.string)
|> glua.map(fn(version) {
"glua supports " <> version
})
|> glua.run(glua.new(), _)
// -> Ok("glua supports Lua 5.3")
glua.run(glua.new(), {
use n <- glua.map(glua.get(keys: ["my_number"]))
n * 2
})
// -> Error(glua.KeyNotFound(["my_number"]))
pub fn new_index(
table ref: Value,
key key: Value,
value val: Value,
) -> Action(Nil, e)
Sets value under key of the provided table.
This might trigger the __newindex metamethod.
Examples
glua.run(glua.new(), {
let fun = fn(_) {
glua.error("this is a read-only table")
}
use tbl <- glua.then(glua.table([]))
use mt <- glua.then(glua.table([#(glua.string("__newindex"), glua.function(fun))]))
use _ <- glua.then(glua.call_function(lib.set_metatable(), [tbl, mt]))
glua.new_index(tbl, glua.string("my_new_key"), glua.string("my_new_value"))
})
// -> Error(glua.LuaRuntimeException(
exception: glua.ErrorCall("this is a read-only table", option.None),
state: _
))
pub fn new_sandboxed(
allow excluded: List(List(String)),
) -> Result(Lua, Error(e))
Creates a new Lua VM instance with sensible modules and functions sandboxed.
Check glua.default_sandbox to see what modules and functions will be sandboxed.
This function accepts a list of paths to Lua values that will be excluded from being sandboxed,
so needed modules or functions can be enabled while keeping sandboxed the rest.
In case you want to sandbox more Lua values, pass to glua.sandbox the returned Lua state.
pub fn returning(
action act: Action(Value, e),
using decoder: decode.Decoder(a),
) -> Action(a, e)
Transforms an Action that returns a reference to a Lua value into an Action that returns
a typed Gleam value.
Examples
let decoder =
decode.dict(decode.string, decode.int)
|> decode.map(dict.to_list)
glua.eval(code: "return { a = 1, b = 2 }")
|> glua.try(apply: list.first)
|> glua.returning(using: decoder)
|> glua.run(glua.new(), _)
// -> Ok([#("a", 1), #("b", 2)])
pub fn returning_multi(
action act: Action(List(Value), e),
using decoder: decode.Decoder(a),
) -> Action(List(a), e)
Same as glua.returning, but works on an Action that returns multiple references to Lua values
instead of a single one.
pub fn run(
state lua: Lua,
action action: Action(return, error),
) -> Result(return, Error(error))
Runs an Action within a Lua environment.
Examples
let state = glua.new()
glua.eval(code: "return 'Hello from Lua!'")
|> glua.returning_multi(using: decode.string)
|> glua.run(state, _)
// -> Ok("Hello from Lua!")
pub fn sandbox(
state lua: Lua,
keys keys: List(String),
) -> Result(Lua, Error(e))
Swaps out the value at keys with a function that causes a Lua error when called.
Examples
let assert Ok(state) = glua.new() |> glua.sandbox(["os"], ["execute"])
let assert Error(glua.LuaRuntimeException(exception, _)) =
glua.run(state, glua.eval(
code: "os.execute(\"rm -f important_file\"); return 0",
))
// 'important_file' was not deleted
assert exception == glua.ErrorCall(["os.execute is sandboxed"])
pub fn set(
keys keys: List(String),
value val: Value,
) -> Action(Nil, e)
Sets a value in the Lua environment.
All nested keys will be created as intermediate tables.
If successfull, this function will return the updated Lua state and the setted value will be available in Lua scripts.
Examples
glua.run(glua.new(), {
use _ <- glua.then(glua.set(
keys: ["my_number"],
value: glua.int(10)
))
glua.get(keys: ["my_number"])
|> glua.returning(using: decode.int)
})
// -> Ok(10)
let emails = ["jhondoe@example.com", "lucy@example.com"]
let assert Ok(results) = glua.run(glua.new(), {
use encoded <- glua.then(glua.table(
list.index_map(emails, fn(email, i) { #(glua.int(i + 1), glua.string(email)) })
))
use _ <- glua.then(glua.set(["info", "emails"], encoded))
glua.eval(code: "return info.emails"))
|> glua.try(list.first)
|> glua.returning(using: glua.table_list_decoder(decode.string))
})
assert results == emails
pub fn set_api(
keys: List(String),
values: List(#(String, Value)),
) -> Action(Nil, e)
Sets a group of values under a particular table in the Lua environment.
pub fn set_lua_paths(paths paths: List(String)) -> Action(Nil, e)
Sets the paths where the Lua runtime will look when requiring other Lua files.
Warning: This function will not work properly if
["package"]or["require"]are sandboxed in the provided Lua state. If you constructed the Lua state usingglua.new_sandboxed, remember to allow the required values by passing[["package"], ["require"]]toglua.new_sandboxed.
Examples
let my_scripts_paths = ["app/scripts/lua/?.lua"]
glua.run(glua.new(), {
use _ <- glua.then(glua.set_lua_paths(paths: my_scripts_paths))
glua.eval(
code: "local my_math = require 'my_script'; return my_math.square(3)"
)
|> glua.try(list.first)
|> glua.returning(decode.int)
})
// -> Ok(9)
pub fn set_private(
state lua: Lua,
key key: String,
value value: a,
) -> Lua
Sets a value that is not exposed to the Lua runtime and can only be accessed from Gleam.
Examples
assert glua.new()
|> glua.set("secret_value", "private_value")
|> glua.get("secret_value", decode.string)
== Ok("secret_value")
pub fn success(value: a) -> Action(a, e)
Creates an Action that always succeeds and returns value.
Examples
glua.run(glua.new(), glua.success("my value"))
// -> Ok("my_value")
pub fn table_list_decoder(
inner decoder: decode.Decoder(a),
) -> decode.Decoder(List(a))
A decoder for list-style Lua tables. This decoder works similarly to ipairs in the sense that it stops when there is a gap in the table list.
Examples
glua.eval("return { 1, 2, 3 }")
|> glua.try(list.first)
|> glua.returning(glua.table_list_decoder(decode.int))
|> glua.run(glua.new(), _)
// -> Ok([1, 2, 3])
glua.eval("return { [1] = 'a', [2] = 'b', [4] = 'd'}")
|> glua.try(list.first)
|> glua.returning(glua.table_list_decoder(decode.string))
|> glua.run(glua.new(), _)
// -> Ok(["a", "b"])
pub fn then(
action: Action(a, e),
next: fn(a) -> Action(b, e),
) -> Action(b, e)
Composes two Actions into a single one, by executing the first one and passing its return value
to a function that returns another Action.
If the first Action returns an Error when executed, then the function is not called
and the error is returned.
This function is the most common way to chain together multiple Actions.
Examples
let my_value = 1
let assert Ok(#(_state, ret)) = glua.run(glua.new(), {
use _ <- glua.then(glua.set(keys: ["my_value"], value: glua.int(my_value)))
glua.get(keys: ["my_value"]) |> glua.returning(decode.int)
})
assert ret == my_value
glua.run(glua.new(), {
use ret <- glua.then(glua.eval_file(path: "./my_file.lua"))
glua.call_function_by_name(path: ["table", "pack"], args: ret)
})
// -> Error(glua.FileNotFound("./my_file.lua"))
pub fn try(
action action: Action(a, e),
apply fun: fn(a) -> Result(b, e),
) -> Action(b, e)
Tries to update the return value of an Action by passing it to a function
that yields a result.
This is a shorthand for writing a case with glua.then:
use fun <- glua.then(glua.get(["string", "reverse"]))
glua.call_function(fun:, args: [glua.string("Hello")])
|> glua.try(list.first)
|> glua.returning(decode.string)
as opposed to this:
use fun <- glua.then(glua.get(["string", "reverse"]))
use return <- glua.then(glua.call_function(fun, [glua.string("Hello")]))
case return {
[first] -> glua.dereference(ref: first, using: decode.string)
_ -> glua.failure(Nil)
}
pub fn userdata(v: anything) -> Action(Value, e)
Encodes any Gleam value as a reference that can be passed to a Lua program.
Deferencing a userdata value inside Lua code will cause a Lua exception.
Examples
pub type User {
User(name: String, is_admin: Bool)
}
let user_decoder = {
use name <- decode.field(1, decode.string)
use is_admin <- decode.field(2, decode.bool)
decode.success(User(name:, is_admin:))
}
glua.run(glua.new(), {
use userdata <- glua.then(userdata(User("Jhon Doe", False)))
use _ <- glua.then(glua.set(
keys: ["a_user"],
value: userdata
))
glua.eval(code: "return a_user")
|> glua.try(list.first)
|> glua.returning(using: user_decoder)
})
// -> Ok(User("Jhon Doe", False))
pub type Person {
Person(name: String, email: String)
}
let assert Error(glua.LuaRuntimeException(glua.IllegalIndex(_), _)) =
glua.run(glua.new(), {
use userdata <- glua.then(glua.userdata(
Person(name: "Lucy", email: "lucy@example.com")
))
use _ <- glua.then(glua.set(
keys: ["lucy"],
value: userdata
))
glua.eval(code: "return lucy.email")
})