
## Behavioral Reference

Exact bash implementation details the Rust engine must preserve or explicitly improve.

### Quoting & Tokenization

- **Bash**: double quotes only, no backslash escapes. Unclosed quotes silently produce partial tokens — Rust should error.
- **Step tokenizer** (`__compile__tokenize_step`): strips `"` from value. `[](){}` → separate tokens. Space/tab splits. Everything else glues into words.
- **Decl tokenizer** (`__compile__tokenize_decl_inner`): keeps `"` in value. `[]` bracket-nested. `{}` not special.
- **Numeric coercion**: bash uses `jq tonumber? // $v` in object literals. The Rust engine replicates the same coercion logic natively (awk/shell arithmetic in emitted code, not jq).

### Comma Rules

Bash is inconsistent — Rust standardizes: **commas are optional whitespace-equivalent separators in all list contexts.**

| Context | Bash behavior |
| --- | --- |
| Step args `(...)` | Not delimiters; stick to words |
| `needs [a, b]` step modifier | Not stripped; commas stick to names |
| `name() needs [a, b] {` block header | Stripped via `tr`; optional |
| `needs [...] then {` | Not stripped |
| `scripts` | Required (`IFS=','`) |

### Identifier Characters

| Context | Regex |
| --- | --- |
| Top-level block names | `[a-zA-Z_][a-zA-Z0-9_/.-]*` |
| Declarative keys | `[a-zA-Z_][a-zA-Z0-9/_-]*` (no `.`) |
| Unquoted atoms | `[a-zA-Z0-9._/@:+-]+` |
| `use` aliases | `[a-zA-Z_][a-zA-Z0-9_-]*` |
| Env keys | `[A-Z_][A-Z0-9_]*` |

### Shell Generation

- **`__resolve__shell_quote`**: wraps in `"` with no escaping. Broken for `"`, `$`, backticks. Rust must use proper shell escaping.
- **Name mangling**: `/` → `__`, `.` → `__`, `-` → `_`. Registry `safe_name` overrides.
- **Variable scoping**: emitted scripts do not use `local`. All variables are mangled with the owning function name as a prefix (`__{mangled_fn}__{varname}`), making them globally unique and greppable. This avoids the non-POSIX `local` keyword while providing better traceability in debugging — `_deploy_single_version` identifies both the function and the variable.
- **Leaf name**: strip longest path prefix through `/`, then through first `.`.
- **Reserved keys** skipped in arg formatting: `needs`, `postinstall`, `export`, `if`, `then`, `elif`, `else`, `var`, `equals`, `name`, `try`, `catch`, `finally`, `with`, `do`, `parallel_all`, `parallel_any`, `each`, `as`, `in`, `op`, and any `_`-prefixed key.

### Bash Bugs to Fix

- `elif` conditions don't support infix expressions
- Infix rewrite failures can be silent (exit status unchecked)
- Empty env surface → no `--help` at all
- Extra CLI args after flags silently dropped
- Conflicting env defaults silently skipped
- `__resolve__normalize_step` builds JSON without escaping op names
- **`if` drops prior steps**: entering `if` resets `block_body`, dropping preceding steps. Rust AST models blocks as `Vec<Statement>`.
- **`std.defer` never drains**: `__run_deferred` never called by ops flow

### Parser State Machine

States: `top`, `env`, `entrypoints`, `decl` (with nesting stack), `imper`, `cond_then`, `cond_elif`, `cond_else`, `try_body`, `try_catch`, `try_finally`, `with_body`, `gen_block_body`, `each_body`, `needs_then_body`.

Structural keywords (`if`, `try`, `with`, `each`, `needs ... then`, block ops) must be on a single line ending with `{`. Multi-line continuation only applies to paren-balanced invocations.
