Skip to content

Instantly share code, notes, and snippets.

@christhekeele
Last active September 17, 2016 04:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save christhekeele/bc9badf1bad500dc3f88e6e490bca39a to your computer and use it in GitHub Desktop.
Save christhekeele/bc9badf1bad500dc3f88e6e490bca39a to your computer and use it in GitHub Desktop.
Check quoted expressions for validity inside guards.
defmodule Guard do
@guardables ~W[
== != === !== > >= < <=
and or not
+ - * /
<>
in
is_atom is_binary is_bitstring is_boolean
is_float is_function is_integer is_list
is_map is_nil is_number is_pid is_port
is_reference is_tuple
abs binary_part bit_size byte_size
div elem hd length map_size node
rem round self tl trunc tuple_size
]a
@doc """
Validates that the given expressions are valid inside guards.
It returns `:ok` if the expression is a valid guard expression. Otherwise it
returns a tuple in the form of `{:error, remainder}` where `remainder` is the
invalid part of the quoted expression.
"""
@spec validate(Macro.t) :: :ok | {:error, term}
def validate(expr) do
find_invalid(expr) || :ok
end
# This is the magic clause
defp find_invalid({left, meta, right})
when is_atom(left) and not left in @guardables and is_list(meta) and not is_atom(right), do:
{:error, {left, meta, right}}
# The rest is just the implementation of `Macro.validate` verbatim
defp find_invalid({left, right}), do:
find_invalid(left) || find_invalid(right)
defp find_invalid({left, meta, right}) when is_list(meta) and (is_atom(right) or is_list(right)), do:
find_invalid(left) || find_invalid(right)
defp find_invalid(list) when is_list(list), do:
Enum.find_value(list, &find_invalid/1)
defp find_invalid(pid) when is_pid(pid), do: nil
defp find_invalid(atom) when is_atom(atom), do: nil
defp find_invalid(num) when is_number(num), do: nil
defp find_invalid(bin) when is_binary(bin), do: nil
defp find_invalid(fun) when is_function(fun) do
unless :erlang.fun_info(fun, :env) == {:env, []} and
:erlang.fun_info(fun, :type) == {:type, :external} do
{:error, fun}
end
end
defp find_invalid(other), do: {:error, other}
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment