Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
4409855
Add alloc table and alloc userdata
DynamicCake Dec 23, 2025
52492ac
Dump gleam/dynamic/decode into de.gleam
DynamicCake Dec 29, 2025
33d36c0
Add run function
DynamicCake Dec 29, 2025
a54b4ee
Change name and remove errors
DynamicCake Dec 29, 2025
191e948
Add classify function in erlang
DynamicCake Dec 29, 2025
4c21353
Make a custom error type
DynamicCake Dec 29, 2025
4ad3aae
Remove decoding functions
DynamicCake Dec 30, 2025
bd79990
Add push path and error
DynamicCake Dec 30, 2025
7699817
Write field code
DynamicCake Dec 31, 2025
ed6b366
Do basic decoders
DynamicCake Dec 31, 2025
3c76537
Make first tests
DynamicCake Dec 31, 2025
f64a689
Test field
DynamicCake Dec 31, 2025
85485e2
Add subfield ok test
DynamicCake Dec 31, 2025
9c5b7f8
Add field metatable test
DynamicCake Dec 31, 2025
ca0bcea
Remove Value
DynamicCake Dec 31, 2025
2ff5610
Add at
DynamicCake Dec 31, 2025
0b1f422
Add optionally at
DynamicCake Dec 31, 2025
131d978
Add optional field
DynamicCake Dec 31, 2025
27be6ef
Add some err tests
DynamicCake Dec 31, 2025
f6fdd85
Add deser.dict
DynamicCake Jan 1, 2026
435eb66
Alternative dict implementation
DynamicCake Jan 1, 2026
6e781bb
Add map
DynamicCake Jan 1, 2026
c200f52
Add list deserializer
DynamicCake Jan 1, 2026
4ed11e3
Add then
DynamicCake Jan 1, 2026
8f48603
Add functions
DynamicCake Jan 2, 2026
2a8868c
Update readme
DynamicCake Jan 2, 2026
600dafd
ref_get does NOT exist
DynamicCake Jan 2, 2026
62a402d
Add other decode functions without tests
DynamicCake Jan 2, 2026
9cc2eef
Clean up warnings
DynamicCake Jan 2, 2026
71988a1
Make success take lua
DynamicCake Jan 2, 2026
8f09682
Add back glua_test
DynamicCake Jan 2, 2026
e834ef7
Fix edge case for nonexistent tables and userdata
DynamicCake Jan 3, 2026
f31b9ef
Remove lua paramater from function
DynamicCake Jan 3, 2026
5af5071
Rename `ValueRef` to just `Value`
DynamicCake Jan 3, 2026
2f615e6
Remove unused functions
DynamicCake Jan 3, 2026
7e02f6f
Make success inherit lua, make deser.run not return lua
DynamicCake Jan 3, 2026
6c4d8ff
Make deser.then provide lua
DynamicCake Jan 3, 2026
71fae55
Remove deserializer state mutation
DynamicCake Jan 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 75 additions & 124 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,68 +1,76 @@
# Glua

A library for embedding Lua in Gleam applications!
A library for embedding Lua in Gleam applications powered by [Luerl](https://github.com/rvirding/luerl)!

[![Package Version](https://img.shields.io/hexpm/v/glua)](https://hex.pm/packages/glua)
[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/glua/)

```sh
gleam add glua@1
gleam add glua@2
```

## Usage

### Executing Lua code

```gleam
let code = "
let code =
"
function greet()
return 'Hello from Lua!'
end

return greet()
"

let assert Ok(#(_state, [result])) = glua.eval(
state: glua.new(),
code:,
using: decode.string
)
// Make a fresh instance lua instance
let lua = glua.new()
let assert Ok(#(_state, [result])) = glua.eval(state: lua, code:)

assert result == "Hello from Lua!"
assert result == glua.string("Hello from Lua!")
```

### Parsing a chunk, then executing it
### Decoding output
The `deser` api is similar to the `decode`
```gleam
pub type Project {
Project(name: String, language: String)
}
let code = "return { name = 'glua', written_in = 'gleam'}"
let assert Ok(#(lua, [table])) = glua.eval(glua.new(), code)

let deserializer = {
use name <- deser.field(glua.string("name"), deser.string)
use language <- deser.field(glua.string("written_in"), deser.string)
deser.success(lua, Project(name:, language:))
}

let assert Ok(#(_lua, project)) = deser.run(lua, table, deserializer)
assert project == Project(name: "glua", language: "gleam")
```

### Parsing a chunk, then executing it
```gleam
let code = "return 'this is a chunk of Lua code'"
let assert Ok(#(state, chunk)) = glua.load(state: glua.new(), code:)
let assert Ok(#(_state, [result])) =
glua.eval_chunk(state:, chunk:, using: decode.string)
let assert Ok(#(_state, [result])) = glua.eval_chunk(state:, chunk:)

assert result == "this is a chunk of Lua code"
assert result == glua.string("this is a chunk of Lua code")
```

### Executing Lua files

```gleam
let assert Ok(#(_state, [n, m])) = glua.eval_file(
state: glua.new(),
path: "./my_lua_files/two_numbers.lua"
using: decode.int
)
let assert Ok(#(_state, [n, m])) =
glua.eval_file(state: glua.new(), path: "./test/lua/two_numbers.lua")

assert n == 1 && m == 2
assert n == glua.int(1) && m == glua.int(2)
```

### Sandboxing

```gleam
let assert Ok(lua) = glua.new() |> glua.sandbox(["os", "execute"])
let assert Error(glua.LuaRuntimeException(exception, _)) = glua.eval(
state: lua,
code: "os.execute('rm -f important_file'); return 0",
using: decode.int
)
let assert Error(glua.LuaRuntimeException(exception, _)) =
glua.eval(state: lua, code: "os.execute('rm -f important_file')")

// 'important_file' was not deleted
assert exception == glua.ErrorCall(["os.execute is sandboxed"])
Expand All @@ -71,139 +79,82 @@ assert exception == glua.ErrorCall(["os.execute is sandboxed"])
### Getting values from Lua

```gleam
let assert Ok(version) = glua.get(
state: glua.new(),
keys: ["_VERSION"],
using: decode.string
)
let assert Ok(version) = glua.get(state: glua.new(), keys: ["_VERSION"])

assert version == "Lua 5.3"
assert version == glua.string("Lua 5.3")
```

### Setting values in Lua

```gleam
// we need to encode any value we want to pass to Lua
let #(lua, encoded) = glua.string(glua.new(), "my_value")

let lua = glua.new()
// `keys` is the full path to where the value will be set
// and any intermediate table will be created if it is not present
let keys = ["my_table", "my_value"]
let assert Ok(lua) = glua.set(state: lua, keys:, value: encoded)
let assert Ok(lua) = glua.set(state: lua, keys:, value: glua.string("my_value"))

// now we can get the value
let assert Ok(value) = glua.get(state: lua, keys:, using: decode.string)
let assert Ok(value) = glua.get(state: lua, keys:)

// or return it from a Lua script
let assert Ok(#(_lua, [returned])) =
glua.eval(
state: lua,
code: "return my_table.my_value",
using: decode.string,
)

assert value == "my_value"
assert returned == "my_value"
```
glua.eval(state: lua, code: "return my_table.my_value")

```gleam
// we can also encode a list of tuples as a table to set it in Lua
let my_table = [
#("my_first_value", 1.2),
#("my_second_value", 2.1)
]

// the function we use to encode the keys and the function we use to encode the values
let encoders = #(glua.string, glua.float)

let #(lua, encoded) = glua.new() |> glua.table(encoders, my_table)
let assert Ok(lua) = glua.set(state: lua, keys: ["my_table"], value: encoded)

// now we can get its values
let assert Ok(#(lua, [result])) = glua.eval(
state: lua,
code: "return my_table.my_second_value",
using: decode.float
)

assert result == 2.1

// or we can get the whole table and decode it back to a list of tuples
assert glua.get(
state: lua,
keys: ["my_table"],
using: glua.table_decoder(decode.string, decode.float)
) == Ok([
#("my_first_value", 1.2),
#("my_second_value", 2.1)
])
assert value == glua.string("my_value")
assert returned == glua.string("my_value")
```

### Calling Lua functions from Gleam

```gleam
// here we use `ref_get` instead of `get` because we need a reference to the function
// and not a decoded value
let lua = glua.new()
let assert Ok(fun) = glua.ref_get(
state: lua,
keys: ["math", "max"]
)

// we need to encode each argument we pass to a Lua function
// `glua.list` encodes a list of values using a single encoder function
let #(lua, args) = glua.list(lua, glua.int, [1, 20, 7, 18])

let assert Ok(#(lua, [result])) = glua.call_function(
state: lua,
ref: fun,
args:,
using: decode.int
)

assert result == 20

// `glua.call_function_by_name` is a shorthand for `glua.ref_get` followed by `glua.call_function`
let assert Ok(#(_lua, [result])) = glua.call_function_by_name(
state: lua,
keys: ["math", "max"],
args:,
using: decode.int
)

assert result == 20
let assert Ok(val) = glua.get(state: lua, keys: ["math", "max"])
let assert Ok(#(lua, fun)) = deser.run(lua, val, deser.function)
let args = [1, 20, 7, 18] |> list.map(glua.int)

let assert Ok(#(lua, [result])) =
glua.call_function(state: lua, fun: fun, args:)

let assert Ok(#(lua, result)) = deser.run(lua, result, deser.number)

assert result == 20.0

// `glua.call_function_by_name` is a shorthand for `glua.get` followed by `glua.call_function`
let assert Ok(#(_lua, [result])) =
glua.call_function_by_name(state: lua, keys: ["math", "max"], args:)

let assert Ok(#(_lua, result)) = deser.run(lua, result, deser.number)

assert result == 20.0
```

### Exposing Gleam functions to Lua

```gleam
let #(lua, fun) = {
use lua, args <- glua.function(glua.new())

let assert [x, min, max] = args
let assert Ok([x, min, max]) = list.try_map(
[x, min, max],
decode.run(_, decode.float)
)
let lua = glua.new()
let #(lua, fun) =
glua.function(lua, fn(lua, args) {
// Since Gleam is a statically typed language, each and every argument must be decoded
let assert [x, min, max] = args
let assert Ok(#(lua, x)) = deser.run(lua, x, deser.number)
let assert Ok(#(lua, min)) = deser.run(lua, min, deser.number)
let assert Ok(#(lua, max)) = deser.run(lua, max, deser.number)

let result = float.clamp(x, min, max)
let result = float.clamp(x, min, max)

glua.list(lua, glua.float, [result])
}
#(lua, [glua.float(result)])
})

let keys = ["my_functions", "clamp"]

let assert Ok(lua) = glua.set(state: lua, keys:, value: fun)

let #(lua, args) = glua.list(lua, glua.float, [2.3, 1.2, 2.1])
let assert Ok(#(_lua, [result])) = glua.call_function_by_name(
state: lua,
keys:,
args:,
using: decode.float
)
let args = [2.3, 1.2, 2.1] |> list.map(glua.float)
let assert Ok(#(_lua, [result])) =
glua.call_function_by_name(state: lua, keys:, args:)

assert result == 2.1
assert result == glua.float(2.1)
```

Further documentation can be found at <https://hexdocs.pm/glua>.
Expand Down
Loading
Loading