Current section
Files
Jump to
Current section
Files
lib/extended_logic.ex
defmodule JSONLogic_ExtendedOperations do
@moduledoc """
Extended operators on top of the existing JSON Logic operators
"""
@doc """
implements the exponential operator
"""
def exponent_op(numbers, data) do
numbers
|> Enum.map(fn x -> JsonLogicXL.resolve(x, data) end)
|> Enum.reduce_while({nil, 0}, fn
number, {nil, total} ->
{:cont, {number, total}}
number, {base, total} when is_number(number) ->
{:cont, {base, total + number}}
%Decimal{} = decimal, {base, total} ->
# Convert Decimal to float
number = Decimal.to_float(decimal)
{:cont, {base, total + number}}
string, {base, total} when is_binary(string) ->
case parse_number(string) do
{:ok, number} ->
{:cont, {base, total + number}}
:error ->
{:cont, {base, total}} # Continue accumulating valid base and exponents
end
_any, {base, total} ->
{:cont, {base, total}} # Continue accumulating valid base and exponents
end)
|> case do
{base, exponent} when is_number(base) and is_number(exponent) ->
result = exponentiate(base, exponent)
result
{base, exponent} when is_binary(base) ->
# Convert base if it's a string
case parse_numeric(base) do
{:ok, base_number} ->
result = exponentiate(base_number, exponent)
result
:error ->
nil
end
{base, exponent} when is_struct(base, Decimal) ->
# Convert base from Decimal to float
base_float = Decimal.to_float(base)
result = exponentiate(base_float, exponent)
result
{_base, _exponent} ->
nil
end
end
defp exponentiate(%Decimal{} = left, right) when is_number(right) do
left_float = Decimal.to_float(left)
exponentiate(left_float, right)
end
defp exponentiate(left, %Decimal{} = right) when is_number(left) do
right_float = Decimal.to_float(right)
exponentiate(left, right_float)
end
defp exponentiate(left, right) when is_binary(left) do
case parse_numeric(left) do
{:ok, number} ->
exponentiate(number, right)
:error ->
nil
end
end
defp exponentiate(left, right) when is_binary(right) do
case parse_numeric(right) do
{:ok, number} ->
exponentiate(left, number)
:error ->
nil
end
end
defp exponentiate(base, exponent) when is_number(base) and is_number(exponent) do
Float.pow(float(base), float(exponent))
end
def parse_numeric(string) do
case Float.parse(string) do
{number, _} ->
{:ok, number}
:error ->
:error
end
end
defp parse_number(value) do
case Integer.parse(value) do
{integer, ""} ->
{:ok, integer}
_ ->
case Float.parse(value) do
{float, ""} ->
{:ok, float}
{float, "."} ->
{:ok, float}
_ ->
:error
end
end
end
def float(value) when is_integer(value), do: value * 1.0
def float(value) when is_float(value), do: value
@doc """
implements the xlookup operator
"""
def xlookup_op([match, opts, map_key_prop, map_val_prop | _rest_of_list], data) do
xlookup(JsonLogicXL.resolve(match, data), JsonLogicXL.resolve(opts, data),
JsonLogicXL.resolve(map_key_prop, data), JsonLogicXL.resolve(map_val_prop, data))
end
defguardp xlookup_guard(match, opts, k_prop, v_prop) when (is_binary(match) or is_number(match)) and is_list(opts)
and is_binary(k_prop) and is_binary(v_prop)
defp xlookup(match, options, key_prop, val_prop) when
xlookup_guard(match, options, key_prop, val_prop)
do
options |> Enum.find_value(fn opt ->
if opt[key_prop] == match, do: opt[val_prop]
end)
end
@doc """
implements the ln (natural log) operator
"""
def natural_log_op(num, data) do
natural_log(JsonLogicXL.resolve(num, data))
end
defp natural_log(val) when is_binary(val) do
{:ok, num} = parse_number(val)
natural_log(num)
end
defp natural_log(val) when is_number(val) do
:math.log(val)
end
@doc """
implements the range_lookup operator
"""
def range_lookup_op([value, ranges], data), do: range_lookup_op([value, ranges, nil], data)
def range_lookup_op([value, ranges, default_value | _], data) do
range_lookup(JsonLogicXL.resolve(value, data), JsonLogicXL.resolve(ranges, data),
JsonLogicXL.resolve(default_value, data))
end
defp range_lookup(value, ranges, default_val)
defp range_lookup(value, ranges, default_val) when is_binary(value) do
{:ok, num} = parse_number(value)
range_lookup(num, ranges, default_val)
end
defp range_lookup(value, ranges, default_val) when is_number(value) and is_list(ranges) do
res = Enum.find_value(ranges, fn x ->
min_check = x["min"] == nil || value >= x["min"]
max_check = x["max"] == nil || value < x["max"]
if min_check && max_check do
x["result"]
end
end)
if res != nil do
res
else
default_val
end
end
@eulers_constant 2.7182818284590452353602874713526624977572470936999595749669676277
@doc """
raises eulers number to a power passed in.
Similar to Excel's "exp" function.
"""
def eulers_exponent_op(val, data), do: eulers_exponent(JsonLogicXL.resolve(val, data))
defp eulers_exponent(val) when is_binary(val) do
{:ok, num} = parse_number(val)
eulers_exponent(num)
end
defp eulers_exponent(val) when is_number(val) do
@eulers_constant ** val
end
end