99 lines
2.1 KiB
Elixir
99 lines
2.1 KiB
Elixir
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
|