From 4e32bac819df10de707d355f1cdd12b01c416d1a Mon Sep 17 00:00:00 2001 From: selenil Date: Sat, 21 Feb 2026 16:10:03 -0500 Subject: [PATCH 1/4] add index and new_index functions --- src/glua.gleam | 27 +++++++++++++++++++++++++++ src/glua_ffi.erl | 16 ++++++++++++++-- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/glua.gleam b/src/glua.gleam index 24c2f1e..c415ace 100644 --- a/src/glua.gleam +++ b/src/glua.gleam @@ -1164,3 +1164,30 @@ pub fn call_function_by_name( use fun <- then(get(keys)) call_function(fun, args) } + +pub fn index(table ref: Value, key key: String) -> Action(Value, e) { + Action(do_index(_, ref, key)) +} + +@external(erlang, "glua_ffi", "get_table_key") +fn do_index( + state: Lua, + ref: Value, + key: String, +) -> Result(#(Lua, Value), LuaError(e)) + +pub fn new_index( + table ref: Value, + key key: String, + value val: Value, +) -> Action(Nil, e) { + Action(do_new_index(_, ref, key, val)) +} + +@external(erlang, "glua_ffi", "set_table_key") +fn do_new_index( + state: Lua, + ref: Value, + key: String, + val: Value, +) -> Result(#(Lua, Nil), LuaError(e)) diff --git a/src/glua_ffi.erl b/src/glua_ffi.erl index acf808c..877304c 100644 --- a/src/glua_ffi.erl +++ b/src/glua_ffi.erl @@ -3,8 +3,8 @@ -import(ttdict, [fold/3]). -include_lib("luerl/include/luerl.hrl"). --export([get_stacktrace/1, dereference/2, coerce/1, coerce_nil/0, wrap_fun/1, decode_fun/1, sandbox_fun/1, get_table_keys/2, - get_private/2, set_table_keys/3, load/2, load_file/2, eval/2, eval_file/2, +-export([get_stacktrace/1, dereference/2, coerce/1, coerce_nil/0, wrap_fun/1, decode_fun/1, sandbox_fun/1, get_table_key/3, + get_table_keys/2, get_private/2, set_table_key/4, set_table_keys/3, load/2, load_file/2, eval/2, eval_file/2, eval_chunk/2, call_function/3]). @@ -238,6 +238,12 @@ decode_fun(Fun) -> _ -> {error, fun(_) -> {action, fun(State) -> {ok, {State, nil}} end} end} end. +get_table_key(Lua, Tref, Key) -> + case luerl_emul:get_table_key(Tref, Key, Lua) of + {nil, _St} -> {error, {key_not_found, [Key]}}; + {Value, St} -> {ok, {St, Value}} + end. + get_table_keys(Lua, Keys) -> case luerl:get_table_keys(Keys, Lua) of {ok, nil, _} -> @@ -248,6 +254,12 @@ get_table_keys(Lua, Keys) -> to_gleam(Other) end. +set_table_key(Lua, Tref, Key, Value) -> + case luerl_emul:set_table_key(Tref, Key, Value, Lua) of + {lua_error, _, _} = Err -> {error, map_error(Err)}; + St -> {ok, {St, nil}} + end. + set_table_keys(Lua, Keys, Value) -> to_gleam(luerl:set_table_keys(Keys, Value, Lua)). From c731012803597cec4841ff99f7127841e3908b8e Mon Sep 17 00:00:00 2001 From: selenil Date: Sat, 21 Feb 2026 16:45:27 -0500 Subject: [PATCH 2/4] fix crashes --- src/glua_ffi.erl | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/glua_ffi.erl b/src/glua_ffi.erl index 877304c..77cbe48 100644 --- a/src/glua_ffi.erl +++ b/src/glua_ffi.erl @@ -239,9 +239,11 @@ decode_fun(Fun) -> end. get_table_key(Lua, Tref, Key) -> - case luerl_emul:get_table_key(Tref, Key, Lua) of + try luerl_emul:get_table_key(Tref, Key, Lua) of {nil, _St} -> {error, {key_not_found, [Key]}}; {Value, St} -> {ok, {St, Value}} + catch + error:{lua_error, _, _} = Err -> {error, map_error(Err)} end. get_table_keys(Lua, Keys) -> @@ -255,9 +257,11 @@ get_table_keys(Lua, Keys) -> end. set_table_key(Lua, Tref, Key, Value) -> - case luerl_emul:set_table_key(Tref, Key, Value, Lua) of - {lua_error, _, _} = Err -> {error, map_error(Err)}; - St -> {ok, {St, nil}} + try + St = luerl_emul:set_table_key(Tref, Key, Value, Lua), + {ok, {St, nil}} + catch + error:{lua_error, _, _} = Err -> {error, map_error(Err)} end. set_table_keys(Lua, Keys, Value) -> From f3744b5c7629b60a964e99465f208daa779339a5 Mon Sep 17 00:00:00 2001 From: selenil Date: Sat, 28 Feb 2026 14:42:26 -0500 Subject: [PATCH 3/4] add docs --- src/glua.gleam | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/glua.gleam b/src/glua.gleam index c415ace..b2b740c 100644 --- a/src/glua.gleam +++ b/src/glua.gleam @@ -1165,6 +1165,26 @@ pub fn call_function_by_name( call_function(fun, args) } +/// Gets the value at `keys` under the provided table. +/// +/// This might trigger the `__index` metamethod. +/// +/// ## Examples +/// +/// ```gleam +/// 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, "a_key") +/// }) +/// // -> Ok(#(_state, ["fixed value"])) +/// ``` pub fn index(table ref: Value, key key: String) -> Action(Value, e) { Action(do_index(_, ref, key)) } @@ -1176,6 +1196,29 @@ fn do_index( key: String, ) -> Result(#(Lua, Value), LuaError(e)) +/// Sets `value` under `key` of the provided table. +/// +/// This might trigger the `__newindex` metamethod. +/// +/// ## Examples +/// +/// ```gleam +/// 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, "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_index( table ref: Value, key key: String, From fc62a738f31a2b458e9cbdfc0bd5397b000d74f5 Mon Sep 17 00:00:00 2001 From: selenil Date: Sat, 28 Feb 2026 15:02:29 -0500 Subject: [PATCH 4/4] add tests --- test/glua_test.gleam | 142 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) diff --git a/test/glua_test.gleam b/test/glua_test.gleam index d28b2ae..13e1c5c 100644 --- a/test/glua_test.gleam +++ b/test/glua_test.gleam @@ -8,6 +8,7 @@ import gleam/pair import gleam/result import gleeunit import glua +import glua/lib pub fn main() -> Nil { gleeunit.main() @@ -906,3 +907,144 @@ pub fn decode_function_test() { assert exn == glua.BadArith("*", ["3", "true"]) } + +pub fn index_test() { + let assert Ok(#(state, tbl)) = + glua.run(glua.new(), { + glua.table([#(glua.string("a_key"), glua.string("a value"))]) + }) + + assert glua.run( + state, + glua.index(tbl, "a_key") |> glua.returning(decode.string), + ) + |> result.map(pair.second) + == Ok("a value") + + assert glua.run(state, { + use tbl <- glua.then( + glua.table([#(glua.string("a_key"), glua.string("a value"))]), + ) + glua.index(tbl, "other_key") + }) + == Error(glua.KeyNotFound(["other_key"])) + + let assert Ok(#(state, tbl)) = + glua.run(glua.new(), { + use tbl <- glua.then(glua.table([])) + use mt <- glua.then( + glua.table([ + #( + glua.string("__index"), + glua.function(fn(args) { + let assert [_table, key] = args + + use k <- glua.then(glua.dereference(key, decode.string)) + case int.parse(k) { + Ok(_) -> "integer" + Error(_) -> "other" + } + |> glua.string + |> list.wrap + |> glua.success + }), + ), + ]), + ) + + use _ <- glua.then( + glua.call_function_by_name(["setmetatable"], [tbl, mt]), + ) + glua.success(tbl) + }) + + assert glua.run(state, glua.index(tbl, "1") |> glua.returning(decode.string)) + |> result.map(pair.second) + == Ok("integer") + + assert glua.run(state, glua.index(tbl, "a") |> glua.returning(decode.string)) + |> result.map(pair.second) + == Ok("other") + + assert glua.run(state, glua.index(tbl, "2") |> glua.returning(decode.int)) + |> result.map(pair.second) + == Error( + glua.UnexpectedResultType([decode.DecodeError("Int", "String", [])]), + ) +} + +pub fn new_index_test() { + let assert Ok(#(state, tbl)) = glua.run(glua.new(), { glua.table([]) }) + + assert glua.run(state, { + use _ <- glua.then(glua.new_index(tbl, "a_key", glua.int(1))) + use ref <- glua.then(glua.index(tbl, "a_key")) + glua.dereference(ref, decode.int) + }) + |> result.map(pair.second) + == Ok(1) + + let assert Ok(#(state, tbl)) = + glua.run(glua.new(), { + use tbl <- glua.then(glua.table([])) + use mt <- glua.then( + glua.table([ + #( + glua.string("__newindex"), + glua.function(fn(args) { + let assert [table, key, value] = args + + use i <- glua.then(glua.dereference(value, decode.int)) + + let value = glua.int(i * i) + glua.call_function(lib.raw_set(), [table, key, value]) + }), + ), + ]), + ) + + use _ <- glua.then( + glua.call_function_by_name(["setmetatable"], [tbl, mt]), + ) + glua.success(tbl) + }) + + assert glua.run(state, { + use _ <- glua.then(glua.new_index(tbl, "a_key", glua.int(6))) + use ref <- glua.then(glua.index(tbl, "a_key")) + glua.dereference(ref, decode.int) + }) + |> result.map(pair.second) + == Ok(36) + + assert glua.run(state, { + use _ <- glua.then(glua.new_index(tbl, "other_key", glua.int(7))) + use ref <- glua.then(glua.index(tbl, "other_key")) + glua.dereference(ref, decode.int) + }) + |> result.map(pair.second) + == Ok(49) + + let assert Ok(#(state, tbl)) = + glua.run(glua.new(), { + use tbl <- glua.then(glua.table([])) + use mt <- glua.then( + glua.table([ + #( + glua.string("__newindex"), + glua.function(fn(_) { glua.error("read-only table") }), + ), + ]), + ) + + use _ <- glua.then( + glua.call_function_by_name(["setmetatable"], [tbl, mt]), + ) + glua.success(tbl) + }) + + let assert Error(glua.LuaRuntimeException( + exception: glua.ErrorCall(message: "read-only table", level: _), + state: _, + )) = glua.run(state, glua.new_index(tbl, "my_key", glua.string("my value"))) +}