# Parser

`dflparser.y` is the Bison LALR(1) grammar for the whole netlist language - circuit description, the `control` block, and the standalone expression/parameter-list forms used to re-parse strings at runtime - producing three different top-level results depending on which of the [lexer](dfllexer.md)'s three input modes drives it.

## Three top-level productions

Matching the lexer's synthetic entry tokens: `INNETLIST subckt_build END` builds a full circuit into `tables` (a `ParserTables&` passed in as a `%parse-param`); `INEXPR expr END` fills in `*expressionPtr`; `INPARAMS opt_broken_parameter_list END` fills in `*parametersPtr`. Only the netlist path calls `tables.verify()` afterward - the other two only ever populate their own out-parameter, so there is nothing accumulated in `tables` for `verify()` to check.

## `subckt_build`: one accumulator for both the toplevel circuit and subcircuits

A left-recursive nonterminal seeded either by `TITLE NEWLINE` (the toplevel circuit) or `SUBCKT IDENTIFIER (...) NEWLINE` (a subcircuit definition), with every subsequent netlist statement - `subcktparameters` (a `parameters` line), `model`, `instance`, `behavioral` (a behavioral/Verilog-A-expression source), `condblock`, a nested `subckt`, `global`, `ground`, `load`, `embed`, `control_block`, or a bare `NEWLINE` - folded into it one rule at a time. Which seed rule fired sets an `isToplevel` flag, letting the same accumulator serve both contexts while still rejecting, with a located `Status::Syntax` error, constructs that only make sense once per file (`global`, `ground`, `load`, `embed`, and at most one `control_block`) when they turn up inside a subcircuit body instead.

## Conditional blocks

`condblock_build`/`condblock` compile `@if`/`@elseif`/`@else`/`@end` into a `PTBlockSequence` - a list of (condition expression, block) pairs, built the same accumulator way, with each `instance`/`model`/nested `condblock` folded into whichever block (`.back()`) is currently open. `@else` is represented as a branch whose condition is a default-constructed, empty `Rpn()` rather than a dedicated "no condition" flag - downstream elaboration code treats an empty compiled expression as unconditionally true.

## Expressions: the parser *is* the compiler

Every arithmetic/comparison/bitwise/logical grammar rule directly emits [`Rpn`](rpnexpr.md) entries via `$$.extend(...)` as it reduces - there is no separate AST-to-RPN lowering pass; a parsed expression *is* its compiled form by the time the rule finishes. A single comma-separated `exprlist` nonterminal backs all three bracketed forms: a function-call argument list `f(a,b)`, a vector literal `[a,b]` (`Rpn::PackVec`), and a list literal `{a,b}` (`Rpn::PackList`) - see [expression format](rpnexpr.md). `[]`/`[,]` and `{}`/`{,}` are dedicated empty-literal rules (both spellings produce a zero-arity `PackVec`/`PackList`, since `exprlist` itself always holds at least one expression). Every vector/list/function-call argument count is checked against `std::numeric_limits<Rpn::Arity>::max()` before being narrowed and handed to `Rpn::FunctionCall`/`PackVec`/etc. - since arity is stored as a fixed-width `uint32_t` in the compiled form and a too-long argument list would silently overflow it otherwise.

The `%right QUESTION` / `%left OR` / ... / `%right POWER` / `%right NEG NOT BITNOT` precedence table mirrors `Rpn::opMap`, the runtime priority table in [expression format](rpnexpr.md) - the two must stay in agreement, since `opMap`'s job is to reconstruct the same grouping this table enforces, in reverse, when pretty-printing a compiled expression back into text.

## Parameter lists and constant folding

`parameter_expression`/`parameter_list` fold each `name = expr` into a `paramlist`, tracking one source location per name purely to produce a "parameter first defined here" message on a duplicate. Immediately after parsing each parameter's expression, the grammar action asks [`RpnEvaluator::isConstant()`](rpneval.md) whether it can be evaluated once and for all right now: if so, it is evaluated on the spot and stored as a plain `PTParameterValue`; otherwise it is kept as a `PTParameterExpression`, to be (re-)evaluated per instantiation. This is the concrete site where "is this expression constant" is actually used - constant folding of parameter defaults happens once, at parse time, rather than being redone on every instance the default applies to.

## Instances, models, loads, embeds, globals, grounds, subcircuit parameters

Each is a small, largely self-contained rule that builds the matching parse-tree node directly from tokens, with no intermediate representation between the grammar and the output structure. `subcktparameters` (`parameters name=expr ...`) reuses `opt_broken_parameter_list` like an instance's own parameters do, and is folded into the enclosing `subckt_build`'s parameter set with the same duplicate-name check as instance/model parameters.

## `behavioral`

`IDENTIFIER LPAREN terminal_list RPAREN opt_broken_parameter_list NEWLINE` builds a `PTBehavioral` (a behavioral/expression-defined source) directly out of its parameter list rather than treating the parameters generically: the rule action walks both the constant-valued and expression-valued parameters looking for exactly one of `v`/`potential` (a potential/voltage source) or `i`/`flow` (a flow/current source) to use as the source expression, plus an optional `discipline` parameter - a constant 3-element string vector giving the discipline name and its potential/flow accessor names (defaulting to `electrical`/`V`/`I`). Any other parameter name, a `discipline` given as an expression rather than a constant vector, two expressions defining the source, or none at all, are all reported with `status.set(...)`/`YYERROR` right in this rule rather than being deferred to later elaboration.

## `save`, `analysis`, `sweep`, and the generic `command`

`save` (`savecmd`/`saves`), `analysis` (`pre_analysis`/`analysis_with_params`), and `sweep` (`sweeps`) each get dedicated grammar because their syntax doesn't fit the generic shape below: `save` takes a parenthesized argument-list-per-name shorthand, and an `analysis` statement can be preceded by any number of `sweep` clauses that must be collected first. A parsed `save` statement is nonetheless folded back into an ordinary `PTCommand` before being added to `tables`, using the static `saveCmd` identifier (`Id::createStatic("save")` at namespace scope - see [Identifiers](identifier.md) for why static identifiers must be constructed this way, before any ordinary one) - so code that walks the control block's command list later doesn't need a special case for `save`.

`command` is the generic catch-all for every other control-block statement: `IDENTIFIER` optionally followed by bareword `keywords`, a parenthesized `exprlist`, and/or a `name=value` parameter list, in whichever combination a given rule enumerates (there's no single unified "argument list" nonterminal spanning all three shapes at once). This is what lets the control block support an open-ended set of statement kinds (`print`, `alter`, `option`, ...) through one grammar production instead of one per statement.

## Error handling

Every semantic-level failure the grammar actions detect - redefinition, an argument count out of range, `global`/`ground`/`load`/`embed`/`control_block` used inside a subcircuit, a `subckt`/`ends` name mismatch, and so on - is reported the same way: `status.set(...)` (optionally followed by `status.extend(...)` for extra context, see [Status](status.md)) immediately followed by `YYERROR`, Bison's macro for aborting the current parse as an error. No production uses Bison's `error` token, so there is no error-recovery rule anywhere in the grammar - parsing stops at the first error rather than attempting to resynchronize and continue, consistent throughout the parser and lexer.

`Parser::error()`, Bison's required syntax-error callback, is separate from this: it fires only for errors Bison itself detects (an unexpected token, not a semantic check a grammar action performs) and reports a plain `Status::Syntax` - `%define parse.error verbose` asks Bison to include the expected-token list in the message.
