# Infix expressions

## Usage

Boolean and comparison expressions in `if` / `elif` conditions use registry-driven infix operators, with parentheses for grouping.

```ops
@check {
  if $BRANCH == "main" && $CI == "true" {
    bash.shellcheck()
  }
  if ($A == "1" || $B == "2") && $C == "3" {
    bash.shellcheck()
  }
}

if $BRANCH == "main" && $RELEASE_ON_MAIN {
  tag()
  deploy(deploy-token: $DEPLOY_TOKEN)
} elif $BRANCH == "dev" {
  tag()
}
```

## Grammar

```peg
InfixExpr       = InfixAtom (INFIX_OP InfixAtom)*
InfixAtom       = '(' InfixExpr ')' | Step | VarRef | Literal

INFIX_OP        = (registry-driven: ops with --infix flag + symbol aliases)
```

## IR representation

Canonical expression shape lives in [control-flow](../ir/control-flow.md).

```json
{
  "if": {
    "expr": {
      "op": "condition.and",
      "left": { "op": "condition.eq", "left": {"var": "BRANCH"}, "right": "main" },
      "right": { "op": "condition.eq", "left": {"var": "CI"}, "right": "true" }
    }
  },
  "then": [...]
}
```

## Runtime

- **`--infix`** — Binary operator between expressions; registered on ops.
- **`--symbol TOKEN`** — Maps surface tokens to ops (e.g. `&&` → `condition.and`).
- **Precedence** — `or` (1) < `and` (2) < comparison (3). Custom tiers via `--infix --precedence N`.

## Implementation

**Build tier: Tier 12 — Infix expressions**

- **registry.rs** — Build infix map from `dsl_infix` + aliases with precedence tiers.
- **ast.rs** — `InfixExpr { op, left, right }` as a binary tree.
- **parser.rs** — Pratt parser in `if` conditions; parentheses override precedence.
- **lower.rs** — Emit `{"op": "condition.eq", "left": ..., "right": ...}` with connector ops.

**Validation:** bats tests for `==`, `&&`, `||`, precedence, parentheses, and nesting.

## Design decisions

- Infix is registry-driven so new operators and precedences ship with ops, not hard-coded parser tables only.
- Parenthesized atoms keep precedence explicit and match familiar condition syntax.

## Caveats

- **Bash:** Infix rewrite failures can be silent (exit status unchecked). The Rust implementation must handle errors explicitly.
- **Bash:** `elif` conditions do not support infix expressions. The Rust parser fixes this.

## Open questions

- **`elif` / top-level `if` Pratt entry point** (settled): share one parser entry point. Divergence, if any, happens at emit.
