Source Locations¶
The normative rule is in the Bars specification; this page covers how it appears in fixtures and in the reference implementation.
KbSourceLocation, attached to parse- and render-time exceptions, reports a failing expression's
position as:
line— 1-based line number.column— 0-based position within the line, counted in Unicode code points, not raw UTF-16 code units, and not raw bytes.
The code-point rule¶
A character outside the Basic Multilingual Plane (for example, an emoji) is one code point — and
therefore advances column by one — even though it occupies two UTF-16 Chars in a Kotlin
String, and a variable number of bytes in UTF-8. This follows from how the reference implementation
tokenizes: the ANTLR Kotlin runtime's StringCharStream indexes by decoded code point, not by
Char/UTF-16 unit.
This is not an implementation artifact of the ANTLR/Kotlin toolchain. A port written in a language
whose native string type is UTF-16-backed (Java, C#, JavaScript) or UTF-8-backed (Rust, Go, Swift's
String internals) must count in Unicode code points for column, which typically means iterating
grapheme/scalar values explicitly rather than trusting the host string type's native "length" or
index operations, most of which count code units, not code points.
Line counting¶
line is 1-based and advances once per line break. A \r\n sequence advances it exactly once —
not twice for the \r and the \n separately — so a port that naively counts \n occurrences in a
\r\n-normalized source, or counts both characters of the pair, reports the wrong line past the
first \r\n in the template. column always resets to (and is counted from) the start of the
failing expression's own line, not from the start of the template.
This applies uniformly to both render-time failures and the grammar's own parse-time failures
(KB-1001, KB-1002): a syntax error on line three of a template reports line 3, not a fixed
line 1 for every parse error.
Fixture¶
conformance/render/source-location.json locks this down with several cases:
- A non-BMP emoji (a UTF-16 surrogate pair, one Unicode code point) placed before a failing expression. A port that counts UTF-16 units (giving a column two higher than expected past the emoji) or raw UTF-8 bytes (giving a column three or four higher) computes a different column and fails the fixture.
- LF-only (
\n) and CRLF (\r\n) multi-line templates that must report the same line number for an equivalent failing expression, pinning the "one line break, one line advance" rule above. - A single-line template with leading text before the failing expression, pinning that
columncounts from the start of the line rather than the start of the template. - A multi-line template whose failure is a grammar-level
KB-1001, pinning that parse-time failures report real source lines too, not just render-time failures.
Where line/column are asserted¶
An error expectation in a render fixture (see Fixture Schemas)
may optionally carry line and/or column alongside errorCode:
Both fields are asserted only when present, so existing error fixtures that assert only errorCode
are unaffected. When a fixture omits line/column, a runner should not fail the case merely
because its own location happens to differ — only assert what the fixture actually names.
Partials and the template stack¶
KbSourceLocation also carries a templateStack: List<KbTemplateOrigin> recording which template —
root or a named partial — was active at the point of failure. See
Fixture Schemas for how partials fixtures assert this via
partialStack.
A partial reached through a static {{> key }} symbol carries no KbSourceLocation at all — only
a dynamic {{> (expr) }} key has a parsed expression to attach a location to. See
Fixture Schemas for which error
codes this affects and the fixtures that pin it.