kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit c69c97e24cedb77bac58398c95fec87a0c06328b
parent c289ef65e1f1eaa78ef7a2370c97a9d7653a6eb6
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Fri, 12 Jun 2026 09:42:54 -0700

feat(pp): implement __has_include, __has_include_next, and #include_next

__has_include / __has_include_next were never implemented, so in a #if they
fell through to the identifier->0 rewrite and always read false. Modern system
headers (e.g. the macOS SDK) then took their fallback #else typedefs, which
collided with the real declarations pulled in elsewhere. Add a prepass_has_include
pass (mirroring prepass_defined, run before macro expansion so the <...> operand
isn't mistaken for comparison operators) that resolves the header against the
include search path; report defined(__has_include) as 1 for the guarded idiom.

#include_next was recognized by the lexer but dispatched nowhere. Add the
directive, thread the originating search-dir index through TokSrc.inc_next_start
(memoized via IncResolved.next_start), and resume the search one dir past where
the current file was found.

Diffstat:
Mlang/cpp/pp/pp.c | 4++++
Mlang/cpp/pp/pp_directive.c | 240+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Mlang/cpp/pp/pp_priv.h | 12++++++++++++
Mmk/test.mk | 6+++++-
Atest/pp/cases/95_has_include.c | 26++++++++++++++++++++++++++
Atest/pp/cases/95_has_include.expected | 6++++++
Atest/pp/cases/95_inc.h | 1+
Atest/pp/run_include_next.sh | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 352 insertions(+), 37 deletions(-)

diff --git a/lang/cpp/pp/pp.c b/lang/cpp/pp/pp.c @@ -246,6 +246,10 @@ static void pp_intern_keywords(Pp* pp) { pp->sym_define = kit_sym_intern(p->c, KIT_SLICE_LIT("define")); pp->sym_undef = kit_sym_intern(p->c, KIT_SLICE_LIT("undef")); pp->sym_include = kit_sym_intern(p->c, KIT_SLICE_LIT("include")); + pp->sym_include_next = kit_sym_intern(p->c, KIT_SLICE_LIT("include_next")); + pp->sym_has_include = kit_sym_intern(p->c, KIT_SLICE_LIT("__has_include")); + pp->sym_has_include_next = + kit_sym_intern(p->c, KIT_SLICE_LIT("__has_include_next")); pp->sym_if = kit_sym_intern(p->c, KIT_SLICE_LIT("if")); pp->sym_ifdef = kit_sym_intern(p->c, KIT_SLICE_LIT("ifdef")); pp->sym_ifndef = kit_sym_intern(p->c, KIT_SLICE_LIT("ifndef")); diff --git a/lang/cpp/pp/pp_directive.c b/lang/cpp/pp/pp_directive.c @@ -121,13 +121,20 @@ static void prepass_defined(Pp* pp, const Tok* in, u32 nin, TokVec* out) { } { Tok t; + /* `__has_include` / `__has_include_next` are not macros (their operand + * isn't a normal token sequence) but, like clang/GCC, they answer + * `defined()` with 1 so the guarded idiom + * #if defined(__has_include) && __has_include(<x>) + * — common in system headers — takes its intended branch. */ + int is_defined = mt_get(pp, ident) != NULL || + ident == pp->sym_has_include || + ident == pp->sym_has_include_next; memset(&t, 0, sizeof(t)); 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, mt_get(pp, ident) ? KIT_SLICE_LIT("1") - : KIT_SLICE_LIT("0")); + t.spelling = kit_sym_intern( + pp->pool->c, is_defined ? KIT_SLICE_LIT("1") : KIT_SLICE_LIT("0")); tv_push(pp, out, t); } i = j - 1; @@ -388,15 +395,114 @@ static i64 ee_ternary(EE* e) { return c; } +/* 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, + 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, + 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, + const u8** data, size_t* size, + char* resolved, size_t resolved_cap, + int* resolved_system_out, + u32* next_start_out); + +/* Pre-pass: replace `__has_include(<h>)` / `__has_include("h")` (and the + * `__has_include_next` variant) with the pp-number 1 or 0 per whether the + * header resolves against the include search path — the same search a real + * #include would perform. Runs BEFORE macro expansion so the `<...>` form's + * `<` and `>` are never mistaken for comparison operators, and so the header + * tokens are not subject to the surrounding #if's identifier→0 rewrite. The + * operand itself is still macro-expanded (via parse_include_path), matching + * the `__has_include(HEADER_MACRO)` idiom. Output is a fresh TokVec. */ +static void prepass_has_include(Pp* pp, const Tok* in, u32 nin, TokVec* out) { + u32 i; + for (i = 0; i < nin; ++i) { + int is_has = 0, is_next = 0; + u32 j, op_n, depth, s; + const Tok* op_first; + char path[4096]; + char resolved[4096]; + int system_form = 0, resolved_system = 0, present = 0; + u32 next_start = 0, next_search = 0; + const u8* data; + size_t size; + Tok t; + const char* what; + + if (in[i].kind == TOK_IDENT) { + if (in[i].v.ident == pp->sym_has_include) + is_has = 1; + else if (in[i].v.ident == pp->sym_has_include_next) + is_has = is_next = 1; + } + if (!is_has) { + tv_push(pp, out, in[i]); + continue; + } + 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); + } + ++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 == '(') { + ++depth; + } else if (in[j].kind == TOK_PUNCT && in[j].v.punct == ')') { + if (--depth == 0) break; + } + } + if (j >= nin) compiler_panic(pp->c, 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), + &system_form); + if (is_next) { + for (s = pp->nsources; s > 0; --s) { + TokSrc* tp = &pp->sources[s - 1]; + if (tp->kind == SRC_LEX && tp->lex) { + next_search = tp->inc_next_start; + break; + } + } + present = find_and_open_include_next(pp, path, next_search, &data, &size, + resolved, sizeof(resolved), + &resolved_system, &next_start); + } else { + present = find_and_open_include(pp, path, system_form, in[i].loc, &data, + &size, resolved, sizeof(resolved), + &resolved_system, &next_start); + } + + memset(&t, 0, sizeof(t)); + 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")); + tv_push(pp, out, t); + i = j; /* resume past the matching ')' */ + } +} + i64 eval_if_expr(Pp* pp, const Tok* line, u32 n, SrcLoc loc) { TokVec defs = {0}; + TokVec hasinc = {0}; TokVec exp = {0}; TokVec defs2 = {0}; EE e; i64 v; prepass_defined(pp, line, n, &defs); - expand_for_if(pp, defs.data, defs.n, &exp); + prepass_has_include(pp, defs.data, defs.n, &hasinc); + expand_for_if(pp, hasinc.data, hasinc.n, &exp); prepass_defined(pp, exp.data, exp.n, &defs2); replace_remaining_if_identifiers(pp, &defs2); @@ -759,13 +865,16 @@ static Sym inc_resolve_key(Pp* pp, const char* path, size_t plen, int system, /* Record a successful dir-search resolution under its spelling key, so the * next request for the same spelling skips the search. No-op when the key - * was unavailable (rkey == 0). `resolved` is a NUL-terminated path. */ + * was unavailable (rkey == 0). `resolved` is a NUL-terminated path. + * `next_start` is the inc_dirs index a #include_next from the resolved file + * resumes at (0 for the includer-relative / absolute wins). */ static void inc_resolve_record(Pp* pp, Sym rkey, const char* resolved, - int resolved_system) { + int resolved_system, u32 next_start) { IncResolved e; if (!rkey) return; e.path = kit_sym_intern(pp->pool->c, kit_slice_cstr(resolved)); e.system = (u8)(resolved_system ? 1 : 0); + e.next_start = next_start; IncResolveMap_set(&pp->inc_resolve, rkey, e); } @@ -778,18 +887,50 @@ static void inc_resolve_record(Pp* pp, Sym rkey, const char* resolved, * (with the same system flag and, for quoted form, the same includer dir) * was resolved before, we go straight to the winning path — skipping the * dir-by-dir ENOENT storm — and the content cache serves its bytes. */ +/* Walk pp->inc_dirs[start ..] looking for `path`, opening the first hit. + * On success fills data/size, writes the resolved path and its -isystem + * 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) { + char buf[4096]; + size_t plen = kit_slice_cstr(path).len; + u32 i; + for (i = start; i < pp->ninc_dirs; ++i) { + const char* d = pp->inc_dirs[i].path; + size_t dlen = kit_slice_cstr(d).len; + if (dlen + 1 + plen + 1 > sizeof(buf)) continue; + memcpy(buf, d, dlen); + buf[dlen] = '/'; + memcpy(buf + dlen + 1, path, plen); + buf[dlen + 1 + plen] = 0; + if (open_include_cached(pp, buf, data, size)) { + if (dlen + 1 + plen + 1 > resolved_cap) return 0; + memcpy(resolved, buf, dlen + 1 + plen + 1); + *resolved_system_out = pp->inc_dirs[i].system ? 1 : 0; + *next_start = i + 1; + return 1; + } + } + return 0; +} + static int find_and_open_include(Pp* pp, const char* path, int system, SrcLoc loc, const u8** data, size_t* size, char* resolved, size_t resolved_cap, - int* resolved_system_out) { + int* resolved_system_out, u32* next_start_out) { char buf[4096]; - u32 i; size_t plen = kit_slice_cstr(path).len; Sym rkey = 0; /* Absolute paths and the includer-relative ("...") step are not system - * search dirs; only a configured -isystem dir flips this to 1 below. */ + * search dirs; only a configured -isystem dir flips this to 1 below. A + * #include_next from such a file resumes at the head of the search path. */ *resolved_system_out = 0; + *next_start_out = 0; if (plen > 0 && path[0] == '/') { if (open_include_cached(pp, path, data, size)) { @@ -814,6 +955,7 @@ static int find_and_open_include(Pp* pp, const char* path, int system, if (!open_include_cached(pp, rp.s, data, size)) return 0; memcpy(resolved, rp.s, rp.len + 1); *resolved_system_out = hit->system ? 1 : 0; + *next_start_out = hit->next_start; return 1; } } @@ -830,31 +972,37 @@ static int find_and_open_include(Pp* pp, const char* path, int system, if (open_include_cached(pp, buf, data, size)) { if (dlen + 1 + plen + 1 > resolved_cap) return 0; memcpy(resolved, buf, dlen + 1 + plen + 1); - inc_resolve_record(pp, rkey, resolved, 0); + inc_resolve_record(pp, rkey, resolved, 0, 0); return 1; } } } } - for (i = 0; i < pp->ninc_dirs; ++i) { - const char* d = pp->inc_dirs[i].path; - size_t dlen = kit_slice_cstr(d).len; - if (dlen + 1 + plen + 1 > sizeof(buf)) continue; - memcpy(buf, d, dlen); - buf[dlen] = '/'; - memcpy(buf + dlen + 1, path, plen); - buf[dlen + 1 + plen] = 0; - if (open_include_cached(pp, buf, data, size)) { - if (dlen + 1 + plen + 1 > resolved_cap) return 0; - memcpy(resolved, buf, dlen + 1 + plen + 1); - *resolved_system_out = pp->inc_dirs[i].system ? 1 : 0; - inc_resolve_record(pp, rkey, resolved, *resolved_system_out); - return 1; - } + if (search_inc_dirs(pp, path, 0, data, size, resolved, resolved_cap, + resolved_system_out, next_start_out)) { + inc_resolve_record(pp, rkey, resolved, *resolved_system_out, + *next_start_out); + return 1; } return 0; } +/* #include_next: like #include, but the search resumes at `start` (one past + * the dir the including file was found in) and skips both the absolute-path + * fast path and the includer-relative step. Bypasses the resolution memo — + * the memo key does not encode the resume point, and #include_next is rare. */ +static int find_and_open_include_next(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_out) { + *resolved_system_out = 0; + *next_start_out = 0; + inc_cache_ensure(pp); + return search_inc_dirs(pp, path, start, data, size, resolved, resolved_cap, + resolved_system_out, next_start_out); +} + /* Decode a directly-lexed TOK_HEADER name into a NUL-terminated path. * Classifies <...> (system) vs "..." (local), enforces the destination * capacity, and writes the unwrapped contents to out. `what` is the @@ -942,42 +1090,59 @@ static void parse_include_path(Pp* pp, const Tok* line, u32 n, SrcLoc loc, } } -static void do_include(Pp* pp, const Tok* line, u32 n, SrcLoc loc) { +/* Shared core for #include and #include_next (is_next == 1). */ +static void do_include(Pp* pp, const Tok* line, u32 n, SrcLoc loc, + int is_next) { char path[4096]; char resolved[4096]; int system_form = 0; int resolved_system = 0; + u32 next_start = 0; const u8* data; size_t size; Lexer* lex; u32 includer_id = 0; + u32 next_search = 0; u32 included_id; u32 i; + int found; TokSrc s; parse_include_path(pp, line, n, loc, path, sizeof(path), &system_form); - if (!find_and_open_include(pp, path, system_form, loc, &data, &size, resolved, - sizeof(resolved), &resolved_system)) { - compiler_panic(pp->c, loc, "#include: file not found: %.*s", - KIT_SLICE_ARG(kit_slice_cstr(path))); - } - - /* Walk the source stack to find the current includer's file_id. */ + /* Locate the current includer (topmost SRC_LEX source): its file_id seeds + * the include graph, and — for #include_next — its inc_next_start is the + * point in the search path from which we resume. */ for (i = pp->nsources; i > 0; --i) { TokSrc* tp = &pp->sources[i - 1]; if (tp->kind == SRC_LEX && tp->lex) { includer_id = lex_file_id(tp->lex); + next_search = tp->inc_next_start; break; } } + if (is_next) + found = find_and_open_include_next(pp, path, next_search, &data, &size, + resolved, sizeof(resolved), + &resolved_system, &next_start); + else + found = find_and_open_include(pp, path, system_form, loc, &data, &size, + resolved, sizeof(resolved), &resolved_system, + &next_start); + if (!found) { + compiler_panic(pp->c, loc, "%s: file not found: %.*s", + is_next ? "#include_next" : "#include", + KIT_SLICE_ARG(kit_slice_cstr(path))); + } + lex = lex_open_mem(pp->c, resolved, (const char*)data, size); included_id = lex_file_id(lex); memset(&s, 0, sizeof(s)); s.kind = SRC_LEX; s.lex = lex; + s.inc_next_start = next_start; src_push(pp, s); kit_source_add_include(pp->c, includer_id, included_id, loc, system_form, @@ -1391,11 +1556,12 @@ static void do_embed(Pp* pp, const Tok* line, u32 n, SrcLoc loc) { { /* #embed does not record a dependency edge, so the resolved-dir system - * flag is not consumed here. */ + * flag and #include_next resume point are not consumed here. */ int embed_resolved_system = 0; + u32 embed_next_start = 0; if (!find_and_open_include(pp, path, system_form, loc, &data, &size, resolved, sizeof(resolved), - &embed_resolved_system)) { + &embed_resolved_system, &embed_next_start)) { compiler_panic(pp->c, loc, "#embed: file not found: %.*s", KIT_SLICE_ARG(kit_slice_cstr(path))); } @@ -1505,7 +1671,9 @@ void process_directive(Pp* pp, SrcLoc hash_loc) { else if (name == pp->sym_endif) do_endif(pp, hash_loc); else if (name == pp->sym_include) - do_include(pp, line + 1, n - 1, hash_loc); + do_include(pp, line + 1, n - 1, hash_loc, 0); + else if (name == pp->sym_include_next) + do_include(pp, line + 1, n - 1, hash_loc, 1); else if (name == pp->sym_line) do_line(pp, line + 1, n - 1, hash_loc); else if (name == pp->sym_pragma) diff --git a/lang/cpp/pp/pp_priv.h b/lang/cpp/pp/pp_priv.h @@ -66,6 +66,13 @@ typedef struct TokSrc { * surrounding quotes) used by __FILE__ when set. */ i32 line_delta; Sym file_override; + /* 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 + * top-level translation unit and for files found via the includer-relative + * ("...") step or an absolute path, so an `#include_next` there scans the + * whole search path (matching GCC). */ + u32 inc_next_start; } TokSrc; typedef enum IfState { @@ -111,6 +118,8 @@ KIT_HASHMAP_DEFINE(IncCache, Sym, IncEntry, inc_hash_); typedef struct IncResolved { 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; static inline u32 incres_hash_(Sym s) { return kit_hash_u32((u32)s); } KIT_HASHMAP_DEFINE(IncResolveMap, Sym, IncResolved, incres_hash_); @@ -184,6 +193,9 @@ struct Pp { Sym sym_define; Sym sym_undef; Sym sym_include; + Sym sym_include_next; /* GCC/clang #include_next extension */ + Sym sym_has_include; /* __has_include() #if operator */ + Sym sym_has_include_next; /* __has_include_next() #if operator */ Sym sym_if; Sym sym_ifdef; Sym sym_ifndef; diff --git a/mk/test.mk b/mk/test.mk @@ -111,6 +111,7 @@ TEST_TARGETS = \ test-pp-err \ test-pp-ok \ test-pp-file-escape \ + test-pp-include-next \ test-rt-headers \ test-rt-runtime \ test-rt-backtrace \ @@ -259,7 +260,7 @@ test-wasm-toy: bin test-wasm-c: bin $(PARSE_RUNNER) @KIT_TEST_PATHS=W KIT_TEST_ALLOW_SKIP=1 KIT=$(abspath $(BIN)) bash test/parse/run.sh -test-pp: test-pp-ok test-pp-err test-pp-file-escape +test-pp: test-pp-ok test-pp-err test-pp-file-escape test-pp-include-next test-pp-ok: bin @KIT=$(abspath $(BIN)) test/pp/run.sh @@ -270,6 +271,9 @@ test-pp-err: bin test-pp-file-escape: bin @KIT=$(abspath $(BIN)) bash test/pp/run_file_escape.sh +test-pp-include-next: bin + @KIT=$(abspath $(BIN)) bash test/pp/run_include_next.sh + # Best-effort kit binary build: Layer D needs build/kit, but the # binary may not link until enough libkit symbols exist. The harness # detects a missing binary and skips that layer; don't break test-elf diff --git a/test/pp/cases/95_has_include.c b/test/pp/cases/95_has_include.c @@ -0,0 +1,26 @@ +/* __has_include / __has_include_next operator (§6.10.1, clang/GCC extension). + * Run with -I . so the angle-bracket search dir is the cases directory. */ +#if __has_include("95_inc.h") +quote_yes +#else +quote_no +#endif +#if __has_include(<95_inc.h>) +angle_yes +#else +angle_no +#endif +#if __has_include(<no_such_header_zzqq.h>) +missing_yes +#else +missing_no +#endif +#if defined(__has_include) +have_op +#endif +#if defined(__has_include) && __has_include("95_inc.h") +guarded_yes +#endif +#if __has_include(<sys/types.h>) ? 0 : 1 +no_sysroot_ok +#endif diff --git a/test/pp/cases/95_has_include.expected b/test/pp/cases/95_has_include.expected @@ -0,0 +1,6 @@ +quote_yes +angle_yes +missing_no +have_op +guarded_yes +no_sysroot_ok diff --git a/test/pp/cases/95_inc.h b/test/pp/cases/95_inc.h @@ -0,0 +1 @@ +/* Sibling header probed by 95_has_include.c via __has_include. */ diff --git a/test/pp/run_include_next.sh b/test/pp/run_include_next.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +# test/pp/run_include_next.sh — regression for `#include_next` and +# `__has_include_next` (clang/GCC preprocessor extensions). +# +# #include_next resumes the header search in the directories *after* the one the +# current file was found in — the mechanism system-header wrappers use to pull in +# the "real" header of the same name further down the search path. It cannot be +# exercised by the single-`-I` golden corpus (test/pp/run.sh), so it gets its own +# two-directory harness here. +# +# Layout: dirs a/ and b/ both hold widget.h. Compiling `#include <widget.h>` with +# `-I a -I b` resolves to a/widget.h; its `#include_next <widget.h>` must then +# reach b/widget.h (the next dir), not loop back on itself. __has_include_next +# from a/ must see b/widget.h as present, and from b/ (the last dir) as absent. +# +# Host-target only: no sysroot needed (all headers are local). + +set -u + +ROOT=${KIT_TEST_ROOT:-$(cd "$(dirname "$0")/../.." && pwd)} +KIT=${KIT:-"$ROOT/build/kit"} +export KIT + +KIT_KIT_DIR="$ROOT/test/lib" +. "$ROOT/test/lib/kit_sh_kit.sh" +kit_require_kit pp-include-next + +kit_report_init +work=$(mktemp -d "${TMPDIR:-/tmp}/kit-pp-incnext.XXXXXX") +trap 'rm -rf "$work"' EXIT +cd "$work" || exit 2 + +mkdir -p a b +cat > a/widget.h <<'EOF' +marker_a_before +#if __has_include_next(<widget.h>) +a_sees_next +#else +a_no_next +#endif +#include_next <widget.h> +marker_a_after +EOF +cat > b/widget.h <<'EOF' +marker_b +#if __has_include_next(<widget.h>) +b_sees_next +#else +b_no_next +#endif +EOF +printf '#include <widget.h>\ndone\n' > main.c + +# emit_seq NAME : `kit cc -E -I a -I b main.c` must succeed and its output tokens +# must appear in the exact relative order of the remaining args. +emit_seq() { + name=$1; shift + if ! "$KIT" cc -E -I a -I b main.c -o "$work/$name.pp" \ + >"$work/$name.out" 2>"$work/$name.err"; then + kit_fail "$name" "kit cc -E failed; see $work/$name.err" + return + fi + # Collapse to one token per line, then check the needles appear in order. + tr -s '[:space:]' '\n' < "$work/$name.pp" > "$work/$name.toks" + local prev=0 line + for tok in "$@"; do + line=$(grep -nxF "$tok" "$work/$name.toks" | head -1 | cut -d: -f1) + if [ -z "$line" ]; then + kit_fail "$name" "missing token '$tok'"; return + fi + if [ "$line" -le "$prev" ]; then + kit_fail "$name" "token '$tok' out of order (line $line <= $prev)"; return + fi + prev=$line + done + kit_pass "$name" +} + +# include_next from a/ reaches b/ (and a/ continues after); __has_include_next +# is true in a/ (b follows) and false in b/ (last dir). +emit_seq include_next_order \ + marker_a_before a_sees_next marker_b b_no_next marker_a_after done + +# A token that must NOT appear: a/ must not loop back onto itself (a_no_next), +# and b/ must not think another widget.h follows it (b_sees_next). +if grep -qxF a_no_next "$work/include_next_order.toks" 2>/dev/null \ + || grep -qxF b_sees_next "$work/include_next_order.toks" 2>/dev/null; then + kit_fail include_next_no_loop "unexpected a_no_next/b_sees_next token" +else + kit_pass include_next_no_loop +fi + +kit_summary pp-include-next +kit_exit