# File Stack

`FileStack` (`filestack.h`/`filestack.cpp`) records the provenance of every source - file or in-memory string - that contributed text to one parse: the `include` tree, library sections, and string sources. It exists so that a compact [`Loc`](sourceloc.md) elsewhere in the simulator can later be resolved back to a real file name, its inclusion chain, and a line number, without every `Loc` needing to carry that information directly.

## Two levels of indirection

A `FileStack` itself is registered by a process-wide `FileStackIndex id_` in a static `idToFs` map, resolved back to the object via `FileStack::lookup(id)`. This is what lets [`Loc`](sourceloc.md) store a 4-byte `FileStackIndex` instead of a full pointer, and still degrade gracefully (`lookup()` returns `nullptr`) if that particular `FileStack` has since been destroyed - e.g. after a one-off expression string is parsed and its throwaway `FileStack` goes out of scope, but a `Loc` referencing it is still lying around in a compiled `Rpn`.

IDs are assigned from a monotonically increasing counter (`at`, starting at 1, `0` reserved for `badFileStackId`) but are recycled: once the last live `FileStack` is destroyed (`count` drops to `0`), the counter resets to `1`, so the *next* `FileStack` created reuses the same id. `ParserTables` owns exactly one `FileStack` for the lifetime of one parse/circuit, so this only matters across separate lifetimes (e.g. loading a fresh circuit after the previous one was fully torn down) - but it does mean `lookup()`'s `nullptr` fallback only holds until a new `FileStack` claims the freed id; a `Loc` that outlives both its own `FileStack` *and* survives into a next one that reused the id would resolve to the wrong (unrelated) `FileStack` rather than failing safely. In practice a `Loc` and the `FileStack` it points into are owned by the same `ParserTables`/compiled-expression lifetime, so they are destroyed together and this is not reachable through ordinary use.

Both size limits are enforced: `FileStack()` throws `std::length_error` if the process-wide id counter would overflow `maxFileStackId`, and `addFile()`/`addStringFile()`/`addRpnString()` each throw the same if their own `stack` (or `rpnStringStack`) has already reached `maxFileId` entries - keeping every id representable in the compact `FileStackFileIndex`/`FileStackIndex` types [`Loc`](sourceloc.md) is built to store cheaply.

Within one `FileStack`, individual sources are addressed by `FileStackFileIndex`:

- **Non-negative** indices are positions in the `stack` vector - both real files (`addFile()`) and in-memory strings registered as pseudo-files (`addStringFile()`) share this namespace.
- **Negative** indices are a second, separate namespace reserved for `addRpnString()`/`rpnStringStack` (see below) - `isFileEntry()` (`id>=0` and in range) and `isString()` (`id<0`) together decode which namespace an id falls in, and `cString()` dispatches on that to find the backing `const char*`.
- `badFileId` (`FileStackFileIndex`'s max value) is a third, distinct sentinel for "no id at all" - not folded into either namespace above.

## Adding sources

`addFile()` is the `include`-resolution logic: an absolute path is tried alone; a relative path is tried first against the including file's own directory (`parentId`'s `canonicalName`), then the current directory, then the given search path (see [File Access](filesystem.md)). A file with no `parentId` is refused once the stack already holds entries - the first (root) file of a `FileStack` must be established before anything else can be recorded relative to it.

`addStringFile()` registers an in-memory string as a pseudo-file (`fileName = "<string input>"`, no real path on disk), copying the string into the `FileStack`'s own `CStringPool` ([Memory Pools](pool.md)) so the returned `const char*` stays valid for the `FileStack`'s whole lifetime, independent of the caller's original string, which may be moved-from or destroyed immediately after.

`addRpnString()` is explicitly documented in the source as unused scaffolding: a cheaper, dedicated namespace for expression-only string sources (one `const char*` in a `deque`, versus a full stack entry with three `std::string`s per source), intended for a future optimization and not yet wired into the [parser](dflparser.md).

## Library sections and the inclusion chain

`setSection()`/`sectionName()` tag a file entry with the named "section" it represents (`include "lib.osdi" section=foo`) - used by the [lexer](dfllexer.md)'s section-scanning states to find the requested section inside one physical library file, and by `Loc::toString()`'s formatting.

`parentId()`/`inclusionLine()` form the linked structure `Loc::toString()` ([Source Location Tracking](sourceloc.md)) walks to print a full "included on line N of parent, included on line M of grandparent, ..." trail for an error located deep inside nested `include`s.

`FileStack` is move-only (copy constructor/assignment deleted) - consistent with owning a `CStringPool` and being registered by identity in a static lookup table, neither of which should be duplicated.
