Packages
expression
2.5.4
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/callbacks.ex
defmodule Expression.Callbacks do
@moduledoc """
Use this module to implement one's own callbacks.
The standard callbacks available are implemented in `Expression.Callbacks.Standard`.
```elixir
defmodule MyCallbacks do
use Expression.Callbacks
@doc \"\"\"
Roll a dice and randomly return a number between 1 and 6.
\"\"\"
def dice_roll(ctx) do
Enum.random(1..6)
end
end
```
"""
alias Expression.Callbacks.Standard
@reserved_words ~w[and if or not]
@doc """
Convert a string function name into an atom meant to handle
that function
Reserved words such as `and`, `if`, and `or` are automatically suffixed
with an `_` underscore.
"""
def atom_function_name(function_name) when function_name in @reserved_words,
do: atom_function_name("#{function_name}_")
def atom_function_name(function_name) do
String.to_atom(function_name)
end
@doc """
Handle a function call while evaluating the AST.
Handlers in this module are either:
1. The function name as is
2. The function name with an underscore suffix if the function name is a reserved word
3. The function name suffixed with `_vargs` if the takes a variable set of arguments
"""
@spec handle(module :: module, function_name :: binary, arguments :: [any], context :: map) ::
{:ok, any} | {:error, :not_implemented}
def handle(module \\ Standard, function_name, arguments, context) do
case implements(module, function_name, arguments) do
{:exact, module, function_name, _arity} ->
{:ok, apply(module, function_name, [context] ++ arguments)}
{:vargs, module, function_name, _arity} ->
{:ok, apply(module, function_name, [context, arguments])}
{:error, reason} ->
{:error, reason}
end
end
def implements(module \\ Standard, function_name, arguments) do
exact_function_name = atom_function_name(function_name)
vargs_function_name = atom_function_name("#{function_name}_vargs")
Code.ensure_loaded!(Standard)
cond do
# Check if the exact function signature has been implemented
function_exported?(module, exact_function_name, length(arguments) + 1) ->
{:exact, module, exact_function_name, length(arguments) + 1}
# Check if it's been implemented to accept a variable amount of arguments
function_exported?(module, vargs_function_name, 2) ->
{:vargs, module, vargs_function_name, 2}
# Check if the exact function signature has been implemented
function_exported?(Standard, exact_function_name, length(arguments) + 1) ->
{:exact, Standard, exact_function_name, length(arguments) + 1}
# Check if it's been implemented to accept a variable amount of arguments
function_exported?(Standard, vargs_function_name, 2) ->
{:vargs, Standard, vargs_function_name, 2}
# Otherwise fail
true ->
{:error, "#{function_name} is not implemented."}
end
end
defmacro __using__(_opts) do
quote do
import Expression.Callbacks.EvalHelpers
defdelegate handle(module \\ __MODULE__, function_name, arguments, context),
to: Expression.Callbacks
end
end
end