Skip to content

Commit

Permalink
(Breaking) Make Lua.Table.as_string more powerful (#48)
Browse files Browse the repository at this point in the history
  • Loading branch information
davydog187 authored Sep 27, 2024
1 parent a7b64a0 commit 31826ed
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 33 deletions.
71 changes: 42 additions & 29 deletions lib/lua/table.ex
Original file line number Diff line number Diff line change
Expand Up @@ -55,34 +55,57 @@ defmodule Lua.Table do
iex> Lua.Table.as_string([{"a", 1}, {"b", 2}])
"{a = 1, b = 2}"
Lists that "look" like Lua tables are treated as lists
iex> Lua.Table.as_string([{1, "foo"}, {2, "bar"}])
~S[{"foo", "bar"}]
### Options
* `:userdata` - A 1-arity function used to format userdata. Defaults to `fn _ -> "<userdata>"`
Lists are treated as lists
iex> Lua.Table.as_string(["a", "b", "c"])
~S[{"a", "b", "c"}]
Regular maps are always treated as tables
iex> Lua.Table.as_string(%{1 => "foo", "bar" => "baz"})
~S<{[1] = "foo", bar = "baz"}>
### Options
* `:formatter` - A 2-arity function used to format values before serialization. The key and value
are passed as arguments. If there is no key, it will default to `nil`.
"""
def as_string(table, opts \\ []) do
opts = Keyword.put_new(opts, :userdata, fn _ -> "<userdata>" end)
opts = Keyword.validate!(opts, formatter: &default_formatter/2)

"{" <> print_table(table, opts[:userdata]) <> "}"
"{" <> print_table(table, opts[:formatter]) <> "}"
end

defp default_formatter(_key, value), do: value

# List
defp print_table([{1, _} | _] = list, userdata_formatter) do
defp print_table([{1, _} | _] = list, formatter) do
list
|> Enum.reduce([], fn {_, value}, acc ->
[acc, if(acc == [], do: "", else: ", "), format_value(value, userdata_formatter)]
|> Enum.reduce([], fn {key, value}, acc ->
[acc, if(acc == [], do: "", else: ", "), format_value(key, value, formatter)]
end)
|> IO.iodata_to_binary()
end

# List
defp print_table([value | _] = list, formatter) when not is_tuple(value) do
list
|> Enum.reduce([], fn value, acc ->
[acc, if(acc == [], do: "", else: ", "), format_value(nil, value, formatter)]
end)
|> IO.iodata_to_binary()
end

# Table
defp print_table(table, userdata_formatter) do
defp print_table(table, formatter) do
table
|> Enum.reduce([], fn {key, value}, acc ->
key_str = format_key(key)
value_str = format_value(value, userdata_formatter)
value_str = format_value(key, value, formatter)

entry = "#{key_str} = #{value_str}"

Expand Down Expand Up @@ -112,26 +135,16 @@ defmodule Lua.Table do
end
end

defp format_value(value, _) when is_number(value) do
to_string(value)
end

defp format_value(true, _), do: "true"
defp format_value(false, _), do: "false"
defp format_value(nil, _), do: "nil"

defp format_value(value, formatter) when is_list(value) do
str = print_table(value, formatter)

"{#{str}}"
end

defp format_value({:userdata, value}, formatter) do
format_value(formatter.(value), formatter)
end

defp format_value(value, _formatter) do
inspect(value)
defp format_value(key, value, formatter) do
case formatter.(key, value) do
list when is_list(list) -> "{#{print_table(list, formatter)}}"
{:userdata, _value} -> inspect("<userdata>")
true -> "true"
false -> "false"
nil -> nil
number when is_number(number) -> to_string(number)
other -> inspect(other)
end
end

@doc """
Expand Down
24 changes: 20 additions & 4 deletions test/lua/table_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,16 @@ defmodule Lua.TableTest do

doctest Lua.Table

defmacro assert_table(table) do
quote bind_quoted: [table: table] do
defmacro assert_table(table, expected \\ :reflect) do
quote bind_quoted: [table: table, expected: Macro.escape(expected)] do
assert output = Lua.Table.as_string(table)
assert {[ret], _lua} = Lua.eval!("return " <> output)
assert ret == table

case expected do
:reflect -> assert ret == table
expected -> assert ret == expected
end

output
end
end
Expand All @@ -34,12 +39,23 @@ defmodule Lua.TableTest do
assert_table(big_table)
end

test "it can handle lists of values" do
list = ["a", "b", "c", "d"]
assert_table(list, [{1, "a"}, {2, "b"}, {3, "c"}, {4, "d"}])
end

test "it can handle useradata" do
table = [{"a", 1}, {"b", {:userdata, ~D[2024-09-22]}}]

assert Lua.Table.as_string(table) == ~S[{a = 1, b = "<userdata>"}]

assert Lua.Table.as_string(table, userdata: &inspect/1) == ~S<{a = 1, b = "~D[2024-09-22]"}>
assert Lua.Table.as_string(table,
formatter: fn
_, {:userdata, value} -> inspect(value)
_, value -> value
end
) ==
~S<{a = 1, b = "~D[2024-09-22]"}>
end

# We can't handle self-referential tables as
Expand Down

0 comments on commit 31826ed

Please sign in to comment.