Packages

Lets you use short syntax for maps and define guards inline

Current section

Files

Jump to
shortdef lib shortdef.ex
Raw

lib/shortdef.ex

defmodule ShortDef do
defmacro __using__(_) do
quote do
import Kernel, except: [def: 2]
import ShortDef, only: [def: 2]
end
end
defmacro def(head, body) do
new_head = transform_head(head)
# |> IO.inspect(label: :new_head)
quote do
Kernel.def(unquote(new_head), unquote(body))
end
end
def transform_head({:when, [line: line], [head, condition]}) do
new_head = transform_head(head)
case new_head do
{:when, [line: _], [h, c]} ->
c = condition |> merge_condition(c)
{:when, [line: line], [h, c]}
_ ->
{:when, [line: line], [new_head, condition]}
end
end
def transform_head(head) do
{fun_name, [line: line], args} = head
# transforming a list of args = transforming a single arg that is a list
{new_args, guards} = transform_arg(args, [])
new_head = {fun_name, [line: line], new_args}
case guards do
[] ->
new_head
[c] ->
{:when, [line: line], [new_head, c]}
_ ->
c = {:and, [line: line], guards}
{:when, [line: line], [new_head, c]}
end
end
defp merge_condition(c1, c2) do
list1 =
case c1 do
{:and, [line: _], list} -> list
_ -> [c1]
end
list2 =
case c2 do
{:and, [line: _], list} -> list
_ -> [c2]
end
[line: line] = elem(c1, 1)
{:and, [line: line], list1 ++ list2}
end
def transform_arg({:=, [line: line], [arg, var]}, guards) do
{new_arg, guards} = transform_arg(arg, guards)
{
{:=, [line: line], [new_arg, var]},
guards
}
end
def transform_arg(args, guards) when is_list(args) do
# Apply transform_arg for every arg
{new_args, guards} =
args
|> Enum.reduce({[], guards}, fn arg, {new_args, new_guards} ->
{new_arg, new_guards} = transform_arg(arg, new_guards)
{[new_arg | new_args], new_guards}
end)
new_args = new_args |> Enum.reverse()
{new_args, guards}
end
def transform_arg({:%, [line: line], [aliases, inner_map]}, guards) do
{new_map, guards} = transform_arg(inner_map, guards)
{
{:%, [line: line], [aliases, new_map]},
guards
}
end
def transform_arg({:%{}, [line: line], items}, guards) when is_list(items) do
# Apply transform_item for every item
{new_items, guards} =
items
|> Enum.reduce({[], guards}, fn item, {new_items, new_guards} ->
{new_item, new_guards} = transform_item(item, new_guards)
{[new_item | new_items], new_guards}
end)
new_items = new_items |> Enum.reverse()
{
{:%{}, [line: line], new_items},
guards
}
end
def transform_arg({:{}, [line: line], args}, guards) when is_list(args) do
{new_args, guards} = transform_arg(args, guards)
{
{:{}, [line: line], new_args},
guards
}
end
def transform_arg({first, second}, guards) do
# 2-element tuple for some reason is a special case
{new_first, guards} = transform_arg(first, guards)
{new_second, guards} = transform_arg(second, guards)
{
{new_first, new_second},
guards
}
end
def transform_arg({name, [line: _], [x]} = g, guards) when is_atom(name) do
# Responsible for the guard short form:
# def f(is_integer(x)) do end -> def f(x) when is_integer(x) do end
guards = [g | guards]
transform_arg(x, guards)
end
def transform_arg(arg, guards) do
{arg, guards}
end
def transform_item({name, [line: line], nil}, guards) when is_atom(name) do
# Responsible for the short form of maps:
# %{x} -> %{x: x}
{
{name, {name, [line: line], nil}},
guards
}
end
def transform_item({name, value}, guards) when is_atom(name) do
{value, guards} = transform_arg(value, guards)
{
{name, value},
guards
}
end
def transform_item(
{
guard_name, [line: _l1],
[{item_name, [line: _l2], nil} = item]
} = g,
guards)
when is_atom(guard_name) and is_atom(item_name) do
# Responsible for the guard short form when it's a map item:
# def f(%{is_integer(x)}) do end
# -> def f(%{x: x}) when is_integer(x) do end
guards = [g | guards]
transform_item(item, guards)
end
end