# Declarative blocks

## Usage

```ops
install.system = {
  shellcheck: { version: "0.9.0" }
  direnv: {}
}

install = {
  system = {
    lefthook: { version: 0.9.0 pkg: lefthook }
  }
}
```

## Grammar

```
DeclBlock       = DeclName '=' '{' DeclBody '}'
DeclName        = IDENT ('.' IDENT)*
DeclBody        = (DeclEntry | NestedDecl)*
DeclEntry       = DECL_KEY ':' DeclValue
NestedDecl      = IDENT '=' '{' DeclBody '}'
DeclValue       = '{' (DECL_KEY ':' Atom)* '}'
              | '[' IDENT* ']'
              | Atom
DECL_KEY        = [a-zA-Z_][a-zA-Z0-9/_-]*
```

## IR representation

Canonical declaration placement lives in [ir_schema](../ir_schema.md) and [values-and-types](../ir/values-and-types.md).

```json
{
  "role": "declaration",
  "name": "decl_block.qualified.name",
  "payload": [
    { "role": "op", "payload": "decl_block.qualified.name" },
    { "role": "entry", "name": "key", "payload": "value" },
    { "role": "need", "payload": [{ "role": "pipeline", "payload": "dep" }] }
  ]
}
```

## Implementation

**Build tier:** Tier 6 — Declarative blocks

| Layer | Responsibility |
|-------|----------------|
| `ast.rs` | `TopLevel::DeclBlock { name, entries }`, `DeclEntry` variants |
| `parser.rs` | `name =` triggers declarative mode; `key: {` is value object; `child = {` is nested namespace; recursive |
| `lower.rs` | Flatten qualified names (`install.system.shellcheck`) into declaration objects |

**Validates:** bats tests for empty entries, nesting, `needs` in decl values, qualified keys.

## Design decisions

- **Declarative keys:** `[a-zA-Z_][a-zA-Z0-9/_-]*` — no dot in keys; dots separate namespaces.

## Caveats

- **Decl tokenizer** (`__compile__tokenize_decl_inner`): keeps `"` in value; `[]` bracket-nested; `{}` not special in the same way as the main ops grammar.
