nx_linemap.nx
buildroot/runtime/nx_linemap.nx
about
nx_linemap.nx -- OUT-OF-BAND expanded-line -> (file, source-line) map.
WHY THIS EXISTS
nx_cc compiles PRE-EXPANDED source: nx_import.nx splices every imported file
inline into ONE flat buffer, so every line number the lexer produces is a line
in that flat stream. Consequences, both measured:
* a diagnostic can say WHERE the compiler was but never WHICH FILE the author
must open (nx_parse.nx says so in-source: "Mapping back to per-file lines is
a separate rung"; the /compare/lang matrix scores the 5W+H voice 24/25 and
names the missing point as the file name);
* nx_dwarf_line.nx -- a complete DWARF v5 .debug_line builder -- has no file
table to emit, so it sits built and unwired along with 11 sibling organs.
This module is the substrate both of those need. It is NOT itself the fix to
either; it is the thing they were both missing.
DESIGN DECISION -- a SPAN TABLE, not C's `#line` directives
The C toolchain solves this IN BAND: cpp injects `#line N "file"` into the
expanded text and the compiler's lexer understands them. We reject that, and
the reasons are worth stating because the in-band design is the older and more
familiar one:
1. EVERY consumer of the expanded buffer must then understand a second syntax
that is not NishiLang -- the lexer, and anything that ever reads the buffer
for any other purpose. One producer's convenience becomes every reader's
obligation.
2. It is FORGEABLE FROM ORDINARY SOURCE. The expanded buffer carries string
literals verbatim; a literal containing a newline followed by
`#line 1 "victim.nx"` would relabel the real code after it. Diagnostics --
and later, debug info a debugger trusts -- could be made to point at an
innocent file BY WRITING A STRING. Putting metadata in a channel that also
carries attacker-writable text is a confusion of layers, and the estate has
already paid for that class once (the in-STRING `}` that closed a brace skip
mid-body, nx_import.nx skip_func_main, debt 1785894599).
3. It perturbs the very bytes nx_diag_caret prints, so the snippet machinery
would have to learn to skip directives to avoid showing them to the author.
Clang (SourceManager) and rustc (SourceMap) both keep this mapping OUT of the
text, beside it. We match that known good.
THE COST WE ACCEPT, STATED PLAINLY: an out-of-band map is a parallel structure
that can DESYNC from the buffer it describes -- it is only as true as its
producer. In-band directives cannot desync, because they ARE the buffer. That is
dependencies 0 imports · 6 importers
imports: none
imported by: nx_import.nxnx_ir.nxnx_lsp.nxnx_parse.nxnx_parse_field_candidate_t280.nxnx_x86_64_ctx.nx
structs
| 86 | struct LineMap |
consts
| 80 | const NX_LM_MAX_SPANS: i64 = 8192 |
| 81 | const NX_LM_MAX_FILES: i64 = 512 |
| 82 | const NX_LM_PATH_LEN: i64 = 1024 |
| 83 | const NX_LM_MAX_FUNCS: i64 = 4096 |
| 84 | const NX_LM_FNAME_LEN: i64 = 128 |
| 253 | const NX_LM_STMT_SLOTS: i64 = 262144 |
functions
| 61 | func lm_ovf_puts(s: *u8) -> i64 called by 1: lm_add_span |
| 67 | func lm_ovf_putn(v: i64) -> i64 called by 1: lm_add_span |
| 106 | func lm_new() -> *LineMap called by 1: expand_ctx_enable_linemap |
| 133 | func lm_slen(s: *u8) -> i64 |
| 139 | func lm_at(p: *u8, d: i64) -> *u8 |
| 144 | func lm_seq(a: *u8, b: *u8) -> i64 called by 1: lm_intern_file |
| 154 | func lm_copy(dst: *u8, src: *u8, n: i64) -> i64 |
| 166 | func lm_intern_file(lm: *LineMap, path: *u8) -> i64 |
| 188 | func lm_file_path(lm: *LineMap, id: i64) -> *u8 |
| 198 | func lm_basename(p: *u8) -> *u8 |
| 229 | func lm_set_debug(v: i64) -> i64 |
| 234 | func lm_debug_on() -> i64 called by 1: alloc_instr |
| 260 | func lm_stmt_init() -> i64 called by 1: lm_stmt_stamp |
| 273 | func lm_stmt_set_cur(line: i64) -> i64 { lm_stmt_cur_line = line; return 0 } |
| 274 | func lm_stmt_cur() -> i64 { return lm_stmt_cur_line } |
| 275 | func lm_stmt_count() -> i64 { return lm_stmt_n } |
| 276 | func lm_stmt_overflowed() -> i64 { return lm_stmt_full } |
| 278 | func lm_stmt_slot(p: i64) -> i64 { return (p >> 8) & (NX_LM_STMT_SLOTS - 1) } |
| 280 | func lm_stmt_stamp(p: i64) -> i64 |
| 306 | func lm_stmt_lookup(p: i64) -> i64 calls 1: lm_stmt_slot |
| 321 | func lm_set_active(lm: *LineMap) -> i64 |
| 326 | func lm_get_active() -> *LineMap |
| 335 | func lm_fn_record(lm: *LineMap, name: *u8, nlen: i64, out_line: i64) -> i64 |
| 353 | func lm_fn_lookup(lm: *LineMap, name: *u8, nlen: i64) -> i64 calls 1: lm_at |
| 375 | func lm_add_span(lm: *LineMap, out_line: i64, file_id: i64, src_line: i64) -> i64 |
| 439 | func lm_lookup(lm: *LineMap, out_line: i64, o_file: *i64, o_src: *i64) -> i64 |