AoC19/lib/intcode/expression_evaluator.ex

107 lines
2.3 KiB
Elixir

defmodule ExpressionEvaluator do
import NimbleParsec
number = integer(min: 1)
variable = ascii_string([?a..?z, ?A..?Z, ?_], min: 1)
whitespace = ascii_string([?\s], min: 1)
factor =
choice([
ignore(ascii_char([?(]))
|> concat(parsec(:expr))
|> ignore(ascii_char([?)])),
number,
variable,
ignore(ascii_char([?-]))
|> concat(number)
|> tag(:neg),
ignore(ascii_char([?-]))
|> concat(variable)
|> tag(:neg)
])
defcombinatorp(
:term,
choice([
factor
|> optional(ignore(whitespace))
|> ignore(ascii_char([?*]))
|> optional(ignore(whitespace))
|> concat(parsec(:term))
|> tag(:mul),
factor
|> optional(ignore(whitespace))
|> ignore(ascii_char([?/]))
|> optional(ignore(whitespace))
|> concat(parsec(:term))
|> tag(:div),
factor
])
)
defcombinatorp(
:expr,
choice([
parsec(:term)
|> optional(ignore(whitespace))
|> ignore(ascii_char([?+]))
|> optional(ignore(whitespace))
|> concat(parsec(:expr))
|> tag(:add),
parsec(:term)
|> optional(ignore(whitespace))
|> ignore(ascii_char([?-]))
|> optional(ignore(whitespace))
|> concat(parsec(:expr))
|> tag(:sub),
parsec(:term)
])
)
defparsec(:parse, parsec(:expr))
def eval(expr, vars \\ %{})
def eval(expr, vars) when is_map(vars) do
eval(expr, fn name ->
Map.fetch!(vars, name)
end)
end
def eval(expr, get_var) when is_function(get_var) do
{:ok, expr, _, _, _, _} = parse(expr)
do_eval(expr, get_var)
end
def do_eval([{op, [a, b]}], get_var) do
do_op(op, do_eval_single(a, get_var), do_eval_single(b, get_var))
end
def do_eval([it], get_var) do
do_eval_single(it, get_var)
end
def do_eval_single({:neg, [it]}, get_var) do
-1 * do_eval_single(it, get_var)
end
def do_eval_single(it, _) when is_integer(it) do
it
end
def do_eval_single(it, get_var) when is_binary(it) do
get_var.(it)
end
def do_eval_single(it, get_var) do
do_eval([it], get_var)
end
def do_op(:add, a, b), do: a + b
def do_op(:sub, a, b), do: a - b
def do_op(:mul, a, b), do: a * b
def do_op(:div, a, b), do: floor(a / b)
end