defmodule Formula.Function.Runtime do
@moduledoc """
Evaluate the function
"""
alias Explorer.Series
alias Formula.Errors.DivideByZeroError
alias Formula.Errors.InvalidArgumentsError
alias Formula.Errors.InvalidFormulaError
alias Formula.Function.DecimalOperation
alias Formula.Function.SeriesOperation
@type success :: {:ok, term()}
@type failure ::
{:error, DivideByZeroError.t() | InvalidFormulaError.t() | InvalidArgumentsError.t()}
@spec exec(String.t(), list(Decimal.t())) :: success | failure
def exec(name, arguments) do
case call(name, arguments) do
{:error, error} -> {:error, error}
value -> {:ok, value}
end
end
defp call(operation, [%Decimal{}, %Decimal{}] = arguments) do
DecimalOperation.call(operation, arguments)
end
defp call(operation, [%Series{}, %Series{}] = arguments) do
with true <- SeriesOperation.is_equal_length?(arguments),
%Series{} = series <- SeriesOperation.call(operation, arguments) do
series
else
false ->
{:error,
InvalidArgumentsError.exception(
message: "list must be same length",
arguments: Enum.map(arguments, &Series.to_list/1)
)}
{:error, error} ->
{:error, error}
end
end
defp call(operation, [%Series{} = a, %Decimal{} = b]) do
case SeriesOperation.call(operation, [a, Decimal.to_float(b)]) do
%Series{} = series -> series
{:error, error} -> {:error, error}
end
end
defp call(operation, [%Decimal{} = a, %Series{} = b]) do
case SeriesOperation.call(operation, [Decimal.to_float(a), b]) do
%Series{} = series -> series
{:error, error} -> {:error, error}
end
end
defp call(operation, [%Series{} = series]) do
case SeriesOperation.call(operation, series) do
value when is_float(value) -> Decimal.from_float(value)
value when is_integer(value) -> Decimal.new(value)
{:error, error} -> {:error, error}
end
end
defp call(operation, arguments) do
{:error,
InvalidArgumentsError.exception(
message: "Invalid data type",
operation: operation,
arguments: inspect(arguments)
)}
end
end