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