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