Spek is a boolean expression engine for Elixir.

It allows you to model, optimize, and evaluate rules using composable expressions.

This module contains all expression builder, evaluation, and optimization functions. Please refer to the readme for details.

Example

Given this check module:

defmodule DeviceChecks do
  import Spek.Macros

  defcheck device_online(device, reason: :device_offline) do
    device.online?
  end

  defcheck battery_above_20(device, reason: :battery_too_low) do
    device.battery_level > 20
  end

  defcheck charging(device) do
    device.charging?
  end

  defcheck low_power_mode_enabled(device) do
    device.low_power_mode?
  end
end

We can compose, optimize, and evaluate rules like this:

iex> import Spek
iex> battery_safe =
...>   all_of([
...>     DeviceChecks.device_online_check(),
...>     DeviceChecks.battery_above_20_check()
...>   ])
iex> charging_safe =
...>   all_of([
...>     DeviceChecks.device_online_check(),
...>     DeviceChecks.charging_check()
...>   ])
iex> rule =
...>   any_of([
...>     battery_safe,
...>     charging_safe,
...>     DeviceChecks.low_power_mode_enabled_check()
...>   ])
%Spek.AnyOf{
  children: [
    %Spek.AllOf{
      children: [
        %Spek.Check{
          module: DeviceChecks,
          fun: :device_online,
          args: [:ctx]
        },
        %Spek.Check{
          module: DeviceChecks,
          fun: :battery_above_20,
          args: [:ctx]
        }
      ]
    },
    %Spek.AllOf{
      children: [
        %Spek.Check{
          module: DeviceChecks,
          fun: :device_online,
          args: [:ctx]
        },
        %Spek.Check{
          module: DeviceChecks,
          fun: :charging,
          args: [:ctx]
        }
      ]
    },
    %Spek.Check{
      module: DeviceChecks,
      fun: :low_power_mode_enabled,
      args: [:ctx]
    }
  ]
}
iex> rule = optimize(rule)
%Spek.AnyOf{
  children: [
    %Spek.AllOf{
      children: [
        %Spek.Check{
          module: DeviceChecks,
          fun: :device_online,
          args: [:ctx]
        },
        %Spek.AnyOf{
          children: [
            %Spek.Check{
              module: DeviceChecks,
              fun: :battery_above_20,
              args: [:ctx]
            },
            %Spek.Check{
              module: DeviceChecks,
              fun: :charging,
              args: [:ctx]
            }
          ]
        }
      ]
    },
    %Spek.Check{
      module: DeviceChecks,
      fun: :low_power_mode_enabled,
      args: [:ctx]
    }
  ]
}
iex> device = %{
...>   online?: true,
...>   battery_level: 12,
...>   charging?: false,
...>   low_power_mode?: false
...> }
iex> Spek.eval?(rule, device)
false
iex> device = %{
...>   online?: false,
...>   battery_level: 25,
...>   charging?: false,
...>   low_power_mode?: false
...> }
iex> Spek.eval_tree(rule, device)
{
  :error,
  %Spek.EvaluationError{
    expression: %Spek.AnyOf{
      children: [
        %Spek.AllOf{
          satisfied?: false,
          children: [
            %Spek.Check{
              module: DeviceChecks,
              fun: :device_online,
              args: [:ctx],
              result: {:error, :device_offline},
              satisfied?: false
            }
          ]
        },
        %Spek.Check{
          module: DeviceChecks,
          fun: :low_power_mode_enabled,
          args: [:ctx],
          result: {:error, :failed},
          satisfied?: false
        }
      ],
      satisfied?: false
    },
    message: "rule evaluation failed"
  }
}

Summary

Builder Functions

Builds an expression that requires all children to be true.

Builds an expression that requires both children to be true.

Builds an expression that requires at least one child to be true.

Builds an expression that requires at least one of two children to be true.

Builds an expression that is always false.

Builds an expression that always evaluates to the same value.

Builds an expression that evaluates to true unless both children are true.

Negates the given expression.

Builds an expression that requires all of its children to be false.

Builds an expression that evaluates to true if both children are false.

Builds an expression that is always true.

Builds the exclusive or of the given expressions.

Evaluation Functions

Collects all tagged results from an expression into a list.

Collects the success or error results of an expression into a list.

Evaluates the given expression and returns :ok or an error tuple.

Evaluates the given expression and raises an exception if it is not satisfied.

Evaluates the given expression and returns the result as a boolean.

Evaluates the given expression with eval_tree/2 and collects the results into a list with collect_results/2.

Evaluates the given expression with eval_tree/2 and collects the results into a list with collect_results/2.

Evaluates the given expression with eval_tree_all/2 and collects the results into a list with collect_results/2.

Evaluates the given expression with eval_tree_all/2 and collects the results into a list with collect_results/2.

Evaluates the given expression and returns the expression annotated with evaluation results.

Evaluates the given expression and returns the expression annotated with evaluation results or raises an error.

Evaluates the full given expression and returns the expression annotated with the expression results.

Evaluates the full given expression and returns the expression annotated with evaluation results or raises an error.

Filters the given enumerable to only retain the items that satisfy the given expression.

Filters the given enumerable to only retain the items that do not satisfy the given expression.

Optimization Functions

Performs boolean algebra transformations on the given expression.

Types

The evaluation context as passed to the evaluation functions.

A boolean expression tree.

An expression result value that results in a failed evaluation.

The result of an expression evaluation.

An expression result value that results in a successful evaluation.

Builder Functions

all_of(children)

@spec all_of([expression()]) :: Spek.AllOf.t()

Builds an expression that requires all children to be true.

Example

iex> all_of([
...>   check(MyModule, :session_active, []),
...>   check(MyModule, :permissions_valid, [])
...> ])
%Spek.AllOf{
  children: [
    %Spek.Check{module: MyModule, fun: :session_active, args: []},
    %Spek.Check{module: MyModule, fun: :permissions_valid, args: []}
  ]
}

all_of(a, b)

@spec all_of(expression(), expression()) :: Spek.AllOf.t()

Builds an expression that requires both children to be true.

Example

iex> all_of(
...>   check(RenderingChecks, :color_profile_valid, []),
...>   check(RenderingChecks, :frame_rate_supported, [])
...> )
%Spek.AllOf{
  children: [
    %Spek.Check{module: RenderingChecks, fun: :color_profile_valid, args: []},
    %Spek.Check{module: RenderingChecks, fun: :frame_rate_supported, args: []}
  ]
}

any_of(children)

@spec any_of([expression()]) :: Spek.AnyOf.t()

Builds an expression that requires at least one child to be true.

Example

iex> any_of([
...>   check(AudioPipelineChecks, :waveform_detected, []),
...>   check(AudioPipelineChecks, :silence_threshold_exceeded, [])
...> ])
%Spek.AnyOf{
  children: [
    %Spek.Check{module: AudioPipelineChecks, fun: :waveform_detected, args: []},
    %Spek.Check{module: AudioPipelineChecks, fun: :silence_threshold_exceeded, args: []}
  ]
}

any_of(a, b)

@spec any_of(expression(), expression()) :: Spek.AnyOf.t()

Builds an expression that requires at least one of two children to be true.

Example

iex> any_of(
...>   check(SubtitleChecks, :burn_in_detected, []),
...>   check(SubtitleChecks, :timecode_aligned, [])
...> )
%Spek.AnyOf{
  children: [
    %Spek.Check{module: SubtitleChecks, fun: :burn_in_detected, args: []},
    %Spek.Check{module: SubtitleChecks, fun: :timecode_aligned, args: []}
  ]
}

check(module, fun, args \\ [:ctx])

@spec check(module(), fun(), Spek.Check.args()) :: Spek.Check.t()

Builds a check.

Example

iex> check(WeatherChecks, :temperature_below_freezing, [0])
%Spek.Check{module: WeatherChecks, fun: :temperature_below_freezing, args: [0]}

fail(result \\ false)

@spec fail(falsy()) :: Spek.Literal.t()

Builds an expression that is always false.

Example

iex> fail()
%Spek.Literal{result: false, satisfied?: false}

iex> fail(:error)
%Spek.Literal{result: :error, satisfied?: false}

iex> fail({:error, :insufficient_lighting})
%Spek.Literal{result: {:error, :insufficient_lighting}, satisfied?: false}

literal(result)

@spec literal(result()) :: Spek.Literal.t()

Builds an expression that always evaluates to the same value.

Examples

iex> literal(true)
%Spek.Literal{result: true, satisfied?: true}

iex> literal(:ok)
%Spek.Literal{result: :ok, satisfied?: true}

iex> literal({:ok, "render_queue_ready"})
%Spek.Literal{result: {:ok, "render_queue_ready"}, satisfied?: true}

iex> literal(false)
%Spek.Literal{result: false, satisfied?: false}

iex> literal(:error)
%Spek.Literal{result: :error, satisfied?: false}

iex> literal({:error, :codec_not_supported})
%Spek.Literal{result: {:error, :codec_not_supported}, satisfied?: false}

nand(a, b)

@spec nand(expression(), expression()) :: expression()

Builds an expression that evaluates to true unless both children are true.

Example

iex> nand(check(RenderChecks, :gpu_available, []), check(RenderChecks, :texture_cache_warm, []))
%Spek.Not{
  expression: %Spek.AllOf{
    children: [
      %Spek.Check{module: RenderChecks, fun: :gpu_available, args: []},
      %Spek.Check{module: RenderChecks, fun: :texture_cache_warm, args: []}
    ]
  }
}

negate(expression)

@spec negate(expression()) :: Spek.Not.t()

Negates the given expression.

Examples

iex> negate(literal(true))
%Spek.Not{expression: %Spek.Literal{result: true, satisfied?: true}}

iex> negate(check(EncodingChecks, :keyframe_aligned, []))
%Spek.Not{
  expression: %Spek.Check{module: EncodingChecks, fun: :keyframe_aligned, args: []}
}

none(children)

@spec none([expression()]) :: expression()

Builds an expression that requires all of its children to be false.

Example

iex> none([check(AudioChecks, :noise_floor_exceeded, []), check(AudioChecks, :clipping_detected, [])])
%Spek.Not{
  expression: %Spek.AnyOf{
    children: [
      %Spek.Check{module: AudioChecks, fun: :noise_floor_exceeded, args: []},
      %Spek.Check{module: AudioChecks, fun: :clipping_detected, args: []}
    ]
  }
}

nor(a, b)

@spec nor(expression(), expression()) :: expression()

Builds an expression that evaluates to true if both children are false.

Example

iex> nor(check(VideoChecks, :frame_dropped, []), check(VideoChecks, :desync_detected, []))
%Spek.Not{
  expression: %Spek.AnyOf{
    children: [
      %Spek.Check{module: VideoChecks, fun: :frame_dropped, args: []},
      %Spek.Check{module: VideoChecks, fun: :desync_detected, args: []}
    ]
  }
}

pass(result \\ true)

@spec pass(truthy()) :: Spek.Literal.t()

Builds an expression that is always true.

Example

iex> pass()
%Spek.Literal{result: true, satisfied?: true}

iex> pass(:ok)
%Spek.Literal{result: :ok, satisfied?: true}

iex> pass({:ok, "proxy_stream_ready"})
%Spek.Literal{result: {:ok, "proxy_stream_ready"}, satisfied?: true}

xor(a, b)

@spec xor(expression(), expression()) :: expression()

Builds the exclusive or of the given expressions.

Example

iex> xor(check(PipelineChecks, :transcode_complete, []), check(PipelineChecks, :thumbnail_generated, []))
%Spek.AnyOf{
  children: [
    %Spek.AllOf{
      children: [
        %Spek.Check{module: PipelineChecks, fun: :transcode_complete, args: []},
        %Spek.Not{
          expression: %Spek.Check{
            module: PipelineChecks,
            fun: :thumbnail_generated,
            args: []
          }
        }
      ]
    },
    %Spek.AllOf{
      children: [
        %Spek.Not{
          expression: %Spek.Check{
            module: PipelineChecks,
            fun: :transcode_complete,
            args: []
          }
        },
        %Spek.Check{module: PipelineChecks, fun: :thumbnail_generated, args: []}
      ]
    }
  ]
}

Evaluation Functions

collect_results(expression)

@spec collect_results(expression()) :: [term()]

Collects all tagged results from an expression into a list.

Results are returned without their :ok / :error tags.

Examples

Literals and checks contribute only tagged tuple payloads. The plain result values :ok, :error, true and false are not returned.

iex> Spek.collect_results(
...>   %Literal{result: {:ok, "good"}, satisfied?: true}
...> )
["good"]

iex> Spek.collect_results(
...>   %Literal{result: true, satisfied?: true}
...> )
[]

iex> Spek.collect_results(
...>   %Check{
...>     module: Checks,
...>     fun: :some_check,
...>     args: [],
...>     result: {:error, "bad"},
...>     satisfied?: false
...>   }
...> )
["bad"]

The results of nested expressions are flattened.

iex> expression =
...>   %AllOf{
...>     children: [
...>       %Literal{result: {:ok, "a"}, satisfied?: true},
...>       %Literal{result: {:error, "b"}, satisfied?: false},
...>       %Check{
...>         module: Checks,
...>         fun: :some_check,
...>         args: [],
...>         result: {:ok, "c"},
...>         satisfied?: true
...>       }
...>     ]
...>   }
iex> Spek.collect_results(expression)
["a", "b", "c"]

collect_results(expression, only)

@spec collect_results(expression(), :ok | :error) :: [term()]

Collects the success or error results of an expression into a list.

Examples

The returned list only contains results using :ok or :error tuples.

iex> Spek.collect_results(
...>   %Literal{result: {:ok, "good"}, satisfied?: true},
...>   :ok
...> )
["good"]

iex> Spek.collect_results(
...>   %Check{
...>     module: Checks,
...>     fun: :some_check,
...>     args: [],
...>     result: {:error, "bad"},
...>     satisfied?: false
...>   },
...>   :error
...> )
["bad"]

The plain result values :ok, :error, true and false are not returned.

iex> Spek.collect_results(
...>   %Check{
...>     module: Checks,
...>     fun: :some_check,
...>     args: [],
...>     result: false,
...>     satisfied?: false
...>   },
...>   :error
...> )
[]

The results of nested expressions are flattened.

iex> expression =
...>   %AllOf{
...>     children: [
...>       %Literal{result: {:ok, "a"}, satisfied?: true},
...>       %Literal{result: {:error, "b"}, satisfied?: false},
...>       %Check{
...>         module: Checks,
...>         fun: :some_check,
...>         args: [],
...>         result: {:ok, "c"},
...>         satisfied?: true
...>       }
...>     ]
...>   }
iex> Spek.collect_results(expression, :ok)
["a", "c"]
iex> Spek.collect_results(expression, :error)
["b"]

Not reverses which tagged results are returned:

iex> expression =
...>   %Not{
...>     satisfied?: true,
...>     expression: %Literal{
...>       result: {:error, "bad"},
...>       satisfied?: false
...>     }
...>   }
iex> Spek.collect_results(expression, :ok)
["bad"]
iex> Spek.collect_results(expression, :error)
[]

eval(expression, context \\ [])

@spec eval(expression(), context()) :: :ok | {:error, Spek.EvaluationError.t()}

Evaluates the given expression and returns :ok or an error tuple.

Stops early as soon as the final outcome is determined.

Examples

iex> eval(
...>   %Check{module: String, fun: :starts_with?, args: [:ctx, "scene_"]},
...>   "scene_042_render_complete"
...> )
:ok

iex> eval(
...>   %Check{module: String, fun: :starts_with?, args: [:ctx, "scene_"]},
...>   "shot_042_render_complete"
...> )
{:error, %Spek.EvaluationError{message: "rule evaluation failed"}}

eval!(expression, context \\ [])

@spec eval!(expression(), context()) :: :ok | no_return()

Evaluates the given expression and raises an exception if it is not satisfied.

Stops early as soon as the final outcome is determined.

Examples

iex> eval!(
...>   %Check{module: String, fun: :starts_with?, args: [:ctx, "scene_"]},
...>   "scene_042_render_complete"
...> )
:ok

iex> eval!(
...>   %Check{module: String, fun: :starts_with?, args: [:ctx, "scene_"]},
...>   "shot_042_render_complete"
...> )
** (Spek.EvaluationError) rule evaluation failed

eval?(expr, context \\ [])

@spec eval?(expression(), context()) :: boolean()

Evaluates the given expression and returns the result as a boolean.

Stops early as soon as the final outcome is determined.

Examples

iex> eval?(
...>   %Check{module: String, fun: :starts_with?, args: [:ctx, "scene_"]},
...>   "scene_042_render_complete"
...> )
true

iex> eval?(
...>   %Check{module: String, fun: :starts_with?, args: [:ctx, "scene_"]},
...>   "shot_042_render_complete"
...> )
false

eval_collect(expression, context \\ [])

@spec eval_collect(expression(), context()) ::
  {:ok, [term()]} | {:error, Spek.EvaluationError.t()}

Evaluates the given expression with eval_tree/2 and collects the results into a list with collect_results/2.

In the success case, an :ok tuple with the list of success results is returned.

In the error case, an :error tuple with a Spek.EvaluationError struct that contains both the evaluation tree and the list of error results.

Examples

iex> eval_collect(
...>   %Check{module: Date, fun: :from_iso8601, args: [:ctx]},
...>   "2000-01-12"
...> )
{:ok, [~D[2000-01-12]]}

iex> eval_collect(
...>   %Check{module: Date, fun: :from_iso8601, args: [:ctx]},
...>   "2000-01.12"
...> )
{
  :error,
  %Spek.EvaluationError{
    expression: %Spek.Check{
      module: Date,
      fun: :from_iso8601,
      args: [:ctx],
      result: {:error, :invalid_format},
      satisfied?: false
    },
    results: [:invalid_format],
    message: "rule evaluation failed"
  }
}

eval_collect!(expression, context \\ [])

@spec eval_collect!(expression(), context()) :: [term()] | no_return()

Evaluates the given expression with eval_tree/2 and collects the results into a list with collect_results/2.

In the success case, the list of success results is returned.

In the error case, a Spek.EvaluationError exception is raised that contains both the evaluation tree and the list of error results.

Examples

iex> eval_collect!(
...>   %Check{module: Date, fun: :from_iso8601, args: [:ctx]},
...>   "2000-01-12"
...> )
[~D[2000-01-12]]

iex> eval_collect!(
...>   %Check{module: Date, fun: :from_iso8601, args: [:ctx]},
...>   "2000-01.12"
...> )
** (Spek.EvaluationError) rule evaluation failed

eval_collect_all(expression, context \\ [])

@spec eval_collect_all(expression(), context()) ::
  {:ok, [term()]} | {:error, Spek.EvaluationError.t()}

Evaluates the given expression with eval_tree_all/2 and collects the results into a list with collect_results/2.

In the success case, an :ok tuple with the list of success results is returned.

In the error case, an :error tuple with a Spek.EvaluationError struct that contains both the evaluation tree and the list of error results.

Examples

iex> eval_collect_all(
...>   %Check{module: Date, fun: :from_iso8601, args: [:ctx]},
...>   "2000-01-12"
...> )
{:ok, [~D[2000-01-12]]}

iex> eval_collect_all(
...>   %Check{module: Date, fun: :from_iso8601, args: [:ctx]},
...>   "2000-01.12"
...> )
{
  :error,
  %Spek.EvaluationError{
    expression: %Spek.Check{
      module: Date,
      fun: :from_iso8601,
      args: [:ctx],
      result: {:error, :invalid_format},
      satisfied?: false
    },
    results: [:invalid_format],
    message: "rule evaluation failed"
  }
}

eval_collect_all!(expression, context \\ [])

@spec eval_collect_all!(expression(), context()) :: [term()] | no_return()

Evaluates the given expression with eval_tree_all/2 and collects the results into a list with collect_results/2.

In the success case, the list of success results is returned.

In the error case, a Spek.EvaluationError exception is raised that contains both the evaluation tree and the list of error results.

Examples

iex> eval_collect_all!(
...>   %Check{module: Date, fun: :from_iso8601, args: [:ctx]},
...>   "2000-01-12"
...> )
[~D[2000-01-12]]

iex> eval_collect_all!(
...>   %Check{module: Date, fun: :from_iso8601, args: [:ctx]},
...>   "2000-01.12"
...> )
** (Spek.EvaluationError) rule evaluation failed

eval_tree(expression, context \\ [])

@spec eval_tree(expression(), context()) ::
  {:ok, expression()} | {:error, Spek.EvaluationError.t()}

Evaluates the given expression and returns the expression annotated with evaluation results.

Stops early as soon as the final outcome is determined. The returned expression only contains the evaluated parts.

Examples

iex> eval_tree(
...>   %Check{module: String, fun: :starts_with?, args: [:ctx, "scene_"]},
...>   "scene_042_render_complete"
...> )
{
  :ok,
  %Spek.Check{
    module: String,
    fun: :starts_with?,
    args: [:ctx, "scene_"],
    result: true,
    satisfied?: true
  }
}

iex> eval_tree(
...>   %Check{module: String, fun: :starts_with?, args: [:ctx, "scene_"]},
...>   "shot_042_render_complete"
...> )
{
  :error,
  %Spek.EvaluationError{
    expression: %Spek.Check{
      module: String,
      fun: :starts_with?,
      args: [:ctx, "scene_"],
      result: false,
      satisfied?: false
    },
    message: "rule evaluation failed"
  }
}

eval_tree!(expression, context \\ [])

@spec eval_tree!(expression(), context()) :: expression() | no_return()

Evaluates the given expression and returns the expression annotated with evaluation results or raises an error.

Stops early as soon as the final outcome is determined. The returned expression only contains the evaluated parts.

Raises an exception if the rule is not satisfied. Unlike eval!/2, the exception contains the evaluated expression.

Examples

iex> eval_tree!(
...>   %Check{module: String, fun: :starts_with?, args: [:ctx, "scene_"]},
...>   "scene_042_render_complete"
...> )
%Spek.Check{
  module: String,
  fun: :starts_with?,
  args: [:ctx, "scene_"],
  result: true,
  satisfied?: true
}

iex> eval_tree!(
...>   %Check{module: String, fun: :starts_with?, args: [:ctx, "scene_"]},
...>   "shot_042_render_complete"
...> )
** (Spek.EvaluationError) rule evaluation failed

eval_tree_all(expression, context \\ [])

@spec eval_tree_all(expression(), context()) ::
  {:ok, expression()} | {:error, Spek.EvaluationError.t()}

Evaluates the full given expression and returns the expression annotated with the expression results.

Always evaluates the entire expression, even if the final outcome could be determined earlier.

Examples

iex> eval_tree_all(
...>   all_of([
...>     %Check{module: String, fun: :starts_with?, args: [:ctx, "scene_"]},
...>     %Check{module: String, fun: :ends_with?, args: [:ctx, "_render_complete"]}
...>   ]),
...>   "scene_042_render_complete"
...> )
{
  :ok,
  %Spek.AllOf{
    children: [
      %Spek.Check{
        module: String,
        fun: :starts_with?,
        args: [:ctx, "scene_"],
        result: true,
        satisfied?: true
      },
      %Spek.Check{
        module: String,
        fun: :ends_with?,
        args: [:ctx, "_render_complete"],
        result: true,
        satisfied?: true
      }
    ],
    satisfied?: true
  }
}

iex> eval_tree_all(
...>   all_of([
...>     %Check{module: String, fun: :starts_with?, args: [:ctx, "scene_"]},
...>     %Check{module: String, fun: :ends_with?, args: [:ctx, "_render_complete"]}
...>   ]),
...>   "shot_042_render_complete"
...> )
{
  :error,
  %Spek.EvaluationError{
    expression: %Spek.AllOf{
      children: [
        %Spek.Check{
          module: String,
          fun: :starts_with?,
          args: [:ctx, "scene_"],
          result: false,
          satisfied?: false
        },
        %Spek.Check{
          module: String,
          fun: :ends_with?,
          args: [:ctx, "_render_complete"],
          result: true,
          satisfied?: true
        }
      ],
      satisfied?: false
    },
    message: "rule evaluation failed"
  }
}

eval_tree_all!(expression, context \\ [])

@spec eval_tree_all!(expression(), context()) :: expression() | no_return()

Evaluates the full given expression and returns the expression annotated with evaluation results or raises an error.

Raises if the expression is not satisfied. Unlike eval!/2, the raised exception contains the evaluated expression.

Always evaluates the entire expression, even if the final outcome could be determined earlier.

Examples

iex> eval_tree_all!(
...>   any_of([
...>     %Check{module: String, fun: :starts_with?, args: [:ctx, "scene_"]},
...>     %Check{module: String, fun: :ends_with?, args: [:ctx, "_render_complete"]}
...>   ]),
...>   "scene_042_render_complete"
...> )
%Spek.AnyOf{
  satisfied?: true,
  children: [
    %Spek.Check{
      module: String,
      fun: :starts_with?,
      args: [:ctx, "scene_"],
      result: true,
      satisfied?: true
    },
    %Spek.Check{
      module: String,
      fun: :ends_with?,
      args: [:ctx, "_render_complete"],
      result: true,
      satisfied?: true
    }
  ]
}

iex> eval_tree_all!(
...>   %Check{module: String, fun: :starts_with?, args: [:ctx, "scene_"]},
...>   "shot_042_render_complete"
...> )
** (Spek.EvaluationError) rule evaluation failed

filter(items, expression)

@spec filter(Enumerable.t(), expression()) :: Enumerable.t()

Filters the given enumerable to only retain the items that satisfy the given expression.

Example

iex> filter(
...>   ["scene_042_render_complete", "shot_017_proxy_ready"],
...>   %Check{module: String, fun: :starts_with?, args: [:ctx, "scene_"]}
...> )
["scene_042_render_complete"]

reject(items, expression)

@spec reject(Enumerable.t(), expression()) :: Enumerable.t()

Filters the given enumerable to only retain the items that do not satisfy the given expression.

Example

iex> reject(
...>   ["scene_042_render_complete", "shot_017_proxy_ready"],
...>   %Check{module: String, fun: :starts_with?, args: [:ctx, "scene_"]}
...> )
["shot_017_proxy_ready"]

Optimization Functions

optimize(literal)

@spec optimize(expression()) :: expression()

Performs boolean algebra transformations on the given expression.

Examples

iex> Spek.optimize(%AnyOf{
...>   children: [
...>     %AllOf{
...>       children: [
...>         %Check{module: RenderChecks, fun: :gpu_available, args: []},
...>         %Check{module: RenderChecks, fun: :texture_cache_warm, args: []}
...>       ]
...>     },
...>     %AllOf{
...>       children: [
...>         %Check{module: RenderChecks, fun: :color_space_valid, args: []},
...>         %Check{module: RenderChecks, fun: :gpu_available, args: []}
...>       ]
...>     },
...>     %Check{module: RenderChecks, fun: :fallback_renderer_enabled, args: []}
...>   ]
...> })
%AnyOf{
  children: [
    %AllOf{
      children: [
        %Check{module: RenderChecks, fun: :gpu_available, args: []},
        %AnyOf{
          children: [
            %Check{module: RenderChecks, fun: :texture_cache_warm, args: []},
            %Check{module: RenderChecks, fun: :color_space_valid, args: []}
          ]
        }
      ]
    },
    %Check{module: RenderChecks, fun: :fallback_renderer_enabled, args: []}
  ]
}

Optimizations

OptimizationFormula
Identity (AND)A and true = A
Identity (OR)A or false = A
Annihilation (AND)A and false = false
Annihilation (OR)A or true = true
Double negation eliminationnot (not A) = A
Negation of literalsnot true = false, not false = true
De Morgan’s law (AND)not (A and B) = (not A) or (not B)
De Morgan’s law (OR)not (A or B) = (not A) and (not B)
Empty conjunctionallof() = true
Empty disjunctionanyof() = false
Single-child conjunction eliminationallof(A) = A
Single-child disjunction eliminationanyof(A) = A
Deduplication (AND)A and A = A
Deduplication (OR)A or A = A
Factoring OR over AND(A and B) or (A and C) = A and (B or C)
Factoring AND over OR(A or B) and (A or C) = A or (B and C)
Absorption (OR)A or (A and B) = A
Absorption (AND)A and (A or B) = A

Types

context()

@type context() :: term()

The evaluation context as passed to the evaluation functions.

expression()

@type expression() ::
  Spek.AllOf.t()
  | Spek.AnyOf.t()
  | Spek.Check.t()
  | Spek.Literal.t()
  | Spek.Not.t()

A boolean expression tree.

falsy()

@type falsy() :: false | :error | {:error, term()}

An expression result value that results in a failed evaluation.

result()

@type result() :: truthy() | falsy()

The result of an expression evaluation.

truthy()

@type truthy() :: true | :ok | {:ok, term()}

An expression result value that results in a successful evaluation.