# Expression Format

`Rpn` (declared in `rpnexpr.h`, implemented in `rpnexpr.cpp`) is the compiled form of a netlist expression: a flat, linear sequence of `Entry` nodes instead of a tree, meant to be walked once, left to right, by the [evaluator](rpneval.md) as a simple stack machine - no recursion, no tree traversal. The parser (`dflparser.y`) compiles expressions directly into this form as it reduces grammar rules.

## Entry

`Rpn::Entry` wraps a `std::variant<Value, Op, Identifier, FunctionCall, PackVec, PackList, Jump, Branch, MakeBoolean>`. `Entry::type()` returns `Type(data.index())` - the `Type` enum (`TValue=0, TOp=1, ...`) is declared in exactly the same order as the variant's alternatives, so the variant's own index doubles as the type tag with no separate field or switch needed to set it.

`Rpn` is move-only (copy constructor/assignment deleted), like the other evaluator-facing types ([`RpnStack`](rpnstack.md), `ContextStack`, `RpnEvaluator`, see [Context](context.md)/[Evaluator](rpneval.md)) - a compiled expression is built once by the parser and then owned by whatever parsed structure holds it (e.g. a parameter's default-value expression), not copied around. `begin()`/`end()`/`cbegin()`/`cend()` and `operator[]` give direct access to the underlying entry sequence for callers (the evaluator, `str()`) that need to walk or index it directly.

Each payload struct is kept small (their byte sizes are noted next to each definition in `rpnexpr.h`) because a single expression can contain many entries. The payload structs themselves carry no location at all; `Entry` instead holds one `Loc` directly (`loc_`, [Location](location.md)/[Source Location Tracking](sourceloc.md)) alongside the variant, set via `setLocation()`/read via `location()`. This is a deliberate uniform choice - "no per-variant-alternative field, no separate index/table," per the comment in `rpnexpr.h` - and `Loc` itself (a `FileStackIndex`+`FileStackFileIndex`+line+column quadruple, see `sourceloc.h`) is already sized to be cheap to store per entry. A freshly constructed `Entry` starts with `loc_` equal to `Loc::bad` until `setLocation()`/`extend()` assigns a real one.

## Building an expression: `extend()`

Expressions are assembled bottom-up as the parser reduces grammar productions, using two `extend()` overloads:

- `extend(Rpn&& other)` splices another already-compiled sub-expression onto the end - used whenever a grammar rule combines sub-expressions it already has as `Rpn` values (e.g. `expr PLUS expr`). Since each entry carries its own `Loc` directly, splicing is a plain move of every entry with no location bookkeeping to rebase.
- `extend(Entry&& other, Loc l)` appends one new entry together with its source location in lockstep.

## Operators and `OpSelect`

`OpCode` covers arithmetic, comparison, bitwise, and logical operators, plus `OpSelect` for the indexing operator `x[i]` (evaluated by `mathFuncSelector2`, see [builtin operators and functions](rpnbuiltin.md)). `OpAnd`, `OpOr`, and `OpQuestion` are special: see below.

## Control flow: short-circuiting `&&`/`||` and the ternary operator

`&&`, `||`, and `?:` are not compiled as ordinary eager binary operators, because that would always evaluate both operands - wrong for short-circuit semantics, and wasteful (or observably wrong, for a `?:` branch containing a Monte Carlo generator call) for the branch not taken. Instead the parser compiles them into explicit conditional control flow using three entry types:

- **`MakeBoolean`**: pops the top value, calls `Value::checkCondition()` on it (see [Values](value.md)), and pushes back a canonical `0`/`1` `Int`. Used to normalize an operand to a boolean before it is tested or combined.
- **`Branch(offset, flags)`**: pops (usually) the top value as a condition and, depending on `flags`, conditionally jumps `offset` entries forward. `BrFalse` negates the test (branch when the condition is *false*, not true). `BrKeepOnBranch`/`BrKeepOnNoBranch` control whether the tested value is left on the stack instead of discarded, for exactly the direction named - this is what actually implements short-circuiting: the value that made the branch decision becomes the expression's result on the shortcut path, while control skips straight past the code that would have computed the other operand.
- **`Jump(offset, flags)`**: an unconditional forward jump by `offset` entries.

`BrHidden` on a `Jump`/`Branch` (and `MakeBoolean`) marks it as compiler-generated scaffolding that `str()` should not try to render.

The three compiled shapes, from the parser's own comments in `dflparser.y`:

```text
a && b:
        a
        makeboolean
        branch-if-false end   (BrFalse | BrKeepOnBranch)
        b
        makeboolean
        op(and)                 // no-op at evaluation; reprints "&&"
  end:

a || b:
        a
        makeboolean
        branch-if-true end     (BrKeepOnBranch)
        b
        makeboolean
        op(or)                  // no-op at evaluation; reprints "||"
  end:

a ? b : c:
        a
        branch-if-false false  (BrFalse)
        b
        jump end
  false: c
  end:   op(question)          // no-op at evaluation; reprints "?:"
```

The trailing `OpAnd`/`OpOr`/`OpQuestion` entry in each shape does nothing when the [evaluator](rpneval.md) runs it (the branching already produced the right value on the stack) - it exists purely so `str()` can reconstruct the `&&`/`||`/`?:` text. The parser cross-checks its own offset arithmetic immediately after emitting each shape (see the `branchPos+branchOffset != ...` checks in `dflparser.y`) and reports `Status::Unsupported` "Internal error" if a shape was miscompiled, since that would otherwise manifest as a silent infinite loop or corrupted stack at evaluation time.

## Pretty-printing: `str()`

`Rpn::str()` reconstructs a readable expression string by walking entries left to right and maintaining a stack of `(substring, precedence)` pairs, consulting the static `opMap` (operator symbol plus a numeric priority mirroring the parser's own precedence table) to decide when a sub-expression needs parentheses. `TJump`, `TBranch`, and `TMakeBoolean` entries are skipped - their visible effect is already captured by the plain `&&`/`||`/`?:` text produced from the trailing `Op` entry. This is what turns a compiled expression back into netlist-like source text for error messages, so a user sees their own syntax rather than the internal branch machinery.

## Arity and jump offsets

`Rpn::Arity` (`RpnArity`, `uint32_t`) counts function/pack arguments; `Rpn::manyArgs` (`UINT32_MAX`) marks a builtin's `maxArity` as unbounded (e.g. `interleave`, see [Context](context.md)'s builtins table). `Rpn::JumpOffset` (`RpnJumpOffset`, `int32_t`) is a signed entry-count delta, always forward in practice but signed so a zero offset (which would be an infinite loop) is distinguishable from a valid one at evaluation time.

## Translating to Verilog-A: `verilogA()`

`Rpn::verilogA(discipline, potAccess, flowAccess, behavData, s)` (implemented in `rpnexprva.cpp`, not `rpnexpr.cpp`) walks a compiled expression and renders it as Verilog-A source text instead of evaluating it, filling in `behavData` (an `RPNBehavioralVA`, `rpnexprva.h`): the generated `vaCode`, plus parallel tables recording which parameters, potential nodes (`v(...)`), and flow/current terminals (`i(...)`) the expression referenced (each entry is `(name, sanitized Verilog-A name, ..., source entry index)`). This is how a netlist `B` (behavioral) source's RPN expression becomes a standalone Verilog-A module that gets compiled through the same OpenVAF/OSDI path as any other device - see `PTBlock::processBehaviorals()` in `lib/parseroutput.cpp`, the sole caller, which assembles one such module per behavioral source and appends it to a generated Verilog-A file. `sanitizeVariable(s, atBeginning)` is the helper both `verilogA()` and its caller use to turn an arbitrary netlist identifier into a legal Verilog-A one, replacing disallowed characters with `_` (and rejecting a leading `$`).
