Statifier.Validator.Context (Statifier v2.0.0)

Copy Markdown View Source

The throwaway index the checks share, built once per Statifier.Validator.validate/2 call and discarded afterward. It must never be returned, cached, or handed to the compiler: Statifier.Compiler builds the real interned index, and sharing this one would couple two layers the architecture keeps independent. Correctness over speed here, per the bead's own design note.

states maps a named state's id to its struct. ancestors maps a named state's id to the list of its named ancestors' ids, itself excluded - descendant?/3 turns that into a list-membership test rather than a re-walk. The list carries no order contract: membership is the whole of what anything reads, and walk/3 builds it innermost-first to avoid an append per tree level. A state with a nil id cannot be named by anything, so it is absent from both maps.

parents maps every state struct (nameless ones included) to its immediate parent - another State.t(), or the Document.t() for a top-level state - which is why it is keyed by the struct itself rather than by id.

transitions is the flat [{transition, owner}] list: every <transition> in the document, tagged with the slot it came from. owner is {:plain, state} for a state's or parallel's own transitions, {:initial, state} for the transitions inside that state's <initial> element, and {:history, state} for a :history state's own transitions (its default-transition candidates). Checks 2, 4, and 5 all consume this one traversal instead of each re-walking the tree.

invoke_content_markup? mirrors Statifier.compile/2's invoke_content_markup option (ADR-0042), defaulting to false. It is not a general validation off-switch: Statifier.Validator.Checks.Boilerplate is the only check that reads it, and only to accept a root that resolves to no namespace at all, never a root that declares a wrong one.

Summary

Functions

Walks document once, threading ancestry as it descends and collecting the shared transition list. source is the binary document was parsed from; it is stored verbatim for checks that need Location.slice/2 and is not otherwise read here.

Whether ancestor_id is one of descendant_id's named ancestors.

Types

owner()

@type owner() ::
  {:plain, Statifier.Document.State.t()}
  | {:initial, Statifier.Document.State.t()}
  | {:history, Statifier.Document.State.t()}

t()

@type t() :: %Statifier.Validator.Context{
  ancestors: %{optional(String.t()) => [String.t()]},
  invoke_content_markup?: boolean(),
  parents: %{
    required(Statifier.Document.State.t()) =>
      Statifier.Document.State.t() | Statifier.Document.t()
  },
  source: binary(),
  states: %{optional(String.t()) => Statifier.Document.State.t()},
  transitions: [{Statifier.Document.Transition.t(), owner()}]
}

Functions

build(document, source, opts \\ [])

@spec build(document :: Statifier.Document.t(), source :: binary(), opts :: keyword()) ::
  t()

Walks document once, threading ancestry as it descends and collecting the shared transition list. source is the binary document was parsed from; it is stored verbatim for checks that need Location.slice/2 and is not otherwise read here.

opts defaults to []; invoke_content_markup: true sets the same-named boolean field, per Statifier.Validator.validate/3.

descendant?(context, ancestor_id, descendant_id)

@spec descendant?(
  context :: t(),
  ancestor_id :: String.t(),
  descendant_id :: String.t()
) :: boolean()

Whether ancestor_id is one of descendant_id's named ancestors.