defmodule Patch.Macro do @doc """ Utility function that acts like `inspect/1` but prints out the Macro as code. """ @spec debug(ast :: Macro.t()) :: Macro.t() def debug(ast) do ast |> Macro.to_string() |> IO.puts() ast end @doc """ Performs an non-hygienic match. If the match succeeds true is returned, otherwise a MatchError is raised. Since the match is non-hygienic pins can be used from the user-scope and binds will effect user-scope. """ @spec match(pattern :: Macro.t(), expression :: Macro.t()) :: Macro.t() defmacro match(pattern, expression) do user_pattern = user_variables(pattern) pattern_expression = pattern_expression(pattern) variables = variables(pattern) quote generated: true do unquote(pattern_expression) = case unquote(expression) do unquote(user_pattern) -> _ = unquote(variables) unquote(expression) _ -> raise MatchError, term: unquote(expression) end _ = unquote(variables) true end end @doc """ Performs a match, return true if match matches, false otherwise. """ @spec match?(pattern :: Macro.t(), expression :: Macro.t()) :: Macro.t() defmacro match?(pattern, expression) do quote generated: true do try do Patch.Macro.match(unquote(pattern), unquote(expression)) true rescue MatchError -> false end end end ## Private defp pattern_expression(pattern) do Macro.prewalk(pattern, fn {:^, _, [{name, meta, _}]} -> {name, meta, nil} {:_, _, _} -> unique_variable() node -> node end) end defp unique_variable do {:"_ignore#{:erlang.unique_integer([:positive])}", [generated: true], nil} end defp user_variables(pattern) do Macro.prewalk(pattern, fn {name, meta, context} when is_atom(name) and is_atom(context) -> {name, meta, nil} node -> node end) end defp variables(pattern) do pattern |> Macro.prewalk([], fn {:_, _, _} = node, acc -> {node, acc} {:@, anno, _}, acc -> # Replace module attribute with wildcard so we don't convert into a variable {{:_, anno, nil}, acc} {name, meta, context} = node, acc when is_atom(name) and is_atom(context) -> ignored? = name |> Atom.to_string() |> String.starts_with?("_") if ignored? do {node, acc} else {node, [{name, meta, nil} | acc]} end node, acc -> {node, acc} end) |> elem(1) end end