Packages
expression
1.1.1
2.49.1
2.49.0
2.48.0
2.47.5
2.47.4
2.47.3
2.47.2
2.47.1
2.47.0
2.46.0
2.45.1
2.45.0
2.44.1
2.44.0
2.43.0
2.42.1
2.41.4
2.41.3
2.41.2
2.41.0
2.40.0
2.38.0
2.37.0
2.36.2
2.36.1
2.36.0
2.35.0
2.34.0
2.33.1
2.33.0
2.32.1
2.31.6
2.31.3
2.31.0
2.30.0
2.28.0
2.27.3
2.27.2
2.27.1
2.27.0
2.26.1
2.26.0
2.25.0
2.24.2
2.24.1
2.24.0
2.23.9
2.23.8
2.23.7
2.23.6
2.23.4
2.23.3
2.23.2
2.23.1
2.23.0
2.22.2
2.22.0
2.21.0
2.20.0
2.19.0
2.18.0
2.17.0
2.16.0
2.14.0
2.13.0
2.12.0
2.11.0
2.10.1
2.10.0
2.9.0
2.8.0
2.6.0
2.5.8
2.5.7
2.5.6
2.5.5
2.5.4
2.5.3
2.5.2
2.5.1
2.5.0
2.4.1
2.4.0
2.3.2
2.3.1
2.3.0
2.2.1
2.2.0
2.1.1
2.1.0
2.0.5
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
1.1.1
1.1.0
1.0.0
0.7.2
0.7.1
0.7.0
0.6.0
0.5.0
0.4.3
0.4.2
0.4.1
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.1
0.1.0
A Excel like expression parser, compatible with FLOIP Expression language.
Current section
Files
Jump to
Current section
Files
lib/expression/eval.ex
defmodule Expression.Eval do
@moduledoc """
Take an AST and evaluate it.
"""
import Expression.Ast, only: [fold_infixl: 1]
def evaluate(ast, context, mod) do
{:ok, evaluate!(ast, context, mod)}
rescue
error in RuntimeError -> {:error, error.message}
end
def evaluate!(ast, context, mod) do
context = Expression.Context.new(context)
resp =
ast
|> Enum.reduce([], fn
{:escaped_at, ["@@"]}, acc ->
["@" | acc]
{:substitution, ast}, acc ->
[eval!(fold_infixl(ast), context, mod) | acc]
{:text, text}, acc ->
[text | acc]
end)
case resp do
[value] ->
default_value(value)
values ->
values
|> Enum.map(&default_value/1)
|> Enum.map(&to_string/1)
|> Enum.reverse()
|> Enum.join()
end
end
def eval!(ast, _ctx, _mod) when is_number(ast), do: ast
def eval!(ast, _ctx, _mod) when is_binary(ast), do: ast
def eval!(ast, _ctx, _mod) when is_boolean(ast), do: ast
def eval!({:variable, [k | remainder]}, ctx, mod) do
value = get_var!(ctx, [k])
Enum.reduce(remainder, value, fn
{:list, index}, context_value when is_integer(index) ->
Enum.at(context_value, index)
{:list, value}, context_value ->
index = eval!(value, ctx, mod)
Enum.at(context_value, index)
{:attribute, _key}, nil ->
nil
{:attribute, key}, context_value ->
Map.get(context_value, key)
end)
end
# def eval!({:list, value}, ctx, _mod), do: Enum.at(ctx, value)
def eval!({:literal, value}, _ctx, _mod), do: value
def eval!({:substitution, ast}, ctx, mod), do: eval!(fold_infixl(ast), ctx, mod)
def eval!({:block, ast}, ctx, mod), do: eval!(fold_infixl(ast), ctx, mod)
# function calls without arguments
def eval!({:function, [name]}, ctx, mod),
do: eval!({:function, [name, {:arguments, []}]}, ctx, mod)
def eval!({:function, [name, ast]}, ctx, mod) do
case mod.handle(name, eval!(ast, ctx, mod), ctx) do
{:ok, value} -> value
{:error, reason} -> "ERROR: #{inspect(reason)}"
end
end
def eval!({:arguments, ast}, ctx, mod), do: Enum.map(ast, &eval!(&1, ctx, mod))
def eval!({:+, [a, b]}, ctx, mod), do: eval!(a, ctx, mod, :num) + eval!(b, ctx, mod, :num)
def eval!({:-, [a, b]}, ctx, mod), do: eval!(a, ctx, mod, :num) - eval!(b, ctx, mod, :num)
def eval!({:*, [a, b]}, ctx, mod), do: eval!(a, ctx, mod, :num) * eval!(b, ctx, mod, :num)
def eval!({:/, [a, b]}, ctx, mod), do: eval!(a, ctx, mod, :num) / eval!(b, ctx, mod, :num)
def eval!({:>, [a, b]}, ctx, mod), do: eval!(a, ctx, mod, :num) > eval!(b, ctx, mod, :num)
def eval!({:>=, [a, b]}, ctx, mod), do: eval!(a, ctx, mod, :num) >= eval!(b, ctx, mod, :num)
def eval!({:<, [a, b]}, ctx, mod), do: eval!(a, ctx, mod, :num) < eval!(b, ctx, mod, :num)
def eval!({:<=, [a, b]}, ctx, mod), do: eval!(a, ctx, mod, :num) <= eval!(b, ctx, mod, :num)
def eval!({:==, [a, b]}, ctx, mod), do: eval!(a, ctx, mod) == eval!(b, ctx, mod)
def eval!({:!=, [a, b]}, ctx, mod), do: eval!(a, ctx, mod) != eval!(b, ctx, mod)
def eval!({:^, [a, b]}, ctx, mod), do: :math.pow(eval!(a, ctx, mod), eval!(b, ctx, mod))
def eval!({:&, [a, b]}, ctx, mod), do: [a, b] |> Enum.map_join("", &eval!(&1, ctx, mod))
defp eval!(ast, ctx, mod, type), do: ast |> eval!(ctx, mod) |> guard_type!(type)
defp get_var!(nil, k), do: "ERROR: #{inspect(k)} cannot be read from value `nil`"
defp get_var!(ctx, k), do: get_in(ctx, k)
defp default_value(%{"__value__" => default_value}), do: default_value
defp default_value(value), do: value
defp guard_type!(v, :num) when is_number(v), do: v
defp guard_type!(v, :num), do: raise("expression is not a number: `#{inspect(v)}`")
end