StatifierBlocks.Predicates.TruthTable (StatifierBlocks v0.27.0)

Copy Markdown View Source

A table spec plus rows in, a struct with per-cell outcomes, first-match-wins selection and expectation status out.

Why expected is compared against selection, not raw truth

core.branch (lib/statifier_blocks/core/branch.ex, ADR-0002 decision 10) stores an ordered arms list and tries them in order: the first arm whose condition holds runs, and every later arm is skipped even if its own condition would also hold. That ordering is a config-level fact, not an implementation detail - the branch's moduledoc says arms are tried in order and slots/1 returns them in that order followed by otherwise.

So a truth table over a branch has to model the same thing a branch does: which arm is taken, not which arms are individually true. A cell here carries both readings. outcome is the raw {:ok, boolean()} | {:error, reason} from Predicates.evaluate/2 - what the expression alone says. selected? is true | false | :undecidable, from one ordered first-match-wins pass over the row's columns, mirroring what core.branch would actually run. expected, a row's declared answer for one column, is compared against selected? rather than outcome, because "does this fixture behave like the branch it describes" is a question about which arm wins, not about which conditions are independently true. Two columns can both raw-evaluate true in the same row; at most one of them is selected.

The otherwise column

At most one column may carry source: nil - the otherwise column - and it is never evaluated as an expression (outcome: nil). It must be the last column if present, because it is the branch's fallback arm and a fallback that could still be overridden by a later real arm would not be a fallback. build/2 rejects a spec that breaks either rule before touching any row.

The five-value status

status is deliberately wider than a true | false | unset tri-state, because "the row's expectation was contradicted" and "this cell's answer is unknowable" are different findings that a renderer, and an author reading one, need to tell apart:

  • :match - selected? is a boolean and equals expected.
  • :mismatch - selected? is a boolean and differs from expected.
  • :unchecked - selected? is a boolean but the row declares no expected value for this column.
  • :error - this column's own outcome is an error; nothing about selection can be said until the expression itself is fixed.
  • :undecidable - this column's own outcome is fine, but an earlier column in the row errored, so whether this column would even have been reached is unknown. Reporting false here would claim "no arm before this one matched", which is a guess this package does not make.

A row's bindings can also fail to build a context at all (an undefined variable, a binding conflict). Then the whole row carries error: reason and cells: [] rather than guessing at any column - a renderer checks row.error first.

Summary

Functions

Builds a %TruthTable{} from a table spec and a list of row specs.

Flattens every cell's status across every row, in row-then-column order, so a caller can assert a whole table's shape in one pattern match.

Types

build_error()

@type build_error() ::
  {:duplicate_column, String.t()}
  | {:otherwise_not_last, String.t()}
  | {:invalid_source, String.t()}

t()

@type t() :: %StatifierBlocks.Predicates.TruthTable{
  columns: [StatifierBlocks.Predicates.TruthTable.Column.t()],
  description: String.t() | nil,
  name: String.t(),
  paths: [String.t()],
  rows: [StatifierBlocks.Predicates.TruthTable.Row.t()]
}

Functions

build(spec, rows)

@spec build(spec :: map(), rows :: [map()]) :: {:ok, t()} | {:error, build_error()}

Builds a %TruthTable{} from a table spec and a list of row specs.

Validates the spec first - column keys must be unique ({:error, {:duplicate_column, key}}), at most one column may have source: nil and it must be last ({:error, {:otherwise_not_last, key}}), and every column's source must be a binary or nil ({:error, {:invalid_source, key}}). A malformed spec is an error return; a failing cell is data, because showing which cells fail is the point of the table - rows still build and render around a bad cell or a bad row.

statuses(truth_table)

Flattens every cell's status across every row, in row-then-column order, so a caller can assert a whole table's shape in one pattern match.