94 lines
1.9 KiB
Elixir
94 lines
1.9 KiB
Elixir
|
defmodule ExpressionEvaluator do
|
||
|
def eval(expr, vars) do
|
||
|
expr
|
||
|
|> String.to_charlist()
|
||
|
|> tokenize()
|
||
|
|> run(vars)
|
||
|
|
||
|
# |> parse()
|
||
|
|
||
|
# |> run(vars)
|
||
|
end
|
||
|
|
||
|
@digits ?0..?9
|
||
|
@alpha ?a..?z
|
||
|
@operators [?+, ?-, ?*]
|
||
|
|
||
|
def do_tokenize([first | _] = str) when first in @digits do
|
||
|
{digits, rest} = Enum.split_while(str, &(&1 in @digits))
|
||
|
num = digits |> to_string() |> String.to_integer()
|
||
|
{{:number, num}, rest}
|
||
|
end
|
||
|
|
||
|
def do_tokenize([?( | rest]) do
|
||
|
{:lparen, rest}
|
||
|
end
|
||
|
|
||
|
def do_tokenize([?) | rest]) do
|
||
|
{:rparen, rest}
|
||
|
end
|
||
|
|
||
|
def do_tokenize([op | rest]) when op in @operators do
|
||
|
atom = [op] |> to_string() |> String.to_atom()
|
||
|
{{:operator, atom}, rest}
|
||
|
end
|
||
|
|
||
|
def do_tokenize([first | _] = str) when first in @alpha do
|
||
|
{var, rest} = Enum.split_while(str, &(&1 in @alpha))
|
||
|
{{:variable, var}, rest}
|
||
|
end
|
||
|
|
||
|
def tokenize([]), do: []
|
||
|
|
||
|
def tokenize(str) do
|
||
|
{token, rest} =
|
||
|
str
|
||
|
|> Enum.drop_while(&(&1 == ?\s))
|
||
|
|> do_tokenize()
|
||
|
|
||
|
[token | tokenize(rest)]
|
||
|
end
|
||
|
|
||
|
# def to_rpn([{:number, _} = num | rest]) do
|
||
|
# [num | to_rpn(rest)]
|
||
|
# end
|
||
|
#
|
||
|
# def to_rpn([{:operator, _} = op | rest]) do
|
||
|
# [op | to_rpn(rest)]
|
||
|
# end
|
||
|
#
|
||
|
# def to_rpn
|
||
|
|
||
|
# def parse(tokens) do
|
||
|
# {expr, rest} = do_parse(tokens)
|
||
|
# [expr |
|
||
|
# end
|
||
|
|
||
|
def run([:lparen | _] = tokens, vars) do
|
||
|
paren_tokens =
|
||
|
tokens
|
||
|
|> Enum.drop(1)
|
||
|
|> Enum.reduce_while({1, []}, fn token, {count, paren_tokens} ->
|
||
|
count =
|
||
|
case token do
|
||
|
:lparen -> count + 1
|
||
|
:rparen -> count - 1
|
||
|
_ -> count
|
||
|
end
|
||
|
|
||
|
if count == 0 do
|
||
|
{:halt, Enum.reverse(paren_tokens)}
|
||
|
else
|
||
|
{:cont, {count, [token | paren_tokens]}}
|
||
|
end
|
||
|
end)
|
||
|
|
||
|
# paren_expr = run(paren_tokens)
|
||
|
rest_tokens = Enum.drop(tokens, length(paren_tokens))
|
||
|
|
||
|
paren_res = run(paren_tokens, vars)
|
||
|
|
||
|
run([{:number, paren_res} | rest_tokens])
|
||
|
end
|
||
|
end
|