# Control Flow

## Why This Exists

Control flow must stay structural through lowering so the resolver can validate dependencies and the emitter can generate POSIX code without recovering semantics from shell snippets.

## Producers

- **Parser lowering:** emits `if`, `elif`, `else`, `try`, `catch`, `finally`, `with`, `match`, `each`, dependency constructs, and block-op bodies.
- **Resolver:** validates expressions, branch dependencies, catches, variable bindings, and skip propagation.

## Consumers

- **Resolver:** checks branch-local values, dependency availability, type-compatible match expressions, and recoverable errors.
- **Emitter:** lowers structured control flow to POSIX `if`, `case`, loops, trap/defer behavior, and error capture.

## Branches

Branching and recovery forms share the same shape: a `branch` node whose payload is an array of clauses. Each clause has a `role` (e.g., `case`, `default`, `finally`), and its own payload contains an optional `when` condition, bindings, and body steps. Surface syntax like `if` / `elif` / `else`, `match`, and `try` / `catch` / `finally` all lower into branches.

### Conditional Branch

```json
{
  "role": "branch",
  "payload": [
    {
      "role": "case",
      "payload": [
        { "role": "when", "payload": [
          { "role": "expr", "payload": [
            { "role": "op", "payload": "eq" },
            { "role": "left", "payload": [{ "role": "var", "payload": "BRANCH" }] },
            { "role": "right", "payload": "main" }
          ]}
        ]},
        {
          "role": "call",
          "payload": [{ "role": "target", "payload": "git.tag" }]
        }
      ]
    },
    {
      "role": "case",
      "payload": [
        { "role": "when", "payload": [
          {
            "role": "call",
            "payload": [
              { "role": "target", "payload": "git.bump" }
            ]
          }
        ]},
        { "role": "export", "payload": "VERSION" },
        {
          "role": "call",
          "payload": [{ "role": "target", "payload": "deploy" }]
        }
      ]
    },
    {
      "role": "default",
      "payload": [
        {
          "role": "call",
          "payload": [{ "role": "target", "payload": "ci.validate" }]
        }
      ]
    }
  ]
}
```

In OPX:

```
Br01_ 0.branch
Cs01_ 1.case
Wn01_ 2.when
Ex01_ 3.expr
Op01_ 4.op:string."eq"
Lf01_ 4.left
Vr01_ 5.var:string."BRANCH"
Rt01_ 4.right:string."main"
Cl01_ 2.call
Tg01_ 3.target:string."git.tag"
Cs02_ 1.case
Wn02_ 2.when
Cl02_ 3.call
Tg02_ 4.target:string."git.bump"
Xp01_ 2.export:string."VERSION"
Cl03_ 2.call
Tg03_ 3.target:string."deploy"
Df01_ 1.default
Cl04_ 2.call
Tg04_ 3.target:string."ci.validate"
```

An `if git.bump() export VERSION { ... }` condition is just a `case` clause whose `when` payload contains a call node. The `export` binding sits in the clause's payload alongside the body steps.

### Failure Branch

```json
{
  "role": "branch",
  "payload": [
    {
      "role": "case",
      "payload": [
        { "role": "when", "payload": "always" },
        {
          "role": "call",
          "payload": [{ "role": "target", "payload": "release" }]
        }
      ]
    },
    {
      "role": "case",
      "payload": [
        { "role": "when", "payload": [
          { "role": "failure", "payload": "previous" }
        ]},
        { "role": "error", "payload": "error" },
        {
          "role": "throw",
          "payload": [
            { "role": "rethrow", "payload": [{ "role": "var", "payload": "error" }] }
          ]
        }
      ]
    },
    {
      "role": "finally",
      "payload": [
        { "role": "when", "payload": "always" },
        {
          "role": "call",
          "payload": [{ "role": "target", "payload": "cleanup" }]
        }
      ]
    }
  ]
}
```

`try` / `catch` / `finally` is a branch where the catch clause is conditioned on failure of the preceding body. The `finally` clause uses `when: "always"` — a scalar payload for a simple unconditional trigger.

In OPX:

```
Br02_ 0.branch
Cs03_ 1.case
Wn03_ 2.when:string."always"
Cl05_ 2.call
Tg05_ 3.target:string."release"
Cs04_ 1.case
Wn04_ 2.when
Fl01_ 3.failure:string."previous"
Er01_ 2.error:string."error"
Th01_ 2.throw
Rt02_ 3.rethrow
Vr02_ 4.var:string."error"
Fn01_ 1.finally
Wn05_ 2.when:string."always"
Cl06_ 2.call
Tg06_ 3.target:string."cleanup"
```

### Match Branch

```json
{
  "role": "branch",
  "payload": [
    { "role": "input", "payload": [{ "role": "var", "payload": "BRANCH" }] },
    {
      "role": "case",
      "payload": [
        { "role": "when", "payload": [{ "role": "pattern", "payload": "main" }] },
        {
          "role": "call",
          "payload": [{ "role": "target", "payload": "deploy" }]
        }
      ]
    },
    {
      "role": "case",
      "payload": [
        { "role": "when", "payload": [{ "role": "pattern", "payload": "dev" }] },
        {
          "role": "call",
          "payload": [{ "role": "target", "payload": "validate" }]
        }
      ]
    },
    {
      "role": "default",
      "payload": [
        {
          "role": "call",
          "payload": [{ "role": "target", "payload": "validate" }]
        }
      ]
    }
  ]
}
```

Match branches include an `input` item alongside the clauses. The generator distinguishes match from conditional branches by the presence of `input`.

In OPX:

```
Br03_ 0.branch
In01_ 1.input
Vr03_ 2.var:string."BRANCH"
Cs05_ 1.case
Wn06_ 2.when
Pt01_ 3.pattern:string."main"
Cl07_ 2.call
Tg07_ 3.target:string."deploy"
Cs06_ 1.case
Wn07_ 2.when
Pt02_ 3.pattern:string."dev"
Cl08_ 2.call
Tg08_ 3.target:string."validate"
Df02_ 1.default
Cl09_ 2.call
Tg09_ 3.target:string."validate"
```

## Loop Shape

```json
{
  "role": "loop",
  "payload": [
    { "role": "binding", "payload": "target" },
    { "role": "iterable", "payload": [{ "role": "var", "payload": "TARGETS" }] },
    {
      "role": "call",
      "payload": [{ "role": "target", "payload": "build" }]
    }
  ]
}
```

The `binding` and `iterable` roles are loop setup. Remaining execution-role items (`call`, `branch`, etc.) are the loop body.

In OPX:

```
Lp01_ 0.loop
Bn01_ 1.binding:string."target"
It01_ 1.iterable
Vr04_ 2.var:string."TARGETS"
Cl10_ 1.call
Tg10_ 2.target:string."build"
```

## Invariants

- `when` conditions are structured values, expressions, calls, patterns, or failure conditions — never embedded shell.
- Scalar `when` payloads (e.g., `"always"`) are simple triggers. Composite `when` payloads are expressions, calls, or patterns.
- Clause roles are validated against their parent branch. A conditional branch allows `case` and `default`; failure handling additionally allows `finally`.
- `elif`, `else`, `catch`, and `finally` are clauses in the same parent branch so source recovery and emission do not depend on adjacent statement guessing.
- Match expressions are limited to scalar equality-compatible values documented in [control-flow](../features/control-flow.md).
- `each` sources must be list-like after type validation.
- `throw` payloads use the `@error` shape from [error-handling](../features/error-handling.md).
- Future registry-provided control forms should prefer adding `when` roles or branch clause roles before inventing a new top-level node shape.

## Design References

- [control-flow](../features/control-flow.md) defines `if`, `match`, `each`, expression limits, and newline rules.
- [error-handling](../features/error-handling.md) defines `try`, `catch`, `finally`, and `throw`.
- [with-blocks](../features/with-blocks.md) defines scoped setup/undo behavior.
- [dependency-constructs](../features/dependency-constructs.md) defines `needs-then` and non-failing dependency checks.
