diff --git a/chibicc.h b/chibicc.h index 00faf02..51aefe6 100644 --- a/chibicc.h +++ b/chibicc.h @@ -234,6 +234,7 @@ struct Node { }; Node *new_cast(Node *expr, Type *ty); +int64_t const_expr(Token **rest, Token *tok); Obj *parse(Token *tok); // diff --git a/parse.c b/parse.c index 9482cca..98389e3 100644 --- a/parse.c +++ b/parse.c @@ -129,7 +129,6 @@ static int64_t eval2(Node *node, char **label); static int64_t eval_rval(Node *node, char **label); static Node *assign(Token **rest, Token *tok); static Node *logor(Token **rest, Token *tok); -static int64_t const_expr(Token **rest, Token *tok); static double eval_double(Node *node); static Node *conditional(Token **rest, Token *tok); static Node *logand(Token **rest, Token *tok); @@ -1551,7 +1550,7 @@ static int64_t eval_rval(Node *node, char **label) { error_tok(node->tok, "invalid initializer"); } -static int64_t const_expr(Token **rest, Token *tok) { +int64_t const_expr(Token **rest, Token *tok) { Node *node = conditional(rest, tok); return eval(node); } diff --git a/preprocess.c b/preprocess.c index a5f7316..b19b8a4 100644 --- a/preprocess.c +++ b/preprocess.c @@ -1,5 +1,14 @@ #include "chibicc.h" +// `#if` can be nested, so we use a stack to manage nested `#if`s. +typedef struct CondIncl CondIncl; +struct CondIncl { + CondIncl *next; + Token *tok; +}; + +static CondIncl *cond_incl; + static bool is_hash(Token *tok) { return tok->at_bol && equal(tok, "#"); } @@ -22,6 +31,13 @@ static Token *copy_token(Token *tok) { return t; } +static Token *new_eof(Token *tok) { + Token *t = copy_token(tok); + t->kind = TK_EOF; + t->len = 0; + return t; +} + // Append tok2 to the end of tok1. static Token *append(Token *tok1, Token *tok2) { if (!tok1 || tok1->kind == TK_EOF) @@ -36,6 +52,54 @@ static Token *append(Token *tok1, Token *tok2) { return head.next; } +// Skip until next `#endif`. +static Token *skip_cond_incl(Token *tok) { + while (tok->kind != TK_EOF) { + if (is_hash(tok) && equal(tok->next, "endif")) + return tok; + tok = tok->next; + } + return tok; +} + +// Copy all tokens until the next newline, terminate them with +// an EOF token and then returns them. This function is used to +// create a new list of tokens for `#if` arguments. +static Token *copy_line(Token **rest, Token *tok) { + Token head = {}; + Token *cur = &head; + + for (; !tok->at_bol; tok = tok->next) + cur = cur->next = copy_token(tok); + + cur->next = new_eof(tok); + *rest = tok; + return head.next; +} + +// Read and evaluate a constant expression. +static long eval_const_expr(Token **rest, Token *tok) { + Token *start = tok; + Token *expr = copy_line(rest, tok->next); + + if (expr->kind == TK_EOF) + error_tok(start, "no expression"); + + Token *rest2; + long val = const_expr(&rest2, expr); + if (rest2->kind != TK_EOF) + error_tok(rest2, "extra token"); + return val; +} + +static CondIncl *push_cond_incl(Token *tok) { + CondIncl *ci = calloc(1, sizeof(CondIncl)); + ci->next = cond_incl; + ci->tok = tok; + cond_incl = ci; + return ci; +} + // Visit all tokens in `tok` while evaluating preprocessing // macros and directives. static Token *preprocess2(Token *tok) { @@ -50,6 +114,7 @@ static Token *preprocess2(Token *tok) { continue; } + Token *start = tok; tok = tok->next; if (equal(tok, "include")) { @@ -72,6 +137,22 @@ static Token *preprocess2(Token *tok) { continue; } + if (equal(tok, "if")) { + long val = eval_const_expr(&tok, tok); + push_cond_incl(start); + if (!val) + tok = skip_cond_incl(tok); + continue; + } + + if (equal(tok, "endif")) { + if (!cond_incl) + error_tok(start, "stray #endif"); + cond_incl = cond_incl->next; + tok = skip_line(tok->next); + continue; + } + // `#`-only line is legal. It's called a null directive. if (tok->at_bol) continue; @@ -86,6 +167,8 @@ static Token *preprocess2(Token *tok) { // Entry point function of the preprocessor. Token *preprocess(Token *tok) { tok = preprocess2(tok); + if (cond_incl) + error_tok(cond_incl->tok, "unterminated conditional directive"); convert_keywords(tok); return tok; } diff --git a/test/macro.c b/test/macro.c index 3777adc..dd8262b 100644 --- a/test/macro.c +++ b/test/macro.c @@ -14,6 +14,18 @@ int main() { assert(5, include1, "include1"); assert(7, include2, "include2"); +#if 0 +#include "/no/such/file" + assert(0, 1, "1"); +#endif + + int m = 0; + +#if 1 + m = 5; +#endif + assert(5, m, "m"); + printf("OK\n"); return 0; } diff --git a/tokenize.c b/tokenize.c index 07596ec..f5f8e92 100644 --- a/tokenize.c +++ b/tokenize.c @@ -30,7 +30,7 @@ static void verror_at(char *filename, char *input, int line_no, line--; char *end = loc; - while (*end != '\n') + while (*end && *end != '\n') end++; // Print out the line.