# Stack

`RpnStack` (`rpnstack.h`/`rpnstack.cpp`) is the value stack the [evaluator](rpneval.md) computes on while walking a compiled [expression](rpnexpr.md). It is a thin, deliberately minimal wrapper around `std::vector<Value>` (see [Values](value.md)), shaped around how the builtin-function wrappers in [builtin operators and functions](rpnbuiltin.md) actually use it: addressing a fixed-size window of the most recently pushed values as "arguments," and overwriting one of them in place with the result rather than pushing a new one.

Like `RpnEvaluator` and `ContextStack`, `RpnStack` is move-only (copy constructor/assignment deleted) - it is evaluator-owned working storage, not a value meant to be duplicated.

## Indexing from the top

`get(i)` is 0-indexed from the top of the stack (`i=0` is the top, `i=1` the value below it, ...) and returns `nullptr` if `i` is out of range rather than throwing. Builtin wrappers rely on this offset-from-top addressing directly: arguments are pushed left-to-right during evaluation, so "the last `argc` values" are exactly `get(argc-1) .. get(0)` in argument order - no separate argument array is needed.

## Popping

Three `pop` overloads suit three different needs:

- **`pop()`** discards the top value. This is the common case: a binary operator wrapper computes its result into the position of the *first* operand in place (see `rpnMathFunc2` in [builtin operators and functions](rpnbuiltin.md)) and only needs to discard the now-redundant second operand.
- **`pop(Value& v)`** swaps the top value into `v` (an allocation-free extraction, using `Value`'s `swap()`) and removes it - used once, at the end of `RpnEvaluator::evaluate()`, to hand back the expression's single final result.
- **`pop(n)`** bulk-removes the top `n` entries via a single `resize()`, used by variadic builtins (`vectorPack`, `listPack`, ...) to discard every processed argument but the one now holding the result, in one step instead of `n` individual calls.

## Pushing

`push(const Value&)`/`push(Value&&)` are direct `push_back` forwarding, copy and move respectively.

## Why in-place, not push-based

Because a compiled expression's arguments are always evaluated once, left to right, immediately before the operator/function that consumes them, the natural place to write a result is over one of those arguments rather than in a fresh stack slot - this is also why `Value`'s own move-assignment reuses existing heap storage when the type doesn't change (see [Values](value.md)): the same "avoid an allocation when a compatible container is already sitting where the result needs to go" idea recurs at both the stack level and the individual `Value` level, since expression evaluation runs once per instance, per Monte Carlo sample, per rebuild, and can happen very often over a simulation run.
