Packages

Mathematical expression parser and evaluator in Elixir. Tags: math, parse, parser, eval

Current section

Files

Jump to
abacus lib runtime scope.ex
Raw

lib/runtime/scope.ex

defmodule Abacus.Runtime.Scope do
@moduledoc """
Contains helper functions to work with the scope of an Abacus script at runtime
"""
@doc """
Tries to get values from a subject.
Subjects can be:
* maps
* keyword lists
* lists (with integer keys)
* nil (special case so access to undefined variables will not crash execution)
Keys can be:
* strings
* atoms (you can pass a resolve map that converts the given atom key into a string key)
* positive integers (for lists only)
The Abacus parser will reference this function extensively like this:
`a.b[2]` will turn into
```
get_in(get_in(var0, :var1, var_lookup), 2, var_lookup)
```
in this case, `var_lookup` will be:
```
%{
var1: "a",
var2: "b"
}
```
"""
@type key :: String.t | atom
@type index :: integer
@spec get_in(nil, key | index, map) :: nil
@spec get_in(map, key, map) :: nil | term
@spec get_in(list, index, map) :: nil | term
@spec get_in(list, key, map) :: nil | term
def get_in(subject, key_or_index, var_lookup \\ %{})
def get_in(nil, _, _), do: nil
def get_in(subject, key, lookup) when is_atom(key) do
key = Map.get(lookup, key, to_string(key))
get_in(subject, key, lookup)
end
def get_in(subject, key, _) when (is_map(subject) or is_list(subject)) and is_binary(key) do
Enum.reduce_while(subject, nil, fn {k, v}, _ ->
case to_string(k) do
^key -> {:halt, v}
_ -> {:cont, nil}
end
end)
end
def get_in(list, index, _) when is_list(list) and is_integer(index) and index >= 0 do
Enum.at(list, index, nil)
end
@doc """
Renames first-level variables into their respective `:"var\#{x}"` atoms
"""
def prepare_scope(scope, lookup) when is_map(scope) or is_list(scope) do
scope = Map.merge(default_scope(), Enum.into(scope, %{}))
Enum.map(scope, fn
{k, v} ->
case Map.get(lookup, to_string(k)) do
nil ->
# will not be used anyway
{k, v}
varname ->
{varname, v}
end
v ->
v
end)
|> Enum.filter(fn
{bin, _} when is_binary(bin) -> false
_ -> true
end)
end
def prepare_scope(primitive, _), do: primitive
@math_funs_1 ~w[
acos
acosh
asin
asinh
atan
atanh
ceil
cos
cosh
exp
floor
log
log10
log2
sin
sinh
sqrt
tan
tanh
]a
@doc """
Returns a scope with some default functions and constants
"""
def default_scope do
constants = %{
PI: :math.pi(),
round: &apply(Float, :round, [&1, &2])
}
Enum.map(@math_funs_1, fn fun ->
{fun, &apply(:math, fun, [&1])}
end)
|> Enum.into(%{})
|> Map.merge(constants)
end
end