# `WorkflowStem.Compiler`
[🔗](https://github.com/fosferon/workflow_stem/blob/main/lib/workflow_stem/compiler.ex#L1)

Turns an extended workflow spec (see `WorkflowStem.SpecBehaviour`) into a
list of ALF component descriptors.

Bridges the data-only spec format and ALF's full DSL. Specs that declare
no routes fall through as an empty list — the caller should keep using
the static `WorkflowStem.Pipelines.Stepwise` pipeline in that case.

## Supported primitives (1:1 with ALF DSL — see `deps/alf/lib/dsl.ex`)

    {:stage,      name_or_mod, opts}               # stage/2
    {:switch,     name,        %{key => body}}     # switch/2
    {:composer,   module,      opts}               # composer/2 (fan-out/in)
    {:goto,       name,        opts}               # goto/2
    {:goto_point, name}                            # goto_point/2
    {:done,       name,        opts}               # done/2
    {:dead_end,   name}                            # dead_end/2
    {:from,       module,      opts}               # from/2
    {:plug_with,  module,      body}               # plug_with/2 + do block
    {:tbd,        name}                            # tbd/2

`opts` is a keyword list (all optional): `:count`, `:opts` (a keyword list
of user options passed to the component), and `:to`/`:if` for `:goto`,
`:memo` for `:composer`.

A state's `:route` may be a single tuple or a list. Inside branches (the
bodies of `:switch` and `:plug_with`), the contents are lists of the same
tuple shapes — compiled recursively.

Descriptors are plain data so they can be inspected in tests and cached
without ALF being started. A later step will emit these into a real ALF
module via `Code.compile_quoted/1`.

# `descriptor`

```elixir
@type descriptor() :: {atom(), term(), map()} | {atom(), term()}
```

# `primitive_tuple`

```elixir
@type primitive_tuple() :: tuple()
```

# `components_for`

```elixir
@spec components_for(WorkflowStem.IR.t()) :: [descriptor()]
```

Returns a flat list of component descriptors for every `:route` in the
spec. Each descriptor's `:state` field records the owning state name.

Raises `ArgumentError` if a `:switch` or `:goto` primitive references a
routing name absent from the spec's `:routing` map, or if a primitive
tuple is malformed. Use `validate/1` for a non-raising check.

# `components_for_engine`

```elixir
@spec components_for_engine(WorkflowStem.IR.t()) :: [descriptor()]
```

Returns the full engine-topology descriptor list for a spec, ready to
be emitted via `WorkflowStem.Pipeline.Builder.build/3`.

The topology mirrors the static `WorkflowStem.Pipelines.Stepwise`
pipeline but replaces its single `StepwiseAction` stage with a
`switch(:__current_state__, branches: ...)` that dispatches per-state:

    [
      stage(StepwiseContextMerge),
      switch(:__current_state__, branches: %{
        state_a: <descriptors from state_a's :route, or default>,
        state_b: <descriptors from state_b's :route, or default>,
        ...
      }),
      stage(StepwiseAdvance),
      stage(StepwiseEntryAction),
      stage(FsmBreakpoint),
      stage(StepwiseProjection)
    ]

States without a `:route` get a default body of `[stage(StepwiseAction)]`
so the existing per-state action dispatcher keeps working for them.

Use `engine_routing/1` to get the routing-resolver map that must
accompany these descriptors when calling `Builder.build/3`.

# `engine_routing`

```elixir
@spec engine_routing(WorkflowStem.IR.t()) :: map()
```

Returns the routing map that must be passed to `Builder.build/3`
alongside `components_for_engine/1`.

Merges the spec's user-declared `:routing` with the internal
`:__current_state__` delegate that the synthesised wrapper switch
needs to resolve.

# `has_routes?`

```elixir
@spec has_routes?(WorkflowStem.IR.t()) :: boolean()
```

Returns `true` if any state in the spec declares a `:route`.

# `validate`

```elixir
@spec validate(WorkflowStem.IR.t()) ::
  :ok | {:error, {:missing_routing, atom()} | {:bad_primitive, term()}}
```

Non-raising spec validation. Walks every route (including nested
`:switch` branches and `:plug_with` bodies) and checks that each
`:switch`/`:goto` primitive's routing name resolves.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
