defmodule ExpressionEvaluator do import NimbleParsec number = integer(min: 1) variable = ascii_string([?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 \\ %{}) do {:ok, expr, _, _, _, _} = parse(expr) do_eval(expr, vars) end def do_eval([{op, [a, b]}], vars) do do_op(op, do_eval_single(a, vars), do_eval_single(b, vars)) end def do_eval([it], vars) do do_eval_single(it, vars) end def do_eval_single({:neg, [it]}, vars) do -1 * do_eval_single(it, vars) end def do_eval_single(it, _) when is_integer(it) do it end def do_eval_single(it, vars) when is_binary(it) do Map.fetch!(vars, it) end def do_eval_single(it, vars) do do_eval([it], vars) 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