lib/evalex.ex

defmodule EvalEx do
  @moduledoc """
  EvalEx is a powerful expression evaluation library for Elixir,
  based on [evalexpr](https://github.com/ISibboI/evalexpr) using [rustler](https://github.com/rusterlium/rustler).
  """

  @type evalex_any :: number() | boolean() | String.t() | nil | [evalex_any()]

  @type evalex_error ::
          :wrong_operator_amount
          | :wrong_function_argument_amount
          | :expected_string
          | :expected_int
          | :expected_float
          | :expected_number
          | :expected_number_or_string
          | :expected_boolean
          | :expected_tuple
          | :exepcted_fixed_length_tuple
          | :expected_empty
          | :append_to_leaf_node
          | :precedence_violation
          | :variable_identifier_not_found
          | :function_identefier_not_found
          | :type_error
          | :wrong_type_combination
          | :unmatched_l_brace
          | :unmatched_r_brace
          | :missing_operator_outside_of_brace
          | :unmatched_partial_token
          | :addition_error
          | :subtraction_error
          | :negation_error
          | :multiplication_error
          | :division_error
          | :modulation_error
          | :invaild_regex
          | :context_not_mutable
          | :illegal_escape_sequence
          | :custom_message
          | :unknown

  @doc """
  Evaluates the given expression and returns the result.

  ## Examples

      iex> EvalEx.eval("1 + 1")
      {:ok, 2}

      iex> EvalEx.eval("a * b", %{"a" => 10, "b" => 10})
      {:ok, 100}

      iex> EvalEx.eval("a == b", %{"a" => "tonio", "b" => "wanda"})
      {:ok, false}

      iex> EvalEx.eval("a != b", %{"a" => "tonio", "b" => "wanda"})
      {:ok, true}

      iex> EvalEx.eval("len(a)", %{"a" => [1, 2, 3]})
      {:ok, 3}

      iex> EvalEx.eval("a + b", %{"a" => 10})
      {:error,
      {:variable_identifier_not_found,
      "Variable identifier is not bound to anything by context: \"b\"."}}

      iex> {:ok, precompiled_expression} = EvalEx.precompile_expression("1 + 1")
      {:ok,
      %EvalEx.PrecompiledExpression{
      reference: #Reference<0.2278913865.304611331.189837>,
      resource: #Reference<0.2278913865.304742403.189834>
      }}

      iex> EvalEx.eval(precompiled_expression)
      {:ok, 2}
  """
  @doc since: "0.1.0"
  @spec eval(String.t() | EvalEx.PrecompiledExpression.t(), map()) ::
          {:ok, evalex_any()} | {:error, {evalex_error(), String.t()}}
  def eval(expression, context \\ %{})

  def eval(%EvalEx.PrecompiledExpression{resource: resource}, %{} = context),
    do: EvalEx.Native.eval_precompiled_expression(resource, context)

  def eval(expression, context) when is_binary(expression),
    do: EvalEx.Native.eval(expression, %{} = context)

  @doc """
  Precompiles the given expression.
  """
  @doc since: "0.1.1"
  @spec precompile_expression(String.t()) ::
          {:ok, EvalEx.PrecompiledExpression.t()} | {:error, {evalex_error(), String.t()}}
  def precompile_expression(expression) do
    case EvalEx.Native.precompile_expression(expression) do
      {:ok, resource} ->
        {:ok, EvalEx.PrecompiledExpression.wrap_resource(resource)}

      {:error, error} ->
        {:error, error}
    end
  end
end