Created
August 16, 2020 21:38
-
-
Save polvalente/284641f9e9c29fb826e34c0439b07482 to your computer and use it in GitHub Desktop.
Multi-comparator expression parser
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule MultiBoolComp do | |
@moduledoc """ | |
Defines an `sequential_bool` macro that parses a sequence of boolean comparisons in-line, such as: | |
`sequential_bool(a < b >= c == d <= e)` which would be equivalent to `a < b and b >= c and c == d and d <= e` | |
Only the native comparators are accepted (<, >, <=, >=, ==, !=, ===, !==) | |
### Examples: | |
iex> MultiBoolComp.sequential_bool(1 <= 4) | |
true | |
iex> MultiBoolComp.sequential_bool(1 < 2 == 2 < 4 != 6 >= 5 <= 7) | |
true | |
iex> MultiBoolComp.sequential_bool(1 < 2 == 3 < 4 != 6 >= 5 <= 7) | |
false | |
""" | |
defmacro sequential_bool(ast) do | |
# IO.inspect(ast, label: "original ast") | |
{_node, flattened_ast} = flatten_bool_ast(ast) | |
# IO.inspect(flattened_ast, label: "flattened_ast") | |
esc_flattened_ast = Macro.escape(flattened_ast) | |
quote location: :keep do | |
MultiBoolComp.run(unquote(esc_flattened_ast)) | |
end | |
end | |
def run(%{args: [nil]}), do: false | |
def run(%{operators: []}), do: true | |
def run(%{operators: [op | op_tl], args: [l, r | arg_tl]}) do | |
# IO.puts("Evaluating #{l} #{op} #{r} to: #{apply(Kernel, op, [l, r])}") | |
if apply(Kernel, op, [l, r]) do | |
run(%{operators: op_tl, args: [r | arg_tl]}) | |
else | |
false | |
end | |
end | |
@doc """ | |
Flattens a boolean-expression AST, by transforming all boolean function calls into | |
A single function call to `MultiBoolComp.run/1` | |
""" | |
def flatten_bool_ast(ast) do | |
Macro.prewalk(ast, %{operators: [], args: []}, fn | |
{op, env, [l, r]}, acc when op in ~w(== != === !== <= >=)a -> | |
{_, flattened_l} = flatten_bool_ast(l) | |
{_, flattened_r} = flatten_bool_ast(r) | |
operators = flattened_l.operators ++ [op] ++ flattened_r.operators | |
args = flattened_l.args ++ flattened_r.args | |
updated = acc |> Map.put(:args, args) |> Map.put(:operators, operators) | |
# we return a node without arguments because the | |
# flatten_bool_ast/1 calls have already parsed our arg nodes | |
{{op, env, []}, updated} | |
{op, _env, _} = node, acc -> | |
{node, Map.put(acc, :operators, acc.operators ++ [op])} | |
val, acc -> | |
{val, Map.put(acc, :args, acc.args ++ [val])} | |
end) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment