defmodule ADT do
@moduledoc """
Pseudo-ADT definition generator
"""
@doc """
Use it like this:
> ADT.define foo(a: "default") | bar(b: "value")
> %Foo{a:"value"}
%Foo{a: "value"}
"""
defmacro define(parts) do
parts |> format_parts(__CALLER__) |> Enum.map(&generate_defmodule/1)
end
# Flatten "one | two | three" ("one | (two | three)" in the AST
# to "[one, two, three]"
defp format_parts({:|, _, [elem, rest]}, caller) do
format_parts(elem, caller) ++ format_parts(rest, caller)
end
defp format_parts({name, _, [content]}, caller) do
# name is lowercase atom, need to capitalize + re-atom
# fields is [name: val, name: val]
module_name = name |> to_string |> format_module_name
# then, generate a module name from the string
module_name = Module.concat([caller.module, module_name])
[{module_name, content}]
end
defp format_parts({name, _, []}, _) do
raise "Unable to generate ADT variant #{name}: no fields declared"
end
# Generates an AST for the module definition
# based on the ADT alternative, like:
#
# foo(a: "default")
defp generate_defmodule({name, fields}) do
quote do
defmodule unquote(name) do
defstruct unquote(fields)
end
end
end
# Helper code that really shouldn't be here.
# Transforms "foo_bar" to "FooBar"
defp format_module_name(str) do
Regex.replace(~r/_([a-z])/, String.capitalize(str),
fn _, l -> String.upcase(l) end)
end
end