pp_directive.c (60678B)
1 /* pp_directive.c — if-stack, PP expression evaluator, #include search/open, 2 * #line, #pragma, #error, #embed, and directive dispatch. */ 3 4 #include "pp/pp_priv.h" 5 6 static void destringize(Pp* pp, const Tok* str_tok, char* out, size_t cap, 7 size_t* out_len); 8 static void pp_warn(Pp* pp, SrcLoc loc, const char* fmt, ...); 9 10 /* ============================================================ 11 * If-stack 12 * ============================================================ */ 13 14 static void if_push(Pp* pp, IfFrame f) { 15 if (pp->ifstk_n == pp->ifstk_cap) { 16 u32 nc = pp->ifstk_cap ? pp->ifstk_cap * 2 : 4; 17 pp->ifstk = pp_xrealloc(pp, pp->ifstk, sizeof(IfFrame) * pp->ifstk_cap, 18 sizeof(IfFrame) * nc, _Alignof(IfFrame)); 19 pp->ifstk_cap = nc; 20 } 21 pp->ifstk[pp->ifstk_n++] = f; 22 } 23 24 static IfFrame* if_top(Pp* pp) { 25 return pp->ifstk_n ? &pp->ifstk[pp->ifstk_n - 1] : NULL; 26 } 27 28 static void if_pop(Pp* pp) { 29 if (pp->ifstk_n) --pp->ifstk_n; 30 } 31 32 /* ============================================================ 33 * Directive line reader 34 * ============================================================ */ 35 36 /* Read tokens up through (and including) the next TOK_NEWLINE / TOK_EOF. 37 * Drops the newline; collected tokens are arena-allocated and returned via 38 * *out_toks/out_n. */ 39 void read_directive_line(Pp* pp, Tok** out_toks, u32* out_n) { 40 Tok* buf = NULL; 41 u32 cap = 0, n = 0; 42 Tok t; 43 u8 saved_rd = pp->reading_directive; 44 pp->reading_directive = 1; 45 for (;;) { 46 t = src_next_raw(pp, NULL); 47 if (t.kind == TOK_NEWLINE || t.kind == TOK_EOF) break; 48 if (n == cap) { 49 u32 nc = cap ? cap * 2 : 8; 50 Tok* nb = (Tok*)arena_alloc(pp->arena, sizeof(Tok) * nc, _Alignof(Tok)); 51 if (cap) memcpy(nb, buf, sizeof(Tok) * cap); 52 buf = nb; 53 cap = nc; 54 } 55 buf[n++] = t; 56 } 57 pp->reading_directive = saved_rd; 58 *out_toks = buf; 59 *out_n = n; 60 } 61 62 /* ============================================================ 63 * PP expression evaluator (§6.10.1) 64 * ============================================================ */ 65 66 /* Parse a C integer constant from a pp-number's spelling. Suffixes (u, l, 67 * etc.) are ignored. Recognizes decimal, hex (0x...), and octal (0...). */ 68 static i64 parse_pp_int(const char* s, size_t n) { 69 int base = 10; 70 size_t i = 0; 71 u64 val = 72 0; /* unsigned: #if arithmetic wraps on overflow, signed would be UB */ 73 if (n >= 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { 74 base = 16; 75 i = 2; 76 } else if (n >= 1 && s[0] == '0') { 77 base = 8; 78 i = 1; 79 } 80 for (; i < n; ++i) { 81 char c = s[i]; 82 int d; 83 if (c >= '0' && c <= '9') 84 d = c - '0'; 85 else if (base == 16 && c >= 'a' && c <= 'f') 86 d = c - 'a' + 10; 87 else if (base == 16 && c >= 'A' && c <= 'F') 88 d = c - 'A' + 10; 89 else 90 break; 91 if (d >= base) break; 92 val = val * (u64)base + (u64)d; 93 } 94 return (i64)val; 95 } 96 97 /* Pre-pass: replace `defined X` / `defined ( X )` with a 0/1 pp-number, 98 * preserving the rest of the token sequence. The operand of `defined` is 99 * NOT macro-expanded. Output is a fresh TokVec. */ 100 static void prepass_defined(Pp* pp, const Tok* in, u32 nin, TokVec* out) { 101 u32 i; 102 for (i = 0; i < nin; ++i) { 103 if (in[i].kind == TOK_IDENT && tok_ident(&in[i]) == pp->sym_defined) { 104 int has_paren = 0; 105 Sym ident = 0; 106 u32 j = i + 1; 107 if (j < nin && in[j].kind == TOK_PUNCT && tok_punct(&in[j]) == '(') { 108 has_paren = 1; 109 ++j; 110 } 111 if (j >= nin || in[j].kind != TOK_IDENT) { 112 compiler_panic(pp->c, pp_materialize_loc(pp, in[i].loc), 113 "operand of 'defined' must be an identifier"); 114 } 115 ident = tok_ident(&in[j]); 116 ++j; 117 if (has_paren) { 118 if (j >= nin || in[j].kind != TOK_PUNCT || tok_punct(&in[j]) != ')') { 119 compiler_panic(pp->c, pp_materialize_loc(pp, in[i].loc), 120 "expected ')' after 'defined' operand"); 121 } 122 ++j; 123 } 124 { 125 Tok t; 126 /* `__has_include` / `__has_include_next` are not macros (their operand 127 * isn't a normal token sequence) but, like clang/GCC, they answer 128 * `defined()` with 1 so the guarded idiom 129 * #if defined(__has_include) && __has_include(<x>) 130 * — common in system headers — takes its intended branch. */ 131 int is_defined = mt_get(pp, ident) != NULL || 132 ident == pp->sym_has_include || 133 ident == pp->sym_has_include_next; 134 memset(&t, 0, sizeof(t)); 135 t.kind = TOK_NUM; 136 t.flags = in[i].flags & (TF_AT_BOL | TF_HAS_SPACE); 137 t.loc = in[i].loc; 138 t.aux = 0; 139 t.text = text_intern_ref( 140 pp, is_defined ? kit_slice_cstr("1") : kit_slice_cstr("0")); 141 tv_push(pp, out, t); 142 } 143 i = j - 1; 144 } else { 145 tv_push(pp, out, in[i]); 146 } 147 } 148 } 149 150 /* Macro-expand a sequence of pre-#if tokens to completion. 151 * 152 * Sets pp->in_if_expansion for the duration so pp_next_raw can keep 153 * `defined`-operator operands raw even when they ride out of a macro 154 * body via the ## operator. Without this flag a macro body like 155 * #define G(x) (!defined(__G_DEFINED_ ## x)) 156 * would have the pasted operand expanded if it happens to name an 157 * already-defined macro, leaving the second prepass to choke on 158 * `defined()`. */ 159 static void expand_for_if(Pp* pp, const Tok* in, u32 nin, TokVec* out) { 160 Tok* slice; 161 u8 saved; 162 if (nin == 0) return; 163 slice = arena_array(pp->arena, Tok, nin); 164 memcpy(slice, in, sizeof(Tok) * nin); 165 saved = pp->in_if_expansion; 166 pp->in_if_expansion = 1; 167 pp->defined_skip = 0; 168 expand_arg_to_eof(pp, slice, nin, out); 169 pp->in_if_expansion = saved; 170 pp->defined_skip = 0; 171 } 172 173 /* Replace remaining identifiers with `0` per §6.10.1 ¶4, after `defined` 174 * has been handled. */ 175 static void replace_remaining_if_identifiers(Pp* pp, TokVec* toks) { 176 u32 i; 177 TextRef zero = text_intern_ref(pp, kit_slice_cstr("0")); 178 for (i = 0; i < toks->n; ++i) { 179 if (toks->data[i].kind == TOK_IDENT) { 180 toks->data[i].kind = TOK_NUM; 181 toks->data[i].aux = 0; 182 toks->data[i].text = zero; 183 } 184 } 185 } 186 187 /* Recursive-descent expression evaluator over an expanded token list. */ 188 typedef struct EE { 189 Pp* pp; 190 const Tok* toks; 191 u32 n; 192 u32 pos; 193 SrcLoc loc; 194 SrcLoc op_loc; /* loc of the binary operator being applied (for panics) */ 195 } EE; 196 197 static i64 ee_ternary(EE* e); 198 199 static const Tok* ee_peek(EE* e) { 200 return e->pos < e->n ? &e->toks[e->pos] : NULL; 201 } 202 203 static int ee_match_punct(EE* e, u32 p) { 204 const Tok* t = ee_peek(e); 205 if (t && t->kind == TOK_PUNCT && tok_punct(t) == p) { 206 ++e->pos; 207 return 1; 208 } 209 return 0; 210 } 211 212 static i64 ee_primary(EE* e) { 213 const Tok* t = ee_peek(e); 214 if (!t) compiler_panic(e->pp->c, e->loc, "#if: missing operand"); 215 if (t->kind == TOK_NUM) { 216 KitSlice s = pp_text_slice(e->pp, t); 217 ++e->pos; 218 return parse_pp_int(s.s, s.len); 219 } 220 if (t->kind == TOK_CHR) { 221 /* Treat as the codepoint of the first character (post-decoding 222 * not implemented; cover the common case of a single ASCII 223 * char). */ 224 KitSlice s = pp_text_slice(e->pp, t); 225 ++e->pos; 226 if (s.len >= 3 && s.s[0] == '\'') return (unsigned char)s.s[1]; 227 return 0; 228 } 229 if (t->kind == TOK_PUNCT && tok_punct(t) == '(') { 230 i64 v; 231 ++e->pos; 232 v = ee_ternary(e); 233 if (!ee_match_punct(e, ')')) { 234 compiler_panic(e->pp->c, pp_materialize_loc(e->pp, t->loc), 235 "#if: expected ')'"); 236 } 237 return v; 238 } 239 compiler_panic(e->pp->c, pp_materialize_loc(e->pp, t->loc), 240 "#if: unexpected token in expression"); 241 return 0; 242 } 243 244 static i64 ee_unary(EE* e) { 245 const Tok* t = ee_peek(e); 246 if (t && t->kind == TOK_PUNCT) { 247 u32 p = tok_punct(t); 248 if (p == '!' || p == '-' || p == '+' || p == '~') { 249 i64 v; 250 ++e->pos; 251 v = ee_unary(e); 252 switch (p) { 253 case '!': 254 return v ? 0 : 1; 255 case '-': 256 return -v; 257 case '+': 258 return v; 259 case '~': 260 return ~v; 261 } 262 } 263 } 264 return ee_primary(e); 265 } 266 267 /* One row per binary operator, highest `prec` binds tightest. All listed 268 * operators are left-associative; ternary (right-assoc) stays special-cased 269 * in ee_ternary. `apply` folds (lhs OP rhs) and owns the div/mod-by-zero 270 * panic (it needs the operator loc, threaded via EE::op_loc). */ 271 typedef i64 (*EeApply)(EE* e, i64 a, i64 b); 272 273 static i64 eb_mul(EE* e, i64 a, i64 b) { 274 (void)e; 275 return a * b; 276 } 277 static i64 eb_div(EE* e, i64 a, i64 b) { 278 if (b == 0) compiler_panic(e->pp->c, e->op_loc, "#if: division by zero"); 279 return a / b; 280 } 281 static i64 eb_mod(EE* e, i64 a, i64 b) { 282 if (b == 0) compiler_panic(e->pp->c, e->op_loc, "#if: modulo by zero"); 283 return a % b; 284 } 285 static i64 eb_add(EE* e, i64 a, i64 b) { 286 (void)e; 287 return a + b; 288 } 289 static i64 eb_sub(EE* e, i64 a, i64 b) { 290 (void)e; 291 return a - b; 292 } 293 static i64 eb_shl(EE* e, i64 a, i64 b) { 294 (void)e; 295 return a << b; 296 } 297 static i64 eb_shr(EE* e, i64 a, i64 b) { 298 (void)e; 299 return a >> b; 300 } 301 static i64 eb_lt(EE* e, i64 a, i64 b) { 302 (void)e; 303 return a < b; 304 } 305 static i64 eb_gt(EE* e, i64 a, i64 b) { 306 (void)e; 307 return a > b; 308 } 309 static i64 eb_le(EE* e, i64 a, i64 b) { 310 (void)e; 311 return a <= b; 312 } 313 static i64 eb_ge(EE* e, i64 a, i64 b) { 314 (void)e; 315 return a >= b; 316 } 317 static i64 eb_eq(EE* e, i64 a, i64 b) { 318 (void)e; 319 return a == b; 320 } 321 static i64 eb_ne(EE* e, i64 a, i64 b) { 322 (void)e; 323 return a != b; 324 } 325 static i64 eb_band(EE* e, i64 a, i64 b) { 326 (void)e; 327 return a & b; 328 } 329 static i64 eb_bxor(EE* e, i64 a, i64 b) { 330 (void)e; 331 return a ^ b; 332 } 333 static i64 eb_bor(EE* e, i64 a, i64 b) { 334 (void)e; 335 return a | b; 336 } 337 static i64 eb_logand(EE* e, i64 a, i64 b) { 338 (void)e; 339 return a && b; 340 } 341 static i64 eb_logor(EE* e, i64 a, i64 b) { 342 (void)e; 343 return a || b; 344 } 345 346 typedef struct EeOp { 347 u32 punct; /* P_* / ASCII codepoint of the operator token */ 348 u8 prec; /* higher binds tighter */ 349 EeApply apply; 350 } EeOp; 351 352 static const EeOp ee_ops[] = { 353 {'*', 10, eb_mul}, {'/', 10, eb_div}, {'%', 10, eb_mod}, 354 {'+', 9, eb_add}, {'-', 9, eb_sub}, {P_SHL, 8, eb_shl}, 355 {P_SHR, 8, eb_shr}, {'<', 7, eb_lt}, {'>', 7, eb_gt}, 356 {P_LE, 7, eb_le}, {P_GE, 7, eb_ge}, {P_EQ, 6, eb_eq}, 357 {P_NE, 6, eb_ne}, {'&', 5, eb_band}, {'^', 4, eb_bxor}, 358 {'|', 3, eb_bor}, {P_AND, 2, eb_logand}, {P_OR, 1, eb_logor}, 359 }; 360 361 static const EeOp* ee_lookup_op(const Tok* t) { 362 size_t i; 363 if (!t || t->kind != TOK_PUNCT) return NULL; 364 for (i = 0; i < sizeof(ee_ops) / sizeof(ee_ops[0]); ++i) { 365 if (ee_ops[i].punct == tok_punct(t)) return &ee_ops[i]; 366 } 367 return NULL; 368 } 369 370 /* Precedence-climbing fold of all left-associative binary operators. */ 371 static i64 ee_binary(EE* e, int min_prec) { 372 i64 v = ee_unary(e); 373 for (;;) { 374 const Tok* t = ee_peek(e); 375 const EeOp* op = ee_lookup_op(t); 376 SrcLoc op_loc; 377 i64 rhs; 378 if (!op || op->prec < min_prec) break; 379 op_loc = pp_materialize_loc(e->pp, t->loc); 380 ++e->pos; 381 /* Left-associative: parse the RHS with strictly higher precedence so 382 * same-prec operators fold left-to-right. */ 383 rhs = ee_binary(e, op->prec + 1); 384 e->op_loc = op_loc; 385 v = op->apply(e, v, rhs); 386 } 387 return v; 388 } 389 390 static i64 ee_ternary(EE* e) { 391 i64 c = ee_binary(e, 1); 392 if (ee_match_punct(e, '?')) { 393 i64 a = ee_ternary(e); 394 i64 b; 395 if (!ee_match_punct(e, ':')) { 396 compiler_panic(e->pp->c, e->loc, "#if: ':' expected in ternary"); 397 } 398 b = ee_ternary(e); 399 return c ? a : b; 400 } 401 return c; 402 } 403 404 /* Header-resolution helpers used by the __has_include pre-pass below; the 405 * definitions live further down with the #include machinery. */ 406 static void parse_include_path(Pp* pp, const Tok* line, u32 n, LocRef loc, 407 char* path_out, size_t cap, int* system_out); 408 static int find_and_open_include(Pp* pp, const char* path, int system, 409 LocRef loc, const u8** data, size_t* size, 410 char* resolved, size_t resolved_cap, 411 int* resolved_system_out, u32* next_start_out); 412 static int find_and_open_include_next(Pp* pp, const char* path, u32 start, 413 const u8** data, size_t* size, 414 char* resolved, size_t resolved_cap, 415 int* resolved_system_out, 416 u32* next_start_out); 417 418 /* Pre-pass: replace `__has_include(<h>)` / `__has_include("h")` (and the 419 * `__has_include_next` variant) with the pp-number 1 or 0 per whether the 420 * header resolves against the include search path — the same search a real 421 * #include would perform. Runs BEFORE macro expansion so the `<...>` form's 422 * `<` and `>` are never mistaken for comparison operators, and so the header 423 * tokens are not subject to the surrounding #if's identifier→0 rewrite. The 424 * operand itself is still macro-expanded (via parse_include_path), matching 425 * the `__has_include(HEADER_MACRO)` idiom. Output is a fresh TokVec. */ 426 static void prepass_has_include(Pp* pp, const Tok* in, u32 nin, TokVec* out) { 427 u32 i; 428 for (i = 0; i < nin; ++i) { 429 int is_has = 0, is_next = 0; 430 u32 j, op_n, depth, s; 431 const Tok* op_first; 432 char path[4096]; 433 char resolved[4096]; 434 int system_form = 0, resolved_system = 0, present = 0; 435 u32 next_start = 0, next_search = 0; 436 const u8* data; 437 size_t size; 438 Tok t; 439 const char* what; 440 441 if (in[i].kind == TOK_IDENT) { 442 if (tok_ident(&in[i]) == pp->sym_has_include) 443 is_has = 1; 444 else if (tok_ident(&in[i]) == pp->sym_has_include_next) 445 is_has = is_next = 1; 446 } 447 if (!is_has) { 448 tv_push(pp, out, in[i]); 449 continue; 450 } 451 what = is_next ? "__has_include_next" : "__has_include"; 452 453 j = i + 1; 454 if (j >= nin || in[j].kind != TOK_PUNCT || tok_punct(&in[j]) != '(') { 455 compiler_panic(pp->c, pp_materialize_loc(pp, in[i].loc), 456 "expected '(' after %s", what); 457 } 458 ++j; /* past '(' */ 459 op_first = &in[j]; 460 depth = 1; 461 op_n = 0; 462 for (; j < nin; ++j, ++op_n) { 463 if (in[j].kind == TOK_PUNCT && tok_punct(&in[j]) == '(') { 464 ++depth; 465 } else if (in[j].kind == TOK_PUNCT && tok_punct(&in[j]) == ')') { 466 if (--depth == 0) break; 467 } 468 } 469 if (j >= nin) 470 compiler_panic(pp->c, pp_materialize_loc(pp, in[i].loc), 471 "unterminated %s", what); 472 /* in[j] is the matching ')'. */ 473 474 parse_include_path(pp, op_first, op_n, in[i].loc, path, sizeof(path), 475 &system_form); 476 if (is_next) { 477 for (s = pp->nsources; s > 0; --s) { 478 TokSrc* tp = &pp->sources[s - 1]; 479 if (tp->kind == SRC_LEX && tp->lex) { 480 next_search = tp->inc_next_start; 481 break; 482 } 483 } 484 present = find_and_open_include_next(pp, path, next_search, &data, &size, 485 resolved, sizeof(resolved), 486 &resolved_system, &next_start); 487 } else { 488 present = find_and_open_include(pp, path, system_form, in[i].loc, &data, 489 &size, resolved, sizeof(resolved), 490 &resolved_system, &next_start); 491 } 492 493 memset(&t, 0, sizeof(t)); 494 t.kind = TOK_NUM; 495 t.flags = in[i].flags & (TF_AT_BOL | TF_HAS_SPACE); 496 t.loc = in[i].loc; 497 t.aux = 0; 498 t.text = text_intern_ref( 499 pp, present ? kit_slice_cstr("1") : kit_slice_cstr("0")); 500 tv_push(pp, out, t); 501 i = j; /* resume past the matching ')' */ 502 } 503 } 504 505 i64 eval_if_expr(Pp* pp, const Tok* line, u32 n, LocRef loc) { 506 TokVec defs = {0}; 507 TokVec hasinc = {0}; 508 TokVec exp = {0}; 509 TokVec defs2 = {0}; 510 EE e; 511 i64 v; 512 513 prepass_defined(pp, line, n, &defs); 514 prepass_has_include(pp, defs.data, defs.n, &hasinc); 515 expand_for_if(pp, hasinc.data, hasinc.n, &exp); 516 prepass_defined(pp, exp.data, exp.n, &defs2); 517 replace_remaining_if_identifiers(pp, &defs2); 518 519 e.pp = pp; 520 e.toks = defs2.data; 521 e.n = defs2.n; 522 e.pos = 0; 523 e.loc = pp_materialize_loc(pp, loc); 524 v = ee_ternary(&e); 525 if (e.pos != e.n) { 526 compiler_panic(pp->c, e.loc, 527 "#if: unexpected trailing tokens in expression"); 528 } 529 return v; 530 } 531 532 /* ============================================================ 533 * Conditional inclusion helpers 534 * ============================================================ */ 535 536 static void consume_to_newline(Pp* pp) { 537 Tok t; 538 do { 539 t = src_next_raw(pp, NULL); 540 } while (t.kind != TOK_NEWLINE && t.kind != TOK_EOF); 541 } 542 543 /* Drive the source forward consuming tokens until we either: 544 * - reach a balancing #endif (pops the frame, returns), or 545 * - reach a #elif / #else that flips the top frame to IF_INCLUDE 546 * (returns with that frame active). 547 * Nested #if directives inside the skipped group are tracked via 548 * `local_depth`. Unrecognised directives in skipped groups are tolerated 549 * (§6.10 ¶4, covered by `8c_skipped_relaxed_syntax`). */ 550 static void skip_until_active(Pp* pp) { 551 int local_depth = 0; 552 while (pp->ifstk_n > 0) { 553 IfFrame* top = if_top(pp); 554 Tok t; 555 if (top->state == IF_INCLUDE && local_depth == 0) return; 556 t = src_next_raw(pp, NULL); 557 if (t.kind == TOK_EOF) { 558 compiler_panic(pp->c, pp_materialize_loc(pp, top->loc), 559 "unterminated #if / #ifdef"); 560 } 561 if (t.kind != TOK_PP_HASH || (t.flags & TF_AT_BOL) == 0) continue; 562 563 /* Read directive name (or null directive). */ 564 { 565 Tok nt = src_next_raw(pp, NULL); 566 Sym name; 567 if (nt.kind == TOK_NEWLINE || nt.kind == TOK_EOF) continue; 568 if (nt.kind != TOK_IDENT) { 569 consume_to_newline(pp); 570 continue; 571 } 572 name = tok_ident(&nt); 573 if (name == pp->sym_if || name == pp->sym_ifdef || 574 name == pp->sym_ifndef) { 575 ++local_depth; 576 consume_to_newline(pp); 577 continue; 578 } 579 if (name == pp->sym_endif) { 580 consume_to_newline(pp); 581 if (local_depth > 0) { 582 --local_depth; 583 continue; 584 } 585 if_pop(pp); 586 return; 587 } 588 if (name == pp->sym_else) { 589 consume_to_newline(pp); 590 if (local_depth > 0) continue; 591 if (top->has_else) { 592 compiler_panic(pp->c, pp_materialize_loc(pp, t.loc), 593 "duplicate #else"); 594 } 595 top->has_else = 1; 596 if (top->state == IF_SEEK_TRUE) { 597 top->state = IF_INCLUDE; 598 return; 599 } 600 top->state = IF_DONE; 601 continue; 602 } 603 if (name == pp->sym_elif) { 604 if (local_depth > 0 || top->has_else || top->state == IF_DONE) { 605 consume_to_newline(pp); 606 continue; 607 } 608 if (top->state == IF_SEEK_TRUE) { 609 Tok* line; 610 u32 ln; 611 i64 v; 612 read_directive_line(pp, &line, &ln); 613 v = eval_if_expr(pp, line, ln, t.loc); 614 if (v != 0) { 615 top->state = IF_INCLUDE; 616 return; 617 } 618 continue; 619 } 620 /* Was IF_INCLUDE; #elif means we're done. (Should already 621 * have been transitioned to DONE before entering this 622 * skip — defensive.) */ 623 top->state = IF_DONE; 624 consume_to_newline(pp); 625 continue; 626 } 627 /* Other directive — relaxed: skip silently. */ 628 consume_to_newline(pp); 629 continue; 630 } 631 } 632 } 633 634 /* ============================================================ 635 * Predefined macro name guard 636 * ============================================================ */ 637 638 static int is_predefined_macro_name(Pp* pp, Sym name) { 639 return name == pp->sym_va_args || name == pp->sym_line__ || 640 name == pp->sym_file__ || name == pp->sym_date__ || 641 name == pp->sym_time__; 642 /* __STDC__/__STDC_HOSTED__/__STDC_VERSION__ are registered as real 643 * macros, so the macro-table lookup catches them. */ 644 } 645 646 /* ============================================================ 647 * #ifdef / #if / #elif / #else / #endif 648 * ============================================================ */ 649 650 static void do_ifdef(Pp* pp, const Tok* line, u32 n, int negate, LocRef loc) { 651 int defined; 652 IfFrame f; 653 if (n < 1 || line[0].kind != TOK_IDENT) { 654 compiler_panic(pp->c, pp_materialize_loc(pp, loc), 655 negate ? "#ifndef: expected identifier" 656 : "#ifdef: expected identifier"); 657 } 658 defined = (mt_get(pp, tok_ident(&line[0])) != NULL) || 659 is_predefined_macro_name(pp, tok_ident(&line[0])); 660 if (negate) defined = !defined; 661 memset(&f, 0, sizeof(f)); 662 f.state = defined ? IF_INCLUDE : IF_SEEK_TRUE; 663 f.loc = loc; 664 if_push(pp, f); 665 if (!defined) skip_until_active(pp); 666 } 667 668 static void do_if_directive(Pp* pp, const Tok* line, u32 n, LocRef loc) { 669 i64 v = eval_if_expr(pp, line, n, loc); 670 IfFrame f; 671 memset(&f, 0, sizeof(f)); 672 f.state = v ? IF_INCLUDE : IF_SEEK_TRUE; 673 f.loc = loc; 674 if_push(pp, f); 675 if (!v) skip_until_active(pp); 676 } 677 678 static void do_elif(Pp* pp, LocRef loc) { 679 /* We only reach do_elif from the active branch — meaning the 680 * preceding group emitted code. So we must skip the rest. */ 681 IfFrame* top = if_top(pp); 682 if (!top) compiler_panic(pp->c, pp_materialize_loc(pp, loc), "stray #elif"); 683 if (top->has_else) 684 compiler_panic(pp->c, pp_materialize_loc(pp, loc), "#elif after #else"); 685 top->state = IF_DONE; 686 skip_until_active(pp); 687 } 688 689 static void do_else(Pp* pp, LocRef loc) { 690 IfFrame* top = if_top(pp); 691 if (!top) compiler_panic(pp->c, pp_materialize_loc(pp, loc), "stray #else"); 692 if (top->has_else) 693 compiler_panic(pp->c, pp_materialize_loc(pp, loc), "duplicate #else"); 694 top->has_else = 1; 695 top->state = IF_DONE; 696 skip_until_active(pp); 697 } 698 699 static void do_endif(Pp* pp, LocRef loc) { 700 if (!if_top(pp)) 701 compiler_panic(pp->c, pp_materialize_loc(pp, loc), "stray #endif"); 702 if_pop(pp); 703 } 704 705 /* ============================================================ 706 * #include (§6.10.2) 707 * ============================================================ */ 708 709 /* Arena-heap adapter backing the IncCache slot table: alloc/realloc bump 710 * from pp->arena (immortal until pp_free), free is a no-op. A resize 711 * orphans the small old slot table in the arena — bounded by the final 712 * table size and negligible. Mirrors the parser's c_arena_heap. */ 713 static void* inc_cache_alloc(KitHeap* h, size_t n, size_t align) { 714 return kit_arena_alloc((KitArena*)h->user, n, align); 715 } 716 static void* inc_cache_realloc(KitHeap* h, void* old, size_t old_n, 717 size_t new_n, size_t align) { 718 void* q = kit_arena_alloc((KitArena*)h->user, new_n, align); 719 if (q && old && old_n) memcpy(q, old, old_n < new_n ? old_n : new_n); 720 return q; 721 } 722 static void inc_cache_free(KitHeap* h, void* p, size_t n) { 723 (void)h; 724 (void)p; 725 (void)n; 726 } 727 728 /* Lazily set up the header-content cache, resolution memo, and their 729 * shared arena-backed heap. */ 730 static void inc_cache_ensure(Pp* pp) { 731 if (pp->inc_cache_ready) return; 732 pp->inc_cache_heap.alloc = inc_cache_alloc; 733 pp->inc_cache_heap.realloc = inc_cache_realloc; 734 pp->inc_cache_heap.free = inc_cache_free; 735 pp->inc_cache_heap.user = pp->arena; 736 IncCache_init(&pp->inc_cache, &pp->inc_cache_heap); 737 IncResolveMap_init(&pp->inc_resolve, &pp->inc_cache_heap); 738 pp->inc_cache_ready = 1; 739 } 740 741 /* Intern a candidate/resolved path string into a Sym usable as a cache 742 * key. Returns 0 (an invalid key) only for the empty string, which never 743 * names a real header. */ 744 static Sym inc_cache_key(Pp* pp, const char* path) { 745 return kit_sym_intern(pp->pool->c, kit_slice_cstr(path)); 746 } 747 748 /* Read `path` via the host's file_io and copy its bytes into the pp 749 * arena so they outlive io->release. Returns 1 on success. */ 750 static int try_open_include(Pp* pp, const char* path, const u8** data_out, 751 size_t* size_out) { 752 KitFileData fd; 753 const KitFileIO* io; 754 u8* buf; 755 756 memset(&fd, 0, sizeof(fd)); 757 io = kit_compiler_context(pp->c)->file_io; 758 if (!io || !io->read_all) { 759 compiler_panic(pp->c, (SrcLoc){0, 0, 0}, 760 "#include: env.file_io is not configured"); 761 } 762 if (io->read_all(io->user, path, &fd) != KIT_OK) return 0; 763 { 764 size_t sz = fd.size; 765 buf = (u8*)arena_alloc(pp->arena, sz ? sz : 1, 1); 766 if (sz && fd.data) memcpy(buf, fd.data, sz); 767 if (io->release) io->release(io->user, &fd); /* zeros fd */ 768 *data_out = buf; 769 *size_out = sz; 770 } 771 return 1; 772 } 773 774 /* Open `path` for #include, serving from the header-content cache when the 775 * same resolved path was already read this TU. A hit returns the cached 776 * arena bytes with NO syscalls; a miss reads via try_open_include and 777 * caches the result keyed on the path string. The returned (data,size) is 778 * byte-identical to a fresh read, so this never affects which file wins a 779 * search nor the bytes that feed the lexer. */ 780 static int open_include_cached(Pp* pp, const char* path, const u8** data_out, 781 size_t* size_out) { 782 Sym key; 783 IncEntry* hit; 784 inc_cache_ensure(pp); 785 key = inc_cache_key(pp, path); 786 if (key) { 787 hit = IncCache_get(&pp->inc_cache, key); 788 if (hit) { 789 *data_out = hit->data; 790 *size_out = hit->size; 791 return 1; 792 } 793 } 794 if (!try_open_include(pp, path, data_out, size_out)) return 0; 795 if (key) { 796 IncEntry e = {0}; 797 e.data = *data_out; 798 e.size = *size_out; 799 IncCache_set(&pp->inc_cache, key, e); 800 } 801 return 1; 802 } 803 804 /* Return the includer's directory for resolving a quoted include, or "." 805 * for in-memory/builtin sources (where CWD is the natural fallback, like 806 * gcc treats stdin). `dir_out` must point to a buffer of size >= cap. */ 807 static int includer_dir(Pp* pp, LocRef loc, char* dir_out, size_t cap) { 808 KitSourceFile sf; 809 const char* p = NULL; 810 size_t plen = 0; 811 const char* slash; 812 size_t dlen; 813 memset(&sf, 0, sizeof(sf)); 814 if (kit_source_file(pp->c, loc.file_id, &sf) == 0 && sf.name) { 815 KitSlice s = kit_sym_str(pp->pool->c, sf.name); 816 p = s.s; 817 plen = s.len; 818 } 819 if (!p || plen == 0 || p[0] == '<') { 820 if (cap < 2) return 0; 821 dir_out[0] = '.'; 822 dir_out[1] = 0; 823 return 1; 824 } 825 slash = NULL; 826 { 827 size_t i; 828 for (i = plen; i > 0; --i) { 829 if (p[i - 1] == '/') { 830 slash = p + i - 1; 831 break; 832 } 833 } 834 } 835 if (!slash) { 836 if (cap < 2) return 0; 837 dir_out[0] = '.'; 838 dir_out[1] = 0; 839 return 1; 840 } 841 dlen = (size_t)(slash - p); 842 if (dlen == 0) dlen = 1; /* path was "/x" — dir is "/" */ 843 if (dlen + 1 > cap) return 0; 844 memcpy(dir_out, p, dlen); 845 dir_out[dlen] = 0; 846 return 1; 847 } 848 849 /* Build the resolution-memo key for a (spelling, system, includer-dir) 850 * request and intern it. The key folds the search-affecting inputs into 851 * one string: a leading '<' (system) or '"' (quoted) tag, and — for the 852 * quoted form, whose includer-relative step is includer-specific — the 853 * includer directory followed by a '\n' separator (which can appear in 854 * neither a path nor a header spelling) before the spelling. System form 855 * skips the includer step, so its key omits the dir. Returns 0 only if 856 * the key would overflow the scratch buffer (memo simply not used). */ 857 static Sym inc_resolve_key(Pp* pp, const char* path, size_t plen, int system, 858 LocRef loc) { 859 char key[4096 + 4096 + 8]; 860 size_t pos = 0; 861 key[pos++] = system ? '<' : '"'; 862 if (!system) { 863 char dir[4096]; 864 if (!includer_dir(pp, loc, dir, sizeof(dir))) return 0; 865 { 866 size_t dlen = kit_slice_cstr(dir).len; 867 if (pos + dlen + 1 + plen >= sizeof(key)) return 0; 868 memcpy(key + pos, dir, dlen); 869 pos += dlen; 870 key[pos++] = '\n'; 871 } 872 } else if (pos + plen >= sizeof(key)) { 873 return 0; 874 } 875 memcpy(key + pos, path, plen); 876 pos += plen; 877 return kit_sym_intern(pp->pool->c, (KitSlice){.s = key, .len = pos}); 878 } 879 880 /* Record a successful dir-search resolution under its spelling key, so the 881 * next request for the same spelling skips the search. No-op when the key 882 * was unavailable (rkey == 0). `resolved` is a NUL-terminated path. 883 * `next_start` is the inc_dirs index a #include_next from the resolved file 884 * resumes at (0 for the includer-relative / absolute wins). */ 885 static void inc_resolve_record(Pp* pp, Sym rkey, const char* resolved, 886 int resolved_system, u32 next_start) { 887 IncResolved e; 888 if (!rkey) return; 889 e.path = kit_sym_intern(pp->pool->c, kit_slice_cstr(resolved)); 890 e.system = (u8)(resolved_system ? 1 : 0); 891 e.next_start = next_start; 892 IncResolveMap_set(&pp->inc_resolve, rkey, e); 893 } 894 895 /* Search for a header. Absolute paths are opened verbatim. Quoted form 896 * ("...") additionally searches the directory of the file containing the 897 * #include first (per C §6.10.2); bracket form (<...>) skips that step. 898 * Both forms then walk the configured -I / -isystem dirs in order. 899 * 900 * A resolution memo short-circuits the repeat case: when the same spelling 901 * (with the same system flag and, for quoted form, the same includer dir) 902 * was resolved before, we go straight to the winning path — skipping the 903 * dir-by-dir ENOENT storm — and the content cache serves its bytes. */ 904 /* Walk pp->inc_dirs[start ..] looking for `path`, opening the first hit. 905 * On success fills data/size, writes the resolved path and its -isystem 906 * flag, and sets *next_start to one past the winning dir (the resume point 907 * for a subsequent #include_next from the resolved file). Returns 0 if no 908 * configured dir holds the header. */ 909 static int search_inc_dirs(Pp* pp, const char* path, u32 start, const u8** data, 910 size_t* size, char* resolved, size_t resolved_cap, 911 int* resolved_system_out, u32* next_start) { 912 char buf[4096]; 913 size_t plen = kit_slice_cstr(path).len; 914 u32 i; 915 for (i = start; i < pp->ninc_dirs; ++i) { 916 const char* d = pp->inc_dirs[i].path; 917 size_t dlen = kit_slice_cstr(d).len; 918 if (dlen + 1 + plen + 1 > sizeof(buf)) continue; 919 memcpy(buf, d, dlen); 920 buf[dlen] = '/'; 921 memcpy(buf + dlen + 1, path, plen); 922 buf[dlen + 1 + plen] = 0; 923 if (open_include_cached(pp, buf, data, size)) { 924 if (dlen + 1 + plen + 1 > resolved_cap) return 0; 925 memcpy(resolved, buf, dlen + 1 + plen + 1); 926 *resolved_system_out = pp->inc_dirs[i].system ? 1 : 0; 927 *next_start = i + 1; 928 return 1; 929 } 930 } 931 return 0; 932 } 933 934 static int find_and_open_include(Pp* pp, const char* path, int system, 935 LocRef loc, const u8** data, size_t* size, 936 char* resolved, size_t resolved_cap, 937 int* resolved_system_out, 938 u32* next_start_out) { 939 char buf[4096]; 940 size_t plen = kit_slice_cstr(path).len; 941 Sym rkey = 0; 942 943 /* Absolute paths and the includer-relative ("...") step are not system 944 * search dirs; only a configured -isystem dir flips this to 1 below. A 945 * #include_next from such a file resumes at the head of the search path. */ 946 *resolved_system_out = 0; 947 *next_start_out = 0; 948 949 if (plen > 0 && path[0] == '/') { 950 if (open_include_cached(pp, path, data, size)) { 951 if (plen + 1 > resolved_cap) return 0; 952 memcpy(resolved, path, plen + 1); 953 return 1; 954 } 955 return 0; 956 } 957 958 /* Probe the resolution memo (covers only the dir-search winners below; 959 * absolute paths return above). On a hit, reopen the recorded resolved 960 * path — which the content cache already holds, so this is syscall-free 961 * — and reproduce the byte-identical resolved string + system flag. */ 962 inc_cache_ensure(pp); 963 rkey = inc_resolve_key(pp, path, plen, system, loc); 964 if (rkey) { 965 IncResolved* hit = IncResolveMap_get(&pp->inc_resolve, rkey); 966 if (hit) { 967 KitSlice rp = kit_sym_str(pp->pool->c, hit->path); 968 if (rp.len + 1 > resolved_cap) return 0; 969 if (!open_include_cached(pp, rp.s, data, size)) return 0; 970 memcpy(resolved, rp.s, rp.len + 1); 971 *resolved_system_out = hit->system ? 1 : 0; 972 *next_start_out = hit->next_start; 973 return 1; 974 } 975 } 976 977 if (!system) { 978 char dir[4096]; 979 if (includer_dir(pp, loc, dir, sizeof(dir))) { 980 size_t dlen = kit_slice_cstr(dir).len; 981 if (dlen + 1 + plen + 1 <= sizeof(buf)) { 982 memcpy(buf, dir, dlen); 983 buf[dlen] = '/'; 984 memcpy(buf + dlen + 1, path, plen); 985 buf[dlen + 1 + plen] = 0; 986 if (open_include_cached(pp, buf, data, size)) { 987 if (dlen + 1 + plen + 1 > resolved_cap) return 0; 988 memcpy(resolved, buf, dlen + 1 + plen + 1); 989 inc_resolve_record(pp, rkey, resolved, 0, 0); 990 return 1; 991 } 992 } 993 } 994 } 995 if (search_inc_dirs(pp, path, 0, data, size, resolved, resolved_cap, 996 resolved_system_out, next_start_out)) { 997 inc_resolve_record(pp, rkey, resolved, *resolved_system_out, 998 *next_start_out); 999 return 1; 1000 } 1001 return 0; 1002 } 1003 1004 /* #include_next: like #include, but the search resumes at `start` (one past 1005 * the dir the including file was found in) and skips both the absolute-path 1006 * fast path and the includer-relative step. Bypasses the resolution memo — 1007 * the memo key does not encode the resume point, and #include_next is rare. */ 1008 static int find_and_open_include_next(Pp* pp, const char* path, u32 start, 1009 const u8** data, size_t* size, 1010 char* resolved, size_t resolved_cap, 1011 int* resolved_system_out, 1012 u32* next_start_out) { 1013 *resolved_system_out = 0; 1014 *next_start_out = 0; 1015 inc_cache_ensure(pp); 1016 return search_inc_dirs(pp, path, start, data, size, resolved, resolved_cap, 1017 resolved_system_out, next_start_out); 1018 } 1019 1020 /* Decode a directly-lexed TOK_HEADER name into a NUL-terminated path. 1021 * Classifies <...> (system) vs "..." (local), enforces the destination 1022 * capacity, and writes the unwrapped contents to out. `what` is the 1023 * directive name used in panic messages (e.g. "#include", "#embed"). */ 1024 static void header_name_to_path(Pp* pp, KitSlice slc, char* out, size_t cap, 1025 int* system_out, LocRef loc, const char* what) { 1026 const char* s = slc.s; 1027 size_t slen = slc.len; 1028 if (slen < 2) 1029 compiler_panic(pp->c, pp_materialize_loc(pp, loc), 1030 "%s: malformed header name", what); 1031 if (s[0] == '<' && s[slen - 1] == '>') 1032 *system_out = 1; 1033 else if (s[0] == '"' && s[slen - 1] == '"') 1034 *system_out = 0; 1035 else 1036 compiler_panic(pp->c, pp_materialize_loc(pp, loc), 1037 "%s: malformed header name", what); 1038 if (slen - 2 + 1 > cap) 1039 compiler_panic(pp->c, pp_materialize_loc(pp, loc), "%s: path too long", 1040 what); 1041 memcpy(out, s + 1, slen - 2); 1042 out[slen - 2] = 0; 1043 } 1044 1045 /* Parse the directive arguments into (path, system_flag). Handles: 1046 * - directly-lexed TOK_HEADER: < ... > or " ... " 1047 * - macro-replaced form: line is macro-expanded, then expected to 1048 * produce either a TOK_STR ("...") or a < ... > sequence. */ 1049 static void parse_include_path(Pp* pp, const Tok* line, u32 n, LocRef loc, 1050 char* path_out, size_t cap, int* system_out) { 1051 if (n == 0) 1052 compiler_panic(pp->c, pp_materialize_loc(pp, loc), 1053 "#include: missing path"); 1054 1055 if (line[0].kind == TOK_HEADER) { 1056 KitSlice sl = pp_text_slice(pp, &line[0]); 1057 header_name_to_path(pp, sl, path_out, cap, system_out, loc, "#include"); 1058 return; 1059 } 1060 1061 /* Macro-replaced form. */ 1062 { 1063 TokVec exp = {0}; 1064 Tok* slice = arena_array(pp->arena, Tok, n); 1065 memcpy(slice, line, sizeof(Tok) * n); 1066 expand_arg_to_eof(pp, slice, n, &exp); 1067 1068 if (exp.n == 0) { 1069 compiler_panic(pp->c, pp_materialize_loc(pp, loc), 1070 "#include: empty after macro replacement"); 1071 } 1072 if (exp.data[0].kind == TOK_STR) { 1073 KitSlice sl = pp_text_slice(pp, &exp.data[0]); 1074 const char* s = sl.s; 1075 size_t slen = sl.len; 1076 if (slen < 2 || s[0] != '"' || s[slen - 1] != '"') { 1077 compiler_panic(pp->c, pp_materialize_loc(pp, loc), 1078 "#include: malformed string"); 1079 } 1080 if (slen - 2 + 1 > cap) { 1081 compiler_panic(pp->c, pp_materialize_loc(pp, loc), 1082 "#include: path too long"); 1083 } 1084 memcpy(path_out, s + 1, slen - 2); 1085 path_out[slen - 2] = 0; 1086 *system_out = 0; 1087 return; 1088 } 1089 if (exp.data[0].kind == TOK_PUNCT && tok_punct(&exp.data[0]) == '<') { 1090 size_t pos = 0; 1091 u32 i; 1092 for (i = 1; i < exp.n; ++i) { 1093 size_t slen = 0; 1094 const char* s = NULL; 1095 if (exp.data[i].kind == TOK_PUNCT && tok_punct(&exp.data[i]) == '>') { 1096 break; 1097 } 1098 { 1099 KitSlice sl = pp_text_slice(pp, &exp.data[i]); 1100 if (sl.len) { 1101 s = sl.s; 1102 slen = sl.len; 1103 } 1104 } 1105 if (s && pos + slen + 1 <= cap) { 1106 memcpy(path_out + pos, s, slen); 1107 pos += slen; 1108 } 1109 } 1110 path_out[pos] = 0; 1111 *system_out = 1; 1112 return; 1113 } 1114 compiler_panic(pp->c, pp_materialize_loc(pp, loc), 1115 "#include: expected \"...\" or <...> after expansion"); 1116 } 1117 } 1118 1119 /* Shared core for #include and #include_next (is_next == 1). */ 1120 static void do_include(Pp* pp, const Tok* line, u32 n, LocRef loc, 1121 int is_next) { 1122 char path[4096]; 1123 char resolved[4096]; 1124 int system_form = 0; 1125 int resolved_system = 0; 1126 u32 next_start = 0; 1127 const u8* data; 1128 size_t size; 1129 Lexer* lex; 1130 u32 includer_id = 0; 1131 u32 next_search = 0; 1132 u32 included_id; 1133 u32 i; 1134 int found; 1135 TokSrc s; 1136 Sym pkey; 1137 1138 parse_include_path(pp, line, n, loc, path, sizeof(path), &system_form); 1139 1140 /* Locate the current includer (topmost SRC_LEX source): its file_id seeds 1141 * the include graph, and — for #include_next — its inc_next_start is the 1142 * point in the search path from which we resume. */ 1143 for (i = pp->nsources; i > 0; --i) { 1144 TokSrc* tp = &pp->sources[i - 1]; 1145 if (tp->kind == SRC_LEX && tp->lex) { 1146 includer_id = lex_file_id(tp->lex); 1147 next_search = tp->inc_next_start; 1148 break; 1149 } 1150 } 1151 1152 if (is_next) 1153 found = find_and_open_include_next(pp, path, next_search, &data, &size, 1154 resolved, sizeof(resolved), 1155 &resolved_system, &next_start); 1156 else 1157 found = find_and_open_include(pp, path, system_form, loc, &data, &size, 1158 resolved, sizeof(resolved), &resolved_system, 1159 &next_start); 1160 if (!found) { 1161 compiler_panic(pp->c, pp_materialize_loc(pp, loc), 1162 "%s: file not found: %.*s", 1163 is_next ? "#include_next" : "#include", 1164 KIT_SLICE_ARG(kit_slice_cstr(path))); 1165 } 1166 1167 /* Multiple-include optimization: if this resolved file was already lexed and 1168 * is either #pragma once or still wrapped by a defined #ifndef guard, skip 1169 * re-lexing it entirely — it would emit no tokens anyway. This is the classic 1170 * controlling-macro optimization (tcc/gcc/clang have it); kit's IncCache 1171 * already serves the bytes syscall-free, but re-lexing them through 1172 * skip_until_active still costs the full lex+intern of every token. */ 1173 pkey = inc_cache_key(pp, resolved); 1174 if (pkey) { 1175 for (i = pp->nsources; i > 0; --i) { 1176 TokSrc* tp = &pp->sources[i - 1]; 1177 if (tp->kind == SRC_LEX && tp->once && tp->path_key == pkey) return; 1178 } 1179 } 1180 if (pkey && pp->inc_cache_ready) { 1181 IncEntry* e = IncCache_get(&pp->inc_cache, pkey); 1182 if (e && (e->once || (e->guard && mt_get(pp, e->guard) != NULL))) return; 1183 } 1184 1185 /* Inherit the parser-feed newline policy from the primary source: in cc mode 1186 * an #include'd file's non-directive newlines are dropped at the lexer too 1187 * (the parser never sees them); -E / cpp leaves them on. */ 1188 { 1189 SourceSpec spec; 1190 memset(&spec, 0, sizeof spec); 1191 spec.name = kit_slice_cstr(resolved); 1192 spec.bytes = (const char*)data; 1193 spec.len = (u32)size; 1194 spec.flags = (pp->parser_feed ? SRC_PARSER_FEED : 0u) | 1195 (resolved_system ? SRC_SYSTEM : 0u); 1196 lex = lex_open(pp->c, &spec); 1197 } 1198 included_id = lex_file_id(lex); 1199 /* Adopt the source buffer + splice table for lazy loc/text materialization 1200 * (must happen before the source is pushed). */ 1201 pp_register_srcinfo(pp, lex); 1202 1203 memset(&s, 0, sizeof(s)); 1204 s.kind = SRC_LEX; 1205 s.lex = lex; 1206 s.inc_next_start = next_start; 1207 s.path_key = pkey; 1208 s.guard_if_base = pp->ifstk_n; 1209 src_push(pp, s); 1210 1211 kit_source_add_include(pp->c, includer_id, included_id, 1212 pp_materialize_loc(pp, loc), system_form, 1213 resolved_system); 1214 } 1215 1216 /* ============================================================ 1217 * #line (§6.10.4) 1218 * ============================================================ */ 1219 1220 /* Find the topmost SRC_LEX source on the stack — that's the "current 1221 * file" whose line/file should track #line directives. */ 1222 TokSrc* current_lex_src(Pp* pp) { 1223 u32 i; 1224 for (i = pp->nsources; i > 0; --i) { 1225 TokSrc* s = &pp->sources[i - 1]; 1226 if (s->kind == SRC_LEX) return s; 1227 } 1228 return NULL; 1229 } 1230 1231 static void do_line(Pp* pp, const Tok* line, u32 n, LocRef loc) { 1232 /* Macro-replace arguments first (a2). */ 1233 TokVec exp = {0}; 1234 Tok* slice; 1235 TokSrc* lex_src; 1236 i64 target_line; 1237 Sym target_file = 0; 1238 1239 if (n == 0) 1240 compiler_panic(pp->c, pp_materialize_loc(pp, loc), 1241 "#line: missing arguments"); 1242 slice = arena_array(pp->arena, Tok, n); 1243 memcpy(slice, line, sizeof(Tok) * n); 1244 expand_arg_to_eof(pp, slice, n, &exp); 1245 1246 if (exp.n == 0 || exp.data[0].kind != TOK_NUM) { 1247 compiler_panic(pp->c, pp_materialize_loc(pp, loc), 1248 "#line: expected line number"); 1249 } 1250 { 1251 KitSlice s = pp_text_slice(pp, &exp.data[0]); 1252 target_line = parse_pp_int(s.s, s.len); 1253 } 1254 if (exp.n >= 2) { 1255 if (exp.data[1].kind != TOK_STR) { 1256 compiler_panic(pp->c, pp_materialize_loc(pp, loc), 1257 "#line: file argument must be a string"); 1258 } 1259 { 1260 KitSlice s = pp_text_slice(pp, &exp.data[1]); 1261 if (s.len >= 2 && s.s[0] == '"' && s.s[s.len - 1] == '"') { 1262 /* Destringize to logical bytes (undo \" and \\): the overlay file is 1263 * stored unescaped, like a real source path, and __FILE__ re-escapes 1264 * it uniformly when expanded. */ 1265 char* fbuf = (char*)arena_alloc(pp->arena, s.len, 1); 1266 size_t flen = 0; 1267 destringize(pp, &exp.data[1], fbuf, s.len, &flen); 1268 target_file = 1269 kit_sym_intern(pp->pool->c, (KitSlice){.s = fbuf, .len = flen}); 1270 } 1271 } 1272 } 1273 1274 lex_src = current_lex_src(pp); 1275 if (!lex_src) 1276 compiler_panic(pp->c, pp_materialize_loc(pp, loc), 1277 "#line outside any file"); 1278 { 1279 /* Record a positional #line overlay segment: from the cursor offset where 1280 * the directive takes effect onward, the reported line is the physical line 1281 * plus `delta`, and __FILE__ reports `target_file` (0 = none given). */ 1282 LocRef effect = lex_here(lex_src->lex); 1283 u32 phys = pp_phys_line(pp, effect); 1284 i32 delta = (i32)target_line - (i32)phys; 1285 pp_add_line_seg(pp, lex_file_id(lex_src->lex), effect.off, delta, 1286 target_file); 1287 } 1288 } 1289 1290 /* ============================================================ 1291 * #pragma + _Pragma (§6.10.6, §6.10.9) 1292 * ============================================================ */ 1293 1294 /* Push the unmodified directive line back onto the source stack as a 1295 * buffer, so pp_emit_text writes it as-is. SRC_BUF gates directive 1296 * recognition off, so this won't recurse. */ 1297 void emit_pragma_line(Pp* pp, const Tok* line, u32 n, LocRef loc) { 1298 TokVec out = {0}; 1299 u32 i; 1300 Tok hash, ident, nl; 1301 1302 memset(&hash, 0, sizeof(hash)); 1303 hash.kind = TOK_PP_HASH; 1304 hash.flags = TF_AT_BOL; 1305 hash.loc = loc; 1306 hash.aux = '#'; 1307 hash.text = text_none_ref(); 1308 tv_push(pp, &out, hash); 1309 1310 memset(&ident, 0, sizeof(ident)); 1311 ident.kind = TOK_IDENT; 1312 ident.flags = 0; 1313 ident.loc = loc; 1314 ident.aux = pp->sym_pragma_kw; 1315 ident.text = text_sym_ref(pp->sym_pragma_kw); 1316 tv_push(pp, &out, ident); 1317 1318 for (i = 0; i < n; ++i) { 1319 Tok t = line[i]; 1320 /* Force a leading space between tokens. */ 1321 t.flags |= TF_HAS_SPACE; 1322 if (i == 0) { 1323 /* Space between "pragma" and the first arg. */ 1324 } 1325 tv_push(pp, &out, t); 1326 } 1327 1328 memset(&nl, 0, sizeof(nl)); 1329 nl.kind = TOK_NEWLINE; 1330 nl.loc = loc; 1331 tv_push(pp, &out, nl); 1332 1333 push_buf(pp, out.data, out.n); 1334 } 1335 1336 static int pragma_num_u32(Pp* pp, const Tok* t, u32* out) { 1337 const char* s; 1338 size_t len; 1339 u32 v = 0; 1340 KitSlice sl; 1341 if (!t || t->kind != TOK_NUM || !out) return 0; 1342 sl = pp_text_slice(pp, t); 1343 s = sl.s; 1344 len = sl.len; 1345 if (!s || len == 0) return 0; 1346 for (size_t i = 0; i < len; ++i) { 1347 if (s[i] < '0' || s[i] > '9') break; 1348 v = v * 10u + (u32)(s[i] - '0'); 1349 } 1350 *out = v; 1351 return 1; 1352 } 1353 1354 static void handle_pragma_pack(Pp* pp, const Tok* line, u32 n, LocRef loc) { 1355 u32 i = 0; 1356 if (n < 3 || line[0].kind != TOK_IDENT) return; 1357 if (!pp_text_eq_cstr(pp, &line[0], "pack")) return; 1358 if (line[1].kind != TOK_PUNCT || tok_punct(&line[1]) != '(') return; 1359 i = 2; 1360 if (i < n && line[i].kind == TOK_PUNCT && tok_punct(&line[i]) == ')') { 1361 pp->pack_align = 0; 1362 return; 1363 } 1364 if (i < n && line[i].kind == TOK_IDENT) { 1365 if (pp_text_eq_cstr(pp, &line[i], "push")) { 1366 if (pp->pack_stack_n < 1367 (u32)(sizeof pp->pack_stack / sizeof pp->pack_stack[0])) { 1368 pp->pack_stack[pp->pack_stack_n++] = pp->pack_align; 1369 } else { 1370 pp_warn( 1371 pp, pp_materialize_loc(pp, loc), 1372 "#pragma pack(push): pack stack overflow (max %u); push dropped", 1373 (u32)(sizeof pp->pack_stack / sizeof pp->pack_stack[0])); 1374 } 1375 ++i; 1376 if (i < n && line[i].kind == TOK_PUNCT && tok_punct(&line[i]) == ',') { 1377 u32 v = 0; 1378 ++i; 1379 if (i < n && pragma_num_u32(pp, &line[i], &v)) pp->pack_align = v; 1380 } 1381 return; 1382 } 1383 if (pp_text_eq_cstr(pp, &line[i], "pop")) { 1384 if (pp->pack_stack_n) pp->pack_align = pp->pack_stack[--pp->pack_stack_n]; 1385 return; 1386 } 1387 } 1388 { 1389 u32 v = 0; 1390 if (pragma_num_u32(pp, &line[i], &v)) pp->pack_align = v; 1391 } 1392 } 1393 1394 static void do_pragma(Pp* pp, const Tok* line, u32 n, LocRef loc) { 1395 /* #pragma once: mark the current file include-once for the multiple-include 1396 * optimization (see do_include). Still forwarded to output like any pragma. 1397 */ 1398 if (n >= 1 && line[0].kind == TOK_IDENT && 1399 tok_ident(&line[0]) == pp->sym_once) { 1400 TokSrc* gls = current_lex_src(pp); 1401 if (gls) gls->once = 1; 1402 } 1403 /* Forward unrecognised pragmas to the output. STDC pragmas pass 1404 * through too; we don't act on them yet. */ 1405 handle_pragma_pack(pp, line, n, loc); 1406 emit_pragma_line(pp, line, n, loc); 1407 } 1408 1409 /* Destringize a string literal token's content: strip surrounding quotes 1410 * and undo the `\"` and `\\` escapes. Other escape sequences pass 1411 * through verbatim — the result is fed back through the lexer, which 1412 * does its own escape handling for any string literals nested inside. */ 1413 static void destringize(Pp* pp, const Tok* str_tok, char* out, size_t cap, 1414 size_t* out_len) { 1415 KitSlice sl = pp_text_slice(pp, str_tok); 1416 const char* s = sl.s; 1417 size_t slen = sl.len; 1418 size_t i, w = 0; 1419 if (slen < 2 || s[0] != '"' || s[slen - 1] != '"') { 1420 compiler_panic(pp->c, pp_materialize_loc(pp, str_tok->loc), 1421 "_Pragma: argument must be a string literal"); 1422 } 1423 for (i = 1; i + 1 < slen; ++i) { 1424 char c = s[i]; 1425 if (c == '\\' && i + 2 < slen && (s[i + 1] == '\\' || s[i + 1] == '"')) { 1426 ++i; 1427 c = s[i]; 1428 } 1429 if (w + 1 >= cap) 1430 compiler_panic(pp->c, pp_materialize_loc(pp, str_tok->loc), 1431 "_Pragma: payload too long"); 1432 out[w++] = c; 1433 } 1434 out[w] = 0; 1435 *out_len = w; 1436 } 1437 1438 /* Handle a `_Pragma("...")` invocation. Caller has consumed the 1439 * `_Pragma` identifier. Reads `(` STR `)`, destringizes, re-lexes the 1440 * payload, and emits a #pragma directive line. */ 1441 int try_expand_pragma_op(Pp* pp, const Tok* invoke) { 1442 Tok lp, str, rp; 1443 char buf[1024]; 1444 size_t buf_n = 0; 1445 Lexer* lex; 1446 TokVec args = {0}; 1447 1448 /* Peek '(' (skipping NL). Use peek_for_invoke_paren for consistency, 1449 * but we need the saved-back behavior for a non-match. */ 1450 { 1451 int saw_ws; 1452 if (!peek_for_invoke_paren(pp, &saw_ws)) { 1453 return 0; /* not an invocation; emit _Pragma as ident */ 1454 } 1455 (void)saw_ws; 1456 } 1457 /* Read the string literal arg. */ 1458 str = src_next_raw(pp, NULL); 1459 if (str.kind != TOK_STR) { 1460 compiler_panic(pp->c, pp_materialize_loc(pp, invoke->loc), 1461 "_Pragma: expected string literal"); 1462 } 1463 rp = src_next_raw(pp, NULL); 1464 if (rp.kind != TOK_PUNCT || tok_punct(&rp) != ')') { 1465 compiler_panic(pp->c, pp_materialize_loc(pp, invoke->loc), 1466 "_Pragma: expected ')'"); 1467 } 1468 (void)lp; 1469 1470 destringize(pp, &str, buf, sizeof(buf) - 2, &buf_n); 1471 /* Append a NL so the lexer terminates cleanly. */ 1472 buf[buf_n++] = '\n'; 1473 buf[buf_n] = 0; 1474 1475 /* Re-lex into args. Bytes need to live until lex_close; copy into 1476 * arena. */ 1477 { 1478 SourceSpec spec; 1479 char* arena_buf = (char*)arena_alloc(pp->arena, buf_n + 1, 1); 1480 memcpy(arena_buf, buf, buf_n + 1); 1481 memset(&spec, 0, sizeof spec); 1482 spec.name = kit_slice_cstr("<_Pragma>"); 1483 spec.bytes = arena_buf; 1484 spec.len = (u32)buf_n; 1485 lex = lex_open(pp->c, &spec); 1486 } 1487 for (;;) { 1488 Tok t; 1489 lex_next(lex, &t); 1490 if (t.kind == TOK_EOF || t.kind == TOK_NEWLINE) break; 1491 /* These tokens outlive `lex`; a TEXT_SRC spelling points into the lexer's 1492 * own buffer, so intern it now while the lexer is still open. */ 1493 if (t.text.kind == TEXT_SRC) 1494 t.text = text_intern_ref(pp, lex_text_slice(lex, t.text)); 1495 tv_push(pp, &args, t); 1496 } 1497 lex_close(lex); 1498 1499 emit_pragma_line(pp, args.data, args.n, invoke->loc); 1500 return 1; 1501 } 1502 1503 /* ============================================================ 1504 * #error / #warning 1505 * ============================================================ */ 1506 1507 static void directive_message(Pp* pp, const Tok* line, u32 n, CharBuf* cb) { 1508 u32 i; 1509 for (i = 0; i < n; ++i) { 1510 KitSlice slc = pp_text_slice(pp, &line[i]); 1511 const char* s = slc.s; 1512 size_t sl = slc.len; 1513 if (i > 0) cb_putc(pp, cb, ' '); 1514 if (s && sl) cb_append(pp, cb, s, (u32)sl); 1515 } 1516 cb_putc(pp, cb, 0); 1517 } 1518 1519 static void pp_warn(Pp* pp, SrcLoc loc, const char* fmt, ...) { 1520 KitDiagSink* sink = kit_compiler_context(pp->c)->diag; 1521 va_list ap; 1522 if (sink && sink->emit) { 1523 va_start(ap, fmt); 1524 sink->emit(sink, KIT_DIAG_WARN, loc, fmt, ap); 1525 va_end(ap); 1526 } 1527 if (sink) sink->warnings++; 1528 } 1529 1530 static void do_error(Pp* pp, const Tok* line, u32 n, LocRef loc) { 1531 CharBuf cb = {0}; 1532 directive_message(pp, line, n, &cb); 1533 compiler_panic(pp->c, pp_materialize_loc(pp, loc), "#error: %.*s", 1534 KIT_SLICE_ARG(kit_slice_cstr(cb.data ? cb.data : ""))); 1535 } 1536 1537 static void do_warning(Pp* pp, const Tok* line, u32 n, LocRef loc) { 1538 CharBuf cb = {0}; 1539 directive_message(pp, line, n, &cb); 1540 pp_warn(pp, pp_materialize_loc(pp, loc), "#warning: %.*s", 1541 KIT_SLICE_ARG(kit_slice_cstr(cb.data ? cb.data : ""))); 1542 } 1543 1544 /* ============================================================ 1545 * #embed (C23, §6.10.* per N3033) 1546 * ============================================================ */ 1547 1548 static void do_embed(Pp* pp, const Tok* line, u32 n, LocRef loc) { 1549 char path[4096]; 1550 char resolved[4096]; 1551 int system_form = 0; 1552 const u8* data; 1553 size_t size; 1554 u32 j; 1555 /* Optional embed parameters parsed below. */ 1556 i64 limit_n = -1; 1557 Tok* if_empty_toks = NULL; 1558 u32 if_empty_n = 0; 1559 /* Header-name path: first token. */ 1560 u32 arg_start = 0; 1561 1562 if (n == 0) 1563 compiler_panic(pp->c, pp_materialize_loc(pp, loc), "#embed: missing path"); 1564 1565 if (line[0].kind == TOK_HEADER) { 1566 KitSlice slc = pp_text_slice(pp, &line[0]); 1567 header_name_to_path(pp, slc, path, sizeof(path), &system_form, loc, 1568 "#embed"); 1569 arg_start = 1; 1570 } else { 1571 compiler_panic(pp->c, pp_materialize_loc(pp, loc), 1572 "#embed: header-name argument required"); 1573 } 1574 1575 /* Parse trailing parameters: limit(N), if_empty(...). */ 1576 j = arg_start; 1577 while (j < n) { 1578 if (line[j].kind == TOK_IDENT) { 1579 KitSlice slc = pp_text_slice(pp, &line[j]); 1580 const char* s = slc.s; 1581 size_t sl = slc.len; 1582 if (sl == 5 && memcmp(s, "limit", 5) == 0) { 1583 if (j + 1 >= n || line[j + 1].kind != TOK_PUNCT || 1584 tok_punct(&line[j + 1]) != '(') { 1585 compiler_panic(pp->c, pp_materialize_loc(pp, loc), 1586 "#embed: expected '(' after limit"); 1587 } 1588 j += 2; 1589 if (j >= n || line[j].kind != TOK_NUM) { 1590 compiler_panic(pp->c, pp_materialize_loc(pp, loc), 1591 "#embed: limit() expects an integer"); 1592 } 1593 { 1594 KitSlice s2 = pp_text_slice(pp, &line[j]); 1595 limit_n = parse_pp_int(s2.s, s2.len); 1596 } 1597 ++j; 1598 if (j >= n || line[j].kind != TOK_PUNCT || tok_punct(&line[j]) != ')') { 1599 compiler_panic(pp->c, pp_materialize_loc(pp, loc), 1600 "#embed: expected ')' to close limit"); 1601 } 1602 ++j; 1603 continue; 1604 } 1605 if (sl == 8 && memcmp(s, "if_empty", 8) == 0) { 1606 u32 depth = 0; 1607 u32 start; 1608 if (j + 1 >= n || line[j + 1].kind != TOK_PUNCT || 1609 tok_punct(&line[j + 1]) != '(') { 1610 compiler_panic(pp->c, pp_materialize_loc(pp, loc), 1611 "#embed: expected '(' after if_empty"); 1612 } 1613 j += 2; 1614 start = j; 1615 while (j < n) { 1616 if (line[j].kind == TOK_PUNCT) { 1617 if (tok_punct(&line[j]) == '(') 1618 ++depth; 1619 else if (tok_punct(&line[j]) == ')') { 1620 if (depth == 0) break; 1621 --depth; 1622 } 1623 } 1624 ++j; 1625 } 1626 if (j >= n) { 1627 compiler_panic(pp->c, pp_materialize_loc(pp, loc), 1628 "#embed: unterminated if_empty"); 1629 } 1630 if_empty_toks = arena_array(pp->arena, Tok, j - start ? j - start : 1); 1631 if_empty_n = j - start; 1632 memcpy(if_empty_toks, line + start, sizeof(Tok) * if_empty_n); 1633 ++j; /* skip ')' */ 1634 continue; 1635 } 1636 } 1637 compiler_panic(pp->c, pp_materialize_loc(pp, loc), 1638 "#embed: unexpected token in parameter list"); 1639 } 1640 1641 { 1642 /* #embed does not record a dependency edge, so the resolved-dir system 1643 * flag and #include_next resume point are not consumed here. */ 1644 int embed_resolved_system = 0; 1645 u32 embed_next_start = 0; 1646 if (!find_and_open_include(pp, path, system_form, loc, &data, &size, 1647 resolved, sizeof(resolved), 1648 &embed_resolved_system, &embed_next_start)) { 1649 compiler_panic(pp->c, pp_materialize_loc(pp, loc), 1650 "#embed: file not found: %.*s", 1651 KIT_SLICE_ARG(kit_slice_cstr(path))); 1652 } 1653 } 1654 1655 /* Apply limit(). */ 1656 { 1657 size_t emit_n = size; 1658 if (limit_n >= 0 && (u64)limit_n < emit_n) emit_n = (size_t)limit_n; 1659 if (emit_n == 0) { 1660 /* Empty: emit if_empty payload (or nothing). */ 1661 if (if_empty_toks && if_empty_n) { 1662 push_buf(pp, if_empty_toks, if_empty_n); 1663 } 1664 return; 1665 } 1666 /* Build a buffer of pp-numbers separated by ',' punctuators. */ 1667 { 1668 TokVec out = {0}; 1669 size_t i; 1670 for (i = 0; i < emit_n; ++i) { 1671 char numbuf[8]; 1672 int nl = 0; 1673 u8 v = data[i]; 1674 /* "u8 -> decimal" without sprintf. */ 1675 if (v == 0) { 1676 numbuf[nl++] = '0'; 1677 } else { 1678 char tmp[4]; 1679 int k = 0; 1680 while (v) { 1681 tmp[k++] = (char)('0' + (v % 10)); 1682 v /= 10; 1683 } 1684 while (k > 0) numbuf[nl++] = tmp[--k]; 1685 } 1686 { 1687 Tok t; 1688 memset(&t, 0, sizeof(t)); 1689 t.kind = TOK_NUM; 1690 t.loc = loc; 1691 t.aux = 0; 1692 t.text = 1693 text_intern_ref(pp, (KitSlice){.s = numbuf, .len = (size_t)nl}); 1694 if (i == 0) t.flags = TF_AT_BOL; 1695 /* Bytes after a comma get a leading space to match 1696 * clang's `, ` separator format. */ 1697 else 1698 t.flags = TF_HAS_SPACE; 1699 tv_push(pp, &out, t); 1700 } 1701 if (i + 1 < emit_n) { 1702 Tok comma; 1703 memset(&comma, 0, sizeof(comma)); 1704 comma.kind = TOK_PUNCT; 1705 comma.aux = ','; 1706 comma.loc = loc; 1707 comma.text = text_none_ref(); 1708 tv_push(pp, &out, comma); 1709 } 1710 } 1711 push_buf(pp, out.data, out.n); 1712 } 1713 } 1714 } 1715 1716 /* ============================================================ 1717 * Directive dispatch 1718 * ============================================================ */ 1719 1720 void process_directive(Pp* pp, LocRef hash_loc) { 1721 Tok* line; 1722 u32 n; 1723 Sym name; 1724 1725 read_directive_line(pp, &line, &n); 1726 if (n == 0) { 1727 /* Null directive: '#' newline. Nothing to do. */ 1728 return; 1729 } 1730 if (line[0].kind != TOK_IDENT) { 1731 compiler_panic(pp->c, pp_materialize_loc(pp, line[0].loc), 1732 "expected directive name after '#'"); 1733 } 1734 name = tok_ident(&line[0]); 1735 1736 /* Multiple-include-guard state machine, run on the current file source 1737 * before dispatch. The only directive that may open a whole-file guard is 1738 * `#ifndef MACRO` as the very first directive (nothing but whitespace before 1739 * it, at the file's base if-depth); any other directive seen at START, or any 1740 * directive at all after the controlling #endif (AFTER), disqualifies it. 1741 * #pragma is exempt — #pragma once is its own (orthogonal) mechanism. */ 1742 { 1743 TokSrc* gls = current_lex_src(pp); 1744 if (gls && name != pp->sym_pragma) { 1745 if (gls->guard_state == GUARD_START) { 1746 if (name == pp->sym_ifndef && n >= 2 && line[1].kind == TOK_IDENT && 1747 pp->ifstk_n == gls->guard_if_base) { 1748 gls->guard_macro = tok_ident(&line[1]); 1749 gls->guard_state = GUARD_IN; 1750 } else { 1751 gls->guard_state = GUARD_FAILED; 1752 } 1753 } else if (gls->guard_state == GUARD_AFTER) { 1754 gls->guard_state = GUARD_FAILED; 1755 } 1756 } 1757 } 1758 1759 if (name == pp->sym_define) 1760 do_define(pp, line + 1, n - 1); 1761 else if (name == pp->sym_undef) 1762 do_undef(pp, line + 1, n - 1); 1763 else if (name == pp->sym_if) 1764 do_if_directive(pp, line + 1, n - 1, hash_loc); 1765 else if (name == pp->sym_ifdef) 1766 do_ifdef(pp, line + 1, n - 1, 0, hash_loc); 1767 else if (name == pp->sym_ifndef) 1768 do_ifdef(pp, line + 1, n - 1, 1, hash_loc); 1769 else if (name == pp->sym_elif) 1770 do_elif(pp, hash_loc); 1771 else if (name == pp->sym_else) 1772 do_else(pp, hash_loc); 1773 else if (name == pp->sym_endif) 1774 do_endif(pp, hash_loc); 1775 else if (name == pp->sym_include) 1776 do_include(pp, line + 1, n - 1, hash_loc, 0); 1777 else if (name == pp->sym_include_next) 1778 do_include(pp, line + 1, n - 1, hash_loc, 1); 1779 else if (name == pp->sym_line) 1780 do_line(pp, line + 1, n - 1, hash_loc); 1781 else if (name == pp->sym_pragma) 1782 do_pragma(pp, line + 1, n - 1, hash_loc); 1783 else if (name == pp->sym_error) 1784 do_error(pp, line + 1, n - 1, hash_loc); 1785 else if (name == pp->sym_warning) 1786 do_warning(pp, line + 1, n - 1, hash_loc); 1787 else if (name == pp->sym_embed) 1788 do_embed(pp, line + 1, n - 1, hash_loc); 1789 else { 1790 compiler_panic(pp->c, pp_materialize_loc(pp, line[0].loc), 1791 "unsupported directive"); 1792 } 1793 1794 /* The controlling #endif (the one returning the if-stack to the file's base 1795 * depth) closes a candidate guard. After this only whitespace/newlines may 1796 * follow for the guard to hold; any further token/directive flips it to 1797 * FAILED (see the START/AFTER handling above and the content check in 1798 * src_next_raw_into). do_include/do_undef etc. do not change current_lex_src 1799 * out from under us here because this only fires for #endif. */ 1800 if (name == pp->sym_endif) { 1801 TokSrc* gls = current_lex_src(pp); 1802 if (gls && gls->guard_state == GUARD_IN && 1803 pp->ifstk_n == gls->guard_if_base) { 1804 gls->guard_state = GUARD_AFTER; 1805 } 1806 } 1807 }