From 3c49149df60bab73706a1a21632f0777c0193155 Mon Sep 17 00:00:00 2001 From: DynamicCake <128557765+DynamicCake@users.noreply.github.com> Date: Wed, 4 Mar 2026 11:51:38 -1000 Subject: [PATCH 1/4] Add table_list for convenience --- src/glua.gleam | 7 +++++++ test/glua_test.gleam | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/glua.gleam b/src/glua.gleam index d9715a1..8e04978 100644 --- a/src/glua.gleam +++ b/src/glua.gleam @@ -541,6 +541,13 @@ pub fn table(values: List(#(Value, Value))) -> Action(Value, e) { Ok(do_table(values, state) |> pair.swap) } +pub fn table_list(values: List(Value)) -> Action(Value, e) { + use state <- Action + let #(_idx, values) = + list.map_fold(values, 1, fn(acc, val) { #(acc + 1, #(int(acc), val)) }) + Ok(do_table(values, state) |> pair.swap) +} + @external(erlang, "luerl_heap", "alloc_table") fn do_table(values: List(#(Value, Value)), lua: Lua) -> #(Value, Lua) diff --git a/test/glua_test.gleam b/test/glua_test.gleam index d4f7d80..bfaf8c4 100644 --- a/test/glua_test.gleam +++ b/test/glua_test.gleam @@ -42,6 +42,25 @@ pub fn get_table_test() { let assert Ok(_) = glua.run(glua.new(), action) } +pub fn table_list_test() { + let list = ["foo", "bar", "baz", "qux"] + glua.run(glua.new(), { + use val <- glua.then(glua.table_list(list |> list.map(glua.string))) + use decoded <- glua.then(glua.dereference( + val, + glua.table_list_decoder(decode.string), + )) + assert list == decoded + use decoded <- glua.then(glua.dereference( + val, + decode.dict(decode.int, decode.string), + )) + assert dict.to_list(decoded) + == [#(1, "foo"), #(2, "bar"), #(3, "baz"), #(4, "qux")] + glua.success(Nil) + }) +} + pub fn sandbox_test() { let assert Ok(lua) = glua.sandbox(glua.new(), ["math", "max"]) let args = list.map([20, 10], glua.int) From 7fcb4e5bc3d7a89f7a7f855ae49c62e9eaba2daa Mon Sep 17 00:00:00 2001 From: DynamicCake <128557765+DynamicCake@users.noreply.github.com> Date: Sat, 7 Mar 2026 19:00:52 -1000 Subject: [PATCH 2/4] Fix table_list_decoder Previously it relied on dict value ordering (big no no) --- src/glua.gleam | 9 +++++++- test/glua_test.gleam | 55 ++++++++++++++++++++++++++++++++------------ 2 files changed, 48 insertions(+), 16 deletions(-) diff --git a/src/glua.gleam b/src/glua.gleam index 8e04978..80f7ce4 100644 --- a/src/glua.gleam +++ b/src/glua.gleam @@ -565,7 +565,14 @@ fn do_table(values: List(#(Value, Value)), lua: Lua) -> #(Value, Lua) pub fn table_list_decoder( inner decoder: decode.Decoder(a), ) -> decode.Decoder(List(a)) { - decode.dict(decode.int, decoder) |> decode.map(dict.values) + decode.dict(decode.int, decoder) |> decode.map(list_loop(_, [], 1)) +} + +fn list_loop(dict: dict.Dict(Int, a), acc: List(a), idx: Int) { + case dict.get(dict, idx) { + Ok(it) -> list_loop(dict, [it, ..acc], idx + 1) + Error(Nil) -> list.reverse(acc) + } } /// Encodes a Gleam function into a Lua function. diff --git a/test/glua_test.gleam b/test/glua_test.gleam index bfaf8c4..1341a79 100644 --- a/test/glua_test.gleam +++ b/test/glua_test.gleam @@ -44,21 +44,46 @@ pub fn get_table_test() { pub fn table_list_test() { let list = ["foo", "bar", "baz", "qux"] - glua.run(glua.new(), { - use val <- glua.then(glua.table_list(list |> list.map(glua.string))) - use decoded <- glua.then(glua.dereference( - val, - glua.table_list_decoder(decode.string), - )) - assert list == decoded - use decoded <- glua.then(glua.dereference( - val, - decode.dict(decode.int, decode.string), - )) - assert dict.to_list(decoded) - == [#(1, "foo"), #(2, "bar"), #(3, "baz"), #(4, "qux")] - glua.success(Nil) - }) + let assert Ok(Nil) = + glua.run(glua.new(), { + use val <- glua.then(glua.table_list(list |> list.map(glua.string))) + use decoded <- glua.then(glua.dereference( + val, + glua.table_list_decoder(decode.string), + )) + assert list == decoded + use decoded <- glua.then(glua.dereference( + val, + decode.dict(decode.int, decode.string), + )) + assert dict.to_list(decoded) + == [#(1, "foo"), #(2, "bar"), #(3, "baz"), #(4, "qux")] + glua.success(Nil) + }) +} + +pub fn table_list_edge_test() { + let values = + [ + #(-1, "qux"), + #(1, "foo"), + #(2, "bar"), + #(4, "baz"), + #(-123, "red herring"), + ] + |> list.map(pair.map_first(_, glua.int)) + |> list.map(pair.map_second(_, glua.string)) + + let assert Ok(Nil) = + glua.run(glua.new(), { + use val <- glua.then(glua.table(values)) + use decoded <- glua.then(glua.dereference( + val, + glua.table_list_decoder(decode.string), + )) + assert decoded == ["foo", "bar"] + glua.success(Nil) + }) } pub fn sandbox_test() { From 7f2bb9ef3cc1f7f9c3840657586ad838dd430dac Mon Sep 17 00:00:00 2001 From: Cake <128557765+DynamicCake@users.noreply.github.com> Date: Sat, 14 Mar 2026 20:49:49 -1000 Subject: [PATCH 3/4] Improve table_list implementation By using glua.table instead of glua.do_table Co-authored-by: selenil <157198464+selenil@users.noreply.github.com> --- src/glua.gleam | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/glua.gleam b/src/glua.gleam index 80f7ce4..6b5ff0e 100644 --- a/src/glua.gleam +++ b/src/glua.gleam @@ -542,10 +542,10 @@ pub fn table(values: List(#(Value, Value))) -> Action(Value, e) { } pub fn table_list(values: List(Value)) -> Action(Value, e) { - use state <- Action let #(_idx, values) = list.map_fold(values, 1, fn(acc, val) { #(acc + 1, #(int(acc), val)) }) - Ok(do_table(values, state) |> pair.swap) + + table(values) } @external(erlang, "luerl_heap", "alloc_table") From 6627d61d7bfa40a7d69a909d1352c7fc692d0920 Mon Sep 17 00:00:00 2001 From: DynamicCake <128557765+DynamicCake@users.noreply.github.com> Date: Sat, 14 Mar 2026 21:04:31 -1000 Subject: [PATCH 4/4] Add docs clarifying table_list_decoder usage --- src/glua.gleam | 10 ++++++++++ test/glua_test.gleam | 7 +++++++ 2 files changed, 17 insertions(+) diff --git a/src/glua.gleam b/src/glua.gleam index 6b5ff0e..4bbf961 100644 --- a/src/glua.gleam +++ b/src/glua.gleam @@ -552,6 +552,8 @@ pub fn table_list(values: List(Value)) -> Action(Value, e) { fn do_table(values: List(#(Value, Value)), lua: Lua) -> #(Value, Lua) /// 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 /// @@ -562,6 +564,14 @@ fn do_table(values: List(#(Value, Value)), lua: Lua) -> #(Value, Lua) /// |> glua.run(glua.new(), _) /// // -> Ok([1, 2, 3]) /// ``` +/// +/// ```gleam +/// 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 table_list_decoder( inner decoder: decode.Decoder(a), ) -> decode.Decoder(List(a)) { diff --git a/test/glua_test.gleam b/test/glua_test.gleam index 1341a79..157e3f7 100644 --- a/test/glua_test.gleam +++ b/test/glua_test.gleam @@ -43,6 +43,13 @@ pub fn get_table_test() { } pub fn table_list_test() { + let list = + 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(), _) + assert list == Ok(["a", "b"]) + let list = ["foo", "bar", "baz", "qux"] let assert Ok(Nil) = glua.run(glua.new(), {