#-------------------------------------------------------------------------------
# Author: Keith Brings
# Copyright (C) 2018 Noizu Labs, Inc. All rights reserved.
#-------------------------------------------------------------------------------
defmodule Noizu.RuleEngine.Op.ArithmeticOp do
@type t :: %__MODULE__{
name: String.t | nil,
description: String.t | nil,
identifier: String.t | list | tuple, # Materialized Path.
arguments: list,
operation: :"+" | :"-" | :"*" | :"/" | :"^",
arithmetic_strategy: any,
settings: Keyword.t,
}
defstruct [
name: nil,
description: nil,
identifier: nil,
arguments: [],
operation: :"+",
arithmetic_strategy: :default,
settings: [short_circuit?: :auto, async?: :auto, throw_on_timeout?: :auto]
]
end
defimpl Noizu.RuleEngine.ScriptProtocol, for: Noizu.RuleEngine.Op.ArithmeticOp do
alias Noizu.RuleEngine.Helper
#-----------------
# execute!/3
#-----------------
def execute!(this, state, context), do: execute!(this, state, context, %{})
#-----------------
# execute!/4
#-----------------
def execute!(this, state, context, options) do
cond do
Enum.member?([true, :auto, :required], this.settings[:async?]) && (options[:settings] && options.settings.supports_async? == true) -> execute!(:async, this, state, context, options)
this.settings[:async?] == :required -> throw Noizu.RuleEngine.Error.Basic.new("[ScriptError] Unable to perform required async execute on #{this.__struct__} - #{identifier(this, state, context)}", 310)
true -> execute!(:sync, this, state, context, options)
end
end
def perform_arithmetic(left_arg, right_arg, this, state, context, options) do
cs = this.arithmetic_strategy || :default
cond do
cs == :default ->
c = case this.operation do
:"+" -> left_arg + right_arg
:"-" -> left_arg - right_arg
:"*" -> left_arg * right_arg
:"/" -> left_arg / right_arg
:"^" -> :math.pow(left_arg, right_arg)
end
{c, nil}
match?({_,_,3}, cs) ->
{m,f,3} = cs
{:erlang.apply(m, f, [this.operation, left_arg, right_arg]), nil}
match?({_,_,6}, cs) ->
{m,f,6} = cs
:erlang.apply(m, f, [this.operation, left_arg, right_arg, state, context, options])
is_function(cs, 3) -> {cs.(this.operation, left_arg, right_arg), nil}
is_function(cs, 6) -> cs.(this.operation, left_arg, right_arg, state, context, options)
true -> throw Noizu.RuleEngine.Error.Basic.new("[ScriptError] - #{identifier(this, state, context, options)} Invalid comparison strategy #{inspect cs}", 404)
end
end
#-----------------
# execute!/5
#-----------------
def execute!(:sync, this, state, context, options) do
[h|t] = this.arguments
{first_arg, state} = Noizu.RuleEngine.ScriptProtocol.execute!(h, state, context, options)
#cs = this.arithmetic_strategy || :default
{product, state} = Enum.reduce(t, {first_arg, state},
fn(child, {left_arg, state}) ->
{right_arg, state} = Noizu.RuleEngine.ScriptProtocol.execute!(child, state, context, options)
{product, s} = perform_arithmetic(left_arg, right_arg, this, state, context, options)
{product, s || state}
end
)
{product, state}
end
def execute!(:async, this, state, context, options) do
yield_wait = this.settings[:timeout] || options[:timeout] || 15_000
[first_arg|remaining_args] = this.arguments
|> Enum.map(fn(child) -> Task.async(fn -> (Noizu.RuleEngine.ScriptProtocol.execute!(child, state, context, options)) end) end)
|> Task.yield_many(yield_wait)
|> Enum.map(
fn({task, res}) ->
case res do
{:ok, {o, _s}} -> o
_ ->
Task.shutdown(task, yield_wait)
{:error, {Noizu.RuleEngine.ScriptProtocol, {:timeout, task}}}
end
end)
if match?({:error, {Noizu.RuleEngine.ScriptProtocol, {:timeout, _}}}, first_arg) do
{:error, {Noizu.RuleEngine.ScriptProtocol, {:timeout, task}}} = first_arg
throw Noizu.RuleEngine.Error.Basic.new("[ScriptError] - #{identifier(this, state, context, options)} Execute Child Task Failed to Complete #{inspect task}", 404)
end
{product, _} = Enum.reduce(remaining_args || [], first_arg,
fn(right_arg, left_arg) ->
cond do
match?({:error, {Noizu.RuleEngine.ScriptProtocol, {:timeout, _}}}, right_arg) ->
{:error, {Noizu.RuleEngine.ScriptProtocol, {:timeout, task}}} = right_arg
throw Noizu.RuleEngine.Error.Basic.new("[ScriptError] - #{identifier(this, state, context, options)} Execute Child Task Failed to Complete #{inspect task}", 404)
true -> elem(perform_arithmetic(left_arg, right_arg, this, state, context, options), 0)
end
end)
{product, state}
end
#---------------------
# identifier/3
#---------------------
def identifier(this, _state, _context), do: Helper.identifier(this)
#---------------------
# identifier/4
#---------------------
def identifier(this, _state, _context, _options), do: Helper.identifier(this)
#---------------------
# render/3
#---------------------
def render(this, state, context), do: Helper.render_arg_list("CMP #{this.comparison}", identifier(this, state, context), this.arguments || [], state, context, %{})
#---------------------
# render/4
#---------------------
def render(this, state, context, options), do: Helper.render_arg_list("CMP #{this.comparison}", identifier(this, state, context, options), this.arguments || [], state, context, options)
end