diff --git a/src/glua.gleam b/src/glua.gleam index d9715a1..4bbf961 100644 --- a/src/glua.gleam +++ b/src/glua.gleam @@ -541,10 +541,19 @@ 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) { + let #(_idx, values) = + list.map_fold(values, 1, fn(acc, val) { #(acc + 1, #(int(acc), val)) }) + + table(values) +} + @external(erlang, "luerl_heap", "alloc_table") 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 /// @@ -555,10 +564,25 @@ 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)) { - 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 d4f7d80..157e3f7 100644 --- a/test/glua_test.gleam +++ b/test/glua_test.gleam @@ -42,6 +42,57 @@ pub fn get_table_test() { let assert Ok(_) = glua.run(glua.new(), action) } +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(), { + 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() { let assert Ok(lua) = glua.sandbox(glua.new(), ["math", "max"]) let args = list.map([20, 10], glua.int)