commit 54cd542c9e1c6acb458dfe3ff604e99f1926554a
parent 94eaef5e27c8ca1c0e3f9dcc5556f29f7b6503e2
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Mon, 15 Jun 2026 08:30:26 -0700
lex/pp: lean-token boundary rewrite (byte-identical, -1.3% -c)
Clean-break rewrite of the C lexer/preprocessor/parser boundary to the
doc/plan/LEX-PP-API.md "lean token" design:
- Tok carries a compact LocRef (file_id + byte offset) and a TextRef
(source span / interned Sym / canonical-punct), not an eager SrcLoc +
interned spelling; line/col and spelling materialize on demand.
- Slot-based pulls (lex_next / pp_next_raw / pp_next_parse fill caller
slots); PP creates and owns lexers via pp_push_source.
- PP owns a SrcInfo registry (retained source buffers + lazy memchr line
index + #line overlay segments) for loc/text materialization; hidesets
stay PP-internal.
Byte-identical: sqlite -E / -c / -c -g / -S / diagnostics bit-for-bit vs the
pre-rewrite snapshot; test-pp, test-parse, test-toy, test-cg-api, test-dwarf,
test-debug and perf-gate (incl. the splice/#line/diagnostic battery) green.
Net -24M -c instructions (-1.3%) vs baseline, after the loc-materialization
fix (memchr index build + cursored pp_materialize_loc).
This is the boundary redesign only. Drain-measured lex+pp is still ~2.1x tcc
(per-token struct copy, Prosser hidesets, and layered dispatch are unchanged);
the structural per-token work is a follow-on (PERF.md section 4.1). KIT_PP_DRAIN
(lang/c/c.c) isolates lex+pp for that measurement.
Diffstat:
16 files changed, 1650 insertions(+), 1157 deletions(-)
diff --git a/doc/plan/PERF.md b/doc/plan/PERF.md
@@ -39,19 +39,36 @@ readings (`sysctl -n vm.loadavg`) shown only for context.
| compiler | instructions | cycles † | wall † | object |
|---|--:|--:|--:|--:|
| **tcc 0.9.28** | 0.66 B | 0.19 B | 0.06 s | 2.11 MB |
-| **kit** | 1.87 B | 0.52 B | 0.16 s | 1.83 MB |
+| **kit** | 1.84 B | 0.53 B | 0.16 s | 1.83 MB |
| clang 22 | 8.73 B | 2.57 B | 0.81 s | 1.50 MB |
† low-load; instructions is the figure to trust.
kit beats clang and is the fastest *general* backend, but **tcc is ~2.8×
(instructions) ahead** — this is the whole game. **The gap is instructions, not
-cache:** kit's IPC (~3.7) is *higher* than tcc's (~3.5), so both are compute-bound
+cache:** kit's IPC (~3.5) is on par with tcc's (~3.5), so both are compute-bound
on a wide core and there is no hidden cache-miss penalty to claw back. The payoff
mechanism is **fewer retired instructions** (fewer copies, indirect calls,
redundant recomputations) — denser structures help only because they cost fewer
load/store/move *instructions*.
+> **Lean-token lexer/PP rewrite (landed, byte-identical, net win).** The
+> lexer/preprocessor boundary was rewritten to the `doc/plan/LEX-PP-API.md` "lean
+> token" design (compact `LocRef` byte-offset locations materialized on demand;
+> `TextRef` spellings; slot-based pulls). sqlite `-E`, `-c` object, `-g`, `-S`,
+> and diagnostics are **bit-for-bit identical** to the pre-rewrite snapshot
+> (`perf-gate` green incl. the splice/`#line`/diagnostic battery), so **code size
+> is unchanged**. The boundary-only landing was initially a −3.35 % `-c`
+> regression — lazy line/col was a *loss* because line numbers are needed in the
+> happy path anyway (sqlite expands `__LINE__` pervasively, and the parser stamps
+> a CG loc per statement) and the first cut built the line index with a scalar
+> `\n` rescan + a per-loc binary search. Fixing the *implementation* of laziness
+> (build the index with memchr; cursor the materializer — §4.1) turned it into a
+> net **−24 M `-c` win** (1.865 B → 1.841 B), since the scanner no longer builds a
+> `SrcLoc` per token. The remaining ~+19 M parse+CG residual (per-statement loc
+> lookups the old eager-on-token loc got for free) is the optional Stage-2 target
+> in §4.1.
+
### Code size — ≈ parity (1.044× tcc)
The honest metric is **`.text` machine code** (the object file is format-skewed —
@@ -79,44 +96,83 @@ Net +14,972.
## 2. Where the cost is (current profile)
-### Compile is frontend-bound, ~uniform ~3× across phases
+### Compile is frontend-bound — lex+pp ~2.1× tcc, post-PP ~3.8× tcc
-Phase decomposition by subtraction (instructions; `-fsyntax-only` drives the full
-CG value-stack + type lowering routed to the no-op check backend, so `(-c) −
-(-fsyntax-only)` isolates native-emit + object-write):
+Phase split measured directly (instructions, best-of-7), not estimated from `-E`.
+`-E` is a **bad** lex+pp proxy: it re-serializes the token stream to text, work
+neither compiler does at `-c`, and the two serialize at wildly different cost
+(tcc's `-E` 1.17 B is ~0.79 B *serialization* via `get_tok_str` re-stringify +
+whitespace logic, so tcc `-E` even exceeds tcc `-c`; kit's `-E` serializes for
+~0.06 B since tokens carry `TEXT_SRC` spans → emit is a `memcpy`). To isolate
+lex+pp we **drain the token stream to EOF with no parse and no output** in both
+compilers (tcc: `-bench` hook, patched to use `-c` `parse_flags`; kit:
+`KIT_PP_DRAIN`, see §3):
-| phase | kit `-c` | share | ratio to tcc |
+| phase | kit `-c` | tcc `-c` | kit / tcc |
|---|--:|--:|--:|
-| lex + pp | ~0.93 B | ~49 % | ~2.8× |
-| parse + sema + types + CG-drive | ~0.71 B | ~37 % | ~3.5× |
-| native emit + object write | ~0.27 B | ~14 % | **~7×** (worst ratio, smallest slice) |
-| **total** | **1.91 B** | 100 % | **~2.9×** |
-
-Two facts govern strategy: (1) the gap is **roughly uniform across phases** — no
-single hot function to crush; closing it is a campaign across the pipeline. (2)
-**Post-PP work alone (`-c` − `-E` ≈ 0.98 B) is already ~1.5× tcc's entire
-compile** — a *free* lexer would still leave kit above tcc, so the lexer is not
-the sole frontier.
+| **lex + pp** | **0.82 B** | **0.39 B** | **~2.1×** |
+| parse + sema + types + CG-drive | 0.76 B | ┐ 0.27 B | — |
+| native emit + object write | 0.26 B | ┘ (post-PP) | — |
+| **post-PP total** | **1.02 B** | **0.27 B** | **~3.8×** |
+| **total** | **1.84 B** | **0.66 B** | **~2.8×** |
+
+So **both halves are real frontiers** — lex+pp is *not* at parity (an earlier
+claim from the misleading `-E` proxy was wrong). The post-PP ratio is larger, but
+lex+pp is ~44 % of kit's `-c` and a clean 2.1× behind. Note kit's lex+pp drain
+does *less* than tcc's — kit defers number/string decode to the parser (tcc decodes
+in the lexer; bare tcc scan+expand is 0.38 B, +decode 0.39 B) — yet is still ~2.1×
+heavier, so the gap is pure per-token engine overhead, not extra work:
+
+- **tcc: one mutable global token** (`int tok` + `CValue tokc` + `tok_flags`) —
+ the lexer and macro replayer write it in place; no per-token struct is built or
+ copied. kit writes a 32-byte `Tok` (kind/flags/aux/`LocRef`/`TextRef`) per token
+ and copies it lex→pp→parser through slots.
+- **tcc: no per-token location** (diagnostics read a `file->line_num` counter);
+ kit stores a `LocRef` per token (cheap, but ≠ free) and the pp checks it.
+- **tcc: macro bodies are `int[]` token streams** replayed via a `macro_ptr`
+ cursor with **no hidesets** (a cheap nested-macro guard); kit replays `Tok[]`
+ with a Prosser hideset side-channel (hash-consed ids, union/dedup per expansion).
+- **tcc: `TokenSym` caches direct `Sym*` pointers** (define/ident/struct/label) on
+ the interned token, so define-lookup is a pointer-follow; kit re-probes
+ (`pool_intern_slice` 3.9 %, the Sym-indexed macro table 1 load).
+- **tcc: one `next()`** does lex + pp + feeds the parser — no inter-stage boundary;
+ kit layers lex → pp (source stack, `src_next_raw_into`→`pp_pull_into`) →
+ `pp_next_parse` → parser, and the pp re-checks directive/`defined`/dynamic-macro
+ state per token. This is the [[frontend-instruction-halving-pathb]] lever: fewer
+ per-token ops, not less work.
+
+The post-PP 3.8× is the bigger slice: tcc drives a thin `SValue[]` straight into a
+one-pass emitter, while kit routes through the `CgTarget`→`NativeTarget`→`MCEmitter`
+seam and a richer type/ABI layer (§4). Closing either is a campaign, not one hot
+function.
+
+Net of the lean-token rewrite + its loc-materialization fix (golden→now,
+best-of-7): **lex+pp −44 M** (the scanner no longer builds a `SrcLoc` per token —
+`loc` is a byte offset), **parse+sema+CG +19 M** (the residual: a per-statement
+cursor lookup at `pcg_set_loc` the old eager-on-token loc got for free), emit
+flat → **−24 M total (−1.3 %)**.
**Linux callgrind** (inclusive, instruction-grounded — the tool that sees what
-wall-clock `sample` hides; total **1.746 B `Ir`**, glibc/ELF):
+wall-clock `sample` hides; total **1.652 B `Ir`**, glibc/ELF; self-`Ir` summed
+across callgrind's `'2` symbol splits):
| self % | function(s) | subsystem |
|--:|---|---|
-| **16.9** | `lex_next` | scanner |
-| 5.0 / 4.5 / 2.0 | `src_next_raw_into` / `lex_point_at` / `finish_ident` | scanner+pp |
-| 4.4 | `pp_pull_into` | preprocessor |
-| 4.1 | `pool_intern_slice` | interning |
-| 3.1 / 2.8 | `api_unalias_type` / `api_type_pred_bits` | types |
-| 1.5×4 / 1.3 / 1.2 | `resolve_type`,`cg_type_get`,`type_cg_lower`,`api_type_class` / `abi_cg_type_info` / `cg_type_size` | types |
+| **11.7** | `lex_next` | scanner |
+| 4.5 / 3.9 | `src_next_raw_into` / `pool_intern_slice` | pp + interning |
+| 3.8 | `pp_pull_into` | preprocessor |
+| 3.0 / 3.0 | `api_unalias_type` / `api_type_pred_bits` | types |
+| 2.6 | `finish_ident` | scanner |
+| 1.4 / 1.0×2 | `cg_type_get` / `resolve_type`,`api_type_class` | types |
| 1.0 | `aa_emit_mem` | codegen |
+| **0.9** | `pp_materialize_loc` | lazy-loc lookup (was 2.6 % — now cursored) |
+| **0.6** | `srcinfo_build_lines` | lazy-loc index build (was 5.3 % — now memchr) |
-**Subsystem rollup:** scanner ~24 %, **types ~14 %**, preprocessor ~12 %,
-interning ~4 %, codegen/emit ~3 %. The macOS wall-clock `sample` makes the scanner
-*look* like the lone frontier (`lex_next` ~72 %) and renders the type subsystem
-nearly invisible — callgrind reveals **types as the #2 self-`Ir` cluster** (a
-dozen O(1)-but-very-frequent per-query helpers). Trust callgrind for *where*;
-trust macOS instructions for *how much*.
+**Subsystem rollup:** scanner ~18 %, types ~13 %, preprocessor ~11 %, interning
+~4 %, lazy-loc (`pp_materialize_loc` + `srcinfo_build_lines`) ~1.5 %, codegen/emit
+~3 %. The scanner is leaner than pre-rewrite (line tracking + eager literal/punct
+interning left `lex_next`); the lazy-loc machinery that briefly dominated is now
+sub-2 %. Trust callgrind for *where*; trust macOS instructions for *how much*.
### Code size is spill-bound
@@ -158,12 +214,20 @@ m tmp/tinycc/tcc -c sqlite3.c -o /tmp/t.o
Caveat: Apple clang spawns a `cc1` that `/usr/bin/time` does **not** count —
measure it with `cc -fintegrated-cc1 -c …`. kit and tcc are single-process.
-**Phase decomposition** (subtraction; instruction counts are cleanest):
+**Phase decomposition.** Do **not** use `-E` as the lex+pp number — it adds
+token→text serialization neither compiler does at `-c`, and tcc/kit serialize at
+very different cost (§2). Instead **drain the token stream to EOF** (lex+pp only,
+no parse, no output) in each compiler:
```sh
-m build/release/kit cc -E -o /dev/null sqlite3.c --sysroot "$SDK" # lex+pp
-m build/release/kit cc -fsyntax-only sqlite3.c --sysroot "$SDK" # +parse/sema/types/CG-drive
+# kit: KIT_PP_DRAIN runs pp_next_parse to EOF then returns (lang/c/c.c)
+m env KIT_PP_DRAIN=1 build/release/kit cc -c sqlite3.c --sysroot "$SDK" -o /dev/null # lex+pp
+m build/release/kit cc -fsyntax-only sqlite3.c --sysroot "$SDK" # lex+pp +parse/sema/types/CG
m build/release/kit cc -c -o /tmp/k.o sqlite3.c --sysroot "$SDK" # +emit+objwrite
+# tcc: the -bench hook drains next() to EOF; patched (tccpp.c tcc_preprocess)
+# to use -c parse_flags. TCC_DRAIN_FLAGS=c also decodes literals in the lexer.
+m env TCC_DRAIN_FLAGS=c tmp/tinycc/tcc -E -bench sqlite3.c -o /dev/null # tcc lex+pp
+m tmp/tinycc/tcc -c sqlite3.c -o /tmp/t.o # tcc full -c
```
**macOS hotspot sample** (self-time leaves only — see the callgrind caveat). One
@@ -243,7 +307,33 @@ not the swappable vtable), or the code-size track (which *bytes* the NDT emits).
### 4.1 Compile speed (frontend-bound)
-**Status (this campaign).** Most of §4.1 has now landed, byte-identical, for a
+**Lean-token loc-materialization fix — Stage 1 DONE (`-c` −24 M net win).** The
+boundary rewrite's initial +62 M `-c` regression was two lazy-loc implementation
+costs, both now fixed byte-identically (frontend-only, no CG-API change):
+- ✅ **memchr the line-index build.** `srcinfo_build_lines` built the line-start
+ index with a scalar `\n` byte loop; replaced with a memchr sweep (5.3 % → 0.6 %
+ self-`Ir`). The build is genuinely needed in the happy path — `__LINE__` (which
+ sqlite expands pervasively) and diagnostics need line numbers even without
+ `-g` — so the lever was to make it cheap, not to skip it.
+- ✅ **cursor `pp_materialize_loc`.** Per-loc lookup was a fresh binary search;
+ now a `SrcInfo.line_cursor` caches the last line index, so the near-monotonic
+ access pattern (parser advances forward) is O(1), binary search only on a
+ backward jump (2.6 % → 0.9 %).
+
+Result: lex+pp −44 M (the scanner no longer builds a `SrcLoc` per token),
+parse+CG +19 M residual, **net −24 M** (§1, §2), `perf-gate` byte-identical.
+
+**Stage 2 (optional, ~+19 M parse+CG residual).** The residual is the
+per-statement loc lookup at `pcg_set_loc` (CG source-loc) that the old
+eager-on-token loc got for free. `g->cur_loc` is consumed only by DWARF (gated on
+`g->debug`), descriptor locs, and cold CG `compiler_panic`s — so with `-g` off and
+no error it is discarded. Carrying it as a `LocRef` (a `kit_cg_set_locref` +
+a "resolve `LocRef`→`SrcLoc`" callback the frontend registers, so cold CG
+diagnostics still materialize exactly) would skip those per-statement lookups
+entirely. Smaller payoff than Stage 1, needs a contained CG-API addition + the
+parser loc-plumbing; gate: byte-identical (`-g` DWARF + diag battery unchanged).
+
+**Status (prior campaign).** Most of §4.1 has now landed, byte-identical, for a
combined **−43.7 M `-c` instructions (−2.29 %)** on sqlite vs the pre-campaign
snapshot (sqlite `.o` + `-E` bit-for-bit identical; 60/60 gate; full parse/pp/cg
/toy/smoke-x64/rv64/debug/dwarf suites green). Item-by-item below: #1 ✅ (landed
diff --git a/lang/c/c.c b/lang/c/c.c
@@ -54,7 +54,8 @@ static void c_profile_define(KitCompiler* c) {
"compile.c.cleanup");
kit_frontend_profile_define_counter(c, C_PROFILE_COUNTER_PP_INCLUDE_DIRS,
"compile.c.pp_include_dirs");
- kit_frontend_profile_define_counter(c, C_PROFILE_COUNTER_PP_SYSTEM_INCLUDE_DIRS,
+ kit_frontend_profile_define_counter(c,
+ C_PROFILE_COUNTER_PP_SYSTEM_INCLUDE_DIRS,
"compile.c.pp_system_include_dirs");
kit_frontend_profile_define_counter(c, C_PROFILE_COUNTER_PP_DEFINES,
"compile.c.pp_defines");
@@ -118,9 +119,9 @@ static KitStatus c_frontend_compile_cg(KitFrontendState* frontend,
* KitFrontendCompileOptions; the C frontend uses no language_options. */
const KitSlice* bytes;
Pool* pool;
- Lexer* lex;
Pp* pp;
DeclTable* decls;
+ SourceSpec spec;
if (!fe || !fe->c) return KIT_INVALID;
c = fe->c;
@@ -133,15 +134,10 @@ static KitStatus c_frontend_compile_cg(KitFrontendState* frontend,
pool = c_pool_new(c);
kit_frontend_profile_scope_end(c, C_PROFILE_SCOPE_POOL_NEW);
if (!pool) compiler_panic(c, c_no_loc(), "C compiler out of memory");
- kit_frontend_profile_scope_begin(c, C_PROFILE_SCOPE_LEX_OPEN);
- lex = lex_open_mem(c, input->name.s, bytes->s, bytes->len);
- if (lex) lex_skip_shebang(lex);
- kit_frontend_profile_scope_end(c, C_PROFILE_SCOPE_LEX_OPEN);
kit_frontend_profile_scope_begin(c, C_PROFILE_SCOPE_PP_NEW);
pp = pp_new(c);
kit_frontend_profile_scope_end(c, C_PROFILE_SCOPE_PP_NEW);
- if (!lex || !pp || !cg)
- compiler_panic(c, c_no_loc(), "C compiler out of memory");
+ if (!pp || !cg) compiler_panic(c, c_no_loc(), "C compiler out of memory");
kit_frontend_profile_scope_begin(c, C_PROFILE_SCOPE_DECL_NEW);
decls = decl_new(c, pool, cg);
kit_frontend_profile_scope_end(c, C_PROFILE_SCOPE_DECL_NEW);
@@ -158,16 +154,33 @@ static KitStatus c_frontend_compile_cg(KitFrontendState* frontend,
fe_opts->preprocess.nundefines);
c_apply_pp_options(pp, &fe_opts->preprocess);
kit_frontend_profile_scope_end(c, C_PROFILE_SCOPE_PP_OPTIONS);
- /* The C parser drops preprocessor newlines, so suppress non-directive
- * newline tokens at the lexer (primary source + #includes) — ~51% of lexer
- * outputs. Directive-terminating newlines are still emitted, so the PP's
- * directive handling is unaffected. Must be set before pp_push_input so the
- * primary lexer inherits the mode. */
- pp_set_suppress_lexer_newlines(pp, 1);
+ /* SRC_PARSER_FEED: the C parser drops preprocessor newlines, so non-directive
+ * newline tokens are suppressed at every lexer (primary + #includes) — ~51%
+ * of lexer outputs. Directive-terminating newlines are still emitted, so the
+ * PP's directive handling is unaffected. SRC_PRIMARY enables shebang skip. */
kit_frontend_profile_scope_begin(c, C_PROFILE_SCOPE_PP_PUSH_INPUT);
- pp_push_input(pp, lex);
+ memset(&spec, 0, sizeof(spec));
+ spec.name = kit_slice_cstr(input->name.s);
+ spec.bytes = bytes->s;
+ spec.len = (u32)bytes->len;
+ spec.flags = SRC_PRIMARY | SRC_PARSER_FEED;
+ pp_push_source(pp, &spec);
kit_frontend_profile_scope_end(c, C_PROFILE_SCOPE_PP_PUSH_INPUT);
+ /* Perf instrumentation: drain the preprocessed token stream to EOF without
+ * parsing or codegen, to isolate lex+pp instructions (the parser-feed stream
+ * matches -c exactly — newlines suppressed, no text serialization). */
+ if (kit_debug_getenv("KIT_PP_DRAIN")) {
+ Tok t;
+ do {
+ pp_next_parse(pp, &t);
+ } while (t.kind != TOK_EOF);
+ decl_free(decls);
+ pp_free(pp);
+ c_pool_free(pool);
+ return KIT_OK;
+ }
+
kit_frontend_profile_scope_begin(c, C_PROFILE_SCOPE_PARSE_CODEGEN);
parse_c(c, pool, pp, decls, cg, (KitSymVis)fe_opts->code.default_visibility,
(int)fe_opts->code.trivial_auto_var_init);
diff --git a/lang/c/parse/parse.c b/lang/c/parse/parse.c
@@ -79,11 +79,13 @@ static const char* const kw_names[KW_COUNT] = {
* Diagnostics
* ============================================================ */
-static SrcLoc tok_loc(const Tok* t) { return t->loc; }
+static SrcLoc tok_loc(Parser* p, const Tok* t) {
+ return pp_materialize_loc(p->pp, t->loc);
+}
_Noreturn void perr(Parser* p, const char* fmt, ...) {
va_list ap;
- SrcLoc loc = tok_loc(&p->cur);
+ SrcLoc loc = tok_loc(p, &p->cur);
va_start(ap, fmt);
compiler_panicv(p->c, loc, fmt, ap);
}
@@ -107,8 +109,8 @@ static Tok fuse_string_lits(Parser* p, Tok a, Tok b) {
u16 ae = (u16)(a.flags & STR_ENC_MASK);
u16 be = (u16)(b.flags & STR_ENC_MASK);
u16 fused_enc;
- KitSlice a_sl = kit_sym_str(p->pool->c, a.spelling);
- KitSlice b_sl = kit_sym_str(p->pool->c, b.spelling);
+ KitSlice a_sl = pp_text_slice(p->pp, &a);
+ KitSlice b_sl = pp_text_slice(p->pp, &b);
size_t alen = a_sl.len, blen = b_sl.len;
const char* as = a_sl.s;
const char* bs = b_sl.s;
@@ -155,7 +157,8 @@ static Tok fuse_string_lits(Parser* p, Tok a, Tok b) {
}
buf[k++] = '"';
out = a;
- out.spelling = kit_sym_intern(p->pool->c, (KitSlice){.s = buf, .len = k});
+ out.text =
+ text_sym_ref(kit_sym_intern(p->pool->c, (KitSlice){.s = buf, .len = k}));
out.flags = (u16)((a.flags & ~STR_ENC_MASK) | fused_enc);
h->free(h, buf, 0);
return out;
@@ -168,11 +171,12 @@ static Tok fetch_tok(Parser* p) {
t = p->pending;
p->has_pending = 0;
} else {
- t = pp_next(p->pp);
+ pp_next_parse(p->pp, &t);
}
if (t.kind != TOK_STR) return t;
for (;;) {
- Tok n = pp_next(p->pp);
+ Tok n;
+ pp_next_parse(p->pp, &n);
if (n.kind != TOK_STR) {
p->pending = n;
p->has_pending = 1;
@@ -293,7 +297,6 @@ u32 count_recorded_top_level_items(const Tok* vec, u32 len) {
* O(1) per lookup. */
#define SCOPE_INDEX_THRESHOLD 12u
-
Scope* scope_new(Parser* p, Scope* parent) {
Scope* s = arena_new(p->pool->arena, Scope);
if (!s) perr(p, "out of memory in scope_new");
@@ -1304,7 +1307,8 @@ static void parse_function_body(Parser* p, ObjSymId fsym, const Type* fn_ty,
/* Record whether this body emits before func_begin (which is itself gated on
* emit-enabled): goto-label allocation keys off this, not the momentary
* suppress_codegen depth, so a label first referenced inside a constant-false
- * region still gets a real CG-label id rather than the suppression sentinel. */
+ * region still gets a real CG-label id rather than the suppression sentinel.
+ */
p->cur_func_emits = (u8)pcg_emit_enabled(p);
pcg_func_begin(p, &fd);
@@ -1575,7 +1579,8 @@ static void parse_file_scope_asm(Parser* p) {
size_t nbytes;
advance(p); /* asm / __asm__ */
for (;;) {
- if (is_kw(p, &p->cur, KW_VOLATILE)) { /* matches `volatile` and `__volatile__` */
+ if (is_kw(p, &p->cur,
+ KW_VOLATILE)) { /* matches `volatile` and `__volatile__` */
advance(p);
continue;
}
@@ -1752,7 +1757,8 @@ void parse_c(Compiler* c, Pool* pool, Pp* pp, DeclTable* decls, CG* cg,
p.sym_thread_alias = kit_sym_intern(p.pool->c, KIT_SLICE_LIT("__thread"));
/* GNU alias spellings -> their canonical CKw. Registered after the canonical
* keywords above so the canonical mapping always wins (matching the old
- * ident_kw_inline fall-through, which scanned kw_sym[] before the aliases). */
+ * ident_kw_inline fall-through, which scanned kw_sym[] before the aliases).
+ */
(void)KwTab_set(&p.kw_map, p.sym_alignof_alias, (u8)KW_ALIGNOF);
(void)KwTab_set(&p.kw_map, p.sym_asm_alias, (u8)KW_BUILTIN_ASM);
(void)KwTab_set(&p.kw_map, p.sym_inline_alias, (u8)KW_INLINE);
diff --git a/lang/c/parse/parse_expr.c b/lang/c/parse/parse_expr.c
@@ -102,7 +102,7 @@ static u32 cint_bits(Parser* p, const Type* ty);
static int cint_signed(Parser* p, const Type* ty);
static u64 parse_int_literal_u64(Parser* p, const Tok* t, int* decimal_out) {
- KitSlice spell_sl = kit_sym_str(p->pool->c, t->spelling);
+ KitSlice spell_sl = pp_text_slice(p->pp, t);
size_t len = spell_sl.len;
const char* s = spell_sl.s;
size_t i = 0;
@@ -222,7 +222,7 @@ static const Type* int_literal_type(Parser* p, const Tok* t) {
}
double parse_float_literal(Parser* p, const Tok* t) {
- KitSlice spell_sl = kit_sym_str(p->pool->c, t->spelling);
+ KitSlice spell_sl = pp_text_slice(p->pp, t);
size_t len = spell_sl.len;
const char* s = spell_sl.s;
size_t i = 0;
@@ -365,7 +365,7 @@ int string_literal_initializes_array(Parser* p, const Type* elem,
}
i64 decode_char_literal(Parser* p, const Tok* t) {
- KitSlice spell_sl = kit_sym_str(p->pool->c, t->spelling);
+ KitSlice spell_sl = pp_text_slice(p->pp, t);
size_t len = spell_sl.len;
const char* s = spell_sl.s;
size_t i = 0;
@@ -388,12 +388,12 @@ i64 decode_char_literal(Parser* p, const Tok* t) {
if (i >= len || s[i] == '\'') perr(p, "empty character literal");
if (!c_lit_decode_unit(s, len, &i, &unit, &err)) {
compiler_panic(
- p->c, t->loc, "%.*s",
+ p->c, pp_materialize_loc(p->pp, t->loc), "%.*s",
KIT_SLICE_ARG(kit_slice_cstr(err ? err : "bad character literal")));
}
if (!c_lit_encode_char_unit(enc, bits, unit, &v, &err)) {
compiler_panic(
- p->c, t->loc, "%.*s",
+ p->c, pp_materialize_loc(p->pp, t->loc), "%.*s",
KIT_SLICE_ARG(kit_slice_cstr(err ? err : "bad character literal")));
}
if (i >= len || s[i] != '\'') {
@@ -403,7 +403,7 @@ i64 decode_char_literal(Parser* p, const Tok* t) {
}
u8* decode_string_literal(Parser* p, const Tok* t, size_t* nlen_out) {
- KitSlice spell_sl = kit_sym_str(p->pool->c, t->spelling);
+ KitSlice spell_sl = pp_text_slice(p->pp, t);
size_t len = spell_sl.len;
const char* s = spell_sl.s;
size_t i = 0;
@@ -429,12 +429,12 @@ u8* decode_string_literal(Parser* p, const Tok* t, size_t* nlen_out) {
CLitUnit unit;
if (!c_lit_decode_unit(s, len, &i, &unit, &err)) {
compiler_panic(
- p->c, t->loc, "%.*s",
+ p->c, pp_materialize_loc(p->pp, t->loc), "%.*s",
KIT_SLICE_ARG(kit_slice_cstr(err ? err : "bad string literal")));
}
if (!c_lit_append_string_unit(buf, &k, enc, elem_size, unit, &err)) {
compiler_panic(
- p->c, t->loc, "%.*s",
+ p->c, pp_materialize_loc(p->pp, t->loc), "%.*s",
KIT_SLICE_ARG(kit_slice_cstr(err ? err : "bad string literal")));
}
}
@@ -455,7 +455,8 @@ KitCgSym emit_string_literal_to_rodata(Parser* p, const u8* bytes,
u32 elem_size = c_abi_sizeof(p->abi, p->pool, elem_ty);
u32 count = elem_size ? (u32)(nbytes / elem_size) : 0;
const Type* arr_ty = type_array(p->pool, elem_ty, count, 0);
- return kit_cg_const_data(p->cg, bytes, nbytes, c_abi_alignof(p->abi, p->pool, elem_ty),
+ return kit_cg_const_data(p->cg, bytes, nbytes,
+ c_abi_alignof(p->abi, p->pool, elem_ty),
pcg_tid(p, arr_ty));
}
@@ -890,7 +891,8 @@ static CConstInt cexpr_unary(Parser* p, SrcLoc loc) {
const Type* t = parse_type_name(p);
expect_punct(p, ')', "')' after sizeof type-name");
require_sizeof_type(p, t);
- return cint_make_u64(p, ty_size_t(p), c_abi_sizeof(p->abi, p->pool, t));
+ return cint_make_u64(p, ty_size_t(p),
+ c_abi_sizeof(p->abi, p->pool, t));
}
}
}
@@ -912,7 +914,8 @@ static CConstInt cexpr_unary(Parser* p, SrcLoc loc) {
{
const Type* t = parse_type_name(p);
expect_punct(p, ')', "')' after _Alignof type-name");
- return cint_make_u64(p, ty_size_t(p), c_abi_alignof(p->abi, p->pool, t));
+ return cint_make_u64(p, ty_size_t(p),
+ c_abi_alignof(p->abi, p->pool, t));
}
}
}
@@ -962,7 +965,7 @@ static CConstInt cexpr_unary(Parser* p, SrcLoc loc) {
return cint_make_u64(p, ty, (u64)v);
}
if (p->cur.kind == TOK_IDENT) {
- Sym name = p->cur.v.ident;
+ Sym name = tok_ident(&p->cur);
if (name == p->sym_b_offsetof) {
u32 off = 0;
const Type* root;
@@ -1262,7 +1265,7 @@ static int parse_builtin_clear_cache_call(Parser* p, Sym name, SrcLoc loc) {
static MemOrder parse_atomic_mem_order(Parser* p) {
if (p->cur.kind == TOK_NUM) {
- return (MemOrder)eval_const_int(p, p->cur.loc);
+ return (MemOrder)eval_const_int(p, pp_materialize_loc(p->pp, p->cur.loc));
}
parse_assign_expr(p);
to_rvalue(p);
@@ -1310,12 +1313,13 @@ static int offsetof_find_member(Parser* p, const Type* rec_ty, Sym mname,
static const Type* offsetof_designator(Parser* p, const Type* base, u32* off) {
const Type* cur = base;
- if (p->cur.kind != TOK_IDENT || ident_kw_inline(p, p->cur.v.ident) != KW_NONE) {
+ if (p->cur.kind != TOK_IDENT ||
+ ident_kw_inline(p, tok_ident(&p->cur)) != KW_NONE) {
perr(p, "expected member name in __builtin_offsetof");
}
for (;;) {
if (cur->kind == TY_STRUCT || cur->kind == TY_UNION) {
- Sym mname = p->cur.v.ident;
+ Sym mname = tok_ident(&p->cur);
const Type* mty = NULL;
u32 moff = 0;
if (!offsetof_find_member(p, cur, mname, &mty, &moff))
@@ -1330,14 +1334,15 @@ static const Type* offsetof_designator(Parser* p, const Type* base, u32* off) {
}
if (is_punct(&p->cur, '.')) {
advance(p);
- if (p->cur.kind != TOK_IDENT || ident_kw_inline(p, p->cur.v.ident) != KW_NONE) {
+ if (p->cur.kind != TOK_IDENT ||
+ ident_kw_inline(p, tok_ident(&p->cur)) != KW_NONE) {
perr(p, "expected member name after '.'");
}
continue;
}
if (is_punct(&p->cur, '[')) {
advance(p);
- i64 idx = eval_const_int(p, p->cur.loc);
+ i64 idx = eval_const_int(p, pp_materialize_loc(p->pp, p->cur.loc));
expect_punct(p, ']', "']' in __builtin_offsetof");
if (cur->kind != TY_ARRAY) {
perr(p, "__builtin_offsetof '[' on non-array");
@@ -1772,8 +1777,8 @@ static int parse_kit_syscall_call(Parser* p, Sym name, SrcLoc loc) {
}
static int try_parse_builtin_call(Parser* p) {
- Sym name = p->cur.v.ident;
- SrcLoc loc = p->cur.loc;
+ Sym name = tok_ident(&p->cur);
+ SrcLoc loc = pp_materialize_loc(p->pp, p->cur.loc);
if (parse_kit_syscall_call(p, name, loc)) return 1;
@@ -1798,17 +1803,16 @@ static int try_parse_builtin_call(Parser* p) {
name != p->sym_b_trap && name != p->sym_b_unreachable &&
name != p->sym_b_return_address && name != p->sym_b_frame_address &&
name != p->sym_b_readcyclecounter && name != p->sym_b_expect &&
- name != p->sym_b_offsetof &&
- name != p->sym_b_va_start && name != p->sym_b_va_arg &&
- name != p->sym_b_va_end && name != p->sym_b_va_copy &&
- name != p->sym_a_load_n && name != p->sym_a_store_n &&
- name != p->sym_a_exchange_n && name != p->sym_a_fetch_add &&
- name != p->sym_a_fetch_sub && name != p->sym_a_fetch_and &&
- name != p->sym_a_fetch_or && name != p->sym_a_fetch_xor &&
- name != p->sym_a_fetch_nand && name != p->sym_a_cas_n &&
- name != p->sym_a_always_lock_free && name != p->sym_a_is_lock_free &&
- name != p->sym_a_thread_fence && name != p->sym_a_signal_fence &&
- name != p->sym_sync_synchronize) {
+ name != p->sym_b_offsetof && name != p->sym_b_va_start &&
+ name != p->sym_b_va_arg && name != p->sym_b_va_end &&
+ name != p->sym_b_va_copy && name != p->sym_a_load_n &&
+ name != p->sym_a_store_n && name != p->sym_a_exchange_n &&
+ name != p->sym_a_fetch_add && name != p->sym_a_fetch_sub &&
+ name != p->sym_a_fetch_and && name != p->sym_a_fetch_or &&
+ name != p->sym_a_fetch_xor && name != p->sym_a_fetch_nand &&
+ name != p->sym_a_cas_n && name != p->sym_a_always_lock_free &&
+ name != p->sym_a_is_lock_free && name != p->sym_a_thread_fence &&
+ name != p->sym_a_signal_fence && name != p->sym_sync_synchronize) {
return 0;
}
advance(p); /* IDENT */
@@ -1880,7 +1884,7 @@ static int try_parse_builtin_call(Parser* p) {
if (name == p->sym_b_return_address || name == p->sym_b_frame_address) {
/* GCC requires the level to be an integer constant expression. */
int is_return = (name == p->sym_b_return_address);
- i64 level = eval_const_int(p, p->cur.loc);
+ i64 level = eval_const_int(p, pp_materialize_loc(p->pp, p->cur.loc));
expect_punct(p, ')',
"')' after __builtin_return_address/__builtin_frame_address");
if (level < 0)
@@ -1984,7 +1988,8 @@ static int try_parse_builtin_call(Parser* p) {
}
/* __sync_synchronize(): the legacy GCC full barrier. No operands; always a
- * sequentially-consistent fence (the __sync_* family is implicitly seq-cst). */
+ * sequentially-consistent fence (the __sync_* family is implicitly seq-cst).
+ */
if (name == p->sym_sync_synchronize) {
expect_punct(p, ')', "')' after __sync_synchronize");
pcg_set_loc(p, loc);
@@ -1994,7 +1999,7 @@ static int try_parse_builtin_call(Parser* p) {
}
if (name == p->sym_a_always_lock_free || name == p->sym_a_is_lock_free) {
- i64 size = eval_const_int(p, p->cur.loc);
+ i64 size = eval_const_int(p, pp_materialize_loc(p->pp, p->cur.loc));
expect_punct(p, ',', "',' in atomic lock-free builtin");
parse_assign_expr(p);
to_rvalue(p);
@@ -2044,7 +2049,7 @@ static int try_parse_builtin_call(Parser* p) {
coerce_top_to_type(p, val_ty);
expect_punct(p, ',', "',' in __atomic_compare_exchange_n");
- (void)eval_const_int(p, p->cur.loc); /* weak */
+ (void)eval_const_int(p, pp_materialize_loc(p->pp, p->cur.loc)); /* weak */
expect_punct(p, ',', "',' in __atomic_compare_exchange_n");
MemOrder succ = parse_atomic_mem_order(p);
expect_punct(p, ',', "',' in __atomic_compare_exchange_n");
@@ -2157,7 +2162,7 @@ static void parse_primary(Parser* p) {
}
if (t.kind == TOK_IDENT) {
SymEntry* e;
- if (ident_kw_inline(p, t.v.ident) != KW_NONE) {
+ if (ident_kw_inline(p, tok_ident(&t)) != KW_NONE) {
perr(p, "unexpected keyword in expression");
}
{
@@ -2174,15 +2179,16 @@ static void parse_primary(Parser* p) {
* the same value. We synthesize the string lazily — the symbol
* lives in .rodata and the resulting type is `char[N+1]` (with the
* trailing NUL). */
- if (t.v.ident == p->sym_func || t.v.ident == p->sym_func_gcc ||
- t.v.ident == p->sym_pretty_func_gcc) {
+ if (tok_ident(&t) == p->sym_func || tok_ident(&t) == p->sym_func_gcc ||
+ tok_ident(&t) == p->sym_pretty_func_gcc) {
if (p->cur_func_name == 0) {
compiler_panic(
- p->c, t.loc, "'%.*s' used outside a function",
- KIT_SLICE_ARG(kit_slice_cstr(t.v.ident == p->sym_func ? "__func__"
- : t.v.ident == p->sym_func_gcc
- ? "__FUNCTION__"
- : "__PRETTY_FUNCTION__")));
+ p->c, pp_materialize_loc(p->pp, t.loc),
+ "'%.*s' used outside a function",
+ KIT_SLICE_ARG(kit_slice_cstr(
+ tok_ident(&t) == p->sym_func ? "__func__"
+ : tok_ident(&t) == p->sym_func_gcc ? "__FUNCTION__"
+ : "__PRETTY_FUNCTION__")));
}
KitSlice fn_name_sl = kit_sym_str(p->pool->c, p->cur_func_name);
size_t nlen = fn_name_sl.len;
@@ -2199,13 +2205,13 @@ static void parse_primary(Parser* p) {
pcg_push_global(p, sym, arr_ty);
return;
}
- e = scope_lookup(p, t.v.ident);
+ e = scope_lookup(p, tok_ident(&t));
if (!e) {
- KitSlice ident_sl = kit_sym_str(p->pool->c, t.v.ident);
+ KitSlice ident_sl = kit_sym_str(p->pool->c, tok_ident(&t));
size_t nlen = ident_sl.len;
const char* nm = ident_sl.s;
- compiler_panic(p->c, t.loc, "undeclared identifier '%.*s'", (int)nlen,
- nm ? nm : "?");
+ compiler_panic(p->c, pp_materialize_loc(p->pp, t.loc),
+ "undeclared identifier '%.*s'", (int)nlen, nm ? nm : "?");
}
advance(p);
switch (e->kind) {
@@ -2440,10 +2446,11 @@ static void parse_postfix(Parser* p) {
perr(p,
"request for member in something that is not a struct or union");
}
- if (p->cur.kind != TOK_IDENT || ident_kw_inline(p, p->cur.v.ident) != KW_NONE) {
+ if (p->cur.kind != TOK_IDENT ||
+ ident_kw_inline(p, tok_ident(&p->cur)) != KW_NONE) {
perr(p, "expected member name after '.'");
}
- mname = p->cur.v.ident;
+ mname = tok_ident(&p->cur);
advance(p);
lt = type_unqual(p->pool, lt);
if (!find_record_member_path(p, lt, mname, &mty, &off, &bf_off, &bf_w,
@@ -2470,10 +2477,11 @@ static void parse_postfix(Parser* p) {
if (!rec_ty || (rec_ty->kind != TY_STRUCT && rec_ty->kind != TY_UNION)) {
perr(p, "'->' on pointer to non-struct/union");
}
- if (p->cur.kind != TOK_IDENT || ident_kw_inline(p, p->cur.v.ident) != KW_NONE) {
+ if (p->cur.kind != TOK_IDENT ||
+ ident_kw_inline(p, tok_ident(&p->cur)) != KW_NONE) {
perr(p, "expected member name after '->'");
}
- mname = p->cur.v.ident;
+ mname = tok_ident(&p->cur);
advance(p);
if (!find_record_member_path(p, rec_ty, mname, &mty, &off, &bf_off, &bf_w,
&bf_ss))
@@ -2572,11 +2580,12 @@ void parse_unary(Parser* p) {
Sym name;
SrcLoc loc;
advance(p); /* '&&' */
- if (p->cur.kind != TOK_IDENT || ident_kw_inline(p, p->cur.v.ident) != KW_NONE) {
+ if (p->cur.kind != TOK_IDENT ||
+ ident_kw_inline(p, tok_ident(&p->cur)) != KW_NONE) {
perr(p, "expected label name after '&&'");
}
- name = p->cur.v.ident;
- loc = p->cur.loc;
+ name = tok_ident(&p->cur);
+ loc = pp_materialize_loc(p->pp, p->cur.loc);
advance(p);
pcg_push_label_addr(p, take_label_addr(p, name, loc));
return;
@@ -2760,7 +2769,7 @@ void parse_unary(Parser* p) {
}
memset(&buf[len], 0, sizeof(Tok));
buf[len].kind = TOK_PUNCT;
- buf[len].v.punct = ',';
+ buf[len].aux = ',';
++len;
default_buf = buf;
default_len = len;
@@ -3076,8 +3085,7 @@ static void parse_shift(Parser* p) {
const Type* lp = cint_promote_type(p, lt);
if (!type_is_int(lt)) perr(p, "shift operator requires integer operands");
if (pcg_top_type(p) != lp) pcg_convert(p, lp);
- if (bop == BO_SHR_S && !type_is_signed_integer(lp))
- bop = BO_SHR_U;
+ if (bop == BO_SHR_S && !type_is_signed_integer(lp)) bop = BO_SHR_U;
}
parse_add(p);
to_rvalue(p);
@@ -3490,8 +3498,7 @@ void parse_assign_expr(Parser* p) {
}
advance(p);
const Type* lhs = pcg_top_type(p);
- if (compound == BO_SHR_S && !type_is_signed_integer(lhs))
- compound = BO_SHR_U;
+ if (compound == BO_SHR_S && !type_is_signed_integer(lhs)) compound = BO_SHR_U;
{
if (lhs && (lhs->qual & Q_CONST)) {
perr(p, "assignment to const-qualified object");
diff --git a/lang/c/parse/parse_init.c b/lang/c/parse/parse_init.c
@@ -16,7 +16,9 @@
* File-local helpers
* ============================================================ */
-static SrcLoc tok_loc_init(const Tok* t) { return t->loc; }
+static SrcLoc tok_loc_init(Parser* p, const Tok* t) {
+ return pp_materialize_loc(p->pp, t->loc);
+}
static const Type* init_field_type_at(const Type* ty, u16 i) {
const Field* f = &ty->rec.fields[i];
@@ -384,7 +386,7 @@ static void parse_designator_chain(Parser* p, const Type* outer_ty,
if (is_punct(&p->cur, '[')) {
i64 idx;
u32 esz;
- SrcLoc cloc = tok_loc_init(&p->cur);
+ SrcLoc cloc = tok_loc_init(p, &p->cur);
const Type* parent_ty = cur_ty;
u32 parent_off = cur_off;
advance(p);
@@ -416,10 +418,10 @@ static void parse_designator_chain(Parser* p, const Type* outer_ty,
u32 parent_off = cur_off;
advance(p);
if (p->cur.kind != TOK_IDENT ||
- ident_kw_inline(p, p->cur.v.ident) != KW_NONE) {
+ ident_kw_inline(p, tok_ident(&p->cur)) != KW_NONE) {
perr(p, "expected field name after '.'");
}
- fname = p->cur.v.ident;
+ fname = tok_ident(&p->cur);
advance(p);
if (!cur_ty || (cur_ty->kind != TY_STRUCT && cur_ty->kind != TY_UNION)) {
perr(p, "field designator on non-record type");
@@ -1183,18 +1185,20 @@ static int try_parse_static_address_const(Parser* p, CStaticConst* out) {
advance(p);
lit_ty = parse_type_name(p);
expect_punct(p, ')', "')' after compound literal type-name");
- *out = parse_static_compound_literal_after_type(p, lit_ty, t.loc);
+ *out = parse_static_compound_literal_after_type(
+ p, lit_ty, pp_materialize_loc(p->pp, t.loc));
return 1;
}
}
if (p->cur.kind != TOK_IDENT ||
- ident_kw_inline(p, p->cur.v.ident) != KW_NONE) {
+ ident_kw_inline(p, tok_ident(&p->cur)) != KW_NONE) {
perr(p, "expected identifier after '&' in static initializer");
}
- name = p->cur.v.ident;
+ name = tok_ident(&p->cur);
advance(p);
- } else if (t.kind == TOK_IDENT && ident_kw_inline(p, t.v.ident) == KW_NONE) {
- name = t.v.ident;
+ } else if (t.kind == TOK_IDENT &&
+ ident_kw_inline(p, tok_ident(&t)) == KW_NONE) {
+ name = tok_ident(&t);
advance(p);
} else {
return 0;
@@ -1208,7 +1212,7 @@ static int try_parse_static_address_const(Parser* p, CStaticConst* out) {
if (saw_amp && is_punct(&p->cur, '[')) {
SrcLoc cloc;
advance(p);
- cloc = tok_loc_init(&p->cur);
+ cloc = tok_loc_init(p, &p->cur);
element_addend = eval_const_int(p, cloc);
expect_punct(p, ']', "']' after array-subscript constant");
if (tgt_ty && tgt_ty->kind == TY_ARRAY) {
@@ -1223,13 +1227,14 @@ static int try_parse_static_address_const(Parser* p, CStaticConst* out) {
SrcLoc cloc;
i64 v;
advance(p);
- cloc = tok_loc_init(&p->cur);
+ cloc = tok_loc_init(p, &p->cur);
v = eval_const_int(p, cloc);
if (neg) v = -v;
if (tgt_ty && tgt_ty->kind == TY_ARRAY) {
byte_addend += v * (i64)c_abi_sizeof(p->abi, p->pool, tgt_ty->arr.elem);
} else if (tgt_ty && tgt_ty->kind == TY_PTR) {
- byte_addend += v * (i64)c_abi_sizeof(p->abi, p->pool, tgt_ty->ptr.pointee);
+ byte_addend +=
+ v * (i64)c_abi_sizeof(p->abi, p->pool, tgt_ty->ptr.pointee);
} else if (saw_amp) {
byte_addend += v * (i64)c_abi_sizeof(p->abi, p->pool, tgt_ty);
} else {
@@ -1254,11 +1259,11 @@ static CStaticConst parse_static_const(Parser* p, const Type* ty, SrcLoc loc) {
SrcLoc lloc;
advance(p); /* '&&' */
if (p->cur.kind != TOK_IDENT ||
- ident_kw_inline(p, p->cur.v.ident) != KW_NONE) {
+ ident_kw_inline(p, tok_ident(&p->cur)) != KW_NONE) {
perr(p, "expected label name after '&&' in static initializer");
}
- lname = p->cur.v.ident;
- lloc = tok_loc_init(&p->cur);
+ lname = tok_ident(&p->cur);
+ lloc = tok_loc_init(p, &p->cur);
advance(p);
r.kind = C_STATIC_CONST_LABEL_ADDR;
r.label = take_label_addr(p, lname, lloc);
@@ -1303,7 +1308,7 @@ static CStaticConst parse_static_const(Parser* p, const Type* ty, SrcLoc loc) {
if (cast_unqual->kind == TY_PTR &&
(p->cur.kind == TOK_STR || is_punct(&p->cur, '&') ||
(p->cur.kind == TOK_IDENT &&
- ident_kw_inline(p, p->cur.v.ident) == KW_NONE)) &&
+ ident_kw_inline(p, tok_ident(&p->cur)) == KW_NONE)) &&
try_parse_static_address_const(p, &r)) {
return r;
}
@@ -1336,7 +1341,7 @@ static CStaticConst parse_static_const(Parser* p, const Type* ty, SrcLoc loc) {
static void parse_static_bitfield_at(Parser* p, u8* buf, u32 buflen,
u32 rec_offset, const ABIFieldLayout* fl,
const Type* field_ty) {
- SrcLoc cloc = tok_loc_init(&p->cur);
+ SrcLoc cloc = tok_loc_init(p, &p->cur);
CStaticConst parsed = parse_static_const(p, field_ty, cloc);
u32 storage_off = rec_offset + fl->offset;
u32 storage_size = fl->storage_size;
@@ -1564,7 +1569,7 @@ void parse_static_init_at(Parser* p, u8* buf, u32 buflen, u32 offset,
/* Scalar / pointer. */
{
int had_brace = accept_punct(p, '{');
- SrcLoc cloc = tok_loc_init(&p->cur);
+ SrcLoc cloc = tok_loc_init(p, &p->cur);
u32 sz = c_abi_sizeof(p->abi, p->pool, ty);
CStaticConst cv;
if (offset + sz > buflen) perr(p, "initializer overflows object");
diff --git a/lang/c/parse/parse_priv.h b/lang/c/parse/parse_priv.h
@@ -310,7 +310,7 @@ typedef struct Parser {
const Type* cur_func_ret;
Sym sym_b_expect;
Sym sym_b_offsetof;
- Sym sym_b_constant_p; /* __builtin_constant_p */
+ Sym sym_b_constant_p; /* __builtin_constant_p */
Sym sym_b_va_list;
/* Cached singleton for __builtin_va_list — built lazily on first
* mention so every occurrence resolves to the same Type* (and the
@@ -323,10 +323,10 @@ typedef struct Parser {
Sym sym_b_va_arg;
Sym sym_b_va_end;
Sym sym_b_va_copy;
- Sym sym_b_return_address; /* __builtin_return_address */
- Sym sym_b_frame_address; /* __builtin_frame_address */
- Sym sym_b_readcyclecounter; /* __builtin_readcyclecounter */
- Sym sym_kit_syscall[7]; /* __kit_syscall0 .. __kit_syscall6 */
+ Sym sym_b_return_address; /* __builtin_return_address */
+ Sym sym_b_frame_address; /* __builtin_frame_address */
+ Sym sym_b_readcyclecounter; /* __builtin_readcyclecounter */
+ Sym sym_kit_syscall[7]; /* __kit_syscall0 .. __kit_syscall6 */
Sym sym_attribute;
Sym sym_volatile_alias;
Sym sym_alignof_alias;
@@ -353,7 +353,7 @@ typedef struct Parser {
Sym sym_a_is_lock_free;
Sym sym_a_thread_fence;
Sym sym_a_signal_fence;
- Sym sym_sync_synchronize; /* __sync_synchronize (legacy full barrier) */
+ Sym sym_sync_synchronize; /* __sync_synchronize (legacy full barrier) */
Scope* scope;
/* Sym -> innermost-visible binding cache; the O(1) read side of scope_lookup.
@@ -469,18 +469,18 @@ TagEntry* tag_lookup_local(Parser* p, Sym name);
* ============================================================ */
static inline int is_punct(const Tok* t, u32 punct) {
- return t->kind == TOK_PUNCT && t->v.punct == punct;
+ return t->kind == TOK_PUNCT && tok_punct(t) == punct;
}
static inline int is_pp_hash(const Tok* t) { return t->kind == TOK_PP_HASH; }
/* THE keyword classifier — the single canonical way keywordness is decided.
* Interned Sym -> CKw via kw_map; KW_NONE if the Sym is not a keyword. The map
- * holds the canonical keyword spellings AND the GNU alias spellings (`__inline__`
- * etc.), each mapping to its CKw, registered once in the kw_map population in
- * parse_c — so aliases are classified identically to their canonical keyword,
- * with no separate "alias-aware" path. Everything below (and the per-token
- * classify_kw / is_kw adapters) routes through this. */
+ * holds the canonical keyword spellings AND the GNU alias spellings
+ * (`__inline__` etc.), each mapping to its CKw, registered once in the kw_map
+ * population in parse_c — so aliases are classified identically to their
+ * canonical keyword, with no separate "alias-aware" path. Everything below (and
+ * the per-token classify_kw / is_kw adapters) routes through this. */
static inline CKw ident_kw_inline(const Parser* p, Sym name) {
return name ? (CKw)KwTab_get(&p->kw_map, name) : KW_NONE;
}
@@ -488,7 +488,7 @@ static inline CKw ident_kw_inline(const Parser* p, Sym name) {
/* A token's keyword identity (KW_NONE for a non-identifier or non-keyword). The
* one place to classify a Tok: classify once, then compare CKw. */
static inline CKw classify_kw(const Parser* p, const Tok* t) {
- return t->kind == TOK_IDENT ? ident_kw_inline(p, t->v.ident) : KW_NONE;
+ return t->kind == TOK_IDENT ? ident_kw_inline(p, tok_ident(t)) : KW_NONE;
}
/* Is token t the keyword k? Thin boolean shape-adapter over classify_kw. */
diff --git a/lang/c/parse/parse_stmt.c b/lang/c/parse/parse_stmt.c
@@ -11,7 +11,9 @@
* File-local helpers
* ============================================================ */
-static SrcLoc tok_loc_stmt(const Tok* t) { return t->loc; }
+static SrcLoc tok_loc_stmt(Parser* p, const Tok* t) {
+ return pp_materialize_loc(p->pp, t->loc);
+}
static int accept_kw_stmt(Parser* p, CKw k) {
if (!is_kw(p, &p->cur, k)) return 0;
@@ -255,8 +257,9 @@ GotoLabel* label_get_or_create(Parser* p, Sym name, SrcLoc loc) {
* `LABEL:` placement and its `goto LABEL` references can straddle a
* constant-false (suppressed) region. Key allocation off whether the function
* emits at all, not the transient suppress depth — otherwise a label first
- * mentioned inside a suppressed `goto` would cache pcg_label_new's suppression
- * sentinel and later alias the function's first real label ("placed twice"). */
+ * mentioned inside a suppressed `goto` would cache pcg_label_new's
+ * suppression sentinel and later alias the function's first real label
+ * ("placed twice"). */
gl->label = p->cur_func_emits ? kit_cg_label_new(p->cg) : pcg_label_new(p);
gl->placed = 0;
gl->first_use = loc;
@@ -321,11 +324,12 @@ static void parse_goto_stmt(Parser* p) {
parse_computed_goto(p);
return;
}
- if (p->cur.kind != TOK_IDENT || ident_kw_inline(p, p->cur.v.ident) != KW_NONE) {
+ if (p->cur.kind != TOK_IDENT ||
+ ident_kw_inline(p, tok_ident(&p->cur)) != KW_NONE) {
perr(p, "expected label name after 'goto'");
}
- name = p->cur.v.ident;
- loc = tok_loc_stmt(&p->cur);
+ name = tok_ident(&p->cur);
+ loc = tok_loc_stmt(p, &p->cur);
advance(p);
expect_punct(p, ';', "';' after goto");
gl = label_get_or_create(p, name, loc);
@@ -340,8 +344,8 @@ static void parse_goto_stmt(Parser* p) {
}
static void parse_label_stmt(Parser* p) {
- Sym name = p->cur.v.ident;
- SrcLoc loc = tok_loc_stmt(&p->cur);
+ Sym name = tok_ident(&p->cur);
+ SrcLoc loc = tok_loc_stmt(p, &p->cur);
GotoLabel* gl;
advance(p); /* IDENT */
advance(p); /* ':' */
@@ -360,7 +364,7 @@ static void parse_case_stmt(Parser* p) {
i64 v;
CGLabel L;
CaseEntry* ce;
- SrcLoc loc = tok_loc_stmt(&p->cur);
+ SrcLoc loc = tok_loc_stmt(p, &p->cur);
if (!p->cur_switch) perr(p, "'case' label not in switch statement");
v = eval_const_int(p, loc);
for (ce = p->cur_switch->cases; ce; ce = ce->next) {
@@ -529,13 +533,13 @@ static void parse_switch_stmt(Parser* p) {
}
void parse_static_assert(Parser* p) {
- SrcLoc loc = tok_loc_stmt(&p->cur);
+ SrcLoc loc = tok_loc_stmt(p, &p->cur);
i64 v;
if (!accept_kw_stmt(p, KW_STATIC_ASSERT)) {
perr(p, "expected _Static_assert");
}
expect_punct(p, '(', "'(' after _Static_assert");
- v = eval_const_int(p, tok_loc_stmt(&p->cur));
+ v = eval_const_int(p, tok_loc_stmt(p, &p->cur));
expect_punct(p, ',', "',' separating _Static_assert args");
if (p->cur.kind != TOK_STR) {
perr(p, "expected string literal as _Static_assert message");
@@ -546,7 +550,7 @@ void parse_static_assert(Parser* p) {
expect_punct(p, ')', "')' after _Static_assert");
expect_punct(p, ';', "';' after _Static_assert");
if (!v) {
- KitSlice msg_sl = kit_sym_str(p->pool->c, msg.spelling);
+ KitSlice msg_sl = pp_text_slice(p->pp, &msg);
size_t mlen = msg_sl.len;
const char* mstr = msg_sl.s;
compiler_panic(p->c, loc, "static assertion failed: %.*s", (int)mlen,
@@ -588,7 +592,7 @@ static Sym parse_asm_operand_name(Parser* p) {
if (p->cur.kind != TOK_IDENT) {
perr(p, "expected identifier inside '[name]' on asm operand");
}
- name = p->cur.v.ident;
+ name = tok_ident(&p->cur);
advance(p);
expect_punct(p, ']', "']' after asm operand name");
return name;
@@ -629,7 +633,7 @@ static Sym asm_operand_pinned_reg(Parser* p, FrameSlot* slot_out) {
if (p->cur.kind != TOK_IDENT) return 0;
nxt = peek1(p);
if (!is_punct(&nxt, ')')) return 0;
- e = scope_lookup(p, p->cur.v.ident);
+ e = scope_lookup(p, tok_ident(&p->cur));
if (!e || e->kind != SEK_LOCAL) return 0;
if (e->reg_asm_name && slot_out) *slot_out = e->v.slot;
return e->reg_asm_name;
@@ -644,10 +648,11 @@ static void parse_asm_stmt(Parser* p) {
u32 nout = 0, nin = 0, nclob = 0;
u32 cap_out = 0, cap_in = 0, cap_clob = 0;
int saw_goto = 0;
- SrcLoc loc = tok_loc_stmt(&p->cur);
+ SrcLoc loc = tok_loc_stmt(p, &p->cur);
for (;;) {
- if (accept_kw_stmt(p, KW_VOLATILE)) continue; /* `volatile` or `__volatile__` */
+ if (accept_kw_stmt(p, KW_VOLATILE))
+ continue; /* `volatile` or `__volatile__` */
break;
}
if (accept_kw_stmt(p, KW_GOTO)) saw_goto = 1;
@@ -878,8 +883,9 @@ void parse_compound_stmt(Parser* p) {
}
void parse_stmt(Parser* p) {
- pcg_set_loc(p, tok_loc_stmt(&p->cur));
- if (p->cur.kind == TOK_IDENT && ident_kw_inline(p, p->cur.v.ident) == KW_NONE) {
+ pcg_set_loc(p, tok_loc_stmt(p, &p->cur));
+ if (p->cur.kind == TOK_IDENT &&
+ ident_kw_inline(p, tok_ident(&p->cur)) == KW_NONE) {
Tok n = peek1(p);
if (is_punct(&n, ':')) {
parse_label_stmt(p);
diff --git a/lang/c/parse/parse_type.c b/lang/c/parse/parse_type.c
@@ -68,7 +68,9 @@ static const struct {
{"import_name", ATTR_IMPORT_NAME, AS_STRING},
};
-static SrcLoc tok_loc(const Tok* t) { return t->loc; }
+static SrcLoc tok_loc(Parser* p, const Tok* t) {
+ return pp_materialize_loc(p->pp, t->loc);
+}
static void attr_canon_range(const char* s, size_t len, const char** out_p,
size_t* out_len);
@@ -98,8 +100,7 @@ static const Type* attrs_apply_type_mode(Parser* p, const Type* base,
if (a->kind != ATTR_MODE || a->nargs == 0) continue;
if (attr_sym_canon_eq(p, a->v.sym, "TI")) {
const Type* u = type_unqual(p->pool, base);
- int is_unsigned =
- u && type_is_int(u) && type_is_signed_integer(u) == 0;
+ int is_unsigned = u && type_is_int(u) && type_is_signed_integer(u) == 0;
if (!target_has_int128(p)) {
perr(p, "__int128 is not supported on the target architecture");
}
@@ -110,7 +111,7 @@ static const Type* attrs_apply_type_mode(Parser* p, const Type* base,
}
int starts_attr(const Parser* p) {
- return p->cur.kind == TOK_IDENT && p->cur.v.ident == p->sym_attribute;
+ return p->cur.kind == TOK_IDENT && tok_ident(&p->cur) == p->sym_attribute;
}
static int starts_asm_label(const Parser* p) {
@@ -270,7 +271,7 @@ static void parse_attr_args(Parser* p, Attr* a, AttrArgShape shape,
advance(p);
return;
}
- loc = tok_loc(&p->cur);
+ loc = tok_loc(p, &p->cur);
a->v.i = eval_const_int(p, loc);
if (a->kind == ATTR_ALIGNED && a->v.i > 0 &&
(((u64)a->v.i & ((u64)a->v.i - 1u)) != 0)) {
@@ -307,7 +308,7 @@ static void parse_attr_args(Parser* p, Attr* a, AttrArgShape shape,
perr(p, "attribute '%.*s' expects an identifier",
KIT_SLICE_ARG(kit_slice_cstr(attr_diag_name)));
}
- a->v.sym = p->cur.v.ident;
+ a->v.sym = tok_ident(&p->cur);
a->nargs = 1;
advance(p);
expect_punct(p, ')', "')' after attribute identifier argument");
@@ -322,10 +323,10 @@ static void parse_attr_args(Parser* p, Attr* a, AttrArgShape shape,
}
advance(p);
expect_punct(p, ',', "',' after format archetype");
- mloc = tok_loc(&p->cur);
+ mloc = tok_loc(p, &p->cur);
mv = eval_const_int(p, mloc);
expect_punct(p, ',', "',' after format string-index");
- nloc = tok_loc(&p->cur);
+ nloc = tok_loc(p, &p->cur);
nv = eval_const_int(p, nloc);
if (mv < 0 || mv > 0xFFFF || nv < 0 || nv > 0xFFFF) {
perr(p, "attribute 'format' indices out of range");
@@ -348,7 +349,7 @@ Attr* parse_attribute_spec_list(Parser* p) {
Attr* head = NULL;
Attr* tail = NULL;
while (starts_attr(p)) {
- SrcLoc kw_loc = tok_loc(&p->cur);
+ SrcLoc kw_loc = tok_loc(p, &p->cur);
advance(p); /* __attribute__ */
expect_punct(p, '(', "'(' after __attribute__");
expect_punct(p, '(', "'((' after __attribute__");
@@ -366,11 +367,11 @@ Attr* parse_attribute_spec_list(Parser* p) {
if (p->cur.kind != TOK_IDENT) {
perr(p, "expected attribute name");
}
- aname = p->cur.v.ident;
+ aname = tok_ident(&p->cur);
a = arena_new(p->pool->arena, Attr);
if (!a) perr(p, "out of memory in parse_attribute_spec_list");
memset(a, 0, sizeof *a);
- a->loc = tok_loc(&p->cur);
+ a->loc = tok_loc(p, &p->cur);
a->name = aname;
a->kind = (u16)classify_attr(p, aname, &shape);
advance(p);
@@ -607,7 +608,7 @@ int parse_decl_specs(Parser* p, DeclSpecs* out) {
out->vla_byte_slot = FRAME_SLOT_NONE;
out->vla_bounds = NULL;
out->attrs = NULL;
- loc = tok_loc(&p->cur);
+ loc = tok_loc(p, &p->cur);
for (;;) {
Tok t = p->cur;
/* Classify the token's keyword identity exactly once per iteration; the
@@ -704,18 +705,18 @@ int parse_decl_specs(Parser* p, DeclSpecs* out) {
acc.saw_explicit_type = 1;
advance(p);
seen = 1;
- } else if (t.kind == TOK_IDENT && t.v.ident == p->sym_int128) {
+ } else if (t.kind == TOK_IDENT && tok_ident(&t) == p->sym_int128) {
acc.saw_int128 = 1;
acc.saw_explicit_type = 1;
advance(p);
seen = 1;
- } else if (t.kind == TOK_IDENT && t.v.ident == p->sym_int128_t) {
+ } else if (t.kind == TOK_IDENT && tok_ident(&t) == p->sym_int128_t) {
acc.saw_int128 = 1;
acc.saw_signed = 1;
acc.saw_explicit_type = 1;
advance(p);
seen = 1;
- } else if (t.kind == TOK_IDENT && t.v.ident == p->sym_uint128_t) {
+ } else if (t.kind == TOK_IDENT && tok_ident(&t) == p->sym_uint128_t) {
acc.saw_int128 = 1;
acc.saw_unsigned = 1;
acc.saw_explicit_type = 1;
@@ -782,7 +783,7 @@ int parse_decl_specs(Parser* p, DeclSpecs* out) {
const Type* tn = parse_type_name(p);
a = c_abi_alignof(p->abi, p->pool, tn);
} else {
- i64 v = eval_const_int(p, tok_loc(&p->cur));
+ i64 v = eval_const_int(p, tok_loc(p, &p->cur));
if (v < 0) perr(p, "_Alignas requires a non-negative alignment");
a = (u32)v;
}
@@ -821,8 +822,9 @@ int parse_decl_specs(Parser* p, DeclSpecs* out) {
} else if (!acc.saw_explicit_type && !tagged_ty && t.kind == TOK_IDENT &&
tkw == KW_NONE) {
/* tkw == KW_NONE here is exactly ident_kw_inline(t)==KW_NONE: this is the
- * terminal else-if, so every keyword/alias branch above already failed. */
- if (t.v.ident == p->sym_b_va_list) {
+ * terminal else-if, so every keyword/alias branch above already failed.
+ */
+ if (tok_ident(&t) == p->sym_b_va_list) {
if (!p->type_b_va_list)
p->type_b_va_list = c_abi_va_list_type(p->abi, p->pool);
tagged_ty = p->type_b_va_list;
@@ -831,7 +833,7 @@ int parse_decl_specs(Parser* p, DeclSpecs* out) {
seen = 1;
continue;
}
- SymEntry* e = scope_lookup(p, t.v.ident);
+ SymEntry* e = scope_lookup(p, tok_ident(&t));
if (e && e->kind == SEK_TYPEDEF) {
tagged_ty = e->type;
if (e->vla_byte_slot != FRAME_SLOT_NONE) {
@@ -975,7 +977,7 @@ static void parse_member_decls(Parser* p, TypeRecordBuilder* b) {
}
for (;;) {
Sym mname = 0;
- SrcLoc mloc = tok_loc(&p->cur);
+ SrcLoc mloc = tok_loc(p, &p->cur);
const Type* mty;
Field f;
memset(&f, 0, sizeof f);
@@ -1044,9 +1046,10 @@ const Type* parse_struct_or_union(Parser* p, TypeKind kind,
TagDeclKind tdk = (kind == TY_STRUCT) ? TAG_STRUCT : TAG_UNION;
Attr* rec_attrs = NULL;
parse_attrs_into(p, &rec_attrs);
- tag_loc = tok_loc(&p->cur);
- if (p->cur.kind == TOK_IDENT && ident_kw_inline(p, p->cur.v.ident) == KW_NONE) {
- tag_name = p->cur.v.ident;
+ tag_loc = tok_loc(p, &p->cur);
+ if (p->cur.kind == TOK_IDENT &&
+ ident_kw_inline(p, tok_ident(&p->cur)) == KW_NONE) {
+ tag_name = tok_ident(&p->cur);
advance(p);
}
int has_body = is_punct(&p->cur, '{');
@@ -1138,9 +1141,10 @@ const Type* parse_enum(Parser* p, Attr** anon_attrs_out) {
SrcLoc tag_loc;
Attr* rec_attrs = NULL;
parse_attrs_into(p, &rec_attrs);
- tag_loc = tok_loc(&p->cur);
- if (p->cur.kind == TOK_IDENT && ident_kw_inline(p, p->cur.v.ident) == KW_NONE) {
- tag_name = p->cur.v.ident;
+ tag_loc = tok_loc(p, &p->cur);
+ if (p->cur.kind == TOK_IDENT &&
+ ident_kw_inline(p, tok_ident(&p->cur)) == KW_NONE) {
+ tag_name = tok_ident(&p->cur);
advance(p);
}
/* C23 §6.7.2.2: an optional fixed underlying type — `enum [tag] : T` —
@@ -1188,12 +1192,13 @@ const Type* parse_enum(Parser* p, Attr** anon_attrs_out) {
i64 next_val = 0;
for (;;) {
Sym name;
- SrcLoc nloc = tok_loc(&p->cur);
+ SrcLoc nloc = tok_loc(p, &p->cur);
SymEntry* e;
- if (p->cur.kind != TOK_IDENT || ident_kw_inline(p, p->cur.v.ident) != KW_NONE) {
+ if (p->cur.kind != TOK_IDENT ||
+ ident_kw_inline(p, tok_ident(&p->cur)) != KW_NONE) {
perr(p, "expected enumerator name");
}
- name = p->cur.v.ident;
+ name = tok_ident(&p->cur);
advance(p);
i64 val = next_val;
if (accept_punct(p, '=')) {
@@ -1238,7 +1243,7 @@ const Type* parse_enum(Parser* p, Attr** anon_attrs_out) {
int starts_type_name(const Parser* p, const Tok* t) {
if (t->kind != TOK_IDENT) return 0;
- CKw k = ident_kw_inline(p, t->v.ident);
+ CKw k = ident_kw_inline(p, tok_ident(t));
switch (k) {
case KW_VOID:
case KW_CHAR:
@@ -1269,11 +1274,11 @@ int starts_type_name(const Parser* p, const Tok* t) {
case KW_THREAD_LOCAL:
return 1;
case KW_NONE: {
- if (t->v.ident == p->sym_b_va_list) return 1;
- if (t->v.ident == p->sym_int128 || t->v.ident == p->sym_int128_t ||
- t->v.ident == p->sym_uint128_t)
+ if (tok_ident(t) == p->sym_b_va_list) return 1;
+ if (tok_ident(t) == p->sym_int128 || tok_ident(t) == p->sym_int128_t ||
+ tok_ident(t) == p->sym_uint128_t)
return 1;
- SymEntry* e = scope_lookup((Parser*)p, t->v.ident);
+ SymEntry* e = scope_lookup((Parser*)p, tok_ident(t));
return e && e->kind == SEK_TYPEDEF;
}
default:
@@ -1376,7 +1381,7 @@ static void parse_param_array_bound(Parser* p, DeclSuffix* out) {
has_expr = 1;
advance(p);
}
- if (ntoks == 1 && toks[0].kind == TOK_PUNCT && toks[0].v.punct == '*') {
+ if (ntoks == 1 && toks[0].kind == TOK_PUNCT && tok_punct(&toks[0]) == '*') {
has_expr = 0;
}
if (ntoks == 1 && toks[0].kind == TOK_NUM) {
@@ -1431,15 +1436,15 @@ int parse_decl_suffix(Parser* p, DeclSuffix* out) {
perr(p, "array bound requires integer type");
}
if (!is_const_start && t.kind == TOK_IDENT) {
- SymEntry* e = scope_lookup(p, t.v.ident);
+ SymEntry* e = scope_lookup(p, tok_ident(&t));
if (e && e->kind == SEK_ENUM_CST) is_const_start = 1;
if (!is_const_start) {
- CKw k = ident_kw_inline(p, t.v.ident);
+ CKw k = ident_kw_inline(p, tok_ident(&t));
if (k == KW_SIZEOF || k == KW_ALIGNOF) is_const_start = 1;
}
}
if (is_const_start) {
- SrcLoc cloc = tok_loc(&p->cur);
+ SrcLoc cloc = tok_loc(p, &p->cur);
i64 v = eval_const_int(p, cloc);
if (v < 0) perr(p, "negative array size");
out->count = (u32)v;
@@ -1554,8 +1559,9 @@ const Type* parse_declarator_full_info(Parser* p, const Type* base,
int is_inner = 0;
if (is_punct(&n, '*')) {
is_inner = 1;
- } else if (n.kind == TOK_IDENT && ident_kw_inline(p, n.v.ident) == KW_NONE) {
- SymEntry* e = scope_lookup(p, n.v.ident);
+ } else if (n.kind == TOK_IDENT &&
+ ident_kw_inline(p, tok_ident(&n)) == KW_NONE) {
+ SymEntry* e = scope_lookup(p, tok_ident(&n));
if (!(e && e->kind == SEK_TYPEDEF)) is_inner = 1;
}
if (is_inner) {
@@ -1589,9 +1595,10 @@ const Type* parse_declarator_full_info(Parser* p, const Type* base,
}
inner_quals[nptrs_inner++] = q;
}
- if (p->cur.kind == TOK_IDENT && ident_kw_inline(p, p->cur.v.ident) == KW_NONE) {
- name = p->cur.v.ident;
- nloc = tok_loc(&p->cur);
+ if (p->cur.kind == TOK_IDENT &&
+ ident_kw_inline(p, tok_ident(&p->cur)) == KW_NONE) {
+ name = tok_ident(&p->cur);
+ nloc = tok_loc(p, &p->cur);
advance(p);
} else if (is_punct(&p->cur, '(')) {
Tok nn = peek1(p);
@@ -1629,9 +1636,9 @@ const Type* parse_declarator_full_info(Parser* p, const Type* base,
nested_quals[nptrs_nested++] = q;
}
if (p->cur.kind == TOK_IDENT &&
- ident_kw_inline(p, p->cur.v.ident) == KW_NONE) {
- name = p->cur.v.ident;
- nloc = tok_loc(&p->cur);
+ ident_kw_inline(p, tok_ident(&p->cur)) == KW_NONE) {
+ name = tok_ident(&p->cur);
+ nloc = tok_loc(p, &p->cur);
advance(p);
} else if (!allow_abstract) {
perr(p, "expected declarator name");
@@ -1653,9 +1660,10 @@ const Type* parse_declarator_full_info(Parser* p, const Type* base,
}
if (!has_inner_parens) {
- if (p->cur.kind == TOK_IDENT && ident_kw_inline(p, p->cur.v.ident) == KW_NONE) {
- name = p->cur.v.ident;
- nloc = tok_loc(&p->cur);
+ if (p->cur.kind == TOK_IDENT &&
+ ident_kw_inline(p, tok_ident(&p->cur)) == KW_NONE) {
+ name = tok_ident(&p->cur);
+ nloc = tok_loc(p, &p->cur);
advance(p);
} else if (!allow_abstract) {
perr(p, "expected declarator name");
diff --git a/lang/cpp/cpp.c b/lang/cpp/cpp.c
@@ -52,8 +52,8 @@ typedef struct CppRun {
static KitStatus cpp_preprocess_body(KitCompiler* c, void* user) {
CppRun* r = (CppRun*)user;
- Lexer* lex;
Pp* pp;
+ SourceSpec spec;
const KitPreprocessOptions* opts = r->opts;
const KitSlice* input = r->input;
@@ -67,13 +67,16 @@ static KitStatus cpp_preprocess_body(KitCompiler* c, void* user) {
cpp_bad_options(c, "input data is NULL but len > 0");
}
- lex = lex_open_mem(c, r->name.s, input->s, input->len);
- if (lex) lex_skip_shebang(lex);
pp = pp_new(c);
- if (!lex || !pp)
- compiler_panic(c, cpp_no_loc(), "C preprocessor out of memory");
+ if (!pp) compiler_panic(c, cpp_no_loc(), "C preprocessor out of memory");
cpp_apply_options(pp, opts);
- pp_push_input(pp, lex);
+ /* -E / cpp: NOT parser-feed — newlines surface for text reconstruction. */
+ memset(&spec, 0, sizeof(spec));
+ spec.name = kit_slice_cstr(r->name.s);
+ spec.bytes = input->s;
+ spec.len = (u32)input->len;
+ spec.flags = SRC_PRIMARY;
+ pp_push_source(pp, &spec);
pp_emit_text(pp, out);
pp_free(pp);
return KIT_OK;
diff --git a/lang/cpp/lex/lex.c b/lang/cpp/lex/lex.c
@@ -1,4 +1,4 @@
-/* C11 lexer (§6.4). Streams tokens out of a borrowed source buffer.
+/* C11 lexer (§6.4). Streams lean tokens out of a borrowed source buffer.
*
* Tokens are recognized per the standard's lexical grammar:
* - identifiers (§6.4.2) — keyword bucketing happens later in parse_c
@@ -6,19 +6,21 @@
* - string literals (§6.4.5) and character constants (§6.4.4.4)
* including the L/u/u8/U encoding prefixes
* - punctuators (§6.4.6), longest-match, including digraphs
- * - `#` and `##` surface as TOK_PP_HASH / TOK_PP_PASTE so the
- * preprocessor can recognize directives and the paste operator
+ * - `#` and `##` (and their digraph forms `%:` and `%:%:`) become
+ * TOK_PP_HASH / TOK_PP_PASTE so PP can recognize directives and paste
*
- * Comments (§6.4.9) are consumed as whitespace; physical newlines surface
- * as TOK_NEWLINE so PP can implement directive-line semantics.
+ * Comments (§6.4.9) are consumed as whitespace; physical newlines surface as
+ * TOK_NEWLINE so PP can implement directive-line semantics.
*
- * The scanner is a raw-cursor walk: the hot path holds `cur`/`end` as
- * pointers into the (line-splice-folded) buffer and classifies each byte with
- * one load from `cclass[256]`. There is no per-byte struct round-trip, no
- * per-byte column arithmetic, and — on the overwhelmingly common splice-free
- * input — no per-byte splice test. The physical column is *deferred*: it is
- * not tracked per byte but computed once per token from a `line_start` pointer
- * (col = cur - line_start + 1) when a SrcLoc is built. */
+ * Lean-token shape: each token carries a compact LocRef (file_id + byte offset
+ * into the folded buffer) and a TextRef (a source span for source tokens, an
+ * interned Sym for synthetic tokens, or nothing for canonical punctuators). The
+ * scanner does NOT track a running line number — line/col are reconstructed on
+ * demand from the source's retained buffer + splice table (see the PP SrcInfo
+ * registry). The hot path holds `cur`/`end` as pointers into the
+ * (line-splice-folded) buffer and classifies each byte with one load from
+ * `cclass[256]`; because phase-2 splices are folded out up front, the scan
+ * walks a clean buffer with a single code path (no per-byte splice test). */
#include "lex/lex.h"
@@ -93,114 +95,153 @@ static const u8 cclass[256] = {
};
/* clang-format on */
-/* Dense slot for a punctuator's spelling cache. A Punct value indexes
- * `punct_slot[]` to a compact 1-based slot; the few dozen distinct
- * punctuators map to slots 1..PUNCT_SLOTS-1, and every other value (the
- * `default`/unknown-byte path, the digraph-aliased forms) maps to slot 0,
- * which is never cached (those intern their bytes directly). This keeps the
- * per-lexer cache to PUNCT_SLOTS entries instead of one per Punct value
- * (P_HASH_HASH+1 = 279), shrinking `struct Lexer` and its per-open memset by
- * ~1 KB while preserving the per-spelling caching. Slot assignment is
- * arbitrary but stable; only distinctness matters. */
-#define PUNCT_SLOTS 49u
+/* Canonical spelling bytes for the single-char punctuators: punct1[c] == c for
+ * every punctuator codepoint, so a single-char Punct code reconstructs its
+ * spelling as the one byte at &punct1[code]. */
/* clang-format off */
-static const u8 punct_slot[P_HASH_HASH + 1] = {
- /* single-char punctuators (indexed by ASCII codepoint) */
- ['!'] = 1, ['#'] = 2, ['%'] = 3, ['&'] = 4, ['('] = 5, [')'] = 6,
- ['*'] = 7, ['+'] = 8, [','] = 9, ['-'] = 10, ['.'] = 11, ['/'] = 12,
- [':'] = 13, [';'] = 14, ['<'] = 15, ['='] = 16, ['>'] = 17, ['?'] = 18,
- ['['] = 19, [']'] = 20, ['^'] = 21, ['{'] = 22, ['|'] = 23, ['}'] = 24,
- ['~'] = 25,
- /* multi-char punctuators (P_ARROW..P_HASH_HASH) */
- [P_ARROW] = 26, [P_INC] = 27, [P_DEC] = 28, [P_SHL] = 29, [P_SHR] = 30,
- [P_LE] = 31, [P_GE] = 32, [P_EQ] = 33, [P_NE] = 34, [P_AND] = 35,
- [P_OR] = 36, [P_ADD_ASSIGN] = 37, [P_SUB_ASSIGN] = 38, [P_MUL_ASSIGN] = 39,
- [P_DIV_ASSIGN] = 40, [P_MOD_ASSIGN] = 41, [P_AND_ASSIGN] = 42,
- [P_OR_ASSIGN] = 43, [P_XOR_ASSIGN] = 44, [P_SHL_ASSIGN] = 45,
- [P_SHR_ASSIGN] = 46, [P_ELLIPSIS] = 47, [P_HASH_HASH] = 48,
+static const char punct1[256] = {
+ ['!']='!', ['#']='#', ['%']='%', ['&']='&', ['(']='(', [')']=')',
+ ['*']='*', ['+']='+', [',']=',', ['-']='-', ['.']='.', ['/']='/',
+ [':']=':', [';']=';', ['<']='<', ['=']='=', ['>']='>', ['?']='?',
+ ['[']='[', [']']=']', ['^']='^', ['{']='{', ['|']='|', ['}']='}',
+ ['~']='~',
};
/* clang-format on */
+const char* punct_canon(u32 punct, u32* len) {
+ if (punct < 256u) {
+ *len = 1u;
+ return &punct1[punct];
+ }
+ switch (punct) {
+ case P_ARROW:
+ *len = 2;
+ return "->";
+ case P_INC:
+ *len = 2;
+ return "++";
+ case P_DEC:
+ *len = 2;
+ return "--";
+ case P_SHL:
+ *len = 2;
+ return "<<";
+ case P_SHR:
+ *len = 2;
+ return ">>";
+ case P_LE:
+ *len = 2;
+ return "<=";
+ case P_GE:
+ *len = 2;
+ return ">=";
+ case P_EQ:
+ *len = 2;
+ return "==";
+ case P_NE:
+ *len = 2;
+ return "!=";
+ case P_AND:
+ *len = 2;
+ return "&&";
+ case P_OR:
+ *len = 2;
+ return "||";
+ case P_ADD_ASSIGN:
+ *len = 2;
+ return "+=";
+ case P_SUB_ASSIGN:
+ *len = 2;
+ return "-=";
+ case P_MUL_ASSIGN:
+ *len = 2;
+ return "*=";
+ case P_DIV_ASSIGN:
+ *len = 2;
+ return "/=";
+ case P_MOD_ASSIGN:
+ *len = 2;
+ return "%=";
+ case P_AND_ASSIGN:
+ *len = 2;
+ return "&=";
+ case P_OR_ASSIGN:
+ *len = 2;
+ return "|=";
+ case P_XOR_ASSIGN:
+ *len = 2;
+ return "^=";
+ case P_SHL_ASSIGN:
+ *len = 3;
+ return "<<=";
+ case P_SHR_ASSIGN:
+ *len = 3;
+ return ">>=";
+ case P_ELLIPSIS:
+ *len = 3;
+ return "...";
+ case P_HASH_HASH:
+ *len = 2;
+ return "##";
+ default:
+ *len = 0;
+ return "";
+ }
+}
+
struct Lexer {
Compiler* c;
Heap* heap;
/* `src`/`len` are the post-phase-2 (line-splice-folded) logical bytes the
* scanner walks. When the input contains no `\<newline>` (the common case)
* this borrows the input verbatim (owns_src = 0, zero-copy); otherwise it is
- * a folded heap copy. `cur` is the live cursor and `end` == src + len; both
- * stay pointers so the hot scan never reloads pos/len from the struct. */
+ * a folded heap copy. `cur` is the live cursor and `end` == src + len. */
const char* src;
const char* end;
const char* cur;
- size_t len;
- /* Deferred column: the column of a byte at `p` is `(p - line_start) + 1`.
- * `line_start` points just past the last physical line break the cursor has
- * crossed (the byte after a '\n', or the fold point of a `\<newline>`), so
- * no per-byte column arithmetic is needed — only a subtraction per built
- * SrcLoc. */
- const char* line_start;
+ u32 len;
u32 file_id;
- u32 line;
u8 at_bol;
u8 had_space;
/* §5.1.1.2 phase 4 directive context for header-name lexing.
* 0 = none, 1 = saw pp-hash, 2 = saw `#include`/etc and the next
* token may be a header-name. */
u8 dstate;
- u8 owns_src; /* src is a heap-allocated folded copy to free at close */
- /* Newline-token policy. When emit_newlines (the default) is set, every
- * physical newline surfaces as a TOK_NEWLINE (the -E / cpp path needs them
- * for line reconstruction). When cleared (the cc parser-feed path), a
- * NON-directive newline is consumed silently — the line/at_bol/dstate
- * bookkeeping still runs, but no token is returned; the parser never wanted
- * newlines and PP's downstream skip_nl drain becomes a no-op. The newline
- * that TERMINATES a directive line is ALWAYS emitted regardless, so
- * read_directive_line / #if-skip still see their terminator (Option B2). */
+ u8 owns_src; /* src is a heap-allocated folded copy this lexer must free */
+ /* Newline-token policy. When set (default), every physical newline surfaces
+ * as a TOK_NEWLINE (the -E / cpp path needs them). When clear (cc parser
+ * feed), a NON-directive newline is consumed silently; the directive-line
+ * terminating newline is always emitted (Option B2). */
u8 emit_newlines;
/* Set when a directive-introducing `#` (TOK_PP_HASH at BOL) was emitted on
- * the current physical line; persists across the directive's tokens and is
- * cleared by the line-terminating newline. Used to keep emitting that one
- * newline even when emit_newlines is off. */
+ * the current physical line; persists across the directive's tokens, cleared
+ * by the terminating newline. Forces that one newline to surface even when
+ * emit_newlines is off. */
u8 directive_line;
- /* Sorted logical offsets at which a `\<newline>` splice was folded out, so
- * line tracking can still advance the physical line as the cursor passes
- * each. Empty (and NULL) for splice-free input — the common case pays
- * nothing for splices. */
+ /* Sorted logical offsets at which a `\<newline>` splice was folded out. The
+ * scanner never consults these (the folded buffer is splice-free) — they are
+ * retained metadata so the PP SrcInfo registry can reconstruct physical line
+ * numbers from a byte offset. NULL/empty for splice-free input. */
u32* splices;
u32 nsplices;
- u32 next_splice;
- /* Lazily-interned spelling Sym per punctuator, cached so the same ~60
- * distinct punctuator spellings are not re-interned on every occurrence.
- * The table is a compact, densely-indexed array (see PUNCT_SLOTS /
- * punct_slot): a punctuator's Punct value maps through the static
- * `punct_slot[]` table to a small slot, keeping the per-lexer cache to a
- * few hundred bytes instead of the ~1.1 KB a Punct-value-indexed array
- * cost — the bulk of `struct Lexer` and of lex_open_mem's per-open memset.
- * Entry value 0 is the reserved pool "none" sym, an unambiguous
- * not-yet-interned sentinel. The digraph spellings (<: <% %> :> %: %:%:)
- * are NOT cached here: they share a Punct value with their canonical form
- * but spell differently, so they intern their source bytes verbatim each
- * time (see the punct scanner). */
- Sym punct_sym[PUNCT_SLOTS];
+ /* Logical offset of the shebang line's terminating newline (the column origin
+ * of line 1 when a shebang was skipped); 0 when no shebang. */
+ u32 shebang_off;
};
/* §5.1.1.2 translation phase 2: fold every `\<newline>` line splice out of the
- * input once, up front, so the scanner never tests for splices per byte. The
- * common (splice-free) input is borrowed verbatim; otherwise a heap copy holds
- * the folded text and `splices[]` records each fold point (a logical offset) so
- * line numbering still advances across the removed physical newline. */
-static void lex_fold_splices(Lexer* l, const char* src, size_t len) {
- size_t i;
- size_t nspl = 0;
+ * input once, up front, so the scanner walks a clean buffer. The common
+ * (splice-free) input is borrowed verbatim; otherwise a heap copy holds the
+ * folded text and `splices[]` records each fold point (a logical offset) so the
+ * SrcInfo line-map can still account for the removed physical newline. */
+static void lex_fold_splices(Lexer* l, const char* src, u32 len) {
+ u32 i;
+ u32 nspl = 0;
char* buf;
u32* spl;
- size_t w, s, wcap;
- l->next_splice = 0;
- /* Count `\<newline>` splices without a scalar whole-buffer pass: backslash is
- * rare in C, so a memchr-driven sweep skips non-backslash spans at glibc-NEON
- * speed (~1 instruction per 16-32 bytes) instead of testing every byte. The
- * `-1` keeps bs[1] in range, so a trailing lone `\\` at src[len-1] is never
- * counted as a splice — bit-exact with the old scalar `i + 1 < len` loop. */
+ u32 w, s, wcap;
+ /* Count `\<newline>` splices via a memchr-driven sweep over backslashes. The
+ * `-1` keeps bs[1] in range so a trailing lone `\` is never counted. */
if (len) {
const char* p = src;
const char* e = src + len;
@@ -219,14 +260,10 @@ static void lex_fold_splices(Lexer* l, const char* src, size_t len) {
l->nsplices = 0;
return;
}
- /* Each folded splice removes exactly 2 bytes, so the folded length is known
- * up front; allocate it exactly so the close-time free size matches. */
wcap = len - 2u * nspl;
spl = (u32*)l->heap->alloc(l->heap, nspl * sizeof(u32), _Alignof(u32));
buf = wcap ? (char*)l->heap->alloc(l->heap, wcap, 1) : NULL;
if (!spl || (wcap && !buf)) {
- /* OOM: fall back to the raw input. The (rare) splice then survives into a
- * token spelling, but lexing stays memory-safe. */
if (buf) l->heap->free(l->heap, buf, wcap);
if (spl) l->heap->free(l->heap, spl, nspl * sizeof(u32));
l->src = src;
@@ -240,7 +277,7 @@ static void lex_fold_splices(Lexer* l, const char* src, size_t len) {
s = 0;
for (i = 0; i < len;) {
if (i + 1 < len && src[i] == '\\' && src[i + 1] == '\n') {
- spl[s++] = (u32)w; /* fold point = logical offset of the next byte */
+ spl[s++] = w; /* fold point = logical offset of the next byte */
i += 2;
continue;
}
@@ -250,37 +287,12 @@ static void lex_fold_splices(Lexer* l, const char* src, size_t len) {
l->len = w; /* == wcap */
l->owns_src = wcap ? 1u : 0u;
l->splices = spl;
- l->nsplices = (u32)s;
+ l->nsplices = s;
}
-/* Catch line tracking up to the cursor: process every splice fold point at or
- * before `cur` (a `\<newline>` removed there was a physical line break, so it
- * bumps the line and starts a fresh column origin at the fold). Called after a
- * fast scan that may have jumped over fold points; cheap no-op when no splices
- * remain. Only ever called when l->splices != NULL.
- *
- * Bit-exactness: a token body (identifier/number/string/punct/header) contains
- * no '\n', so the only line-break event within it is a fold; processing them
- * in order leaves `line` advanced by one per fold and `line_start` at the last
- * fold — identical to the old per-byte advance, which is observable only at the
- * next SrcLoc (no column is read mid-body). The inclusive `<= cur` bound makes
- * a fold sitting exactly at the cursor (a token that starts on a freshly
- * spliced line) fire here, matching the old "sync when pos == fold". */
-static void lex_catchup_splices(Lexer* l) {
- const char* base = l->src;
- u32 ns = l->next_splice;
- u32 n = l->nsplices;
- while (ns < n && base + l->splices[ns] <= l->cur) {
- l->line++;
- l->line_start = base + l->splices[ns];
- ns++;
- }
- l->next_splice = ns;
-}
-
-/* The off-th logical byte at the cursor, or -1 at end of input. Used only on
- * cold lookahead paths (encoding-prefix detection, punctuator longest-match,
- * UCNs); the hot scan walks `cur`/`end` directly. */
+/* The off-th logical byte at the cursor, or -1 at end of input. Cold lookahead
+ * only (encoding-prefix detection, punctuator longest-match, UCNs); the hot
+ * scan walks `cur`/`end` directly. */
static int peek(const Lexer* l, size_t off) {
const char* p = l->cur + off;
return p < l->end ? (unsigned char)*p : -1;
@@ -292,25 +304,16 @@ static int is_hex_digit(int c) {
(c >= 'A' && c <= 'F');
}
-/* Consume a maximal run of identifier-continuation bytes (CC_IDCONT) from the
- * cursor in a tight raw-pointer loop: one table load per byte, no struct
- * reload, no per-byte column or splice work. No CC_IDCONT byte is '\n', so the
- * line is untouched here; any fold points crossed are reconciled once at the
- * end. Stops at the first non-CC_IDCONT byte (a '\\' UCN lead, a punctuator, or
- * end), which the caller re-examines (e.g. for a UCN continuation). */
+/* Consume a maximal run of identifier-continuation bytes (CC_IDCONT). */
static void scan_ident_run(Lexer* l) {
const char* p = l->cur;
const char* end = l->end;
while (p < end && (cclass[(unsigned char)*p] & CC_IDCONT)) p++;
l->cur = p;
- if (l->splices) lex_catchup_splices(l);
}
-/* Match a UCN at offset `off` from the current position. Returns the total
- * length (6 for \uXXXX, 10 for \UXXXXXXXX), or 0 if no UCN matches. The
- * range constraints from §6.4.3 (no UCN < 00A0 except $/@/`, and none in
- * D800–DFFF) are not enforced here — the lexical form is matched and any
- * downstream phase that cares can diagnose. */
+/* Match a UCN at offset `off`. Returns total length (6 for \uXXXX, 10 for
+ * \UXXXXXXXX), or 0 if no UCN matches. */
static int ucn_len(const Lexer* l, size_t off) {
int n, i;
if (peek(l, off) != '\\') return 0;
@@ -326,182 +329,124 @@ static int ucn_len(const Lexer* l, size_t off) {
return 2 + n;
}
-static SrcLoc lex_here(const Lexer* l) {
- SrcLoc loc;
+static LocRef loc_at(const Lexer* l, const char* p) {
+ LocRef loc;
loc.file_id = l->file_id;
- loc.line = l->line;
- loc.col = (u32)(l->cur - l->line_start) + 1u;
+ loc.off = (u32)(p - l->src);
return loc;
}
-/* Fold splices on (src, len) and reset the scanner cursor/line state to the
- * start of the buffer. Shared by lex_open_mem_sym (fresh lexer) and
- * lex_reset_mem (re-pointed lexer). Does NOT touch file_id, which the caller
- * sets, nor the punct_sym cache, which stays valid across resets (the interned
- * Sym for a spelling is stable). */
-static void lex_point_at(Lexer* l, const char* src, size_t len) {
- lex_fold_splices(l, src ? src : "", src ? len : 0);
- l->cur = l->src;
- l->end = l->src + l->len;
- l->line = 1;
- l->line_start = l->src;
- l->dstate = 0;
- l->directive_line = 0;
- if (l->splices) lex_catchup_splices(l); /* a splice folded at offset 0 -> line 2 */
- l->at_bol = 1;
- l->had_space = 0;
+/* A TEXT_SRC reference to [a,b) in this lexer's logical buffer. */
+static TextRef text_src(const Lexer* l, const char* a, const char* b) {
+ TextRef t;
+ t.kind = TEXT_SRC;
+ t.file_id = l->file_id;
+ t.off = (u32)(a - l->src);
+ t.len_or_sym = (u32)(b - a);
+ return t;
}
-/* Splice-free variant of lex_point_at for buffers PROVABLY free of `\<newline>`
- * splices. Does exactly what lex_point_at does, but takes the nspl==0 branch of
- * lex_fold_splices unconditionally (borrow src verbatim, no splice table)
- * WITHOUT the per-byte memchr scan that, for such a buffer, would only ever
- * conclude "no splices". The cursor/line resets are identical to lex_point_at's
- * (and to the nspl==0 path it inlines: owns_src=0, splices=NULL, no
- * lex_catchup_splices since l->splices stays NULL).
- *
- * PRECONDITION (caller-guaranteed, NOT checked here): (src, len) contains no
- * `\<newline>` line-splice. The ONLY caller is the token-paste path
- * (lex_reset_mem ← paste_tokens): paste content is interned, already-folded
- * token spellings concatenated with one appended '\n', so a lone `\` can never
- * sit immediately before a '\n'. Do NOT route <command-line>/<_Pragma>/file
- * opens through here — those stay on the safe lex_point_at path (a -D value
- * could in theory carry a splice). */
-static void lex_point_at_nosplice(Lexer* l, const char* src, size_t len) {
- l->src = src ? src : "";
- l->len = src ? len : 0;
- l->owns_src = 0;
- l->splices = NULL;
- l->nsplices = 0;
- l->next_splice = 0;
+/* Reset the scanner cursor over (src, len). Shared by lex_open and lex_reset.
+ * Does NOT touch file_id (caller sets it). */
+static void lex_point_at(Lexer* l, const char* src, u32 len, int no_splices) {
+ if (no_splices) {
+ l->src = src ? src : "";
+ l->len = src ? len : 0;
+ l->owns_src = 0;
+ l->splices = NULL;
+ l->nsplices = 0;
+ } else {
+ lex_fold_splices(l, src ? src : "", src ? len : 0);
+ }
l->cur = l->src;
l->end = l->src + l->len;
- l->line = 1;
- l->line_start = l->src;
l->dstate = 0;
l->directive_line = 0;
l->at_bol = 1;
l->had_space = 0;
+ l->shebang_off = 0;
+}
+
+static u32 spec_file_id(Compiler* c, const SourceSpec* spec) {
+ u32 id = 0;
+ Sym name = spec->name_sym ? spec->name_sym : kit_sym_intern(c, spec->name);
+ (void)kit_source_add_memory_sym(c, name, &id);
+ return id;
}
-Lexer* lex_open_mem_sym(Compiler* c, Sym name, const char* src, size_t len) {
+Lexer* lex_open(Compiler* c, const SourceSpec* spec) {
Heap* h = (Heap*)kit_compiler_context(c)->heap;
Lexer* l = (Lexer*)h->alloc(h, sizeof(*l), _Alignof(Lexer));
if (!l) return NULL;
- /* Right-sized zero-init: only punct_sym genuinely needs zeroing (slot value 0
- * is the not-yet-interned sentinel; see punct_spelling). Every other field is
- * assigned before its first read below — c/heap/file_id explicitly here, then
- * src/len/owns_src/splices/nsplices/next_splice via lex_fold_splices and
- * cur/end/line/line_start/dstate/at_bol/had_space via lex_point_at. This
- * replaces a full ~288B struct memset (run on ~6,000 opens) with a ~196B
- * punct_sym clear. */
- memset(l->punct_sym, 0, sizeof l->punct_sym);
l->c = c;
l->heap = h;
- l->file_id = 0;
- /* Default: surface newline tokens (the -E / cpp / paste / _Pragma callers
- * rely on them). The cc parser-feed path opts out via lex_set_emit_newlines.
- * directive_line is (re)initialized by lex_point_at below. */
- l->emit_newlines = 1;
- (void)kit_source_add_memory_sym(c, name, &l->file_id);
- lex_point_at(l, src, len);
+ l->file_id = spec_file_id(c, spec);
+ l->emit_newlines = (spec->flags & SRC_PARSER_FEED) ? 0u : 1u;
+ lex_point_at(l, spec->bytes, spec->len, (spec->flags & SRC_NO_SPLICES) != 0);
return l;
}
-Lexer* lex_open_mem(Compiler* c, const char* name, const char* src,
- size_t len) {
- Sym sym = kit_sym_intern(c, kit_slice_cstr(name));
- return lex_open_mem_sym(c, sym, src, len);
-}
-
-/* Re-point an existing memory lexer at a fresh buffer for reuse across many
- * tiny opens (e.g. `<paste>` buffers), avoiding a fresh alloc(sizeof Lexer) +
- * lex_close free pair per open. Frees the previous folded buffer/splices (if
- * any), re-registers a fresh sequential file_id (the count is byte-observable
- * via DWARF, so it must still be bumped per open), then re-folds + resets the
- * scanner. The borrowed (src, len) must outlive the next use of the lexer. */
-void lex_reset_mem(Lexer* l, Sym name, const char* src, size_t len) {
+void lex_reset(Lexer* l, const SourceSpec* spec) {
if (l->owns_src) l->heap->free(l->heap, (char*)l->src, l->len);
- if (l->splices)
- l->heap->free(l->heap, l->splices, l->nsplices * sizeof(u32));
+ if (l->splices) l->heap->free(l->heap, l->splices, l->nsplices * sizeof(u32));
l->owns_src = 0;
l->src = NULL;
l->splices = NULL;
l->nsplices = 0;
- l->file_id = 0;
- (void)kit_source_add_memory_sym(l->c, name, &l->file_id);
- /* The paste path is the sole caller, and its buffer (concatenated interned
- * token spellings + one trailing '\n') provably has no `\<newline>` splice,
- * so skip the memchr splice scan entirely (see lex_point_at_nosplice). */
- lex_point_at_nosplice(l, src, len);
+ l->file_id = spec_file_id(l->c, spec);
+ l->emit_newlines = (spec->flags & SRC_PARSER_FEED) ? 0u : 1u;
+ lex_point_at(l, spec->bytes, spec->len, (spec->flags & SRC_NO_SPLICES) != 0);
}
void lex_close(Lexer* l) {
if (!l) return;
if (l->owns_src) l->heap->free(l->heap, (char*)l->src, l->len);
- if (l->splices)
- l->heap->free(l->heap, l->splices, l->nsplices * sizeof(u32));
+ if (l->splices) l->heap->free(l->heap, l->splices, l->nsplices * sizeof(u32));
l->heap->free(l->heap, l, sizeof(*l));
}
-/* Skip a script "shebang" line: a `#!` at the very start of the source.
- * The kernel-level `#!/path interpreter` mechanism (used to make a C file
- * executable via `kit run`) leaves the interpreter line as the first line of
- * the file, which is not valid C — `#!` would otherwise be lexed as a `#`
- * directive introducer. We only recognize it at byte 0, so a `#!` anywhere
- * else is left untouched. The line's trailing newline is left in place so the
- * lexer emits its TOK_NEWLINE and line numbering stays accurate (the shebang
- * remains line 1). No-op unless the buffer begins with the two bytes `#!`.
- * Apply only to a primary source file, never to includes/paste buffers. */
+/* Skip a leading `#!` shebang line. The shebang stays logical line 1; record
+ * the terminating newline's offset as the column origin so the materializer
+ * reports column 1 for it (matching the historical column-untracked skip). */
void lex_skip_shebang(Lexer* l) {
if (!l || l->cur != l->src) return;
if (l->len < 2 || l->src[0] != '#' || l->src[1] != '!') return;
while (l->cur < l->end && *l->cur != '\n') l->cur++;
- /* The shebang stays line 1; rebase line_start so the trailing newline still
- * reports column 1 (matching the old, column-untracked skip). */
- l->line_start = l->cur;
+ l->shebang_off = (u32)(l->cur - l->src);
}
-SrcLoc lex_loc(const Lexer* l) { return lex_here(l); }
+LocRef lex_here(const Lexer* l) { return loc_at(l, l->cur); }
u32 lex_file_id(const Lexer* l) { return l->file_id; }
-/* Set the newline-token policy (see the emit_newlines field). Off = suppress
- * non-directive newlines (cc parser feed); on (default) = surface every
- * newline (-E / cpp). Directive-terminating newlines are emitted either way. */
-void lex_set_emit_newlines(Lexer* l, int on) {
- if (l) l->emit_newlines = on ? 1u : 0u;
+const char* lex_buf(const Lexer* l) { return l->src; }
+u32 lex_buf_len(const Lexer* l) { return l->len; }
+int lex_owns_buf(const Lexer* l) { return l->owns_src; }
+u32 lex_shebang_off(const Lexer* l) { return l->shebang_off; }
+const u32* lex_splices(const Lexer* l, u32* n_out) {
+ *n_out = l->nsplices;
+ return l->splices;
}
-
-/* Intern a token's spelling [a, b) straight from the folded buffer (it is
- * already post-phase-2 logical text — splices were removed at open). */
-static Sym lex_intern(Lexer* l, const char* a, const char* b) {
- return kit_sym_intern(l->c,
- (KitSlice){.s = a, .len = (size_t)(b - a)});
+void lex_disown_buf(Lexer* l) {
+ l->owns_src = 0;
+ l->splices = NULL; /* PP now owns buffer + splice table */
+ l->nsplices = 0;
}
-/* Spelling Sym for a non-digraph punctuator, interned once per lexer and then
- * reused for every later occurrence of the same punctuator. The Punct value
- * uniquely identifies the source spelling for every non-digraph form (all '+'
- * tokens are "+", all P_ARROW are "->", ...), so caching by it is exact. The
- * first occurrence interns the exact source bytes [a,b), making the Sym
- * bit-identical to what an unconditional lex_intern would have produced. Pool
- * entry 0 is the reserved "none" sym, so a cached spelling is always nonzero
- * and 0 is an unambiguous not-yet-interned sentinel. Digraphs are excluded by
- * the caller: they share a Punct value with their canonical form but spell
- * differently, so they must intern their source bytes verbatim. */
-static Sym punct_spelling(Lexer* l, u32 punct, const char* a, const char* b) {
- u32 slot = punct_slot[punct];
- Sym s;
- if (!slot) return lex_intern(l, a, b); /* uncached value: intern directly */
- s = l->punct_sym[slot];
- if (!s) s = l->punct_sym[slot] = lex_intern(l, a, b);
+KitSlice lex_text_slice(const Lexer* l, TextRef text) {
+ KitSlice s;
+ if (text.kind == TEXT_SRC) {
+ s.s = l->src + text.off;
+ s.len = text.len_or_sym;
+ return s;
+ }
+ if (text.kind == TEXT_SYM) return kit_sym_str(l->c, (Sym)text.len_or_sym);
+ s.s = "";
+ s.len = 0;
return s;
}
-/* §6.4.7 header-name lookahead: in include-directive context, a `<` or `"`
- * starts a header-name that runs to the matching `>` or `"`. The lexer
- * recognizes only header-name forms (whose contents are implementation
- * defined), not q-char-sequence escape rules. */
+/* §6.4.7 header-name lookahead: include-family keyword set, matched on the raw
+ * source bytes of the directive identifier following a `#`. */
static int matches_include_kw(const char* s, size_t n) {
if (n == 7 && memcmp(s, "include", 7) == 0) return 1;
if (n == 12 && memcmp(s, "include_next", 12) == 0) return 1;
@@ -510,35 +455,11 @@ static int matches_include_kw(const char* s, size_t n) {
return 0;
}
-/* Splice-present per-byte advance: walk one byte, advancing line/line_start
- * across a '\n' and across any fold point now at the cursor exactly as the old
- * bump()+sync did. Used only by the splice-present whitespace/comment skip,
- * where '\n' bytes and fold points interleave and must be processed in order.
- * Cursor must be < end. */
-static void lex_bump_sp(Lexer* l) {
- char ch = *l->cur++;
- if (ch == '\n') {
- l->line++;
- l->line_start = l->cur;
- }
- {
- const char* base = l->src;
- u32 ns = l->next_splice;
- u32 n = l->nsplices;
- while (ns < n && base + l->splices[ns] == l->cur) {
- l->line++;
- l->line_start = l->cur;
- ns++;
- }
- l->next_splice = ns;
- }
-}
-
-/* Fast whitespace + comment skip (splice-free input): a raw-pointer table walk.
- * A token with no leading whitespace pays only the failed CC_SPACE test. '\n'
- * is never CC_SPACE (it surfaces as TOK_NEWLINE), so only a block comment can
- * advance the physical line here. Sets had_space when anything is consumed. */
-static void skip_ws_fast(Lexer* l) {
+/* Fast whitespace + comment skip. The folded buffer is splice-free, so this is
+ * the single skip path. '\n' is never CC_SPACE (it surfaces as TOK_NEWLINE), so
+ * only a block comment can contain a newline here — and line tracking is lazy,
+ * so even those are just skipped. Sets had_space when anything is consumed. */
+static void skip_ws_and_comments(Lexer* l) {
const char* p = l->cur;
const char* end = l->end;
int adv = 0;
@@ -560,10 +481,6 @@ static void skip_ws_fast(Lexer* l) {
p += 2;
break;
}
- if (*p == '\n') {
- l->line++;
- l->line_start = p + 1;
- }
p++;
}
adv = 1;
@@ -575,57 +492,13 @@ static void skip_ws_fast(Lexer* l) {
if (adv) l->had_space = 1;
}
-/* Whitespace + comment skip, splice-present path. Mirrors the old per-byte
- * loop (peek/bump) exactly so line/line_start stay bit-identical when a fold
- * point lands inside a comment between physical newlines. The rare path. */
-static void skip_ws_spliced(Lexer* l) {
- for (;;) {
- int ch = (l->cur < l->end) ? (unsigned char)*l->cur : -1;
- if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\v' || ch == '\f') {
- lex_bump_sp(l);
- l->had_space = 1;
- continue;
- }
- if (ch == '/' && l->cur + 1 < l->end && l->cur[1] == '/') {
- lex_bump_sp(l);
- lex_bump_sp(l);
- while (l->cur < l->end && *l->cur != '\n') lex_bump_sp(l);
- l->had_space = 1;
- continue;
- }
- if (ch == '/' && l->cur + 1 < l->end && l->cur[1] == '*') {
- lex_bump_sp(l);
- lex_bump_sp(l);
- while (l->cur < l->end) {
- if (*l->cur == '*' && l->cur + 1 < l->end && l->cur[1] == '/') {
- lex_bump_sp(l);
- lex_bump_sp(l);
- break;
- }
- lex_bump_sp(l);
- }
- l->had_space = 1;
- continue;
- }
- break;
- }
-}
-
-static void skip_ws_and_comments(Lexer* l) {
- if (l->splices)
- skip_ws_spliced(l);
- else
- skip_ws_fast(l);
-}
-
-/* Consume a pp-number per §6.4.8. The cursor is positioned at the leading
- * digit (or `.` followed by a digit) on entry. No '\n' is consumable here, so
- * line tracking only reconciles fold points crossed (splice-present path). */
+/* Consume a pp-number per §6.4.8; cursor at the leading digit (or `.`+digit).
+ */
static void scan_pp_number(Lexer* l) {
const char* p = l->cur;
const char* end = l->end;
if (p < end && *p == '.') p++;
- if (p < end) p++; /* first digit (dispatch guarantees it is present) */
+ if (p < end) p++; /* first digit (dispatch guarantees it) */
while (p < end) {
unsigned char c = (unsigned char)*p;
if ((c == 'e' || c == 'E' || c == 'p' || c == 'P') && p + 1 < end &&
@@ -638,11 +511,9 @@ static void scan_pp_number(Lexer* l) {
}
}
l->cur = p;
- if (l->splices) lex_catchup_splices(l);
}
-/* 1 if the pp-number text is a floating constant (§6.4.4.2): contains a
- * radix `.`, a hex `p`/`P` exponent, or a decimal `e`/`E` exponent. */
+/* 1 if the pp-number text is a floating constant (§6.4.4.2). */
static int pp_number_is_float(const char* s, size_t n) {
int is_hex = 0;
size_t i = 0;
@@ -664,11 +535,8 @@ static int pp_number_is_float(const char* s, size_t n) {
return 0;
}
-/* Consume a quoted body — string ('"') or character ('\''). The cursor is
- * positioned at the opening quote on entry. Returns 1 on an unterminated or
- * newline-broken literal, 0 on a clean close. A '\n' terminates the literal
- * without being consumed (it surfaces as the next TOK_NEWLINE), so line
- * tracking only reconciles fold points crossed. */
+/* Consume a quoted body — string ('"') or character ('\''); cursor at the open
+ * quote. Returns 1 on an unterminated/newline-broken literal. */
static int scan_quoted(Lexer* l, int quote) {
const char* p = l->cur;
const char* end = l->end;
@@ -699,26 +567,23 @@ static int scan_quoted(Lexer* l, int quote) {
p++;
}
l->cur = p;
- if (l->splices) lex_catchup_splices(l);
return bad;
}
-/* Finish a string/character literal token: skip `sp_len` prefix bytes, consume
- * the quoted body, classify, and intern the full spelling [tok_start, cur). */
+/* Finish a string/character literal: skip `sp_len` prefix bytes, consume the
+ * quoted body, classify, and record the full spelling span [tok_start, cur). */
static void finish_str_lit(Lexer* l, Tok* t, const char* tok_start, int sp_len,
int is_char, u32 encf) {
l->cur += sp_len;
if (scan_quoted(l, is_char ? '\'' : '"')) t->flags |= TF_LITERAL_BAD;
t->kind = (u16)(is_char ? TOK_CHR : TOK_STR);
- t->flags |= encf;
- t->spelling = lex_intern(l, tok_start, l->cur);
- t->v.str = t->spelling;
+ t->flags |= (u16)encf;
+ t->aux = 0;
+ t->text = text_src(l, tok_start, l->cur);
l->dstate = 0;
}
-/* Finish an identifier token (§6.4.2). On entry the cursor is at the first
- * identifier byte (an ident-start byte or a UCN lead). Consume the first
- * element, then alternate maximal CC_IDCONT runs with UCN continuations. */
+/* Finish an identifier token (§6.4.2). Cursor at the first identifier byte. */
static void finish_ident(Lexer* l, Tok* t, const char* tok_start) {
int u = ucn_len(l, 0);
if (u)
@@ -726,81 +591,61 @@ static void finish_ident(Lexer* l, Tok* t, const char* tok_start) {
else
l->cur += 1;
for (;;) {
- scan_ident_run(l); /* consume the maximal CC_IDCONT run in one go */
+ scan_ident_run(l);
u = ucn_len(l, 0);
if (!u) break;
l->cur += u;
}
t->kind = TOK_IDENT;
- t->spelling = lex_intern(l, tok_start, l->cur);
- t->v.ident = t->spelling;
+ t->aux = (u32)kit_sym_intern(
+ l->c, (KitSlice){.s = tok_start, .len = (size_t)(l->cur - tok_start)});
+ t->text = text_src(l, tok_start, l->cur);
if (l->dstate == 1) {
- KitSlice s = kit_sym_str(l->c, t->spelling);
- l->dstate = (s.s && matches_include_kw(s.s, s.len)) ? 2 : 0;
+ l->dstate =
+ matches_include_kw(tok_start, (size_t)(l->cur - tok_start)) ? 2 : 0;
} else {
l->dstate = 0;
}
}
-Tok lex_next(Lexer* l) {
+void lex_next(Lexer* l, Tok* out) {
Tok t;
- SrcLoc tloc;
const char* tok_start;
const char* end = l->end;
unsigned cc;
int ch;
- /* No per-token memset: every content-token path assigns kind/loc/spelling/v,
- * and flags is the only field accumulated via |=, so a deterministic zero
- * base for flags (and the v union, which the number path leaves unwritten) is
- * all the hot path needs. The cold EOF/NEWLINE early returns explicitly clear
- * spelling so the returned token stays byte-identical to the old
- * memset-then-fill token. */
t.flags = 0;
- t.v.ident = 0;
+ t.aux = 0;
- /* Skip whitespace and comments. A newline token is emitted before any
- * subsequent content tokens for the line that follows. */
+ /* Skip whitespace and comments; surface newlines per policy. */
for (;;) {
skip_ws_and_comments(l);
if (l->cur >= end) {
t.kind = TOK_EOF;
t.loc = lex_here(l);
- t.spelling = 0;
- return t;
+ t.text = text_none_ref();
+ *out = t;
+ return;
}
if (*l->cur == '\n') {
- /* Whether this newline must surface: always under emit_newlines (-E /
- * cpp), and on the cc path only when it terminates a directive line
- * (Option B2 — read_directive_line / #if-skip rely on that one). */
int emit_nl = l->emit_newlines || l->directive_line;
- tloc = lex_here(l);
- /* consume the newline */
+ LocRef nloc = lex_here(l);
l->cur++;
- l->line++;
- l->line_start = l->cur;
- if (l->splices) lex_catchup_splices(l);
l->at_bol = 1;
l->had_space = 0;
l->dstate = 0;
- /* The directive line (if any) ends at its terminating newline. */
l->directive_line = 0;
- if (!emit_nl) {
- /* Suppressed non-directive newline: bookkeeping done above (line /
- * line_start / at_bol / dstate / splices), drop the token and keep
- * scanning. The next content token carries TF_AT_BOL via l->at_bol, so
- * BOL detection is unaffected by not materializing the newline. */
- continue;
- }
+ if (!emit_nl) continue;
t.kind = TOK_NEWLINE;
- t.loc = tloc;
- t.spelling = 0;
- return t;
+ t.loc = nloc;
+ t.text = text_none_ref();
+ *out = t;
+ return;
}
break;
}
- tloc = lex_here(l);
tok_start = l->cur;
ch = (unsigned char)*l->cur;
cc = cclass[(unsigned char)ch];
@@ -809,10 +654,10 @@ Tok lex_next(Lexer* l) {
if (l->had_space) t.flags |= TF_HAS_SPACE;
l->at_bol = 0;
l->had_space = 0;
- t.loc = tloc;
+ t.loc = loc_at(l, tok_start);
- /* §6.4.7 header-name: only valid in #include / #embed argument context.
- * Takes precedence over the string-literal reading of a leading `"`. */
+ /* §6.4.7 header-name in #include / #embed argument context. Takes precedence
+ * over the string-literal reading of a leading `"`. */
if (l->dstate == 2 && (ch == '<' || ch == '"')) {
int closer = (ch == '<') ? '>' : '"';
const char* p = l->cur + 1;
@@ -828,75 +673,74 @@ Tok lex_next(Lexer* l) {
p++;
}
l->cur = p;
- if (l->splices) lex_catchup_splices(l);
t.kind = TOK_HEADER;
- t.spelling = lex_intern(l, tok_start, l->cur);
- t.v.str = t.spelling;
+ t.text = text_src(l, tok_start, l->cur);
l->dstate = 0;
- return t;
+ *out = t;
+ return;
}
- /* Identifier (§6.4.2), or an encoding-prefixed string/char literal whose
- * prefix (L/u/u8/U) is itself an identifier-start byte. The prefix+quote
- * forms are matched first since `L"`/`u'`/... is a literal, not the
- * identifier `L`/`u`. */
+ /* Identifier (§6.4.2), or an encoding-prefixed string/char literal. */
if (cc & CC_IDST) {
if (ch == 'L') {
if (peek(l, 1) == '"') {
finish_str_lit(l, &t, tok_start, 1, 0, TF_STR_WIDE);
- return t;
+ *out = t;
+ return;
}
if (peek(l, 1) == '\'') {
finish_str_lit(l, &t, tok_start, 1, 1, TF_STR_WIDE);
- return t;
+ *out = t;
+ return;
}
} else if (ch == 'u') {
if (peek(l, 1) == '8' && peek(l, 2) == '"') {
finish_str_lit(l, &t, tok_start, 2, 0, TF_STR_U8);
- return t;
+ *out = t;
+ return;
}
if (peek(l, 1) == '"') {
finish_str_lit(l, &t, tok_start, 1, 0, TF_STR_U16);
- return t;
+ *out = t;
+ return;
}
if (peek(l, 1) == '\'') {
finish_str_lit(l, &t, tok_start, 1, 1, TF_STR_U16);
- return t;
+ *out = t;
+ return;
}
} else if (ch == 'U') {
if (peek(l, 1) == '"') {
finish_str_lit(l, &t, tok_start, 1, 0, TF_STR_U32);
- return t;
+ *out = t;
+ return;
}
if (peek(l, 1) == '\'') {
finish_str_lit(l, &t, tok_start, 1, 1, TF_STR_U32);
- return t;
+ *out = t;
+ return;
}
}
finish_ident(l, &t, tok_start);
- return t;
+ *out = t;
+ return;
}
/* Bare string / character literal (no encoding prefix). */
if (ch == '"' || ch == '\'') {
finish_str_lit(l, &t, tok_start, 0, ch == '\'', 0);
- return t;
+ *out = t;
+ return;
}
- /* pp-number (§6.4.8), then classified to TOK_NUM / TOK_FLT. */
+ /* pp-number (§6.4.8), classified to TOK_NUM / TOK_FLT. */
if ((cc & CC_DIGIT) || (ch == '.' && is_digit(peek(l, 1)))) {
const char* text;
size_t k;
scan_pp_number(l);
- /* The spelling is contiguous logical text in the folded buffer (splices
- * were removed at open), so classify and intern straight from there. */
text = tok_start;
k = (size_t)(l->cur - tok_start);
t.kind = (u16)(pp_number_is_float(text, k) ? TOK_FLT : TOK_NUM);
- /* Suffix flags for §6.4.4.1 / §6.4.4.2. The parser dispatches on
- * TF_INT_U/L/LL and TF_FLT_F/L to pick a TY_* tag for the literal,
- * so missing flags would silently coerce `42U`/`42.0f` to plain
- * int/double. */
if (t.kind == TOK_FLT) {
size_t j = k;
while (j > 0) {
@@ -935,21 +779,22 @@ Tok lex_next(Lexer* l) {
break;
}
}
- t.spelling = kit_sym_intern(l->c, (KitSlice){.s = text, .len = k});
+ t.aux = 0;
+ t.text = text_src(l, tok_start, l->cur);
l->dstate = 0;
- return t;
+ *out = t;
+ return;
}
- /* Identifier introduced by a UCN (§6.4.3): `\uXXXX`/`\UXXXXXXXX` as the
- * first element. The '\\' lead is not CC_IDST, so it is matched here. */
+ /* Identifier introduced by a UCN (§6.4.3). */
if (ch == '\\' && ucn_len(l, 0)) {
finish_ident(l, &t, tok_start);
- return t;
+ *out = t;
+ return;
}
- /* Punctuator (§6.4.6) — longest match. `#` and `##` (and their digraph
- * forms `%:` and `%:%:`) become TOK_PP_HASH / TOK_PP_PASTE so PP can
- * recognize directives and the paste operator. */
+ /* Punctuator (§6.4.6) — longest match. `#`/`##` (and digraphs `%:`/`%:%:`)
+ * become TOK_PP_HASH / TOK_PP_PASTE. */
{
int n0 = ch;
int n1 = peek(l, 1);
@@ -958,9 +803,8 @@ Tok lex_next(Lexer* l) {
int adv = 1;
u32 punct = P_NONE;
u16 kind = TOK_PUNCT;
- /* Set in the six digraph branches (<: <% %> :> %: %:%:): their source
- * spelling differs from the canonical punct, so they bypass the punct_sym
- * cache and intern verbatim rather than aliasing the canonical spelling. */
+ /* A digraph spells differently from its canonical punct, so it keeps an
+ * exact source span; canonical puncts reconstruct from the code. */
int digraph = 0;
switch (n0) {
@@ -1025,13 +869,11 @@ Tok lex_next(Lexer* l) {
adv = 2;
punct = '[';
digraph = 1;
- } /* digraph */
- else if (n1 == '%') {
+ } else if (n1 == '%') {
adv = 2;
punct = '{';
digraph = 1;
- } /* digraph */
- else {
+ } else {
adv = 1;
punct = '<';
}
@@ -1138,8 +980,7 @@ Tok lex_next(Lexer* l) {
adv = 2;
punct = '}';
digraph = 1;
- } /* digraph */
- else {
+ } else {
adv = 1;
punct = '%';
}
@@ -1149,8 +990,7 @@ Tok lex_next(Lexer* l) {
adv = 2;
punct = ']';
digraph = 1;
- } /* digraph */
- else {
+ } else {
adv = 1;
punct = ':';
}
@@ -1169,30 +1009,25 @@ Tok lex_next(Lexer* l) {
punct = (u32)n0;
break;
default:
- /* Unknown byte. Surface as a single-char punct so the token
- * stream still progresses; PP/parse may diagnose. */
+ /* Unknown byte: surface as a single-char punct so the stream still
+ * progresses; the digraph path keeps its exact source span. */
adv = 1;
punct = (u32)n0;
+ digraph = 1;
break;
}
l->cur += adv;
- if (l->splices) lex_catchup_splices(l);
t.kind = kind;
- t.v.punct = punct;
- t.spelling = digraph ? lex_intern(l, tok_start, l->cur)
- : punct_spelling(l, punct, tok_start, l->cur);
+ t.aux = punct;
+ t.text = digraph ? text_src(l, tok_start, l->cur) : text_none_ref();
if (kind == TOK_PP_HASH) {
l->dstate = 1;
- /* A `#` at beginning-of-line introduces a directive (the PP only treats
- * a TF_AT_BOL, lex-sourced `#` as a directive). Mark the line so its
- * terminating newline is emitted even when emit_newlines is off, keeping
- * read_directive_line's contract intact. A non-BOL `#` (e.g. `a # b`) is
- * not a directive and leaves directive_line untouched. */
if (t.flags & TF_AT_BOL) l->directive_line = 1;
} else {
l->dstate = 0;
}
- return t;
+ *out = t;
+ return;
}
}
diff --git a/lang/cpp/lex/lex.h b/lang/cpp/lex/lex.h
@@ -3,20 +3,40 @@
#include "cpp_support.h"
+/* C11 lexer boundary (§6.4) — the "lean token" contract.
+ *
+ * The scanner streams tokens out of a borrowed, line-splice-folded source
+ * buffer into a caller-provided Tok slot. Unlike the historical token, the lean
+ * token defers the two expensive per-token operations:
+ *
+ * - SOURCE LOCATION is a compact (file_id, byte_off) reference, not an eager
+ * (file_id, line, col) triple. line/col are recovered on demand from the
+ * owning source's retained buffer + splice table (see TextRef / the PP
+ * SrcInfo registry). The scanner no longer tracks a running line number.
+ *
+ * - EXACT SPELLING is a text reference, not an eagerly interned Sym. Source
+ * tokens name a byte span in the post-splice buffer; synthetic tokens carry
+ * an interned Sym; canonical punctuators carry neither and reconstruct
+ * their spelling from the punctuator code. Identifiers are still interned
+ * eagerly (their Sym is the macro/keyword lookup key) and that Sym lives in
+ * `aux`.
+ */
+
typedef enum TokKind {
TOK_EOF = 0,
- TOK_IDENT, /* v.ident */
- TOK_NUM, /* lit */
- TOK_FLT, /* lit */
- TOK_STR, /* lit; v.str is decoded bytes if target-independent */
- TOK_CHR, /* lit */
- TOK_PUNCT, /* v.punct */
- TOK_PP_HASH, /* # */
- TOK_PP_PASTE, /* ## */
- TOK_HEADER, /* header-name in #include / #embed */
- TOK_NEWLINE, /* visible to PP only */
+ TOK_IDENT, /* aux = interned Sym; text = source span */
+ TOK_NUM, /* text = spelling; flags carry TF_INT_* */
+ TOK_FLT, /* text = spelling; flags carry TF_FLT_* */
+ TOK_STR, /* text = spelling; flags carry TF_STR_* / TF_LITERAL_BAD */
+ TOK_CHR, /* text = spelling; flags carry TF_STR_* / TF_LITERAL_BAD */
+ TOK_PUNCT, /* aux = Punct code; text = NONE (canonical) or span (digraph) */
+ TOK_PP_HASH, /* # — aux = '#' */
+ TOK_PP_PASTE, /* ## — aux = P_HASH_HASH */
+ TOK_HEADER, /* header-name in #include / #embed; text = source span */
+ TOK_NEWLINE, /* visible to PP only */
TOK_KW_FIRST,
- /* C11 keywords are inserted into this range by parse_c via pool */
+ /* Historical keyword range, unused by the parser (keyword classification is
+ * Sym-based). Kept for value stability. */
TOK_KW_LAST = 0x1000,
} TokKind;
@@ -64,57 +84,157 @@ typedef enum Punct {
P_HASH_HASH,
} Punct;
+/* ============================================================
+ * Location reference
+ * ============================================================ */
+
+/* A compact source location: a byte offset into the owning source's logical
+ * (post-line-splice-fold) buffer. line/col are materialized on demand via
+ * pp_materialize_loc / lex_materialize_loc, which consult the source's retained
+ * buffer + splice table and the active #line overlay. file_id == 0 is the
+ * "no location" sentinel (materializes to {0,0,0}). */
+typedef struct LocRef {
+ u32 file_id;
+ u32 off;
+} LocRef;
+
+/* ============================================================
+ * Text reference
+ * ============================================================ */
+
+typedef enum TextKind {
+ TEXT_NONE = 0, /* no spelling (EOF/NEWLINE/placemarker); a canonical
+ * punctuator reconstructs its spelling from `aux` instead */
+ TEXT_SRC = 1, /* a byte span in source `file_id`'s logical buffer */
+ TEXT_SYM = 2, /* an interned Sym (synthetic / predefined tokens) */
+} TextKind;
+
+/* Exact spelling reference. For TEXT_SRC, (file_id, off, len) names a span in
+ * the retained post-splice buffer of that source — valid until pp_free, because
+ * the PP SrcInfo registry retains every source buffer. For TEXT_SYM, `sym`
+ * holds the interned spelling. The two share storage in a small flat struct so
+ * the lean token stays trivially copyable. */
+typedef struct TextRef {
+ u32 kind; /* TextKind */
+ u32 file_id;
+ u32 off;
+ u32 len_or_sym; /* TEXT_SRC: byte length; TEXT_SYM: Sym */
+} TextRef;
+
+/* ============================================================
+ * Token
+ * ============================================================ */
+
typedef struct Tok {
- u16 kind;
- u16 flags;
- SrcLoc loc;
- Sym spelling; /* exact token spelling for diagnostics/#/## */
- union {
- Sym ident;
- Sym str;
- u32 punct;
- } v;
+ u16 kind; /* TokKind (+ PP-internal kinds from pp_priv.h) */
+ u16 flags; /* TokFlag bits */
+ u32 aux; /* IDENT: Sym; PUNCT/PP_HASH/PP_PASTE: Punct code;
+ * PP_PARAM: parameter index; otherwise unused (0) */
+ LocRef loc;
+ TextRef text;
} Tok;
+/* Lean accessors. The parser/PP read identifier identity and punctuator code
+ * straight off `aux` — never a symbol-table probe for punctuators. */
+static inline Sym tok_ident(const Tok* t) { return (Sym)t->aux; }
+static inline u32 tok_punct(const Tok* t) { return t->aux; }
+
+/* Lean-token reference constructors (shared by lexer, PP, and parser). */
+static inline TextRef text_none_ref(void) {
+ TextRef t;
+ t.kind = TEXT_NONE;
+ t.file_id = 0;
+ t.off = 0;
+ t.len_or_sym = 0;
+ return t;
+}
+static inline TextRef text_sym_ref(Sym s) {
+ TextRef t;
+ t.kind = TEXT_SYM;
+ t.file_id = 0;
+ t.off = 0;
+ t.len_or_sym = (u32)s;
+ return t;
+}
+static inline LocRef locref_none(void) {
+ LocRef l;
+ l.file_id = 0;
+ l.off = 0;
+ return l;
+}
+
+/* Canonical spelling for a non-digraph punctuator code (incl. '#'/P_HASH_HASH).
+ * Returns a static NUL-terminated string and writes its length to *len. Used to
+ * reconstruct punctuator text for -E / stringize / paste / diagnostics without
+ * a per-lexer spelling cache. Returns "" (len 0) for an unknown code. */
+const char* punct_canon(u32 punct, u32* len);
+
+/* ============================================================
+ * Source spec
+ * ============================================================ */
+
+typedef enum SourceFlag {
+ SRC_PRIMARY = 1u << 0, /* primary translation unit (allows shebang skip) */
+ SRC_SYSTEM = 1u << 1, /* system header (diagnostics/deps property) */
+ SRC_PARSER_FEED = 1u << 2, /* suppress non-directive newline tokens */
+ SRC_NO_SPLICES = 1u << 3, /* caller proves no backslash-newline splice */
+} SourceFlag;
+
+typedef struct SourceSpec {
+ KitSlice name; /* source name; interned unless name_sym != 0 */
+ Sym name_sym; /* pre-interned name (0 = intern `name`) */
+ const char* bytes;
+ u32 len;
+ u32 flags; /* SourceFlag bits */
+} SourceSpec;
+
+/* ============================================================
+ * Lexer
+ * ============================================================ */
+
typedef struct Lexer Lexer;
-/* lex_open_mem borrows (src, len). The lexer does not copy source bytes;
- * tokens carry SrcLoc + Sym spellings into the global pool, but diagnostics
- * and the preprocessor's directive scanner read from the borrowed buffer.
- *
- * Ownership: a Lexer that has been handed to pp_push_input is owned by PP
- * thereafter — PP closes it on EOF-pop or in pp_free. Callers only call
- * lex_close on a Lexer they have not pushed (e.g. standalone .s assembly).
+/* Open a memory lexer over the borrowed (spec->bytes, spec->len). Registers a
+ * fresh sequential compiler file_id for the source. Phase-2 line splices are
+ * folded up front: with splices the lexer builds a folded copy (lex_owns_buf
+ * true) and records each fold offset; without, it borrows the bytes verbatim.
+ * SRC_NO_SPLICES skips the fold scan (caller-guaranteed splice-free buffer).
*
- * The borrowed (src, len) buffer must outlive the Lexer, which for a pushed
- * Lexer means outliving pp_free. */
-Lexer* lex_open_mem(Compiler*, const char* name, const char* src, size_t len);
-/* As lex_open_mem but takes a pre-interned name Sym, so a fixed name reused
- * across many opens (e.g. "<paste>") is interned just once by the caller. */
-Lexer* lex_open_mem_sym(Compiler*, Sym name, const char* src, size_t len);
-/* Re-point an existing memory lexer at a fresh buffer, reusing its allocation
- * (no per-open alloc/free of the Lexer). Still registers a fresh sequential
- * file_id per call, so the file_id order is identical to lex_open_mem_sym. */
-void lex_reset_mem(Lexer*, Sym name, const char* src, size_t len);
+ * Ownership: a Lexer pushed onto PP is owned by PP (closed at EOF-pop or
+ * pp_free). The borrowed bytes — and, once PP adopts it, the folded buffer —
+ * must outlive pp_free. */
+Lexer* lex_open(Compiler*, const SourceSpec* spec);
+/* Re-point an existing lexer at a fresh buffer, reusing the Lexer allocation.
+ * Registers a fresh file_id. Used by the token-paste re-lex (splice-free). */
+void lex_reset(Lexer*, const SourceSpec* spec);
void lex_close(Lexer*);
-/* Skip a leading `#!` script-interpreter ("shebang") line so an executable
- * C file run via `kit run` lexes cleanly. Call only on a freshly-opened
- * primary source lexer, before any token is pulled; no-op otherwise. */
+/* Skip a leading `#!` shebang line on a freshly-opened primary lexer (no-op
+ * otherwise). Must be called before the first token is pulled. */
void lex_skip_shebang(Lexer*);
-/* Newline-token policy. Default ON: every physical newline surfaces as a
- * TOK_NEWLINE (required by the -E / cpp text reconstruction and the paste /
- * _Pragma sub-lexers). Set OFF on the cc parser-feed lexers (primary source +
- * includes) to suppress NON-directive newlines at the source — the parser
- * never wanted them, so the ~51% of lexer outputs that are newlines are never
- * materialized. The newline that TERMINATES a directive line is emitted in
- * either mode so read_directive_line / #if-skip keep their terminator. */
-void lex_set_emit_newlines(Lexer*, int on);
-
-/* Streaming. Returns TOK_EOF repeatedly at end of input. */
-Tok lex_next(Lexer*);
-SrcLoc lex_loc(const Lexer*);
+/* Streaming. Fills the caller's slot; returns TOK_EOF repeatedly at end. */
+void lex_next(Lexer*, Tok* out);
+
+/* The location of the current cursor (used by PP for directive/diagnostic
+ * positions). */
+LocRef lex_here(const Lexer*);
u32 lex_file_id(const Lexer*);
+/* Buffer adoption — the PP SrcInfo registry takes over the lexer's logical
+ * buffer + splice metadata so line/col and TEXT_SRC spellings remain
+ * materializable after the lexer is popped. After lex_disown_buf the lexer no
+ * longer frees the buffer at close. */
+const char* lex_buf(const Lexer*);
+u32 lex_buf_len(const Lexer*);
+int lex_owns_buf(const Lexer*);
+const u32* lex_splices(const Lexer*, u32* nsplices_out);
+u32 lex_shebang_off(
+ const Lexer*); /* logical offset of the shebang newline, or 0 */
+void lex_disown_buf(Lexer*);
+
+/* Resolve a TEXT_SRC reference into this lexer's own live buffer (used by the
+ * paste/_Pragma re-lex, before the result is interned). */
+KitSlice lex_text_slice(const Lexer*, TextRef text);
+
#endif
diff --git a/lang/cpp/pp/pp.c b/lang/cpp/pp/pp.c
@@ -13,6 +13,7 @@
* pp_new/free, predefined macros, lifecycle, keyword interning. */
#include <kit/compile.h>
+#include <kit/source.h>
#include "pp/pp_priv.h"
@@ -58,17 +59,21 @@ void src_pop(Pp* pp) {
--pp->nsources;
}
+/* Synthesize a lean EOF token into *out. */
+static void tok_eof(Tok* out) {
+ out->kind = TOK_EOF;
+ out->flags = 0;
+ out->aux = 0;
+ out->loc = (LocRef){0, 0};
+ out->text = (TextRef){TEXT_NONE, 0, 0, 0};
+}
+
/* Read next raw token from the top source, writing it through `out`. Sets
* *out to TOK_EOF when the stack is empty. Pops empty buffer/lexer sources
* as it descends. `src_kind_out`, if non-NULL, receives the kind of the
* source the token came from (SRC_LEX vs SRC_BUF). Used by pp_next_raw to
* gate directive recognition to lex-sourced tokens only — a `#` produced by
- * macro expansion never starts a directive (§6.10.3.4 ¶3, covered by
- * `63_rescan_not_directive`).
- *
- * This is the out-pointer form: lex_next already returns via sret, so it
- * writes straight into the caller's slot with no inter-frame copy, and the
- * EOF/empty-stack fallthrough is the only path that has to zero *out. */
+ * macro expansion never starts a directive (§6.10.3.4 ¶3). */
void src_next_raw_into(Pp* pp, Tok* out, HidesetId* hs_out, u8* src_kind_out) {
TokSrc* s;
while ((s = src_top(pp)) != NULL) {
@@ -90,13 +95,7 @@ void src_next_raw_into(Pp* pp, Tok* out, HidesetId* hs_out, u8* src_kind_out) {
return;
}
if (s->scope_top) {
- /* Explicit field zeroing in lieu of memset: byte-identical to the
- * old memset-then-kind EOF token, minus the per-EOF libcall. */
- out->kind = TOK_EOF;
- out->flags = 0;
- out->loc = (SrcLoc){0};
- out->spelling = 0;
- out->v.ident = 0;
+ tok_eof(out);
if (hs_out) *hs_out = HS_EMPTY;
if (src_kind_out) *src_kind_out = SRC_BUF;
return;
@@ -105,7 +104,7 @@ void src_next_raw_into(Pp* pp, Tok* out, HidesetId* hs_out, u8* src_kind_out) {
continue;
}
/* SRC_LEX */
- *out = lex_next(s->lex);
+ lex_next(s->lex, out);
if (out->kind == TOK_EOF) {
if (pp->nsources > 1) {
src_pop(pp);
@@ -125,23 +124,14 @@ void src_next_raw_into(Pp* pp, Tok* out, HidesetId* hs_out, u8* src_kind_out) {
!(out->kind == TOK_PP_HASH && (out->flags & TF_AT_BOL))) {
s->guard_state = GUARD_FAILED;
}
- /* Apply #line line-number delta on the way out so the rest of
- * the pipeline sees user-visible line numbers (matters for
- * __LINE__ expansion and for line-tracking output cursors). */
- if (s->line_delta) {
- out->loc.line = (u32)((i32)out->loc.line + s->line_delta);
- }
+ /* #line numbering is no longer applied here: a lean token carries only a
+ * byte offset, and the #line delta is recorded as a positional overlay
+ * segment (see pp_add_line_seg) applied lazily by pp_materialize_loc. */
if (hs_out) *hs_out = HS_EMPTY;
if (src_kind_out) *src_kind_out = SRC_LEX;
return;
}
- /* Explicit field zeroing in lieu of memset: byte-identical to the old
- * memset-then-kind EOF token, minus the per-EOF libcall. */
- out->kind = TOK_EOF;
- out->flags = 0;
- out->loc = (SrcLoc){0};
- out->spelling = 0;
- out->v.ident = 0;
+ tok_eof(out);
if (hs_out) *hs_out = HS_EMPTY;
if (src_kind_out) *src_kind_out = SRC_LEX;
}
@@ -191,7 +181,7 @@ void push_buf_uniform(Pp* pp, Tok* toks, u32 n, HidesetId hs_uniform) {
* (has_loc_override) reproduces the mutations the old fresh-copy path baked in,
* so the shared body is surfaced byte-for-byte without copying it. */
void push_buf_replay(Pp* pp, const Tok* toks, u32 n, HidesetId hs_uniform,
- SrcLoc loc_override, u16 first_flags_or) {
+ LocRef loc_override, u16 first_flags_or) {
TokSrc s;
memset(&s, 0, sizeof(s));
s.kind = SRC_BUF;
@@ -210,36 +200,30 @@ void push_buf_replay(Pp* pp, const Tok* toks, u32 n, HidesetId hs_uniform,
* Public streaming entries
* ============================================================ */
-/* Defined in pp_expand.c (deliberately not in pp_priv.h): the §C.3a
- * newline-filtering reader. Identical to pp_next_raw_into but drops
- * non-directive newlines one frame lower, so the ~51% of produced tokens that
- * this loop would otherwise read-and-discard never round-trip through the call. */
-void pp_next_into(Pp* pp, Tok* out);
-
-Tok pp_next(Pp* pp) {
- /* Public: filter newlines so consumers like the C parser don't need
- * to handle them. pp_emit_text uses pp_next_raw via its own loop.
+void pp_next_parse(Pp* pp, Tok* out) {
+ /* Public parser-feed stream: filter newlines so consumers like the C parser
+ * don't need to handle them. pp_emit_text uses pp_next_raw via its own loop.
*
* The top-of-loop read uses pp_next_into, which already drops non-directive
* newlines internally; the peek-after-hash (t2) and pragma-swallow reads
- * below stay on pp_next_raw_into because they must still see a TOK_NEWLINE
- * (as a non-pragma push-back token, and as the swallow terminator).
+ * below stay on pp_next_raw because they must still see a TOK_NEWLINE (as a
+ * non-pragma push-back token, and as the swallow terminator).
*
- * Also drop forwarded `#pragma` lines: do_pragma pushes the directive
- * back onto the source stack so pp_emit_text can re-emit it verbatim
- * in cpp mode, but the C parser (cc mode) would see the trailing
- * tokens as stray identifiers. When we see TOK_PP_HASH followed by
- * `pragma`, swallow tokens through the next NEWLINE. */
+ * Also drop forwarded `#pragma` lines: do_pragma pushes the directive back
+ * onto the source stack so pp_emit_text can re-emit it verbatim in cpp mode,
+ * but the C parser (cc mode) would see the trailing tokens as stray
+ * identifiers. When we see TOK_PP_HASH followed by `pragma`, swallow tokens
+ * through the next NEWLINE. */
for (;;) {
Tok t;
pp_next_into(pp, &t);
if (t.kind == TOK_PP_HASH) {
Tok t2;
- pp_next_raw_into(pp, &t2);
- if (t2.kind == TOK_IDENT && t2.v.ident == pp->sym_pragma) {
+ pp_next_raw(pp, &t2);
+ if (t2.kind == TOK_IDENT && tok_ident(&t2) == pp->sym_pragma) {
for (;;) {
Tok tt;
- pp_next_raw_into(pp, &tt);
+ pp_next_raw(pp, &tt);
if (tt.kind == TOK_NEWLINE || tt.kind == TOK_EOF) break;
}
continue;
@@ -251,9 +235,11 @@ Tok pp_next(Pp* pp) {
keep[0] = t2;
hs[0] = HS_EMPTY;
push_buf(pp, keep, hs, 1);
- return t;
+ *out = t;
+ return;
}
- return t;
+ *out = t;
+ return;
}
}
@@ -292,7 +278,8 @@ void pp_emit_text(Pp* pp, Writer* out) {
int at_bol = 1;
for (;;) {
Tok t;
- pp_next_raw_into(pp, &t);
+ KitSlice s;
+ pp_next_raw(pp, &t);
if (t.kind == TOK_EOF) break;
if (t.kind == TOK_NEWLINE) {
pp_emit_stage(out, obuf, sizeof obuf, &on, "\n", 1);
@@ -306,16 +293,248 @@ void pp_emit_text(Pp* pp, Writer* out) {
* don't run together. */
pp_emit_stage(out, obuf, sizeof obuf, &on, " ", 1);
}
- if (t.spelling) {
- KitSlice s = kit_sym_str(pp->pool->c, t.spelling);
- pp_emit_stage(out, obuf, sizeof obuf, &on, s.s, s.len);
- }
+ s = pp_text_slice(pp, &t);
+ if (s.len) pp_emit_stage(out, obuf, sizeof obuf, &on, s.s, s.len);
at_bol = 0;
}
if (on) (void)kit_writer_write(out, obuf, on);
}
/* ============================================================
+ * SrcInfo registry — lazy loc + retained text
+ * ============================================================ */
+
+SrcInfo* pp_srcinfo(Pp* pp, u32 file_id) {
+ if (file_id >= pp->srcinfo_cap) {
+ u32 nc = pp->srcinfo_cap ? pp->srcinfo_cap * 2 : 16;
+ while (nc <= file_id) nc *= 2;
+ pp->srcinfo = (SrcInfo*)pp_xrealloc(
+ pp, pp->srcinfo, sizeof(SrcInfo) * pp->srcinfo_cap,
+ sizeof(SrcInfo) * nc, _Alignof(SrcInfo));
+ memset(pp->srcinfo + pp->srcinfo_cap, 0,
+ sizeof(SrcInfo) * (nc - pp->srcinfo_cap));
+ pp->srcinfo_cap = nc;
+ }
+ return &pp->srcinfo[file_id];
+}
+
+void pp_register_srcinfo(Pp* pp, Lexer* lex) {
+ u32 fid = lex_file_id(lex);
+ SrcInfo* si = pp_srcinfo(pp, fid);
+ u32 nspl = 0;
+ si->buf = lex_buf(lex);
+ si->len = lex_buf_len(lex);
+ si->owns_buf = (u8)lex_owns_buf(lex);
+ si->splices = (u32*)lex_splices(lex, &nspl);
+ si->nsplices = nspl;
+ si->shebang_off = lex_shebang_off(lex);
+ si->line_built = 0;
+ si->line_off = NULL;
+ si->nlines = 0;
+ si->line_cursor = 0;
+ si->segs = NULL;
+ si->nsegs = 0;
+ si->segs_cap = 0;
+ /* PP now owns the buffer + splice table; the lexer must not free them. */
+ lex_disown_buf(lex);
+}
+
+void pp_add_line_seg(Pp* pp, u32 file_id, u32 off, i32 delta, Sym file) {
+ SrcInfo* si = pp_srcinfo(pp, file_id);
+ if (si->nsegs == si->segs_cap) {
+ u32 nc = si->segs_cap ? si->segs_cap * 2 : 4;
+ si->segs =
+ (LineSeg*)pp_xrealloc(pp, si->segs, sizeof(LineSeg) * si->segs_cap,
+ sizeof(LineSeg) * nc, _Alignof(LineSeg));
+ si->segs_cap = nc;
+ }
+ si->segs[si->nsegs].off = off;
+ si->segs[si->nsegs].delta = delta;
+ si->segs[si->nsegs].file = file;
+ ++si->nsegs;
+}
+
+/* Build the physical line-start index for a source by replaying the lexer's
+ * line accounting over the (immutable, fully-folded) buffer. A line-break event
+ * has a "trigger" offset equal to the line_start it establishes: a '\n' at
+ * offset p triggers at p+1; a folded splice at offset s triggers at s. The
+ * lexer processes both in scan order, so the sorted multiset of triggers,
+ * prefixed by line 1's origin (shebang_off, else 0), reproduces line/col
+ * exactly: line(off) = 1 + count(triggers <= off); col = off - origin + 1. */
+static void srcinfo_build_lines(Pp* pp, SrcInfo* si) {
+ const char* b = si->buf;
+ const char* e = b + si->len;
+ const char* p;
+ const char* nl;
+ u32 nnl = 0, k, sp;
+ u32* lo;
+ /* Count newlines via memchr (NEON-accelerated) rather than a scalar byte
+ * loop — this build is the one-time cost paid the first time a loc in this
+ * source is materialized. */
+ for (p = b; (nl = (const char*)memchr(p, '\n', (size_t)(e - p))) != NULL;)
+ ++nnl, p = nl + 1;
+ {
+ u32 cap = 1u + nnl + si->nsplices;
+ lo = (u32*)pp_xrealloc(pp, NULL, 0, sizeof(u32) * cap, _Alignof(u32));
+ }
+ k = 0;
+ lo[k++] = si->shebang_off;
+ sp = 0;
+ for (p = b; (nl = (const char*)memchr(p, '\n', (size_t)(e - p))) != NULL;) {
+ u32 nlv = (u32)(nl - b) + 1u;
+ while (sp < si->nsplices && si->splices[sp] < nlv)
+ lo[k++] = si->splices[sp++];
+ lo[k++] = nlv;
+ p = nl + 1;
+ }
+ while (sp < si->nsplices) lo[k++] = si->splices[sp++];
+ si->line_off = lo;
+ si->nlines = k;
+ si->line_cursor = 0;
+ si->line_built = 1;
+}
+
+/* Largest index k in line_off[0..n) with line_off[k] <= off (n >= 1). */
+static u32 line_index(const u32* lo, u32 n, u32 off) {
+ u32 a = 0, b = n;
+ while (a < b) {
+ u32 m = a + (b - a) / 2u;
+ if (lo[m] <= off)
+ a = m + 1u;
+ else
+ b = m;
+ }
+ return a ? a - 1u : 0u;
+}
+
+/* Cursored line lookup: loc materialization walks the source in near-monotonic
+ * byte order (the parser advances forward), so the previously-returned line is
+ * almost always the answer or one step behind. Hit-in-line and short forward
+ * runs are O(1); a far forward jump or any backward jump falls back to the
+ * binary search. Updates the cursor for the next call. */
+static u32 line_index_cursored(SrcInfo* si, u32 off) {
+ const u32* lo = si->line_off;
+ u32 n = si->nlines;
+ u32 c = si->line_cursor;
+ if (c >= n) c = 0;
+ if (lo[c] <= off) {
+ if (c + 1u >= n || off < lo[c + 1u])
+ return c; /* same line — the hot case */
+ {
+ u32 step = 0;
+ while (c + 1u < n && lo[c + 1u] <= off && step < 8u) ++c, ++step;
+ if (c + 1u < n && lo[c + 1u] <= off) c = line_index(lo, n, off);
+ }
+ } else {
+ c = line_index(lo, n, off); /* backward jump */
+ }
+ si->line_cursor = c;
+ return c;
+}
+
+static i32 seg_delta_at(const SrcInfo* si, u32 off) {
+ i32 d = 0;
+ u32 i;
+ for (i = 0; i < si->nsegs; ++i) {
+ if (si->segs[i].off <= off)
+ d = si->segs[i].delta;
+ else
+ break;
+ }
+ return d;
+}
+
+static Sym seg_file_at(const SrcInfo* si, u32 off) {
+ Sym f = 0;
+ u32 i;
+ for (i = 0; i < si->nsegs; ++i) {
+ if (si->segs[i].off <= off)
+ f = si->segs[i].file;
+ else
+ break;
+ }
+ return f;
+}
+
+SrcLoc pp_materialize_loc(Pp* pp, LocRef loc) {
+ SrcLoc r;
+ SrcInfo* si;
+ u32 k;
+ r.file_id = loc.file_id;
+ r.line = 0;
+ r.col = 0;
+ if (!loc.file_id) return r;
+ si = pp_srcinfo(pp, loc.file_id);
+ if (!si->buf) return r;
+ if (!si->line_built) srcinfo_build_lines(pp, si);
+ k = line_index_cursored(si, loc.off);
+ r.line = (u32)((i32)(k + 1u) + seg_delta_at(si, loc.off));
+ r.col = loc.off - si->line_off[k] + 1u;
+ return r;
+}
+
+u32 pp_phys_line(Pp* pp, LocRef loc) {
+ SrcInfo* si;
+ if (!loc.file_id) return 0;
+ si = pp_srcinfo(pp, loc.file_id);
+ if (!si->buf) return 0;
+ if (!si->line_built) srcinfo_build_lines(pp, si);
+ return line_index(si->line_off, si->nlines, loc.off) + 1u;
+}
+
+Sym pp_materialize_file(Pp* pp, LocRef loc) {
+ SrcInfo* si;
+ Sym f;
+ KitSourceFile sf;
+ if (!loc.file_id) return 0;
+ si = pp_srcinfo(pp, loc.file_id);
+ f = si->buf ? seg_file_at(si, loc.off) : 0;
+ if (f) return f;
+ if (kit_source_file(pp->c, loc.file_id, &sf) == KIT_OK) return sf.name;
+ return 0;
+}
+
+/* ============================================================
+ * Text materialization
+ * ============================================================ */
+
+KitSlice pp_text_slice(Pp* pp, const Tok* t) {
+ KitSlice s;
+ switch (t->text.kind) {
+ case TEXT_SRC: {
+ SrcInfo* si = pp_srcinfo(pp, t->text.file_id);
+ s.s = si->buf + t->text.off;
+ s.len = t->text.len_or_sym;
+ return s;
+ }
+ case TEXT_SYM:
+ return kit_sym_str(pp->pool->c, (Sym)t->text.len_or_sym);
+ default: /* TEXT_NONE: a canonical punctuator reconstructs from its code */
+ if (t->kind == TOK_PUNCT || t->kind == TOK_PP_HASH ||
+ t->kind == TOK_PP_PASTE) {
+ u32 n;
+ s.s = punct_canon(t->aux, &n);
+ s.len = n;
+ return s;
+ }
+ s.s = "";
+ s.len = 0;
+ return s;
+ }
+}
+
+Sym pp_text_intern(Pp* pp, const Tok* t) {
+ if (t->text.kind == TEXT_SYM) return (Sym)t->text.len_or_sym;
+ return kit_sym_intern(pp->pool->c, pp_text_slice(pp, t));
+}
+
+int pp_text_eq_cstr(Pp* pp, const Tok* t, const char* s) {
+ KitSlice sl = pp_text_slice(pp, t);
+ size_t n = s ? strlen(s) : 0;
+ return sl.len == n && (n == 0 || memcmp(sl.s, s, n) == 0);
+}
+
+/* ============================================================
* Lifecycle and configuration
* ============================================================ */
@@ -935,6 +1154,19 @@ void pp_free(Pp* pp) {
/* The reused token-paste lexer is never pushed as a source, so close it
* directly here (it is lazily opened on the first `##`). */
if (pp->paste_lex) lex_close(pp->paste_lex);
+ /* Release the SrcInfo registry: each entry's folded buffer (if PP owns it),
+ * splice table, lazy line index, and #line overlay segments. */
+ if (pp->srcinfo) {
+ u32 i;
+ for (i = 0; i < pp->srcinfo_cap; ++i) {
+ SrcInfo* si = &pp->srcinfo[i];
+ if (si->owns_buf && si->buf) pp_xfree(pp, (char*)si->buf, si->len);
+ if (si->splices) pp_xfree(pp, si->splices, si->nsplices * sizeof(u32));
+ if (si->line_off) pp_xfree(pp, si->line_off, si->nlines * sizeof(u32));
+ if (si->segs) pp_xfree(pp, si->segs, si->segs_cap * sizeof(LineSeg));
+ }
+ pp_xfree(pp, pp->srcinfo, sizeof(SrcInfo) * pp->srcinfo_cap);
+ }
pp_xfree(pp, pp->sources, sizeof(TokSrc) * pp->sources_cap);
MacroTab_fini(&pp->macros);
pp_xfree(pp, pp->hsets, sizeof(Hideset*) * pp->hsets_cap);
@@ -947,20 +1179,20 @@ void pp_free(Pp* pp) {
h->free(h, pp, sizeof(*pp));
}
-void pp_set_suppress_lexer_newlines(Pp* pp, int on) {
- if (pp) pp->suppress_lexer_newlines = on ? 1u : 0u;
-}
-
-void pp_push_input(Pp* pp, Lexer* lex) {
+void pp_push_source(Pp* pp, const SourceSpec* spec) {
TokSrc s;
+ Lexer* lex = lex_open(pp->c, spec);
+ if (!lex) compiler_panic(pp->c, (SrcLoc){0, 0, 0}, "pp: out of memory");
+ if (spec->flags & SRC_PRIMARY) lex_skip_shebang(lex);
+ /* Remember parser-feed mode so #include'd lexers inherit it (they build their
+ * own SourceSpec in do_include and OR in SRC_PARSER_FEED from this flag). */
+ if (spec->flags & SRC_PARSER_FEED) pp->parser_feed = 1;
+ /* Adopt the source buffer + splice table for lazy loc/text materialization.
+ */
+ pp_register_srcinfo(pp, lex);
memset(&s, 0, sizeof(s));
s.kind = SRC_LEX;
s.lex = lex;
- /* cc parser-feed mode: drop non-directive newlines at the lexer (see
- * pp_set_suppress_lexer_newlines). pp_push_input is the entry for the primary
- * source; #include'd lexers apply the same mode at their open site in
- * do_include. */
- if (pp->suppress_lexer_newlines) lex_set_emit_newlines(lex, 0);
src_push(pp, s);
}
@@ -978,21 +1210,23 @@ void pp_add_include_dir(Pp* pp, const char* dir, int system) {
}
void pp_define(Pp* pp, const char* name, const char* body) {
- /* Stage 1+2: build a synthetic source line "name body\n" and run it
- * through the lexer + define machinery so command-line -D matches the
- * normal #define path. */
+ /* Build a synthetic source line "name body\n" and run it through the lexer +
+ * define machinery so command-line -D matches the normal #define path. The
+ * buffer is allocated in pp->arena (retained to pp_free) because the
+ * resulting macro-body tokens carry TEXT_SRC spans into it. */
size_t nlen = name ? kit_slice_cstr(name).len : 0;
size_t blen = body ? kit_slice_cstr(body).len : 0;
- Heap* h = pp_heap(pp);
char* buf;
size_t pos = 0;
+ SourceSpec spec;
+ TokSrc s;
Lexer* lex;
Tok* line;
u32 lineN;
if (!name || !*name) return;
/* "name" + " " + "body" + "\n" */
- buf = (char*)h->alloc(h, nlen + 1 + blen + 1 + 1, 1);
+ buf = (char*)arena_alloc(pp->arena, nlen + 1 + blen + 1, 1);
memcpy(buf + pos, name, nlen);
pos += nlen;
buf[pos++] = ' ';
@@ -1001,21 +1235,21 @@ void pp_define(Pp* pp, const char* name, const char* body) {
pos += blen;
}
buf[pos++] = '\n';
- buf[pos] = 0;
- lex = lex_open_mem(pp->c, "<command-line>", buf, pos);
- {
- TokSrc s;
- memset(&s, 0, sizeof(s));
- s.kind = SRC_LEX;
- s.lex = lex;
- src_push(pp, s);
- }
+ memset(&spec, 0, sizeof(spec));
+ spec.name = KIT_SLICE_LIT("<command-line>");
+ spec.bytes = buf;
+ spec.len = (u32)pos;
+ lex = lex_open(pp->c, &spec);
+ pp_register_srcinfo(pp, lex);
+ memset(&s, 0, sizeof(s));
+ s.kind = SRC_LEX;
+ s.lex = lex;
+ src_push(pp, s);
read_directive_line(pp, &line, &lineN);
do_define(pp, line, lineN);
/* Drain anything trailing (shouldn't be any) and pop the lexer. */
src_pop(pp);
- h->free(h, buf, nlen + 1 + blen + 1 + 1);
}
void pp_undef(Pp* pp, const char* name) {
@@ -1027,10 +1261,10 @@ void pp_undef(Pp* pp, const char* name) {
uint32_t pp_pack_alignment(const Pp* pp) { return pp ? pp->pack_align : 0; }
-void pp_add_include_edge(Pp* pp, u32 includer, u32 included, SrcLoc include_loc,
+void pp_add_include_edge(Pp* pp, u32 includer, u32 included, LocRef include_loc,
int system) {
/* This generic edge-recording entry point has no resolved-dir context, so it
* can only fall back to the spelling form for the resolved-system flag. */
- kit_source_add_include(pp->c, includer, included, include_loc, system,
- system);
+ kit_source_add_include(pp->c, includer, included,
+ pp_materialize_loc(pp, include_loc), system, system);
}
diff --git a/lang/cpp/pp/pp.h b/lang/cpp/pp/pp.h
@@ -17,30 +17,51 @@ void pp_define(Pp*, const char* name, const char* body); /* -D */
void pp_undef(Pp*, const char* name); /* -U */
uint32_t pp_pack_alignment(const Pp*);
-/* Pushes a Lexer onto the include stack. PP takes ownership of the Lexer:
- * it is closed when the input hits EOF and is popped, or in pp_free if it
- * is still on the stack. Callers must not call lex_close on a pushed
- * Lexer. The borrowed source buffer (see lex_open_mem) must outlive
- * pp_free. */
-void pp_push_input(Pp*, Lexer*);
+/* Open `spec` as a new top-of-stack source: PP creates the lexer, registers the
+ * source in the SrcInfo registry (so its buffer + line map outlive the lexer
+ * for lazy loc/text materialization), and pushes it. PP owns the lexer and its
+ * buffer thereafter (freed at EOF-pop / pp_free). The borrowed spec->bytes must
+ * outlive pp_free.
+ *
+ * The SRC_PARSER_FEED flag on the primary source selects cc parser-feed mode:
+ * non-directive newline tokens are suppressed at every lexer (primary + each
+ * #include), since the C parser drops them. The -E / cpp path leaves the flag
+ * clear so newlines surface for text reconstruction. SRC_PRIMARY additionally
+ * applies shebang skipping. */
+void pp_push_source(Pp*, const SourceSpec* spec);
void pp_add_include_edge(Pp*, u32 includer_file_id, u32 included_file_id,
- SrcLoc include_loc, int system);
-
-/* cc parser-feed mode: suppress non-directive newline tokens at every
- * SRC_LEX lexer (primary source + #includes). The C parser drops newlines, so
- * materializing them is pure overhead (~51% of lexer outputs). The -E / cpp
- * path must NOT call this (it reconstructs text from newline tokens). Set once
- * after pp_new, before pushing the primary input. Directive-terminating
- * newlines are still emitted, so directive parsing is unaffected. */
-void pp_set_suppress_lexer_newlines(Pp*, int on);
-
-/* Streaming. Yields preprocessed tokens (macro-expanded, directives consumed).
- */
-Tok pp_next(Pp*);
-
-/* Drains pp_next into `out` as preprocessed C source text: token spellings
- * separated by single spaces where TF_HAS_SPACE is set, with newlines for
- * TF_AT_BOL transitions. Stops on TOK_EOF. Used by the C driver. */
+ LocRef include_loc, int system);
+
+/* Streaming. Both fill a caller-owned slot (no by-value sret).
+ *
+ * pp_next_parse: the C parser stream — macro-expanded, directives consumed,
+ * non-directive newlines absent, forwarded #pragma lines swallowed.
+ * pp_next_raw: the -E / cpp stream — macro-expanded, directives consumed,
+ * TOK_NEWLINE preserved for text reconstruction. */
+void pp_next_parse(Pp*, Tok* out);
+void pp_next_raw(Pp*, Tok* out);
+
+/* Drains pp_next_raw into `out` as preprocessed C source text: token spellings
+ * separated by single spaces where TF_HAS_SPACE/TF_AT_BOL is set, with newlines
+ * for TOK_NEWLINE. Stops on TOK_EOF. Used by the C driver. */
void pp_emit_text(Pp*, Writer* out);
+/* ============================================================
+ * Lazy materialization helpers (loc + text)
+ * ============================================================ */
+
+/* Materialize a LocRef into a full (file_id, line, col) SrcLoc, consulting the
+ * source's retained buffer/splice table and the active #line overlay. */
+SrcLoc pp_materialize_loc(Pp*, LocRef loc);
+
+/* Resolve a token's exact spelling. TEXT_SRC reads the retained source buffer;
+ * TEXT_SYM reads the interned symbol; a TEXT_NONE punctuator reconstructs its
+ * canonical spelling from the punctuator code; otherwise returns an empty
+ * slice. The returned bytes are valid until pp_free. */
+KitSlice pp_text_slice(Pp*, const Tok* t);
+/* As pp_text_slice but interns the result and returns the Sym. */
+Sym pp_text_intern(Pp*, const Tok* t);
+/* True iff the token's spelling equals the NUL-terminated cstr. */
+int pp_text_eq_cstr(Pp*, const Tok* t, const char* s);
+
#endif
diff --git a/lang/cpp/pp/pp_directive.c b/lang/cpp/pp/pp_directive.c
@@ -101,23 +101,23 @@ static i64 parse_pp_int(const char* s, size_t n) {
static void prepass_defined(Pp* pp, const Tok* in, u32 nin, TokVec* out) {
u32 i;
for (i = 0; i < nin; ++i) {
- if (in[i].kind == TOK_IDENT && in[i].v.ident == pp->sym_defined) {
+ if (in[i].kind == TOK_IDENT && tok_ident(&in[i]) == pp->sym_defined) {
int has_paren = 0;
Sym ident = 0;
u32 j = i + 1;
- if (j < nin && in[j].kind == TOK_PUNCT && in[j].v.punct == '(') {
+ if (j < nin && in[j].kind == TOK_PUNCT && tok_punct(&in[j]) == '(') {
has_paren = 1;
++j;
}
if (j >= nin || in[j].kind != TOK_IDENT) {
- compiler_panic(pp->c, in[i].loc,
+ compiler_panic(pp->c, pp_materialize_loc(pp, in[i].loc),
"operand of 'defined' must be an identifier");
}
- ident = in[j].v.ident;
+ ident = tok_ident(&in[j]);
++j;
if (has_paren) {
- if (j >= nin || in[j].kind != TOK_PUNCT || in[j].v.punct != ')') {
- compiler_panic(pp->c, in[i].loc,
+ if (j >= nin || in[j].kind != TOK_PUNCT || tok_punct(&in[j]) != ')') {
+ compiler_panic(pp->c, pp_materialize_loc(pp, in[i].loc),
"expected ')' after 'defined' operand");
}
++j;
@@ -136,8 +136,9 @@ static void prepass_defined(Pp* pp, const Tok* in, u32 nin, TokVec* out) {
t.kind = TOK_NUM;
t.flags = in[i].flags & (TF_AT_BOL | TF_HAS_SPACE);
t.loc = in[i].loc;
- t.spelling = kit_sym_intern(
- pp->pool->c, is_defined ? KIT_SLICE_LIT("1") : KIT_SLICE_LIT("0"));
+ t.aux = 0;
+ t.text = text_intern_ref(
+ pp, is_defined ? kit_slice_cstr("1") : kit_slice_cstr("0"));
tv_push(pp, out, t);
}
i = j - 1;
@@ -174,11 +175,12 @@ static void expand_for_if(Pp* pp, const Tok* in, u32 nin, TokVec* out) {
* has been handled. */
static void replace_remaining_if_identifiers(Pp* pp, TokVec* toks) {
u32 i;
- Sym zero = kit_sym_intern(pp->pool->c, KIT_SLICE_LIT("0"));
+ TextRef zero = text_intern_ref(pp, kit_slice_cstr("0"));
for (i = 0; i < toks->n; ++i) {
if (toks->data[i].kind == TOK_IDENT) {
toks->data[i].kind = TOK_NUM;
- toks->data[i].spelling = zero;
+ toks->data[i].aux = 0;
+ toks->data[i].text = zero;
}
}
}
@@ -201,7 +203,7 @@ static const Tok* ee_peek(EE* e) {
static int ee_match_punct(EE* e, u32 p) {
const Tok* t = ee_peek(e);
- if (t && t->kind == TOK_PUNCT && t->v.punct == p) {
+ if (t && t->kind == TOK_PUNCT && tok_punct(t) == p) {
++e->pos;
return 1;
}
@@ -212,7 +214,7 @@ static i64 ee_primary(EE* e) {
const Tok* t = ee_peek(e);
if (!t) compiler_panic(e->pp->c, e->loc, "#if: missing operand");
if (t->kind == TOK_NUM) {
- KitSlice s = kit_sym_str(e->pp->pool->c, t->spelling);
+ KitSlice s = pp_text_slice(e->pp, t);
++e->pos;
return parse_pp_int(s.s, s.len);
}
@@ -220,28 +222,30 @@ static i64 ee_primary(EE* e) {
/* Treat as the codepoint of the first character (post-decoding
* not implemented; cover the common case of a single ASCII
* char). */
- KitSlice s = kit_sym_str(e->pp->pool->c, t->spelling);
+ KitSlice s = pp_text_slice(e->pp, t);
++e->pos;
if (s.len >= 3 && s.s[0] == '\'') return (unsigned char)s.s[1];
return 0;
}
- if (t->kind == TOK_PUNCT && t->v.punct == '(') {
+ if (t->kind == TOK_PUNCT && tok_punct(t) == '(') {
i64 v;
++e->pos;
v = ee_ternary(e);
if (!ee_match_punct(e, ')')) {
- compiler_panic(e->pp->c, t->loc, "#if: expected ')'");
+ compiler_panic(e->pp->c, pp_materialize_loc(e->pp, t->loc),
+ "#if: expected ')'");
}
return v;
}
- compiler_panic(e->pp->c, t->loc, "#if: unexpected token in expression");
+ compiler_panic(e->pp->c, pp_materialize_loc(e->pp, t->loc),
+ "#if: unexpected token in expression");
return 0;
}
static i64 ee_unary(EE* e) {
const Tok* t = ee_peek(e);
if (t && t->kind == TOK_PUNCT) {
- u32 p = t->v.punct;
+ u32 p = tok_punct(t);
if (p == '!' || p == '-' || p == '+' || p == '~') {
i64 v;
++e->pos;
@@ -359,7 +363,7 @@ static const EeOp* ee_lookup_op(const Tok* t) {
size_t i;
if (!t || t->kind != TOK_PUNCT) return NULL;
for (i = 0; i < sizeof(ee_ops) / sizeof(ee_ops[0]); ++i) {
- if (ee_ops[i].punct == t->v.punct) return &ee_ops[i];
+ if (ee_ops[i].punct == tok_punct(t)) return &ee_ops[i];
}
return NULL;
}
@@ -373,7 +377,7 @@ static i64 ee_binary(EE* e, int min_prec) {
SrcLoc op_loc;
i64 rhs;
if (!op || op->prec < min_prec) break;
- op_loc = t->loc;
+ op_loc = pp_materialize_loc(e->pp, t->loc);
++e->pos;
/* Left-associative: parse the RHS with strictly higher precedence so
* same-prec operators fold left-to-right. */
@@ -400,10 +404,10 @@ static i64 ee_ternary(EE* e) {
/* Header-resolution helpers used by the __has_include pre-pass below; the
* definitions live further down with the #include machinery. */
-static void parse_include_path(Pp* pp, const Tok* line, u32 n, SrcLoc loc,
+static void parse_include_path(Pp* pp, const Tok* line, u32 n, LocRef loc,
char* path_out, size_t cap, int* system_out);
static int find_and_open_include(Pp* pp, const char* path, int system,
- SrcLoc loc, const u8** data, size_t* size,
+ LocRef loc, const u8** data, size_t* size,
char* resolved, size_t resolved_cap,
int* resolved_system_out, u32* next_start_out);
static int find_and_open_include_next(Pp* pp, const char* path, u32 start,
@@ -436,9 +440,9 @@ static void prepass_has_include(Pp* pp, const Tok* in, u32 nin, TokVec* out) {
const char* what;
if (in[i].kind == TOK_IDENT) {
- if (in[i].v.ident == pp->sym_has_include)
+ if (tok_ident(&in[i]) == pp->sym_has_include)
is_has = 1;
- else if (in[i].v.ident == pp->sym_has_include_next)
+ else if (tok_ident(&in[i]) == pp->sym_has_include_next)
is_has = is_next = 1;
}
if (!is_has) {
@@ -448,21 +452,24 @@ static void prepass_has_include(Pp* pp, const Tok* in, u32 nin, TokVec* out) {
what = is_next ? "__has_include_next" : "__has_include";
j = i + 1;
- if (j >= nin || in[j].kind != TOK_PUNCT || in[j].v.punct != '(') {
- compiler_panic(pp->c, in[i].loc, "expected '(' after %s", what);
+ if (j >= nin || in[j].kind != TOK_PUNCT || tok_punct(&in[j]) != '(') {
+ compiler_panic(pp->c, pp_materialize_loc(pp, in[i].loc),
+ "expected '(' after %s", what);
}
++j; /* past '(' */
op_first = &in[j];
depth = 1;
op_n = 0;
for (; j < nin; ++j, ++op_n) {
- if (in[j].kind == TOK_PUNCT && in[j].v.punct == '(') {
+ if (in[j].kind == TOK_PUNCT && tok_punct(&in[j]) == '(') {
++depth;
- } else if (in[j].kind == TOK_PUNCT && in[j].v.punct == ')') {
+ } else if (in[j].kind == TOK_PUNCT && tok_punct(&in[j]) == ')') {
if (--depth == 0) break;
}
}
- if (j >= nin) compiler_panic(pp->c, in[i].loc, "unterminated %s", what);
+ if (j >= nin)
+ compiler_panic(pp->c, pp_materialize_loc(pp, in[i].loc),
+ "unterminated %s", what);
/* in[j] is the matching ')'. */
parse_include_path(pp, op_first, op_n, in[i].loc, path, sizeof(path),
@@ -488,14 +495,15 @@ static void prepass_has_include(Pp* pp, const Tok* in, u32 nin, TokVec* out) {
t.kind = TOK_NUM;
t.flags = in[i].flags & (TF_AT_BOL | TF_HAS_SPACE);
t.loc = in[i].loc;
- t.spelling = kit_sym_intern(
- pp->pool->c, present ? KIT_SLICE_LIT("1") : KIT_SLICE_LIT("0"));
+ t.aux = 0;
+ t.text = text_intern_ref(
+ pp, present ? kit_slice_cstr("1") : kit_slice_cstr("0"));
tv_push(pp, out, t);
i = j; /* resume past the matching ')' */
}
}
-i64 eval_if_expr(Pp* pp, const Tok* line, u32 n, SrcLoc loc) {
+i64 eval_if_expr(Pp* pp, const Tok* line, u32 n, LocRef loc) {
TokVec defs = {0};
TokVec hasinc = {0};
TokVec exp = {0};
@@ -513,7 +521,7 @@ i64 eval_if_expr(Pp* pp, const Tok* line, u32 n, SrcLoc loc) {
e.toks = defs2.data;
e.n = defs2.n;
e.pos = 0;
- e.loc = loc;
+ e.loc = pp_materialize_loc(pp, loc);
v = ee_ternary(&e);
if (e.pos != e.n) {
compiler_panic(pp->c, e.loc,
@@ -548,7 +556,8 @@ static void skip_until_active(Pp* pp) {
if (top->state == IF_INCLUDE && local_depth == 0) return;
t = src_next_raw(pp, NULL, NULL);
if (t.kind == TOK_EOF) {
- compiler_panic(pp->c, top->loc, "unterminated #if / #ifdef");
+ compiler_panic(pp->c, pp_materialize_loc(pp, top->loc),
+ "unterminated #if / #ifdef");
}
if (t.kind != TOK_PP_HASH || (t.flags & TF_AT_BOL) == 0) continue;
@@ -561,7 +570,7 @@ static void skip_until_active(Pp* pp) {
consume_to_newline(pp);
continue;
}
- name = nt.v.ident;
+ name = tok_ident(&nt);
if (name == pp->sym_if || name == pp->sym_ifdef ||
name == pp->sym_ifndef) {
++local_depth;
@@ -581,7 +590,8 @@ static void skip_until_active(Pp* pp) {
consume_to_newline(pp);
if (local_depth > 0) continue;
if (top->has_else) {
- compiler_panic(pp->c, t.loc, "duplicate #else");
+ compiler_panic(pp->c, pp_materialize_loc(pp, t.loc),
+ "duplicate #else");
}
top->has_else = 1;
if (top->state == IF_SEEK_TRUE) {
@@ -638,16 +648,16 @@ static int is_predefined_macro_name(Pp* pp, Sym name) {
* #ifdef / #if / #elif / #else / #endif
* ============================================================ */
-static void do_ifdef(Pp* pp, const Tok* line, u32 n, int negate, SrcLoc loc) {
+static void do_ifdef(Pp* pp, const Tok* line, u32 n, int negate, LocRef loc) {
int defined;
IfFrame f;
if (n < 1 || line[0].kind != TOK_IDENT) {
- compiler_panic(pp->c, loc,
+ compiler_panic(pp->c, pp_materialize_loc(pp, loc),
negate ? "#ifndef: expected identifier"
: "#ifdef: expected identifier");
}
- defined = (mt_get(pp, line[0].v.ident) != NULL) ||
- is_predefined_macro_name(pp, line[0].v.ident);
+ defined = (mt_get(pp, tok_ident(&line[0])) != NULL) ||
+ is_predefined_macro_name(pp, tok_ident(&line[0]));
if (negate) defined = !defined;
memset(&f, 0, sizeof(f));
f.state = defined ? IF_INCLUDE : IF_SEEK_TRUE;
@@ -656,7 +666,7 @@ static void do_ifdef(Pp* pp, const Tok* line, u32 n, int negate, SrcLoc loc) {
if (!defined) skip_until_active(pp);
}
-static void do_if_directive(Pp* pp, const Tok* line, u32 n, SrcLoc loc) {
+static void do_if_directive(Pp* pp, const Tok* line, u32 n, LocRef loc) {
i64 v = eval_if_expr(pp, line, n, loc);
IfFrame f;
memset(&f, 0, sizeof(f));
@@ -666,27 +676,30 @@ static void do_if_directive(Pp* pp, const Tok* line, u32 n, SrcLoc loc) {
if (!v) skip_until_active(pp);
}
-static void do_elif(Pp* pp, SrcLoc loc) {
+static void do_elif(Pp* pp, LocRef loc) {
/* We only reach do_elif from the active branch — meaning the
* preceding group emitted code. So we must skip the rest. */
IfFrame* top = if_top(pp);
- if (!top) compiler_panic(pp->c, loc, "stray #elif");
- if (top->has_else) compiler_panic(pp->c, loc, "#elif after #else");
+ if (!top) compiler_panic(pp->c, pp_materialize_loc(pp, loc), "stray #elif");
+ if (top->has_else)
+ compiler_panic(pp->c, pp_materialize_loc(pp, loc), "#elif after #else");
top->state = IF_DONE;
skip_until_active(pp);
}
-static void do_else(Pp* pp, SrcLoc loc) {
+static void do_else(Pp* pp, LocRef loc) {
IfFrame* top = if_top(pp);
- if (!top) compiler_panic(pp->c, loc, "stray #else");
- if (top->has_else) compiler_panic(pp->c, loc, "duplicate #else");
+ if (!top) compiler_panic(pp->c, pp_materialize_loc(pp, loc), "stray #else");
+ if (top->has_else)
+ compiler_panic(pp->c, pp_materialize_loc(pp, loc), "duplicate #else");
top->has_else = 1;
top->state = IF_DONE;
skip_until_active(pp);
}
-static void do_endif(Pp* pp, SrcLoc loc) {
- if (!if_top(pp)) compiler_panic(pp->c, loc, "stray #endif");
+static void do_endif(Pp* pp, LocRef loc) {
+ if (!if_top(pp))
+ compiler_panic(pp->c, pp_materialize_loc(pp, loc), "stray #endif");
if_pop(pp);
}
@@ -792,7 +805,7 @@ static int open_include_cached(Pp* pp, const char* path, const u8** data_out,
/* Return the includer's directory for resolving a quoted include, or "."
* for in-memory/builtin sources (where CWD is the natural fallback, like
* gcc treats stdin). `dir_out` must point to a buffer of size >= cap. */
-static int includer_dir(Pp* pp, SrcLoc loc, char* dir_out, size_t cap) {
+static int includer_dir(Pp* pp, LocRef loc, char* dir_out, size_t cap) {
KitSourceFile sf;
const char* p = NULL;
size_t plen = 0;
@@ -843,7 +856,7 @@ static int includer_dir(Pp* pp, SrcLoc loc, char* dir_out, size_t cap) {
* skips the includer step, so its key omits the dir. Returns 0 only if
* the key would overflow the scratch buffer (memo simply not used). */
static Sym inc_resolve_key(Pp* pp, const char* path, size_t plen, int system,
- SrcLoc loc) {
+ LocRef loc) {
char key[4096 + 4096 + 8];
size_t pos = 0;
key[pos++] = system ? '<' : '"';
@@ -862,8 +875,7 @@ static Sym inc_resolve_key(Pp* pp, const char* path, size_t plen, int system,
}
memcpy(key + pos, path, plen);
pos += plen;
- return kit_sym_intern(pp->pool->c,
- (KitSlice){.s = key, .len = pos});
+ return kit_sym_intern(pp->pool->c, (KitSlice){.s = key, .len = pos});
}
/* Record a successful dir-search resolution under its spelling key, so the
@@ -895,10 +907,9 @@ static void inc_resolve_record(Pp* pp, Sym rkey, const char* resolved,
* flag, and sets *next_start to one past the winning dir (the resume point
* for a subsequent #include_next from the resolved file). Returns 0 if no
* configured dir holds the header. */
-static int search_inc_dirs(Pp* pp, const char* path, u32 start,
- const u8** data, size_t* size, char* resolved,
- size_t resolved_cap, int* resolved_system_out,
- u32* next_start) {
+static int search_inc_dirs(Pp* pp, const char* path, u32 start, const u8** data,
+ size_t* size, char* resolved, size_t resolved_cap,
+ int* resolved_system_out, u32* next_start) {
char buf[4096];
size_t plen = kit_slice_cstr(path).len;
u32 i;
@@ -922,9 +933,10 @@ static int search_inc_dirs(Pp* pp, const char* path, u32 start,
}
static int find_and_open_include(Pp* pp, const char* path, int system,
- SrcLoc loc, const u8** data, size_t* size,
+ LocRef loc, const u8** data, size_t* size,
char* resolved, size_t resolved_cap,
- int* resolved_system_out, u32* next_start_out) {
+ int* resolved_system_out,
+ u32* next_start_out) {
char buf[4096];
size_t plen = kit_slice_cstr(path).len;
Sym rkey = 0;
@@ -1011,17 +1023,22 @@ static int find_and_open_include_next(Pp* pp, const char* path, u32 start,
* capacity, and writes the unwrapped contents to out. `what` is the
* directive name used in panic messages (e.g. "#include", "#embed"). */
static void header_name_to_path(Pp* pp, KitSlice slc, char* out, size_t cap,
- int* system_out, SrcLoc loc, const char* what) {
+ int* system_out, LocRef loc, const char* what) {
const char* s = slc.s;
size_t slen = slc.len;
- if (slen < 2) compiler_panic(pp->c, loc, "%s: malformed header name", what);
+ if (slen < 2)
+ compiler_panic(pp->c, pp_materialize_loc(pp, loc),
+ "%s: malformed header name", what);
if (s[0] == '<' && s[slen - 1] == '>')
*system_out = 1;
else if (s[0] == '"' && s[slen - 1] == '"')
*system_out = 0;
else
- compiler_panic(pp->c, loc, "%s: malformed header name", what);
- if (slen - 2 + 1 > cap) compiler_panic(pp->c, loc, "%s: path too long", what);
+ compiler_panic(pp->c, pp_materialize_loc(pp, loc),
+ "%s: malformed header name", what);
+ if (slen - 2 + 1 > cap)
+ compiler_panic(pp->c, pp_materialize_loc(pp, loc), "%s: path too long",
+ what);
memcpy(out, s + 1, slen - 2);
out[slen - 2] = 0;
}
@@ -1030,12 +1047,14 @@ static void header_name_to_path(Pp* pp, KitSlice slc, char* out, size_t cap,
* - directly-lexed TOK_HEADER: < ... > or " ... "
* - macro-replaced form: line is macro-expanded, then expected to
* produce either a TOK_STR ("...") or a < ... > sequence. */
-static void parse_include_path(Pp* pp, const Tok* line, u32 n, SrcLoc loc,
+static void parse_include_path(Pp* pp, const Tok* line, u32 n, LocRef loc,
char* path_out, size_t cap, int* system_out) {
- if (n == 0) compiler_panic(pp->c, loc, "#include: missing path");
+ if (n == 0)
+ compiler_panic(pp->c, pp_materialize_loc(pp, loc),
+ "#include: missing path");
if (line[0].kind == TOK_HEADER) {
- KitSlice sl = kit_sym_str(pp->pool->c, line[0].spelling);
+ KitSlice sl = pp_text_slice(pp, &line[0]);
header_name_to_path(pp, sl, path_out, cap, system_out, loc, "#include");
return;
}
@@ -1048,36 +1067,41 @@ static void parse_include_path(Pp* pp, const Tok* line, u32 n, SrcLoc loc,
expand_arg_to_eof(pp, slice, NULL, n, &exp);
if (exp.n == 0) {
- compiler_panic(pp->c, loc, "#include: empty after macro replacement");
+ compiler_panic(pp->c, pp_materialize_loc(pp, loc),
+ "#include: empty after macro replacement");
}
if (exp.data[0].kind == TOK_STR) {
- KitSlice sl = kit_sym_str(pp->pool->c, exp.data[0].spelling);
+ KitSlice sl = pp_text_slice(pp, &exp.data[0]);
const char* s = sl.s;
size_t slen = sl.len;
if (slen < 2 || s[0] != '"' || s[slen - 1] != '"') {
- compiler_panic(pp->c, loc, "#include: malformed string");
+ compiler_panic(pp->c, pp_materialize_loc(pp, loc),
+ "#include: malformed string");
}
if (slen - 2 + 1 > cap) {
- compiler_panic(pp->c, loc, "#include: path too long");
+ compiler_panic(pp->c, pp_materialize_loc(pp, loc),
+ "#include: path too long");
}
memcpy(path_out, s + 1, slen - 2);
path_out[slen - 2] = 0;
*system_out = 0;
return;
}
- if (exp.data[0].kind == TOK_PUNCT && exp.data[0].v.punct == '<') {
+ if (exp.data[0].kind == TOK_PUNCT && tok_punct(&exp.data[0]) == '<') {
size_t pos = 0;
u32 i;
for (i = 1; i < exp.n; ++i) {
size_t slen = 0;
const char* s = NULL;
- if (exp.data[i].kind == TOK_PUNCT && exp.data[i].v.punct == '>') {
+ if (exp.data[i].kind == TOK_PUNCT && tok_punct(&exp.data[i]) == '>') {
break;
}
- if (exp.data[i].spelling) {
- KitSlice sl = kit_sym_str(pp->pool->c, exp.data[i].spelling);
- s = sl.s;
- slen = sl.len;
+ {
+ KitSlice sl = pp_text_slice(pp, &exp.data[i]);
+ if (sl.len) {
+ s = sl.s;
+ slen = sl.len;
+ }
}
if (s && pos + slen + 1 <= cap) {
memcpy(path_out + pos, s, slen);
@@ -1088,13 +1112,13 @@ static void parse_include_path(Pp* pp, const Tok* line, u32 n, SrcLoc loc,
*system_out = 1;
return;
}
- compiler_panic(pp->c, loc,
+ compiler_panic(pp->c, pp_materialize_loc(pp, loc),
"#include: expected \"...\" or <...> after expansion");
}
}
/* Shared core for #include and #include_next (is_next == 1). */
-static void do_include(Pp* pp, const Tok* line, u32 n, SrcLoc loc,
+static void do_include(Pp* pp, const Tok* line, u32 n, LocRef loc,
int is_next) {
char path[4096];
char resolved[4096];
@@ -1135,7 +1159,8 @@ static void do_include(Pp* pp, const Tok* line, u32 n, SrcLoc loc,
resolved, sizeof(resolved), &resolved_system,
&next_start);
if (!found) {
- compiler_panic(pp->c, loc, "%s: file not found: %.*s",
+ compiler_panic(pp->c, pp_materialize_loc(pp, loc),
+ "%s: file not found: %.*s",
is_next ? "#include_next" : "#include",
KIT_SLICE_ARG(kit_slice_cstr(path)));
}
@@ -1152,12 +1177,23 @@ static void do_include(Pp* pp, const Tok* line, u32 n, SrcLoc loc,
if (e && (e->once || (e->guard && mt_get(pp, e->guard) != NULL))) return;
}
- lex = lex_open_mem(pp->c, resolved, (const char*)data, size);
- included_id = lex_file_id(lex);
/* Inherit the parser-feed newline policy from the primary source: in cc mode
* an #include'd file's non-directive newlines are dropped at the lexer too
* (the parser never sees them); -E / cpp leaves them on. */
- if (pp->suppress_lexer_newlines) lex_set_emit_newlines(lex, 0);
+ {
+ SourceSpec spec;
+ memset(&spec, 0, sizeof spec);
+ spec.name = kit_slice_cstr(resolved);
+ spec.bytes = (const char*)data;
+ spec.len = (u32)size;
+ spec.flags = (pp->parser_feed ? SRC_PARSER_FEED : 0u) |
+ (resolved_system ? SRC_SYSTEM : 0u);
+ lex = lex_open(pp->c, &spec);
+ }
+ included_id = lex_file_id(lex);
+ /* Adopt the source buffer + splice table for lazy loc/text materialization
+ * (must happen before the source is pushed). */
+ pp_register_srcinfo(pp, lex);
memset(&s, 0, sizeof(s));
s.kind = SRC_LEX;
@@ -1167,7 +1203,8 @@ static void do_include(Pp* pp, const Tok* line, u32 n, SrcLoc loc,
s.guard_if_base = pp->ifstk_n;
src_push(pp, s);
- kit_source_add_include(pp->c, includer_id, included_id, loc, system_form,
+ kit_source_add_include(pp->c, includer_id, included_id,
+ pp_materialize_loc(pp, loc), system_form,
resolved_system);
}
@@ -1186,7 +1223,7 @@ TokSrc* current_lex_src(Pp* pp) {
return NULL;
}
-static void do_line(Pp* pp, const Tok* line, u32 n, SrcLoc loc) {
+static void do_line(Pp* pp, const Tok* line, u32 n, LocRef loc) {
/* Macro-replace arguments first (a2). */
TokVec exp = {0};
Tok* slice;
@@ -1194,26 +1231,30 @@ static void do_line(Pp* pp, const Tok* line, u32 n, SrcLoc loc) {
i64 target_line;
Sym target_file = 0;
- if (n == 0) compiler_panic(pp->c, loc, "#line: missing arguments");
+ if (n == 0)
+ compiler_panic(pp->c, pp_materialize_loc(pp, loc),
+ "#line: missing arguments");
slice = arena_array(pp->arena, Tok, n);
memcpy(slice, line, sizeof(Tok) * n);
expand_arg_to_eof(pp, slice, NULL, n, &exp);
if (exp.n == 0 || exp.data[0].kind != TOK_NUM) {
- compiler_panic(pp->c, loc, "#line: expected line number");
+ compiler_panic(pp->c, pp_materialize_loc(pp, loc),
+ "#line: expected line number");
}
{
- KitSlice s = kit_sym_str(pp->pool->c, exp.data[0].spelling);
+ KitSlice s = pp_text_slice(pp, &exp.data[0]);
target_line = parse_pp_int(s.s, s.len);
}
if (exp.n >= 2) {
if (exp.data[1].kind != TOK_STR) {
- compiler_panic(pp->c, loc, "#line: file argument must be a string");
+ compiler_panic(pp->c, pp_materialize_loc(pp, loc),
+ "#line: file argument must be a string");
}
{
- KitSlice s = kit_sym_str(pp->pool->c, exp.data[1].spelling);
+ KitSlice s = pp_text_slice(pp, &exp.data[1]);
if (s.len >= 2 && s.s[0] == '"' && s.s[s.len - 1] == '"') {
- /* Destringize to logical bytes (undo \" and \\): file_override is
+ /* Destringize to logical bytes (undo \" and \\): the overlay file is
* stored unescaped, like a real source path, and __FILE__ re-escapes
* it uniformly when expanded. */
char* fbuf = (char*)arena_alloc(pp->arena, s.len, 1);
@@ -1226,14 +1267,18 @@ static void do_line(Pp* pp, const Tok* line, u32 n, SrcLoc loc) {
}
lex_src = current_lex_src(pp);
- if (!lex_src) compiler_panic(pp->c, loc, "#line outside any file");
+ if (!lex_src)
+ compiler_panic(pp->c, pp_materialize_loc(pp, loc),
+ "#line outside any file");
{
- /* The next token (post-directive-NL) currently has lex.line ==
- * <lex's line counter>. Set delta so its user-visible line ==
- * target_line. */
- SrcLoc here = lex_loc(lex_src->lex);
- lex_src->line_delta = (i32)target_line - (i32)here.line;
- if (target_file) lex_src->file_override = target_file;
+ /* Record a positional #line overlay segment: from the cursor offset where
+ * the directive takes effect onward, the reported line is the physical line
+ * plus `delta`, and __FILE__ reports `target_file` (0 = none given). */
+ LocRef effect = lex_here(lex_src->lex);
+ u32 phys = pp_phys_line(pp, effect);
+ i32 delta = (i32)target_line - (i32)phys;
+ pp_add_line_seg(pp, lex_file_id(lex_src->lex), effect.off, delta,
+ target_file);
}
}
@@ -1244,7 +1289,7 @@ static void do_line(Pp* pp, const Tok* line, u32 n, SrcLoc loc) {
/* Push the unmodified directive line back onto the source stack as a
* buffer, so pp_emit_text writes it as-is. SRC_BUF gates directive
* recognition off, so this won't recurse. */
-void emit_pragma_line(Pp* pp, const Tok* line, u32 n, SrcLoc loc) {
+void emit_pragma_line(Pp* pp, const Tok* line, u32 n, LocRef loc) {
TokVec out = {0};
HidesetId* hids;
u32 i;
@@ -1254,15 +1299,16 @@ void emit_pragma_line(Pp* pp, const Tok* line, u32 n, SrcLoc loc) {
hash.kind = TOK_PP_HASH;
hash.flags = TF_AT_BOL;
hash.loc = loc;
- hash.spelling = kit_sym_intern(pp->pool->c, KIT_SLICE_LIT("#"));
+ hash.aux = '#';
+ hash.text = text_none_ref();
tv_push(pp, &out, hash);
memset(&ident, 0, sizeof(ident));
ident.kind = TOK_IDENT;
ident.flags = 0;
ident.loc = loc;
- ident.spelling = pp->sym_pragma_kw;
- ident.v.ident = pp->sym_pragma_kw;
+ ident.aux = pp->sym_pragma_kw;
+ ident.text = text_sym_ref(pp->sym_pragma_kw);
tv_push(pp, &out, ident);
for (i = 0; i < n; ++i) {
@@ -1291,7 +1337,7 @@ static int pragma_num_u32(Pp* pp, const Tok* t, u32* out) {
u32 v = 0;
KitSlice sl;
if (!t || t->kind != TOK_NUM || !out) return 0;
- sl = kit_sym_str(pp->pool->c, t->spelling);
+ sl = pp_text_slice(pp, t);
s = sl.s;
len = sl.len;
if (!s || len == 0) return 0;
@@ -1303,44 +1349,36 @@ static int pragma_num_u32(Pp* pp, const Tok* t, u32* out) {
return 1;
}
-static void handle_pragma_pack(Pp* pp, const Tok* line, u32 n, SrcLoc loc) {
+static void handle_pragma_pack(Pp* pp, const Tok* line, u32 n, LocRef loc) {
u32 i = 0;
if (n < 3 || line[0].kind != TOK_IDENT) return;
- {
- KitSlice sl = kit_sym_str(pp->pool->c, line[0].v.ident);
- const char* s = sl.s;
- size_t len = sl.len;
- if (!s || len != 4 || memcmp(s, "pack", 4) != 0) return;
- }
- if (line[1].kind != TOK_PUNCT || line[1].v.punct != '(') return;
+ if (!pp_text_eq_cstr(pp, &line[0], "pack")) return;
+ if (line[1].kind != TOK_PUNCT || tok_punct(&line[1]) != '(') return;
i = 2;
- if (i < n && line[i].kind == TOK_PUNCT && line[i].v.punct == ')') {
+ if (i < n && line[i].kind == TOK_PUNCT && tok_punct(&line[i]) == ')') {
pp->pack_align = 0;
return;
}
if (i < n && line[i].kind == TOK_IDENT) {
- KitSlice sl = kit_sym_str(pp->pool->c, line[i].v.ident);
- const char* s = sl.s;
- size_t len = sl.len;
- if (s && len == 4 && memcmp(s, "push", 4) == 0) {
+ if (pp_text_eq_cstr(pp, &line[i], "push")) {
if (pp->pack_stack_n <
(u32)(sizeof pp->pack_stack / sizeof pp->pack_stack[0])) {
pp->pack_stack[pp->pack_stack_n++] = pp->pack_align;
} else {
pp_warn(
- pp, loc,
+ pp, pp_materialize_loc(pp, loc),
"#pragma pack(push): pack stack overflow (max %u); push dropped",
(u32)(sizeof pp->pack_stack / sizeof pp->pack_stack[0]));
}
++i;
- if (i < n && line[i].kind == TOK_PUNCT && line[i].v.punct == ',') {
+ if (i < n && line[i].kind == TOK_PUNCT && tok_punct(&line[i]) == ',') {
u32 v = 0;
++i;
if (i < n && pragma_num_u32(pp, &line[i], &v)) pp->pack_align = v;
}
return;
}
- if (s && len == 3 && memcmp(s, "pop", 3) == 0) {
+ if (pp_text_eq_cstr(pp, &line[i], "pop")) {
if (pp->pack_stack_n) pp->pack_align = pp->pack_stack[--pp->pack_stack_n];
return;
}
@@ -1351,10 +1389,12 @@ static void handle_pragma_pack(Pp* pp, const Tok* line, u32 n, SrcLoc loc) {
}
}
-static void do_pragma(Pp* pp, const Tok* line, u32 n, SrcLoc loc) {
+static void do_pragma(Pp* pp, const Tok* line, u32 n, LocRef loc) {
/* #pragma once: mark the current file include-once for the multiple-include
- * optimization (see do_include). Still forwarded to output like any pragma. */
- if (n >= 1 && line[0].kind == TOK_IDENT && line[0].v.ident == pp->sym_once) {
+ * optimization (see do_include). Still forwarded to output like any pragma.
+ */
+ if (n >= 1 && line[0].kind == TOK_IDENT &&
+ tok_ident(&line[0]) == pp->sym_once) {
TokSrc* gls = current_lex_src(pp);
if (gls) gls->once = 1;
}
@@ -1370,12 +1410,12 @@ static void do_pragma(Pp* pp, const Tok* line, u32 n, SrcLoc loc) {
* does its own escape handling for any string literals nested inside. */
static void destringize(Pp* pp, const Tok* str_tok, char* out, size_t cap,
size_t* out_len) {
- KitSlice sl = kit_sym_str(pp->pool->c, str_tok->spelling);
+ KitSlice sl = pp_text_slice(pp, str_tok);
const char* s = sl.s;
size_t slen = sl.len;
size_t i, w = 0;
if (slen < 2 || s[0] != '"' || s[slen - 1] != '"') {
- compiler_panic(pp->c, str_tok->loc,
+ compiler_panic(pp->c, pp_materialize_loc(pp, str_tok->loc),
"_Pragma: argument must be a string literal");
}
for (i = 1; i + 1 < slen; ++i) {
@@ -1385,7 +1425,8 @@ static void destringize(Pp* pp, const Tok* str_tok, char* out, size_t cap,
c = s[i];
}
if (w + 1 >= cap)
- compiler_panic(pp->c, str_tok->loc, "_Pragma: payload too long");
+ compiler_panic(pp->c, pp_materialize_loc(pp, str_tok->loc),
+ "_Pragma: payload too long");
out[w++] = c;
}
out[w] = 0;
@@ -1417,14 +1458,16 @@ int try_expand_pragma_op(Pp* pp, const Tok* invoke) {
str = src_next_raw(pp, &hs, NULL);
}
if (str.kind != TOK_STR) {
- compiler_panic(pp->c, invoke->loc, "_Pragma: expected string literal");
+ compiler_panic(pp->c, pp_materialize_loc(pp, invoke->loc),
+ "_Pragma: expected string literal");
}
{
HidesetId hs;
rp = src_next_raw(pp, &hs, NULL);
}
- if (rp.kind != TOK_PUNCT || rp.v.punct != ')') {
- compiler_panic(pp->c, invoke->loc, "_Pragma: expected ')'");
+ if (rp.kind != TOK_PUNCT || tok_punct(&rp) != ')') {
+ compiler_panic(pp->c, pp_materialize_loc(pp, invoke->loc),
+ "_Pragma: expected ')'");
}
(void)lp;
@@ -1436,13 +1479,23 @@ int try_expand_pragma_op(Pp* pp, const Tok* invoke) {
/* Re-lex into args. Bytes need to live until lex_close; copy into
* arena. */
{
+ SourceSpec spec;
char* arena_buf = (char*)arena_alloc(pp->arena, buf_n + 1, 1);
memcpy(arena_buf, buf, buf_n + 1);
- lex = lex_open_mem(pp->c, "<_Pragma>", arena_buf, buf_n);
+ memset(&spec, 0, sizeof spec);
+ spec.name = kit_slice_cstr("<_Pragma>");
+ spec.bytes = arena_buf;
+ spec.len = (u32)buf_n;
+ lex = lex_open(pp->c, &spec);
}
for (;;) {
- Tok t = lex_next(lex);
+ Tok t;
+ lex_next(lex, &t);
if (t.kind == TOK_EOF || t.kind == TOK_NEWLINE) break;
+ /* These tokens outlive `lex`; a TEXT_SRC spelling points into the lexer's
+ * own buffer, so intern it now while the lexer is still open. */
+ if (t.text.kind == TEXT_SRC)
+ t.text = text_intern_ref(pp, lex_text_slice(lex, t.text));
tv_push(pp, &args, t);
}
lex_close(lex);
@@ -1458,8 +1511,7 @@ int try_expand_pragma_op(Pp* pp, const Tok* invoke) {
static void directive_message(Pp* pp, const Tok* line, u32 n, CharBuf* cb) {
u32 i;
for (i = 0; i < n; ++i) {
- KitSlice slc = line[i].spelling ? kit_sym_str(pp->pool->c, line[i].spelling)
- : KIT_SLICE_NULL;
+ KitSlice slc = pp_text_slice(pp, &line[i]);
const char* s = slc.s;
size_t sl = slc.len;
if (i > 0) cb_putc(pp, cb, ' ');
@@ -1479,17 +1531,17 @@ static void pp_warn(Pp* pp, SrcLoc loc, const char* fmt, ...) {
if (sink) sink->warnings++;
}
-static void do_error(Pp* pp, const Tok* line, u32 n, SrcLoc loc) {
+static void do_error(Pp* pp, const Tok* line, u32 n, LocRef loc) {
CharBuf cb = {0};
directive_message(pp, line, n, &cb);
- compiler_panic(pp->c, loc, "#error: %.*s",
+ compiler_panic(pp->c, pp_materialize_loc(pp, loc), "#error: %.*s",
KIT_SLICE_ARG(kit_slice_cstr(cb.data ? cb.data : "")));
}
-static void do_warning(Pp* pp, const Tok* line, u32 n, SrcLoc loc) {
+static void do_warning(Pp* pp, const Tok* line, u32 n, LocRef loc) {
CharBuf cb = {0};
directive_message(pp, line, n, &cb);
- pp_warn(pp, loc, "#warning: %.*s",
+ pp_warn(pp, pp_materialize_loc(pp, loc), "#warning: %.*s",
KIT_SLICE_ARG(kit_slice_cstr(cb.data ? cb.data : "")));
}
@@ -1497,7 +1549,7 @@ static void do_warning(Pp* pp, const Tok* line, u32 n, SrcLoc loc) {
* #embed (C23, §6.10.* per N3033)
* ============================================================ */
-static void do_embed(Pp* pp, const Tok* line, u32 n, SrcLoc loc) {
+static void do_embed(Pp* pp, const Tok* line, u32 n, LocRef loc) {
char path[4096];
char resolved[4096];
int system_form = 0;
@@ -1511,40 +1563,45 @@ static void do_embed(Pp* pp, const Tok* line, u32 n, SrcLoc loc) {
/* Header-name path: first token. */
u32 arg_start = 0;
- if (n == 0) compiler_panic(pp->c, loc, "#embed: missing path");
+ if (n == 0)
+ compiler_panic(pp->c, pp_materialize_loc(pp, loc), "#embed: missing path");
if (line[0].kind == TOK_HEADER) {
- KitSlice slc = kit_sym_str(pp->pool->c, line[0].spelling);
+ KitSlice slc = pp_text_slice(pp, &line[0]);
header_name_to_path(pp, slc, path, sizeof(path), &system_form, loc,
"#embed");
arg_start = 1;
} else {
- compiler_panic(pp->c, loc, "#embed: header-name argument required");
+ compiler_panic(pp->c, pp_materialize_loc(pp, loc),
+ "#embed: header-name argument required");
}
/* Parse trailing parameters: limit(N), if_empty(...). */
j = arg_start;
while (j < n) {
if (line[j].kind == TOK_IDENT) {
- KitSlice slc = kit_sym_str(pp->pool->c, line[j].v.ident);
+ KitSlice slc = pp_text_slice(pp, &line[j]);
const char* s = slc.s;
size_t sl = slc.len;
if (sl == 5 && memcmp(s, "limit", 5) == 0) {
if (j + 1 >= n || line[j + 1].kind != TOK_PUNCT ||
- line[j + 1].v.punct != '(') {
- compiler_panic(pp->c, loc, "#embed: expected '(' after limit");
+ tok_punct(&line[j + 1]) != '(') {
+ compiler_panic(pp->c, pp_materialize_loc(pp, loc),
+ "#embed: expected '(' after limit");
}
j += 2;
if (j >= n || line[j].kind != TOK_NUM) {
- compiler_panic(pp->c, loc, "#embed: limit() expects an integer");
+ compiler_panic(pp->c, pp_materialize_loc(pp, loc),
+ "#embed: limit() expects an integer");
}
{
- KitSlice s2 = kit_sym_str(pp->pool->c, line[j].spelling);
+ KitSlice s2 = pp_text_slice(pp, &line[j]);
limit_n = parse_pp_int(s2.s, s2.len);
}
++j;
- if (j >= n || line[j].kind != TOK_PUNCT || line[j].v.punct != ')') {
- compiler_panic(pp->c, loc, "#embed: expected ')' to close limit");
+ if (j >= n || line[j].kind != TOK_PUNCT || tok_punct(&line[j]) != ')') {
+ compiler_panic(pp->c, pp_materialize_loc(pp, loc),
+ "#embed: expected ')' to close limit");
}
++j;
continue;
@@ -1553,16 +1610,17 @@ static void do_embed(Pp* pp, const Tok* line, u32 n, SrcLoc loc) {
u32 depth = 0;
u32 start;
if (j + 1 >= n || line[j + 1].kind != TOK_PUNCT ||
- line[j + 1].v.punct != '(') {
- compiler_panic(pp->c, loc, "#embed: expected '(' after if_empty");
+ tok_punct(&line[j + 1]) != '(') {
+ compiler_panic(pp->c, pp_materialize_loc(pp, loc),
+ "#embed: expected '(' after if_empty");
}
j += 2;
start = j;
while (j < n) {
if (line[j].kind == TOK_PUNCT) {
- if (line[j].v.punct == '(')
+ if (tok_punct(&line[j]) == '(')
++depth;
- else if (line[j].v.punct == ')') {
+ else if (tok_punct(&line[j]) == ')') {
if (depth == 0) break;
--depth;
}
@@ -1570,7 +1628,8 @@ static void do_embed(Pp* pp, const Tok* line, u32 n, SrcLoc loc) {
++j;
}
if (j >= n) {
- compiler_panic(pp->c, loc, "#embed: unterminated if_empty");
+ compiler_panic(pp->c, pp_materialize_loc(pp, loc),
+ "#embed: unterminated if_empty");
}
if_empty_toks = arena_array(pp->arena, Tok, j - start ? j - start : 1);
if_empty_n = j - start;
@@ -1579,7 +1638,8 @@ static void do_embed(Pp* pp, const Tok* line, u32 n, SrcLoc loc) {
continue;
}
}
- compiler_panic(pp->c, loc, "#embed: unexpected token in parameter list");
+ compiler_panic(pp->c, pp_materialize_loc(pp, loc),
+ "#embed: unexpected token in parameter list");
}
{
@@ -1590,7 +1650,8 @@ static void do_embed(Pp* pp, const Tok* line, u32 n, SrcLoc loc) {
if (!find_and_open_include(pp, path, system_form, loc, &data, &size,
resolved, sizeof(resolved),
&embed_resolved_system, &embed_next_start)) {
- compiler_panic(pp->c, loc, "#embed: file not found: %.*s",
+ compiler_panic(pp->c, pp_materialize_loc(pp, loc),
+ "#embed: file not found: %.*s",
KIT_SLICE_ARG(kit_slice_cstr(path)));
}
}
@@ -1635,8 +1696,9 @@ static void do_embed(Pp* pp, const Tok* line, u32 n, SrcLoc loc) {
memset(&t, 0, sizeof(t));
t.kind = TOK_NUM;
t.loc = loc;
- t.spelling = kit_sym_intern(
- pp->pool->c, (KitSlice){.s = numbuf, .len = (size_t)nl});
+ t.aux = 0;
+ t.text =
+ text_intern_ref(pp, (KitSlice){.s = numbuf, .len = (size_t)nl});
if (i == 0) t.flags = TF_AT_BOL;
/* Bytes after a comma get a leading space to match
* clang's `, ` separator format. */
@@ -1648,9 +1710,9 @@ static void do_embed(Pp* pp, const Tok* line, u32 n, SrcLoc loc) {
Tok comma;
memset(&comma, 0, sizeof(comma));
comma.kind = TOK_PUNCT;
- comma.v.punct = ',';
+ comma.aux = ',';
comma.loc = loc;
- comma.spelling = kit_sym_intern(pp->pool->c, KIT_SLICE_LIT(","));
+ comma.text = text_none_ref();
tv_push(pp, &out, comma);
}
}
@@ -1668,7 +1730,7 @@ static void do_embed(Pp* pp, const Tok* line, u32 n, SrcLoc loc) {
* Directive dispatch
* ============================================================ */
-void process_directive(Pp* pp, SrcLoc hash_loc) {
+void process_directive(Pp* pp, LocRef hash_loc) {
Tok* line;
u32 n;
Sym name;
@@ -1679,9 +1741,10 @@ void process_directive(Pp* pp, SrcLoc hash_loc) {
return;
}
if (line[0].kind != TOK_IDENT) {
- compiler_panic(pp->c, line[0].loc, "expected directive name after '#'");
+ compiler_panic(pp->c, pp_materialize_loc(pp, line[0].loc),
+ "expected directive name after '#'");
}
- name = line[0].v.ident;
+ name = tok_ident(&line[0]);
/* Multiple-include-guard state machine, run on the current file source
* before dispatch. The only directive that may open a whole-file guard is
@@ -1695,7 +1758,7 @@ void process_directive(Pp* pp, SrcLoc hash_loc) {
if (gls->guard_state == GUARD_START) {
if (name == pp->sym_ifndef && n >= 2 && line[1].kind == TOK_IDENT &&
pp->ifstk_n == gls->guard_if_base) {
- gls->guard_macro = line[1].v.ident;
+ gls->guard_macro = tok_ident(&line[1]);
gls->guard_state = GUARD_IN;
} else {
gls->guard_state = GUARD_FAILED;
@@ -1737,7 +1800,8 @@ void process_directive(Pp* pp, SrcLoc hash_loc) {
else if (name == pp->sym_embed)
do_embed(pp, line + 1, n - 1, hash_loc);
else {
- compiler_panic(pp->c, line[0].loc, "unsupported directive");
+ compiler_panic(pp->c, pp_materialize_loc(pp, line[0].loc),
+ "unsupported directive");
}
/* The controlling #endif (the one returning the if-stack to the file's base
diff --git a/lang/cpp/pp/pp_expand.c b/lang/cpp/pp/pp_expand.c
@@ -3,8 +3,9 @@
#include "pp/pp_priv.h"
-static int body_tokens_equal(const Tok* a, u32 na, const Tok* b, u32 nb);
-static int macros_equal(const Macro* a, const Macro* b);
+static int body_tokens_equal(Pp* pp, const Tok* a, u32 na, const Tok* b,
+ u32 nb);
+static int macros_equal(Pp* pp, const Macro* a, const Macro* b);
/* ============================================================
* Hideset table
@@ -137,14 +138,15 @@ void do_define(Pp* pp, const Tok* line, u32 n) {
Macro* m;
u32 i = 0;
Sym name;
- SrcLoc def_loc;
+ LocRef def_loc;
Macro* existing;
if (i >= n || line[i].kind != TOK_IDENT) {
- compiler_panic(pp->c, n ? line[0].loc : (SrcLoc){0, 0, 0},
+ compiler_panic(pp->c,
+ n ? pp_materialize_loc(pp, line[0].loc) : (SrcLoc){0, 0, 0},
"#define: expected macro name");
}
- name = line[i].v.ident;
+ name = tok_ident(&line[i]);
def_loc = line[i].loc;
++i;
@@ -154,21 +156,21 @@ void do_define(Pp* pp, const Tok* line, u32 n) {
/* Function-like vs object-like: '(' immediately after the name with no
* intervening whitespace. */
- if (i < n && line[i].kind == TOK_PUNCT && line[i].v.punct == '(' &&
+ if (i < n && line[i].kind == TOK_PUNCT && tok_punct(&line[i]) == '(' &&
(line[i].flags & TF_HAS_SPACE) == 0) {
Sym* params = NULL;
u32 pcap = 0, pn = 0;
++i;
m->is_func = 1;
- if (i < n && line[i].kind == TOK_PUNCT && line[i].v.punct == ')') {
+ if (i < n && line[i].kind == TOK_PUNCT && tok_punct(&line[i]) == ')') {
++i;
} else {
for (;;) {
if (i >= n) {
- compiler_panic(pp->c, def_loc,
+ compiler_panic(pp->c, pp_materialize_loc(pp, def_loc),
"#define: unterminated parameter list");
}
- if (line[i].kind == TOK_PUNCT && line[i].v.punct == P_ELLIPSIS) {
+ if (line[i].kind == TOK_PUNCT && tok_punct(&line[i]) == P_ELLIPSIS) {
/* Append a synthetic __VA_ARGS__ param so body-rewrite
* matches the standard identifier directly. */
if (pn == pcap) {
@@ -189,7 +191,7 @@ void do_define(Pp* pp, const Tok* line, u32 n) {
params = nb;
pcap = nc;
}
- params[pn++] = line[i].v.ident;
+ params[pn++] = tok_ident(&line[i]);
++i;
/* GNU named variadic: `args...` — the named parameter itself collects
* the trailing arguments (the body refers to it by name rather than
@@ -199,30 +201,32 @@ void do_define(Pp* pp, const Tok* line, u32 n) {
* follows. Linux UAPI headers use this (e.g. <linux/stddef.h>'s
* __struct_group). */
if (i < n && line[i].kind == TOK_PUNCT &&
- line[i].v.punct == P_ELLIPSIS) {
+ tok_punct(&line[i]) == P_ELLIPSIS) {
m->is_variadic = 1;
++i;
}
} else {
- compiler_panic(pp->c, line[i].loc, "#define: bad parameter list");
+ compiler_panic(pp->c, pp_materialize_loc(pp, line[i].loc),
+ "#define: bad parameter list");
}
if (i >= n) {
- compiler_panic(pp->c, def_loc,
+ compiler_panic(pp->c, pp_materialize_loc(pp, def_loc),
"#define: unterminated parameter list");
}
- if (line[i].kind == TOK_PUNCT && line[i].v.punct == ')') {
+ if (line[i].kind == TOK_PUNCT && tok_punct(&line[i]) == ')') {
++i;
break;
}
if (m->is_variadic) {
- compiler_panic(pp->c, line[i].loc,
+ compiler_panic(pp->c, pp_materialize_loc(pp, line[i].loc),
"#define: '...' must be last parameter");
}
- if (line[i].kind == TOK_PUNCT && line[i].v.punct == ',') {
+ if (line[i].kind == TOK_PUNCT && tok_punct(&line[i]) == ',') {
++i;
continue;
}
- compiler_panic(pp->c, line[i].loc, "#define: expected ',' or ')'");
+ compiler_panic(pp->c, pp_materialize_loc(pp, line[i].loc),
+ "#define: expected ',' or ')'");
}
}
m->params = params;
@@ -234,7 +238,7 @@ void do_define(Pp* pp, const Tok* line, u32 n) {
if (name == pp->sym_defined || name == pp->sym_line__ ||
name == pp->sym_file__ || name == pp->sym_date__ ||
name == pp->sym_time__) {
- compiler_panic(pp->c, def_loc,
+ compiler_panic(pp->c, pp_materialize_loc(pp, def_loc),
"#define of a reserved / predefined name is not allowed");
}
/* Static predefineds are already in the macro table; redefining
@@ -248,7 +252,7 @@ void do_define(Pp* pp, const Tok* line, u32 n) {
* by checking whether it's already in the table — at pp_new the
* first call goes through cleanly. */
if (mt_get(pp, name)) {
- compiler_panic(pp->c, def_loc,
+ compiler_panic(pp->c, pp_materialize_loc(pp, def_loc),
"#define of a mandatory predefined macro is not allowed");
}
}
@@ -265,9 +269,12 @@ void do_define(Pp* pp, const Tok* line, u32 n) {
if (m->is_func && t.kind == TOK_IDENT) {
u32 p;
for (p = 0; p < m->n_params; ++p) {
- if (m->params[p] == t.v.ident) {
+ if (m->params[p] == tok_ident(&t)) {
+ /* Rewrite in place to TOK_PP_PARAM; leave `text` as the original
+ * TEXT_SRC span (the param-name spelling, needed by the
+ * redefinition compare). The parameter index lives in `aux`. */
t.kind = TOK_PP_PARAM;
- t.v.punct = p;
+ t.aux = p;
break;
}
}
@@ -275,8 +282,8 @@ void do_define(Pp* pp, const Tok* line, u32 n) {
/* §6.10.3 ¶5: __VA_ARGS__ outside a variadic macro is
* undefined behavior; we diagnose. */
if (!m->is_variadic && t.kind == TOK_IDENT &&
- t.v.ident == pp->sym_va_args) {
- compiler_panic(pp->c, t.loc,
+ tok_ident(&t) == pp->sym_va_args) {
+ compiler_panic(pp->c, pp_materialize_loc(pp, t.loc),
"__VA_ARGS__ may only appear in a variadic macro body");
}
m->body[j] = t;
@@ -289,8 +296,8 @@ void do_define(Pp* pp, const Tok* line, u32 n) {
existing = mt_get(pp, name);
if (existing) {
- if (!macros_equal(existing, m)) {
- compiler_panic(pp->c, def_loc,
+ if (!macros_equal(pp, existing, m)) {
+ compiler_panic(pp->c, pp_materialize_loc(pp, def_loc),
"macro redefined with different replacement");
}
return;
@@ -301,15 +308,16 @@ void do_define(Pp* pp, const Tok* line, u32 n) {
void do_undef(Pp* pp, const Tok* line, u32 n) {
Sym name;
if (!n || line[0].kind != TOK_IDENT) {
- compiler_panic(pp->c, n ? line[0].loc : (SrcLoc){0, 0, 0},
+ compiler_panic(pp->c,
+ n ? pp_materialize_loc(pp, line[0].loc) : (SrcLoc){0, 0, 0},
"#undef: expected identifier");
}
- name = line[0].v.ident;
+ name = tok_ident(&line[0]);
if (name == pp->sym_defined || name == pp->sym_line__ ||
name == pp->sym_file__ || name == pp->sym_date__ ||
name == pp->sym_time__ || name == pp->sym_stdc__ ||
name == pp->sym_stdc_hosted__ || name == pp->sym_stdc_version__) {
- compiler_panic(pp->c, line[0].loc,
+ compiler_panic(pp->c, pp_materialize_loc(pp, line[0].loc),
"#undef of a mandatory predefined name is not allowed");
}
mt_del(pp, name);
@@ -319,12 +327,23 @@ void do_undef(Pp* pp, const Tok* line, u32 n) {
* Body comparison helpers
* ============================================================ */
-static int body_tokens_equal(const Tok* a, u32 na, const Tok* b, u32 nb) {
+static int body_tokens_equal(Pp* pp, const Tok* a, u32 na, const Tok* b,
+ u32 nb) {
u32 i;
if (na != nb) return 0;
for (i = 0; i < na; ++i) {
+ KitSlice as, bs;
if (a[i].kind != b[i].kind) return 0;
- if (a[i].spelling != b[i].spelling) return 0;
+ /* Two body tokens are equal iff their exact spellings match (§6.10.3 ¶1).
+ * Compare the resolved bytes directly: a TOK_PP_PARAM keeps its original
+ * param-name span, so identical parameters compare equal byte-for-byte,
+ * and interned/source spellings compare the same way the old Sym-equality
+ * did (identical interned spelling == identical bytes). */
+ as = pp_text_slice(pp, &a[i]);
+ bs = pp_text_slice(pp, &b[i]);
+ if (as.len != bs.len || (as.len && memcmp(as.s, bs.s, as.len) != 0)) {
+ return 0;
+ }
/* Whitespace separation must match (§6.10.3 ¶2). The first body
* token's leading-space bit is meaningless (it's whatever was
* between macro name and body); skip i==0 for that bit. */
@@ -337,7 +356,7 @@ static int body_tokens_equal(const Tok* a, u32 na, const Tok* b, u32 nb) {
return 1;
}
-static int macros_equal(const Macro* a, const Macro* b) {
+static int macros_equal(Pp* pp, const Macro* a, const Macro* b) {
if (a->is_func != b->is_func) return 0;
if (a->is_variadic != b->is_variadic) return 0;
if (a->n_params != b->n_params) return 0;
@@ -347,7 +366,7 @@ static int macros_equal(const Macro* a, const Macro* b) {
if (a->params[i] != b->params[i]) return 0;
}
}
- return body_tokens_equal(a->body, a->body_len, b->body, b->body_len);
+ return body_tokens_equal(pp, a->body, a->body_len, b->body, b->body_len);
}
/* ============================================================
@@ -434,7 +453,7 @@ int peek_for_invoke_paren(Pp* pp, int* ws_has_space_out) {
return 0;
}
if (t.flags & TF_HAS_SPACE) saw_ws = 1;
- if (t.kind == TOK_PUNCT && t.v.punct == '(') {
+ if (t.kind == TOK_PUNCT && tok_punct(&t) == '(') {
/* Consumed. The newlines we walked past are whitespace and
* dropped (per spec); they don't go back on the stack. */
*ws_has_space_out = saw_ws;
@@ -465,7 +484,7 @@ void expand_arg_to_eof(Pp* pp, Tok* in, HidesetId* hs, u32 nin, TokVec* out) {
src_push(pp, src);
for (;;) {
- pp_next_raw_into(pp, &t); /* drives macro expansion within this scope */
+ pp_next_raw(pp, &t); /* drives macro expansion within this scope */
if (t.kind == TOK_EOF) break;
if (t.kind == TOK_NEWLINE) {
/* Newlines inside an arg act as whitespace; convert to
@@ -496,7 +515,7 @@ typedef struct ArgList {
/* Collect arguments. Caller has just consumed the opening `(`. Returns the
* close-paren's token (used as the invocation's last source location). */
-static Tok read_invocation_args(Pp* pp, const Macro* m, SrcLoc invoke_loc,
+static Tok read_invocation_args(Pp* pp, const Macro* m, LocRef invoke_loc,
ArgList* out) {
TokVec raw = {0};
HsVec raw_hs = {0};
@@ -518,7 +537,7 @@ static Tok read_invocation_args(Pp* pp, const Macro* m, SrcLoc invoke_loc,
for (;;) {
t = src_next_raw(pp, &hs, NULL);
if (t.kind == TOK_EOF) {
- compiler_panic(pp->c, invoke_loc,
+ compiler_panic(pp->c, pp_materialize_loc(pp, invoke_loc),
"unterminated function-like macro invocation");
}
if (t.kind == TOK_NEWLINE) {
@@ -529,7 +548,7 @@ static Tok read_invocation_args(Pp* pp, const Macro* m, SrcLoc invoke_loc,
}
if (t.kind == TOK_PUNCT) {
- u32 p = t.v.punct;
+ u32 p = tok_punct(&t);
if (p == '(') {
++depth;
} else if (p == ')') {
@@ -598,7 +617,7 @@ done:
if (n_args + 1 == (expected ? expected - 1 : 0)) {
/* off by one — fall through to error */
}
- compiler_panic(pp->c, invoke_loc,
+ compiler_panic(pp->c, pp_materialize_loc(pp, invoke_loc),
"too few arguments to variadic macro invocation");
}
/* Synthesize an empty __VA_ARGS__ if caller passed exactly
@@ -618,7 +637,7 @@ done:
if (n_args != expected) {
/* Spec: arity-0 macro `M()` invoked as `M()` is allowed and
* has 0 args. Above logic produces 0 in that case. */
- compiler_panic(pp->c, invoke_loc,
+ compiler_panic(pp->c, pp_materialize_loc(pp, invoke_loc),
"wrong number of arguments to function-like macro");
}
}
@@ -660,17 +679,15 @@ static void preexpand_args(Pp* pp, ArgList* a) {
* `arg[lo..hi)`. The first token's leading-space flag is ignored (leading
* whitespace stripped). Inside string/char-literal spellings, '"' and '\'
* are escaped. */
-static Tok make_stringize(Pp* pp, const Tok* arg, u32 lo, u32 hi, SrcLoc loc) {
+static Tok make_stringize(Pp* pp, const Tok* arg, u32 lo, u32 hi, LocRef loc) {
CharBuf b = {0};
u32 i;
Tok t;
- Sym sp;
cb_putc(pp, &b, '"');
for (i = lo; i < hi; ++i) {
const Tok* at = &arg[i];
- KitSlice sl =
- at->spelling ? kit_sym_str(pp->pool->c, at->spelling) : KIT_SLICE_NULL;
+ KitSlice sl = pp_text_slice(pp, at);
const char* s = sl.s;
size_t slen = sl.len;
if (i > lo && (at->flags & TF_HAS_SPACE)) cb_putc(pp, &b, ' ');
@@ -686,18 +703,17 @@ static Tok make_stringize(Pp* pp, const Tok* arg, u32 lo, u32 hi, SrcLoc loc) {
}
cb_putc(pp, &b, '"');
- sp = kit_sym_intern(pp->pool->c, (KitSlice){.s = b.data, .len = b.len});
memset(&t, 0, sizeof(t));
t.kind = TOK_STR;
+ t.aux = 0;
t.loc = loc;
- t.spelling = sp;
- t.v.str = sp;
+ t.text = text_intern_ref(pp, (KitSlice){.s = b.data, .len = b.len});
return t;
}
/* Concatenate two token spellings and re-lex into a single token. Empty
* (placemarker) sides collapse to the other side per §6.10.3.3 ¶2. */
-static Tok paste_tokens(Pp* pp, Tok lhs, Tok rhs, SrcLoc loc) {
+static Tok paste_tokens(Pp* pp, Tok lhs, Tok rhs, LocRef loc) {
char buf[1024];
size_t alen = 0, blen = 0;
const char* a;
@@ -708,33 +724,38 @@ static Tok paste_tokens(Pp* pp, Tok lhs, Tok rhs, SrcLoc loc) {
if (lhs.kind == TOK_PP_PLACEMARKER) return rhs;
if (rhs.kind == TOK_PP_PLACEMARKER) return lhs;
- if (lhs.spelling) {
- KitSlice s = kit_sym_str(pp->pool->c, lhs.spelling);
- a = s.s;
- alen = s.len;
- } else {
- a = "";
+ {
+ KitSlice s = pp_text_slice(pp, &lhs);
+ if (s.len) {
+ a = s.s;
+ alen = s.len;
+ } else {
+ a = "";
+ }
}
- if (rhs.spelling) {
- KitSlice s = kit_sym_str(pp->pool->c, rhs.spelling);
- b = s.s;
- blen = s.len;
- } else {
- b = "";
+ {
+ KitSlice s = pp_text_slice(pp, &rhs);
+ if (s.len) {
+ b = s.s;
+ blen = s.len;
+ } else {
+ b = "";
+ }
}
if (alen + blen + 2 > sizeof(buf)) {
- compiler_panic(pp->c, loc, "token paste: spelling too long");
+ compiler_panic(pp->c, pp_materialize_loc(pp, loc),
+ "token paste: spelling too long");
}
if (alen) memcpy(buf, a, alen);
if (blen) memcpy(buf + alen, b, blen);
buf[alen + blen] = '\n';
buf[alen + blen + 1] = 0;
- /* INVARIANT for lex_reset_mem's no-splice fast path: this buffer is two
- * already-folded interned token spellings (a, b) followed by exactly one
- * '\n'. A token spelling never ends in a lone '\\' immediately before that
- * '\n' (a backslash-newline would have been folded out at the original lex),
- * so the buffer is free of `\<newline>` line-splices and the paste lexer
- * (lex_reset_mem → lex_point_at_nosplice) can skip the splice scan. */
+ /* INVARIANT for the re-lex's no-splice fast path: this buffer is two
+ * already-folded token spellings (a, b) followed by exactly one '\n'. A
+ * token spelling never ends in a lone '\\' immediately before that '\n' (a
+ * backslash-newline would have been folded out at the original lex), so the
+ * buffer is free of `\<newline>` line-splices and the paste lexer is opened
+ * with SRC_NO_SPLICES to skip the splice scan. */
/* Intern the "<paste>" name once and reuse a single lexer across pastes: the
* lexer is re-pointed (not reallocated) per paste, but still draws a fresh
@@ -743,21 +764,35 @@ static Tok paste_tokens(Pp* pp, Tok lhs, Tok rhs, SrcLoc loc) {
if (!pp->paste_name_sym) {
pp->paste_name_sym = kit_sym_intern(pp->c, kit_slice_cstr("<paste>"));
}
- if (pp->paste_lex) {
- lex_reset_mem(pp->paste_lex, pp->paste_name_sym, buf, alen + blen + 1);
- } else {
- pp->paste_lex =
- lex_open_mem_sym(pp->c, pp->paste_name_sym, buf, alen + blen + 1);
+ {
+ SourceSpec spec;
+ memset(&spec, 0, sizeof spec);
+ spec.name_sym = pp->paste_name_sym;
+ spec.bytes = buf;
+ spec.len = (u32)(alen + blen + 1);
+ spec.flags = SRC_NO_SPLICES;
+ if (pp->paste_lex) {
+ lex_reset(pp->paste_lex, &spec);
+ } else {
+ pp->paste_lex = lex_open(pp->c, &spec);
+ }
}
lex = pp->paste_lex;
- t1 = lex_next(lex);
- t2 = lex_next(lex);
+ lex_next(lex, &t1);
+ lex_next(lex, &t2);
if (t1.kind == TOK_EOF) {
/* Both empty (shouldn't reach here since we handled placemarkers). */
return lhs;
}
if (t2.kind != TOK_NEWLINE && t2.kind != TOK_EOF) {
- compiler_panic(pp->c, loc, "token pasting yields multiple tokens, invalid");
+ compiler_panic(pp->c, pp_materialize_loc(pp, loc),
+ "token pasting yields multiple tokens, invalid");
+ }
+
+ /* The paste buffer is transient (a stack array re-pointed per paste), so a
+ * source-derived spelling must be interned before the buffer is reused. */
+ if (t1.text.kind == TEXT_SRC) {
+ t1.text = text_intern_ref(pp, lex_text_slice(lex, t1.text));
}
/* Inherit positional flags from LHS (it sat in the same slot). */
@@ -794,11 +829,11 @@ static void subst_phase1_impl(Pp* pp, const Macro* m, ArgList* a,
if (bt->kind == TOK_PP_HASH) {
/* §6.10.3.2: # must be followed by a parameter. */
if (j + 1 >= m->body_len || m->body[j + 1].kind != TOK_PP_PARAM) {
- compiler_panic(pp->c, bt->loc,
+ compiler_panic(pp->c, pp_materialize_loc(pp, bt->loc),
"'#' is not followed by a macro parameter");
}
{
- u32 p = m->body[j + 1].v.punct;
+ u32 p = m->body[j + 1].aux;
u32 lo = a->raw_start[p];
u32 hi = a->raw_start[p + 1];
Tok s = make_stringize(pp, a->raw, lo, hi, invoke->loc);
@@ -814,7 +849,7 @@ static void subst_phase1_impl(Pp* pp, const Macro* m, ArgList* a,
}
}
if (bt->kind == TOK_PP_PARAM) {
- u32 p = bt->v.punct;
+ u32 p = bt->aux;
int adj_paste =
(j > 0 && m->body[j - 1].kind == TOK_PP_PASTE) ||
(j + 1 < m->body_len && m->body[j + 1].kind == TOK_PP_PASTE);
@@ -903,7 +938,7 @@ static void subst_phase2(Pp* pp, const Tok* in, u32 nin, const Tok* invoke,
if (t.kind == TOK_PP_PASTE) {
Tok lhs, rhs, pasted;
if (out->n == 0 || i + 1 >= nin) {
- compiler_panic(pp->c, invoke->loc,
+ compiler_panic(pp->c, pp_materialize_loc(pp, invoke->loc),
"'##' at start or end of replacement list");
}
lhs = out->data[--out->n];
@@ -1006,7 +1041,7 @@ static int try_expand_func_macro(Pp* pp, const Macro* m, const Tok* invoke,
* Defined here; also declared in pp_priv.h so pp.c can call it.
* ============================================================ */
-/* pp_pull_into: shared core of pp_next_raw_into / pp_next_into. Reads from the
+/* pp_pull_into: shared core of pp_next_raw / pp_next_into. Reads from the
* top source into *out, applies macro expansion when an identifier names a
* macro that isn't blue-painted, and consumes directives in-place.
*
@@ -1049,8 +1084,7 @@ static void pp_pull_into(Pp* pp, Tok* out, int skip_nl) {
* guard, so the dead reset on plain-token runs is elided -- byte-identical
* since resetting an already-pristine arena does nothing. */
if (!pp->in_if_expansion &&
- (pp->nsources == 0 ||
- pp->sources[pp->nsources - 1].kind == SRC_LEX)) {
+ (pp->nsources == 0 || pp->sources[pp->nsources - 1].kind == SRC_LEX)) {
if (!kit_arena_is_empty(pp->xarena)) kit_arena_reset(pp->xarena);
}
/* Fast path: top source is a non-exhausted SRC_BUF (the dominant
@@ -1059,8 +1093,7 @@ static void pp_pull_into(Pp* pp, Tok* out, int skip_nl) {
* scope_top-EOF and #line-delta cases never apply to a plain in-bounds
* SRC_BUF read, so they stay on the general (cold) path. */
if (pp->nsources != 0 &&
- (s = &pp->sources[pp->nsources - 1])->kind == SRC_BUF &&
- s->i < s->n) {
+ (s = &pp->sources[pp->nsources - 1])->kind == SRC_BUF && s->i < s->n) {
*out = s->toks[s->i];
hs = s->hs ? s->hs[s->i] : s->hs_uniform;
src_kind = SRC_BUF;
@@ -1120,7 +1153,7 @@ static void pp_pull_into(Pp* pp, Tok* out, int skip_nl) {
* the `defined_skip` field comment in pp_priv.h. */
if (pp->in_if_expansion) {
if (pp->defined_skip == 2) {
- if (out->kind == TOK_PUNCT && out->v.punct == '(') {
+ if (out->kind == TOK_PUNCT && tok_punct(out) == '(') {
pp->defined_skip = 3;
} else if (out->kind == TOK_IDENT) {
/* `defined IDENT` (no parens): mark the operand and reset. */
@@ -1133,19 +1166,19 @@ static void pp_pull_into(Pp* pp, Tok* out, int skip_nl) {
if (out->kind == TOK_IDENT) {
out->flags |= TF_NO_EXPAND;
pp->defined_skip = 4;
- } else if (out->kind == TOK_PUNCT && out->v.punct == ')') {
+ } else if (out->kind == TOK_PUNCT && tok_punct(out) == ')') {
pp->defined_skip = 0;
}
} else if (pp->defined_skip == 4) {
- if (out->kind == TOK_PUNCT && out->v.punct == ')') {
+ if (out->kind == TOK_PUNCT && tok_punct(out) == ')') {
pp->defined_skip = 0;
}
- } else if (out->kind == TOK_IDENT && out->v.ident == pp->sym_defined) {
+ } else if (out->kind == TOK_IDENT && tok_ident(out) == pp->sym_defined) {
pp->defined_skip = 2;
}
}
if (out->kind == TOK_IDENT && (out->flags & TF_NO_EXPAND) == 0) {
- Sym id = out->v.ident;
+ Sym id = tok_ident(out);
/* Dynamic predefined macros: __LINE__ / __FILE__ /
* __DATE__ / __TIME__. Always expand, ignoring the macro
@@ -1153,7 +1186,7 @@ static void pp_pull_into(Pp* pp, Tok* out, int skip_nl) {
if (id == pp->sym_line__) {
char tmp[16], buf[16];
int k = 0, j = 0;
- u32 ln = out->loc.line;
+ u32 ln = pp_materialize_loc(pp, out->loc).line;
if (ln == 0)
buf[k++] = '0';
else {
@@ -1164,25 +1197,17 @@ static void pp_pull_into(Pp* pp, Tok* out, int skip_nl) {
while (j > 0) buf[k++] = tmp[--j];
}
out->kind = TOK_NUM;
- out->spelling =
- kit_sym_intern(pp->pool->c, (KitSlice){.s = buf, .len = (size_t)k});
+ out->aux = 0;
+ out->text = text_intern_ref(pp, (KitSlice){.s = buf, .len = (size_t)k});
return;
}
if (id == pp->sym_file__) {
- TokSrc* ls = current_lex_src(pp);
- Sym name = 0;
+ /* pp_materialize_file resolves the __FILE__ name at this location,
+ * applying any active #line overlay; no current-lexer probe needed. */
+ Sym name = pp_materialize_file(pp, out->loc);
size_t nlen = 0;
const char* nstr = NULL;
char* buf;
- if (ls && ls->file_override) {
- name = ls->file_override;
- } else if (ls) {
- KitSourceFile sf;
- memset(&sf, 0, sizeof(sf));
- if (kit_source_file(pp->c, lex_file_id(ls->lex), &sf) == 0) {
- name = sf.name;
- }
- }
if (name) {
KitSlice s = kit_sym_str(pp->pool->c, name);
nstr = s.s;
@@ -1206,22 +1231,21 @@ static void pp_pull_into(Pp* pp, Tok* out, int skip_nl) {
}
buf[bn++] = '"';
out->kind = TOK_STR;
- out->spelling =
- kit_sym_intern(pp->pool->c, (KitSlice){.s = buf, .len = bn});
- out->v.str = out->spelling;
+ out->aux = 0;
+ out->text = text_intern_ref(pp, (KitSlice){.s = buf, .len = bn});
}
return;
}
if (id == pp->sym_date__) {
out->kind = TOK_STR;
- out->spelling = pp->val_date_str;
- out->v.str = out->spelling;
+ out->aux = 0;
+ out->text = text_sym_ref(pp->val_date_str);
return;
}
if (id == pp->sym_time__) {
out->kind = TOK_STR;
- out->spelling = pp->val_time_str;
- out->v.str = out->spelling;
+ out->aux = 0;
+ out->text = text_sym_ref(pp->val_time_str);
return;
}
if (id == pp->sym__pragma) {
@@ -1247,20 +1271,14 @@ static void pp_pull_into(Pp* pp, Tok* out, int skip_nl) {
}
}
-/* pp_next_raw_into: out-pointer form, every token surfaced (TOK_NEWLINE
- * preserved for pp_emit_text and for directive/pragma line termination). */
-void pp_next_raw_into(Pp* pp, Tok* out) { pp_pull_into(pp, out, /*skip_nl=*/0); }
+/* pp_next_raw: public out-pointer form (pp.h), every token surfaced
+ * (TOK_NEWLINE preserved for pp_emit_text and for directive/pragma line
+ * termination). The mutual-recursion entry: expand_arg_to_eof and the -E loop
+ * call it; it drives directives and expansion. */
+void pp_next_raw(Pp* pp, Tok* out) { pp_pull_into(pp, out, /*skip_nl=*/0); }
-/* pp_next_into: out-pointer form for the C-parser feed (pp_next). Identical to
- * pp_next_raw_into but drops non-directive newlines internally, so the 51% of
- * produced tokens that pp_next would otherwise discard never escape this frame.
- * Declared locally (extern) in pp.c — kept out of pp_priv.h on purpose. */
+/* pp_next_into: out-pointer form for the C-parser feed (pp_next_parse).
+ * Identical to pp_next_raw but drops non-directive newlines internally, so the
+ * 51% of produced tokens that pp_next_parse would otherwise discard never
+ * escape this frame. Declared in pp_priv.h. */
void pp_next_into(Pp* pp, Tok* out) { pp_pull_into(pp, out, /*skip_nl=*/1); }
-
-/* Thin by-value shim: the mutual-recursion entry (expand_arg_to_eof) and any
- * external caller use this; the hot -E loops call pp_next_raw_into directly. */
-Tok pp_next_raw(Pp* pp) {
- Tok t;
- pp_next_raw_into(pp, &t);
- return t;
-}
diff --git a/lang/cpp/pp/pp_priv.h b/lang/cpp/pp/pp_priv.h
@@ -5,11 +5,10 @@
#ifndef KIT_PP_PRIV_H
#define KIT_PP_PRIV_H
+#include <kit/support/symtab.h>
#include <stdlib.h>
#include <string.h>
-#include <kit/support/symtab.h>
-
#include "cpp_support.h"
#include "pp/pp.h"
@@ -27,7 +26,7 @@
typedef struct Macro {
Sym name;
- SrcLoc def_loc;
+ LocRef def_loc;
u8 is_func;
u8 is_variadic;
/* Pure cache of the body (NOT part of macro identity): 1 iff the body
@@ -37,7 +36,7 @@ typedef struct Macro {
u8 pad[1];
u32 n_params;
Sym* params; /* parameter names */
- Tok* body; /* body tokens; TOK_PP_PARAM kind + v.punct=idx */
+ Tok* body; /* body tokens; TOK_PP_PARAM kind + aux=param idx */
u32 body_len;
} Macro;
@@ -51,7 +50,8 @@ typedef u32 HidesetId;
typedef struct Hideset {
u32 n;
- u32 hash; /* content hash of names[0..n) — dedup index key (see hs_register) */
+ u32 hash; /* content hash of names[0..n) — dedup index key (see hs_register)
+ */
Sym names[1]; /* flexible; allocated with extra trailing slots */
} Hideset;
@@ -102,15 +102,12 @@ typedef struct TokSrc {
HidesetId* hs;
HidesetId hs_uniform;
/* Per-token loc applied when has_loc_override (see scope_top block). */
- SrcLoc loc_override;
+ LocRef loc_override;
u32 i;
u32 n;
- /* #line state (SRC_LEX only). line_delta is added to every emitted
- * token's loc.line on its way out so __LINE__ and the output cursor
- * see user-visible numbering. file_override is the Sym (without
- * surrounding quotes) used by __FILE__ when set. */
- i32 line_delta;
- Sym file_override;
+ /* #line state is recorded positionally in the source's SrcInfo overlay
+ * segments (see SrcInfo / pp_materialize_loc), not on the frame: a lean
+ * token carries only a byte offset, and line/file are resolved on demand. */
/* SRC_LEX only: the index into pp->inc_dirs from which a `#include_next`
* (or `__has_include_next`) appearing in this file begins its search —
* i.e. one past the search dir this file was itself found in. 0 for the
@@ -139,15 +136,52 @@ typedef struct IfFrame {
u8 state;
u8 has_else;
u8 pad[2];
- SrcLoc loc;
+ LocRef loc;
} IfFrame;
+/* ============================================================
+ * Source-info registry (lazy loc + retained text)
+ * ============================================================ */
+
+/* A #line overlay segment: from byte offset `off` onward (until the next
+ * segment), the reported line number is the physical line plus `delta`, and
+ * __FILE__ reports `file` (0 = the source's registry name). Recorded by do_line
+ * in offset order; consulted by pp_materialize_loc / pp_materialize_file. */
+typedef struct LineSeg {
+ u32 off;
+ i32 delta;
+ Sym file;
+} LineSeg;
+
+/* Per-source retained metadata, keyed by file_id. The folded logical buffer and
+ * splice table are retained until pp_free so that (a) TEXT_SRC spellings stay
+ * resolvable after the lexer is popped (macro bodies, parser lookahead/replay),
+ * and (b) line/col can be reconstructed lazily from a byte offset. The line
+ * index is built on first materialization by scanning the buffer for '\n' and
+ * merging the splice fold points. */
+typedef struct SrcInfo {
+ const char* buf; /* folded logical buffer (borrowed/arena/owned) */
+ u32 len;
+ u8 owns_buf; /* PP frees buf at pp_free (a folded heap copy) */
+ u8 line_built; /* line_off has been computed */
+ u32 shebang_off;
+ u32* splices; /* fold offsets; PP frees at pp_free */
+ u32 nsplices;
+ u32* line_off; /* line_off[k] = start offset of physical line k+1 */
+ u32 nlines;
+ u32 line_cursor; /* last line index returned — loc materialization is
+ * near-monotonic, so this makes the common lookup O(1) */
+ LineSeg* segs; /* #line overlay, offset-ordered */
+ u32 nsegs;
+ u32 segs_cap;
+} SrcInfo;
+
/* Sym-keyed hashmaps below (include caches). See core/hashmap.h. */
#include <kit/support/hashmap.h>
/* IncCache = Sym(resolved-path) -> cached header bytes. The same guarded
* header included many times across a TU is read from disk once; later
- * inclusions reuse the arena-resident bytes (lex_open_mem only borrows
+ * inclusions reuse the arena-resident bytes (lex_open only borrows
* them and the pp arena keeps them alive until pp_free), saving the
* open+fstat+read+close + malloc/memcpy/free per repeat. Keyed strictly
* on the resolved-path STRING so distinct spellings/symlinks never
@@ -172,8 +206,8 @@ KIT_HASHMAP_DEFINE(IncCache, Sym, IncEntry, inc_hash_);
* exact one a fresh search would produce — byte-identical resolved path
* and resolved_system flag feeding source_add_include / DWARF / -M. */
typedef struct IncResolved {
- Sym path; /* interned resolved path string */
- u8 system; /* the resolved_system flag (winning -isystem) */
+ Sym path; /* interned resolved path string */
+ u8 system; /* the resolved_system flag (winning -isystem) */
u32 next_start; /* inc_dirs index a #include_next from the */
/* resolved file starts at (see TokSrc) */
} IncResolved;
@@ -193,6 +227,12 @@ struct Pp {
u32 nsources;
u32 sources_cap;
+ /* Per-file retained metadata, indexed by compiler file_id (dense from 1).
+ * Holds the retained source buffer + splice table + lazy line index + #line
+ * overlay so loc/text materialize after the lexer is popped. */
+ SrcInfo* srcinfo;
+ u32 srcinfo_cap;
+
/* Macro table (Sym-indexed dense; value = Macro*, NULL = not a macro). */
MacroTab macros;
@@ -325,12 +365,12 @@ struct Pp {
* doesn't count directive-internal tokens (e.g. the `ifndef` / macro name of
* the guard itself) as file content. */
u8 reading_directive;
- /* cc parser-feed mode: when set, every SRC_LEX lexer (the primary source and
- * each #include'd file) is opened with lex_set_emit_newlines(lex, 0) so
- * non-directive newlines are suppressed at the lexer (the parser drops them
- * anyway). The -E / cpp path leaves this 0 so newlines surface for text
- * reconstruction. Set via pp_set_suppress_lexer_newlines after pp_new. */
- u8 suppress_lexer_newlines;
+ /* cc parser-feed mode: set when the primary source is pushed with
+ * SRC_PARSER_FEED, so every SRC_LEX lexer (primary + each #include) is opened
+ * with newline suppression (the parser drops non-directive newlines). The
+ * -E / cpp path leaves this clear so newlines surface for text
+ * reconstruction. */
+ u8 parser_feed;
};
/* ============================================================
@@ -354,6 +394,16 @@ static inline void pp_xfree(Pp* pp, void* p, size_t n) {
}
/* ============================================================
+ * Lean-token constructors (synthetic tokens)
+ * ============================================================ */
+
+/* text_none_ref / text_sym_ref / locref_none live in lex.h (shared with the
+ * parser). text_intern_ref additionally interns, so it needs the Pp pool. */
+static inline TextRef text_intern_ref(Pp* pp, KitSlice sl) {
+ return text_sym_ref(kit_sym_intern(pp->pool->c, sl));
+}
+
+/* ============================================================
* Token-vector helpers
* ============================================================ */
@@ -436,8 +486,8 @@ static inline void cb_putc(Pp* pp, CharBuf* b, char c) {
* ============================================================ */
/* --- pp.c (source stack) → pp_expand.c, pp_directive.c --- */
-/* Out-pointer form (hot path, no 24B sret round-trip); src_next_raw is the
- * by-value shim over it for the cold/general callers. */
+/* Out-pointer form (hot path); src_next_raw is the by-value shim over it for
+ * the cold/general callers. */
void src_next_raw_into(Pp* pp, Tok* out, HidesetId* hs_out, u8* src_kind_out);
Tok src_next_raw(Pp* pp, HidesetId* hs_out, u8* src_kind_out);
void src_push(Pp* pp, TokSrc s);
@@ -446,17 +496,30 @@ void push_buf(Pp* pp, Tok* toks, HidesetId* hs, u32 n);
void push_buf_uniform(Pp* pp, Tok* toks, u32 n, HidesetId hs_uniform);
/* Push an immutable, read-only token buffer (e.g. a no-`##` macro body) for
* pointer-replay. The buffer is never written through s->toks; per-read
- * loc/flags overrides reproduce the mutations the old body-copy path baked in. */
+ * loc/flags overrides reproduce the mutations the old body-copy path baked in.
+ */
void push_buf_replay(Pp* pp, const Tok* toks, u32 n, HidesetId hs_uniform,
- SrcLoc loc_override, u16 first_flags_or);
-
-/* pp_next_raw is the mutual-recursion entry: expand_arg_to_eof calls it,
- * and pp_next_raw drives directives and expansion. Declared non-static so
- * pp_expand.c can call it without a forward decl each time. pp_next_raw_into
- * is the out-pointer form the hot -E loops call directly; pp_next_raw is the
- * by-value shim. */
-void pp_next_raw_into(Pp* pp, Tok* out);
-Tok pp_next_raw(Pp* pp);
+ LocRef loc_override, u16 first_flags_or);
+
+/* pp_next_raw (public, in pp.h) is the mutual-recursion entry:
+ * expand_arg_to_eof and the -E loop call it; it drives directives and
+ * expansion. pp_next_into is the internal newline-dropping pull (skip_nl=1)
+ * used by pp_next_parse. */
+void pp_next_into(Pp* pp, Tok* out);
+
+/* --- SrcInfo registry (pp.c) → pp_directive.c --- */
+/* Get (growing as needed) the SrcInfo for a file_id. */
+SrcInfo* pp_srcinfo(Pp* pp, u32 file_id);
+/* Register a freshly-opened, about-to-be-pushed lexer: adopt its folded buffer
+ * + splice table + shebang offset into the SrcInfo registry (retained to
+ * pp_free) so loc/text stay materializable after the lexer is popped. */
+void pp_register_srcinfo(Pp* pp, Lexer* lex);
+/* Record a #line overlay segment (effect offset, line delta, file name sym). */
+void pp_add_line_seg(Pp* pp, u32 file_id, u32 off, i32 delta, Sym file);
+/* Physical line at a location, ignoring #line overlay (used by do_line). */
+u32 pp_phys_line(Pp* pp, LocRef loc);
+/* __FILE__ name sym at a location (overlay file if set, else registry name). */
+Sym pp_materialize_file(Pp* pp, LocRef loc);
/* --- pp_expand.c → pp.c, pp_directive.c --- */
HidesetId hs_add(Pp* pp, HidesetId id, Sym s);
@@ -477,11 +540,11 @@ static inline void mt_del(Pp* pp, Sym name) {
}
/* --- pp_directive.c → pp_expand.c --- */
-i64 eval_if_expr(Pp* pp, const Tok* line, u32 n, SrcLoc loc);
-void process_directive(Pp* pp, SrcLoc hash_loc);
+i64 eval_if_expr(Pp* pp, const Tok* line, u32 n, LocRef loc);
+void process_directive(Pp* pp, LocRef hash_loc);
/* --- pp_directive.c internal helpers called from pp_expand.c --- */
-void emit_pragma_line(Pp* pp, const Tok* line, u32 n, SrcLoc loc);
+void emit_pragma_line(Pp* pp, const Tok* line, u32 n, LocRef loc);
int peek_for_invoke_paren(Pp* pp, int* ws_has_space_out);
int try_expand_pragma_op(Pp* pp, const Tok* invoke);