diff --git a/preprocess.c b/preprocess.c index a09ba6a..10a85e3 100644 --- a/preprocess.c +++ b/preprocess.c @@ -777,6 +777,45 @@ static char *read_include_path(Token **rest, Token *tok) { error_tok(tok, "expected a filename"); } +// Detect the following "include guard" pattern. +// +// #ifndef FOO_H +// #define FOO_H +// ... +// #endif +static char *detect_include_guard(Token *tok) { + // Detect the first two lines. + if (!is_hash(tok) || !equal(tok->next, "ifndef")) + return NULL; + tok = tok->next->next; + + if (tok->kind != TK_IDENT) + return NULL; + + char *macro = strndup(tok->loc, tok->len); + tok = tok->next; + + if (!is_hash(tok) || !equal(tok->next, "define") || !equal(tok->next->next, macro)) + return NULL; + + // Read until the end of the file. + while (tok->kind != TK_EOF) { + if (!is_hash(tok)) { + tok = tok->next; + continue; + } + + if (equal(tok->next, "endif") && tok->next->next->kind == TK_EOF) + return macro; + + if (equal(tok, "if") || equal(tok, "ifdef") || equal(tok, "ifndef")) + tok = skip_cond_incl(tok->next); + else + tok = tok->next; + } + return NULL; +} + // Read #line arguments static void read_line_marker(Token **rest, Token *tok) { Token *start = tok; @@ -820,9 +859,23 @@ static Token *preprocess2(Token *tok) { if (equal(tok, "include")) { char *path = read_include_path(&tok, tok->next); + + // If we read the same file before, and if the file was guarded + // by the usual #ifndef ... #endif pattern, we may be able to + // skip the file without opening it. + static HashMap include_guards; + char *guard_name = hashmap_get(&include_guards, path); + if (guard_name && hashmap_get(¯os, guard_name)) + continue; + Token *tok2 = tokenize_file(path); if (!tok2) error_tok(start, "%s: cannot open file: %s", path, strerror(errno)); + + guard_name = detect_include_guard(tok2); + if (guard_name) + hashmap_put(&include_guards, path, guard_name); + tok = append(tok2, tok); continue; }