Current section
Files
Jump to
Current section
Files
lib/happy.ex
defmodule Happy do
@moduledoc """
Happy path programming in elixir.
"""
# happy block with at least two expressions
# using custom unhappy path
@doc false
defmacro happy_path([do: block = {:__block__, _, xs = [a, b | c]},
else: unhappy]) do
if Enum.any?(xs, &pattern_match?/1) do
make_happy(a, b, c, unhappy)
else
block
end
end
# happy block with at least two expressions
# using default unhappy path
@doc false
defmacro happy_path([do: block = {:__block__, _, xs = [a, b | c]}]) do
if Enum.any?(xs, &pattern_match?/1) do
make_happy(a, b, c, [])
else
block
end
end
@doc false
defmacro happy_path([do: expr, else: _]), do: expr
@doc false
defmacro happy_path([do: expr]), do: expr
# append unhappy path to case when no more expressions remain
defp make_happy({:case, m = [happy_path: true], [e, [do: xs]]}, [], unhappy) do
{:case, m, [e, [do: xs ++ unhappy]]}
end
defp make_happy(a, [], _u), do: a
defp make_happy(a, [b | xs], u), do: make_happy(a, b, xs, u)
# create nested case expression when two consecutive match found
defp make_happy(a = {:=, _, _}, b = {:=, _, _}, xs = [_ | _], u) do
make_happy(a, [make_happy(b, xs, u)], u)
end
# create a case expression from a to b and continue with rest expressions
defp make_happy(a = {:=, _, _}, b, xs, u) do
happy_case(a, b, xs, u)
end
defp make_happy(a = {:@, _, [{_, _, [{:=, _, _}]}]}, b, xs, u) do
happy_case(a, b, xs, u)
end
defp make_happy(a = {:@, _, [{_, _, [{:when, _, _}]}]}, b, xs, u) do
happy_case(a, b, xs, u)
end
#
defp make_happy(a = {:when, _, [_, {:=, _, _}]}, b, xs, u) do
happy_case(a, b, xs, u)
end
# create another nested case when another pattern matching found in chain
defp make_happy(a = {:case, [happy_path: true], _}, b = {:=, _, _}, xs, u) do
make_happy(a, [make_happy(b, xs, u)], u)
end
# append `b` expression to current block case(p -> ax)
defp make_happy({:case, [happy_path: true],
[e, [do: [{:->, _, [[p], {:__block__, _, ax}]}]]]},
b, xs, u) do
happy_append(e, p, ax, b, xs, u)
end
# create a block by appending `b` expression to current case(p -> a)
defp make_happy({:case, [happy_path: true], [e, [do: [{:->, _, [[p], a]}]]]},
b, xs, u) do
happy_append(e, p, [a], b, xs, u)
end
# create a block with `a` and `b` and continue with chain
defp make_happy(a, b, xs, u) do
quote do
unquote(a)
unquote(b)
end |> make_happy(xs, u)
end
# create a happy case
defp happy_case(a, b, xs, u) do
{e, p} = pattern_match(a)
quote do
case(unquote(e)) do
unquote(p) -> unquote(b)
end
end |> happy_form |> make_happy(xs, u)
end
defp happy_append(e, p, ax, b, xs, u) do
quote do
case(unquote(e)) do
unquote(p) ->
unquote_splicing(ax)
unquote(b)
end
end |> happy_form |> make_happy(xs, u)
end
# mark a form with happy metadata
defp happy_form({x, m, y}) do
{x, [happy_path: true] ++ m, y}
end
# is the given form a pattern match?
defp pattern_match?({:@, _, [{_, _, [{:=, _, [_, _]}]}]}), do: true
defp pattern_match?({:@, _, [{_, _, [{:when, _, _}]}]}), do: true
defp pattern_match?({:when, _, [_, {:=, _, [_, _]}]}), do: true
defp pattern_match?({:=, _, [_, _]}), do: true
defp pattern_match?(_), do: false
defp pattern_match({:@, _, [{t, _, [x = {:when, _, _}]}]}) do
{e, {:when, l, [p, w]}} = pattern_match(x)
{{t, e}, {:when, l, [{t,p}, w]}}
end
defp pattern_match({:@, _, [{t, _, [x = {:=, _, _}]}]}) do
{e, p} = pattern_match(x)
{{t, e}, {t, p}}
end
# a when b = c
defp pattern_match({:when, l, [p, eq = {:=, _, _}]}) do
{e, w} = pattern_match(eq)
{e, {:when, l, [p, w]}}
end
# a = b = c
defp pattern_match({:=, l, [a, {:=, m, [b, c]}]}) do
pattern_match({:=, l, [{:=, m, [a, b]}, c]})
end
defp pattern_match({:=, _, [p, e]}), do: {e, p}
end