# Source Location Tracking

`Loc` (`sourceloc.h`/`sourceloc.cpp`) is the simulator's own compact source location: a `(FileStack id, file id, line, offset)` quadruple, deliberately smaller and cheaper to copy than Bison's [`Location`/`Position`](location.md), because it is what actually gets stored - one per [`Rpn::Entry`](rpnexpr.md) - and a single compiled expression can contain many entries.

## Why it's this small

The header explains the size budget directly: a raw `FileStack*` pointer would make `Loc::Entry` 20 bytes (24 after alignment). Replacing the pointer with a 4-byte `FileStackIndex` - looked up in [`FileStack`](filestack.md)'s static id table on demand - brings a `Loc::Entry` down to a clean 16 bytes (`FileStackIndex` + `FileStackFileIndex` + `SourceLineNumber` + `SourceColumnNumber`, 4 bytes each).

Locations also aren't interned or pooled the way [identifiers](identifier.md) are: the header notes each `Loc` is typically referenced only once, and locations are unique across circuits, so a shared repository wouldn't earn back its cost the way `Id`'s string pool does for names that repeat constantly throughout a netlist.

## Construction and the sentinel

`Loc` is built from either a `FileStackIndex` or a `FileStack*` (converted internally via `->id()`) plus `(file id, line, offset)`. Two constructors from Bison's `Position`/`Location` are present in the header but commented out - dead code deliberately left as a marker of the conversion this type replaces; the actual conversion happens the other way around, via `Position::loc()`/`Location::loc()` (see [Location](location.md)), not through a `Loc` constructor.

`Loc::bad` (and the default constructor) is `(FileStack::badFileStackId, FileStack::badFileId, 0, 0)` - the sentinel for "no location was ever set." `operator bool()` is `true` iff a `Loc` isn't this sentinel, and is checked throughout the codebase before attaching location context: [`Rpn::location()`](rpnexpr.md) returns `Loc::bad` for an entry that never had a location attached, [`Value::checkCondition`](value.md) callers and [`Status::extend(const Loc&)`](status.md) both skip appending anything for a bad `Loc`.

## Resolving and formatting

`data()` resolves `fileStackId_` back to a live `FileStack*` via `FileStack::lookup()`, which can return `nullptr` if that `FileStack` has since been destroyed; every consumer of `data()` has to handle that case.

`toString()` is the simulator's actual diagnostic formatter - the text a [`Status`](status.md)-attached error message ultimately shows for "where." If the `FileStack` is gone it degrades to `"<unknown location>"`. Otherwise:

1. It reads the offending line via [`getLineByOffset()`](filesystem.md), from disk (using `canonicalName()`) if the source was a real file, or from the `FileStack`'s own in-memory buffer (`cString()`) if it was a parsed string.
2. It prints that line - trimmed from the front (`maxFront=35` columns) and back (`maxLen=70` characters total) with `...` markers if it's too long to show usefully - followed by a caret (`^`) aligned under the exact column.
3. It appends `line:column (0xoffset)`, then, unless the source is a plain string (`isString()`), walks the inclusion chain via [`FileStack`](filestack.md)'s `parentId()`/`inclusionLine()`, printing `<filename>[, section <name>]` at each level and `included on line N of` between levels - the complete "where this actually came from" trail through however many nested `include`s produced the location.
