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 a chunk of Lua code that is already loaded into the Lua VM

pub type Chunk

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 an instance of the Lua VM.

pub type Lua

Represents a Lua compilation error

pub type LuaCompileError {
  LuaCompileError(
    line: Int,
    kind: LuaCompileErrorKind,
    message: String,
  )
}

Constructors

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 error function 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 false as the first argument.

  • UnknownException

    An exception that could not be identified

This type is used to represent a value that can never happen. What does that mean exactly?

  • A Bool is a type that has two values: True and False.
  • Nil is a type that has one value: Nil.
  • Never is 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

Represents a value that can be passed to the Lua environment.

pub type Value

Values

pub fn bool(v: Bool) -> Value
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.eval repeatly it is recommended to first convert the code to a chunk by passing it to glua.load, and then evaluate that chunk using glua.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 float(v: Float) -> Value
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 Action with a Never type as the error parameter, meaning that the function cannot invoke glua.failure in its body. If you want to return an error inside that function, you should use glua.error or glua.error_with_code, both of which will call the Lua error function.

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 int(v: Int) -> 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() -> Lua

Creates a new Lua VM instance

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 nil() -> Value
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 using glua.new_sandboxed, remember to allow the required values by passing [["package"], ["require"]] to glua.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 string(v: String) -> 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(values: List(#(Value, Value))) -> Action(Value, e)
pub fn table_list(values: List(Value)) -> Action(Value, e)
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")
  })
Search Document