X-Git-Url: https://git.xonotic.org/?a=blobdiff_plain;f=ftepp.c;h=1d734305406602d4c48e0312554f2b5384e2f70f;hb=a87bc6a5759621ae7d7e018ade969337f239ccd2;hp=fe3b783453762ec73b8f988f5c99d834f864d96b;hpb=7e660951c2086d51a154fd0fff367fb377038cf1;p=xonotic%2Fgmqcc.git diff --git a/ftepp.c b/ftepp.c index fe3b783..1d73430 100644 --- a/ftepp.c +++ b/ftepp.c @@ -58,8 +58,13 @@ typedef struct { bool newline; unsigned int errors; + bool output_on; ppcondition *conditions; ppmacro **macros; + + char *output_string; + + char *itemname; } ftepp_t; #define ftepp_tokval(f) ((f)->lex->tok.value) @@ -87,14 +92,36 @@ static void ftepp_error(ftepp_t *ftepp, const char *fmt, ...) va_end(ap); } +static bool GMQCC_WARN ftepp_warn(ftepp_t *ftepp, int warntype, const char *fmt, ...) +{ + va_list ap; + int lvl = LVL_WARNING; + + if (!OPTS_WARN(warntype)) + return false; + + if (opts_werror) { + lvl = LVL_ERROR; + ftepp->errors++; + } + + va_start(ap, fmt); + con_vprintmsg(lvl, ftepp->lex->tok.ctx.file, ftepp->lex->tok.ctx.line, "error", fmt, ap); + va_end(ap); + return opts_werror; +} + static pptoken *pptoken_make(ftepp_t *ftepp) { pptoken *token = (pptoken*)mem_a(sizeof(pptoken)); token->token = ftepp->token; +#if 0 if (token->token == TOKEN_WHITE) token->value = util_strdup(" "); else +#else token->value = util_strdup(ftepp_tokval(ftepp)); +#endif memcpy(&token->constval, &ftepp->lex->tok.constval, sizeof(token->constval)); return token; } @@ -126,38 +153,53 @@ static void ppmacro_delete(ppmacro *self) mem_d(self); } -static ftepp_t* ftepp_init() +static ftepp_t* ftepp_new() { ftepp_t *ftepp; ftepp = (ftepp_t*)mem_a(sizeof(*ftepp)); memset(ftepp, 0, sizeof(*ftepp)); + ftepp->output_on = true; + return ftepp; } static void ftepp_delete(ftepp_t *self) { size_t i; + if (self->itemname) + mem_d(self->itemname); for (i = 0; i < vec_size(self->macros); ++i) ppmacro_delete(self->macros[i]); vec_free(self->macros); vec_free(self->conditions); - lex_close(self->lex); + if (self->lex) + lex_close(self->lex); mem_d(self); } static void ftepp_out(ftepp_t *ftepp, const char *str, bool ignore_cond) { - if (ignore_cond || - !vec_size(ftepp->conditions) || - vec_last(ftepp->conditions).on) + if (ignore_cond || ftepp->output_on) { - printf("%s", str); + size_t len; + char *data; + len = strlen(str); + data = vec_add(ftepp->output_string, len); + memcpy(data, str, len); } } -ppmacro* ftepp_macro_find(ftepp_t *ftepp, const char *name) +static void ftepp_update_output_condition(ftepp_t *ftepp) +{ + size_t i; + ftepp->output_on = true; + for (i = 0; i < vec_size(ftepp->conditions); ++i) + ftepp->output_on = ftepp->output_on && ftepp->conditions[i].on; +} + +static ppmacro* ftepp_macro_find(ftepp_t *ftepp, const char *name) { size_t i; for (i = 0; i < vec_size(ftepp->macros); ++i) { @@ -167,6 +209,17 @@ ppmacro* ftepp_macro_find(ftepp_t *ftepp, const char *name) return NULL; } +static void ftepp_macro_delete(ftepp_t *ftepp, const char *name) +{ + size_t i; + for (i = 0; i < vec_size(ftepp->macros); ++i) { + if (!strcmp(name, ftepp->macros[i]->name)) { + vec_remove(ftepp->macros, i, 1); + return; + } + } +} + static inline int ftepp_next(ftepp_t *ftepp) { return (ftepp->token = lex_do(ftepp->lex)); @@ -242,7 +295,8 @@ static bool ftepp_define_body(ftepp_t *ftepp, ppmacro *macro) vec_push(macro->output, ptok); ftepp_next(ftepp); } - if (ftepp->token != TOKEN_EOL) { + /* recursive expansion can cause EOFs here */ + if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) { ftepp_error(ftepp, "unexpected junk after macro or unexpected end of file"); return false; } @@ -260,6 +314,12 @@ static bool ftepp_define(ftepp_t *ftepp) case TOKEN_IDENT: case TOKEN_TYPENAME: case TOKEN_KEYWORD: + macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp)); + if (macro && ftepp->output_on) { + if (ftepp_warn(ftepp, WARN_PREPROCESSOR, "redefining `%s`", ftepp_tokval(ftepp))) + return false; + ftepp_macro_delete(ftepp, ftepp_tokval(ftepp)); + } macro = ppmacro_new(ftepp_ctx(ftepp), ftepp_tokval(ftepp)); break; default: @@ -281,7 +341,11 @@ static bool ftepp_define(ftepp_t *ftepp) if (!ftepp_define_body(ftepp, macro)) return false; - vec_push(ftepp->macros, macro); + if (ftepp->output_on) + vec_push(ftepp->macros, macro); + else { + ppmacro_delete(macro); + } return true; } @@ -305,6 +369,7 @@ static void macroparam_clean(macroparam *self) vec_free(self->tokens); } +/* need to leave the last token up */ static bool ftepp_macro_call_params(ftepp_t *ftepp, macroparam **out_params) { macroparam *params = NULL; @@ -313,8 +378,12 @@ static bool ftepp_macro_call_params(ftepp_t *ftepp, macroparam **out_params) size_t parens = 0; size_t i; + if (!ftepp_skipallwhite(ftepp)) + return false; while (ftepp->token != ')') { mp.tokens = NULL; + if (!ftepp_skipallwhite(ftepp)) + return false; while (parens || ftepp->token != ',') { if (ftepp->token == '(') ++parens; @@ -343,10 +412,12 @@ static bool ftepp_macro_call_params(ftepp_t *ftepp, macroparam **out_params) goto on_error; } } + /* need to leave that up if (ftepp_next(ftepp) >= TOKEN_EOF) { ftepp_error(ftepp, "unexpected EOF in macro call"); goto on_error; } + */ *out_params = params; return true; @@ -359,20 +430,178 @@ on_error: return false; } +static bool macro_params_find(ppmacro *macro, const char *name, size_t *idx) +{ + size_t i; + for (i = 0; i < vec_size(macro->params); ++i) { + if (!strcmp(macro->params[i], name)) { + *idx = i; + return true; + } + } + return false; +} + +static void ftepp_stringify_token(ftepp_t *ftepp, pptoken *token) +{ + char chs[2]; + const char *ch; + chs[1] = 0; + switch (token->token) { + case TOKEN_STRINGCONST: + ch = token->value; + while (*ch) { + /* in preprocessor mode strings already are string, + * so we don't get actual newline bytes here. + * Still need to escape backslashes and quotes. + */ + switch (*ch) { + case '\\': ftepp_out(ftepp, "\\\\", false); break; + case '"': ftepp_out(ftepp, "\\\"", false); break; + default: + chs[0] = *ch; + ftepp_out(ftepp, chs, false); + break; + } + ++ch; + } + break; + case TOKEN_WHITE: + ftepp_out(ftepp, " ", false); + break; + case TOKEN_EOL: + ftepp_out(ftepp, "\\n", false); + break; + default: + ftepp_out(ftepp, token->value, false); + break; + } +} + +static void ftepp_stringify(ftepp_t *ftepp, macroparam *param) +{ + size_t i; + ftepp_out(ftepp, "\"", false); + for (i = 0; i < vec_size(param->tokens); ++i) + ftepp_stringify_token(ftepp, param->tokens[i]); + ftepp_out(ftepp, "\"", false); +} + +static void ftepp_recursion_header(ftepp_t *ftepp) +{ + ftepp_out(ftepp, "\n#pragma push(line)\n", false); +} + +static void ftepp_recursion_footer(ftepp_t *ftepp) +{ + ftepp_out(ftepp, "\n#pragma pop(line)\n", false); +} + +static bool ftepp_preprocess(ftepp_t *ftepp); +static bool ftepp_macro_expand(ftepp_t *ftepp, ppmacro *macro, macroparam *params) +{ + char *old_string = ftepp->output_string; + lex_file *old_lexer = ftepp->lex; + bool retval = true; + + size_t o, pi, pv; + lex_file *inlex; + + int nextok; + + /* really ... */ + if (!vec_size(macro->output)) + return true; + + ftepp->output_string = NULL; + for (o = 0; o < vec_size(macro->output); ++o) { + pptoken *out = macro->output[o]; + switch (out->token) { + case TOKEN_IDENT: + case TOKEN_TYPENAME: + case TOKEN_KEYWORD: + if (!macro_params_find(macro, out->value, &pi)) { + ftepp_out(ftepp, out->value, false); + break; + } else { + for (pv = 0; pv < vec_size(params[pi].tokens); ++pv) { + out = params[pi].tokens[pv]; + if (out->token == TOKEN_EOL) + ftepp_out(ftepp, "\n", false); + else + ftepp_out(ftepp, out->value, false); + } + } + break; + case '#': + if (o + 1 < vec_size(macro->output)) { + nextok = macro->output[o+1]->token; + if (nextok == '#') { + /* raw concatenation */ + ++o; + break; + } + if ( (nextok == TOKEN_IDENT || + nextok == TOKEN_KEYWORD || + nextok == TOKEN_TYPENAME) && + macro_params_find(macro, macro->output[o+1]->value, &pi)) + { + ++o; + ftepp_stringify(ftepp, ¶ms[pi]); + break; + } + } + ftepp_out(ftepp, "#", false); + break; + case TOKEN_EOL: + ftepp_out(ftepp, "\n", false); + break; + default: + ftepp_out(ftepp, out->value, false); + break; + } + } + vec_push(ftepp->output_string, 0); + /* Now run the preprocessor recursively on this string buffer */ + /* + printf("__________\n%s\n=========\n", ftepp->output_string); + */ + inlex = lex_open_string(ftepp->output_string, vec_size(ftepp->output_string)-1, ftepp->lex->name); + if (!inlex) { + ftepp_error(ftepp, "internal error: failed to instantiate lexer"); + retval = false; + goto cleanup; + } + ftepp->output_string = old_string; + ftepp->lex = inlex; + ftepp_recursion_header(ftepp); + if (!ftepp_preprocess(ftepp)) { + lex_close(ftepp->lex); + retval = false; + goto cleanup; + } + ftepp_recursion_footer(ftepp); + old_string = ftepp->output_string; + +cleanup: + ftepp->lex = old_lexer; + ftepp->output_string = old_string; + return retval; +} + static bool ftepp_macro_call(ftepp_t *ftepp, ppmacro *macro) { size_t o; macroparam *params = NULL; bool retval = true; - ftepp_next(ftepp); - if (!macro->has_params) { - for (o = 0; o < vec_size(macro->output); ++o) { - ftepp_out(ftepp, macro->output[o]->value, false); - } + if (!ftepp_macro_expand(ftepp, macro, NULL)) + return false; + ftepp_next(ftepp); return true; } + ftepp_next(ftepp); if (!ftepp_skipallwhite(ftepp)) return false; @@ -394,9 +623,9 @@ static bool ftepp_macro_call(ftepp_t *ftepp, ppmacro *macro) goto cleanup; } - - ftepp_out(ftepp, "Parsed macro parameters", false); - goto cleanup; + if (!ftepp_macro_expand(ftepp, macro, params)) + retval = false; + ftepp_next(ftepp); cleanup: for (o = 0; o < vec_size(params); ++o) @@ -595,14 +824,173 @@ static bool ftepp_ifdef(ftepp_t *ftepp, ppcondition *cond) (void)ftepp_next(ftepp); if (!ftepp_skipspace(ftepp)) return false; - if (ftepp->token != TOKEN_EOL) { + /* relaxing this condition + if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) { ftepp_error(ftepp, "stray tokens after #ifdef"); return false; } + */ cond->on = !!macro; return true; } +/** + * undef is also simple + */ +static bool ftepp_undef(ftepp_t *ftepp) +{ + (void)ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + + if (ftepp->output_on) { + switch (ftepp->token) { + case TOKEN_IDENT: + case TOKEN_TYPENAME: + case TOKEN_KEYWORD: + ftepp_macro_delete(ftepp, ftepp_tokval(ftepp)); + break; + default: + ftepp_error(ftepp, "expected macro name"); + return false; + } + } + + (void)ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + /* relaxing this condition + if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) { + ftepp_error(ftepp, "stray tokens after #ifdef"); + return false; + } + */ + return true; +} + +/* Special unescape-string function which skips a leading quote + * and stops at a quote, not just at \0 + */ +static void unescape(const char *str, char *out) { + ++str; + while (*str && *str != '"') { + if (*str == '\\') { + ++str; + switch (*str) { + case '\\': *out++ = *str; break; + case '"': *out++ = *str; break; + case 'a': *out++ = '\a'; break; + case 'b': *out++ = '\b'; break; + case 'r': *out++ = '\r'; break; + case 'n': *out++ = '\n'; break; + case 't': *out++ = '\t'; break; + case 'f': *out++ = '\f'; break; + case 'v': *out++ = '\v'; break; + default: + *out++ = '\\'; + *out++ = *str; + break; + } + ++str; + continue; + } + + *out++ = *str++; + } + *out = 0; +} + +static char *ftepp_include_find(ftepp_t *ftepp, const char *file) +{ + char *filename = NULL; + size_t len; + + if (ftepp->itemname) { + const char *last_slash; + last_slash = strrchr(ftepp->itemname, '/'); + if (last_slash) { + len = last_slash - ftepp->itemname; + memcpy(vec_add(filename, len), ftepp->itemname, len); + vec_push(filename, '/'); + } + else { + len = strlen(ftepp->itemname); + memcpy(vec_add(filename, len), ftepp->itemname, len); + if (vec_last(filename) != '/') + vec_push(filename, '/'); + } + } + len = strlen(file); + memcpy(vec_add(filename, len), file, len); + vec_push(filename, 0); + return filename; +} + +/** + * Include a file. + * FIXME: do we need/want a -I option? + * FIXME: what about when dealing with files in subdirectories coming from a progs.src? + */ +static bool ftepp_include(ftepp_t *ftepp) +{ + lex_file *old_lexer = ftepp->lex; + lex_file *inlex; + lex_ctx ctx; + char lineno[128]; + char *filename; + + (void)ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + + if (ftepp->token != TOKEN_STRINGCONST) { + ftepp_error(ftepp, "expected filename to include"); + return false; + } + + ctx = ftepp_ctx(ftepp); + + unescape(ftepp_tokval(ftepp), ftepp_tokval(ftepp)); + + ftepp_out(ftepp, "\n#pragma file(", false); + ftepp_out(ftepp, ftepp_tokval(ftepp), false); + ftepp_out(ftepp, ")\n#pragma line(1)\n", false); + + filename = ftepp_include_find(ftepp, ftepp_tokval(ftepp)); + inlex = lex_open(filename); + if (!inlex) { + ftepp_error(ftepp, "failed to open include file `%s`", filename); + vec_free(filename); + return false; + } + vec_free(filename); + ftepp->lex = inlex; + if (!ftepp_preprocess(ftepp)) { + lex_close(ftepp->lex); + ftepp->lex = old_lexer; + return false; + } + lex_close(ftepp->lex); + ftepp->lex = old_lexer; + + ftepp_out(ftepp, "\n#pragma file(", false); + ftepp_out(ftepp, ctx.file, false); + snprintf(lineno, sizeof(lineno), ")\n#pragma line(%lu)\n", (unsigned long)(ctx.line+1)); + ftepp_out(ftepp, lineno, false); + + /* skip the line */ + (void)ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + if (ftepp->token != TOKEN_EOL) { + ftepp_error(ftepp, "stray tokens after #include"); + return false; + } + (void)ftepp_next(ftepp); + + return true; +} + /* Basic structure handlers */ static bool ftepp_else_allowed(ftepp_t *ftepp) { @@ -634,11 +1022,15 @@ static bool ftepp_hash(ftepp_t *ftepp) if (!strcmp(ftepp_tokval(ftepp), "define")) { return ftepp_define(ftepp); } + else if (!strcmp(ftepp_tokval(ftepp), "undef")) { + return ftepp_undef(ftepp); + } else if (!strcmp(ftepp_tokval(ftepp), "ifdef")) { if (!ftepp_ifdef(ftepp, &cond)) return false; cond.was_on = cond.on; vec_push(ftepp->conditions, cond); + ftepp->output_on = ftepp->output_on && cond.on; break; } else if (!strcmp(ftepp_tokval(ftepp), "ifndef")) { @@ -647,6 +1039,7 @@ static bool ftepp_hash(ftepp_t *ftepp) cond.on = !cond.on; cond.was_on = cond.on; vec_push(ftepp->conditions, cond); + ftepp->output_on = ftepp->output_on && cond.on; break; } else if (!strcmp(ftepp_tokval(ftepp), "elifdef")) { @@ -657,6 +1050,7 @@ static bool ftepp_hash(ftepp_t *ftepp) pc = &vec_last(ftepp->conditions); pc->on = !pc->was_on && cond.on; pc->was_on = pc->was_on || pc->on; + ftepp_update_output_condition(ftepp); break; } else if (!strcmp(ftepp_tokval(ftepp), "elifndef")) { @@ -668,6 +1062,7 @@ static bool ftepp_hash(ftepp_t *ftepp) pc = &vec_last(ftepp->conditions); pc->on = !pc->was_on && cond.on; pc->was_on = pc->was_on || pc->on; + ftepp_update_output_condition(ftepp); break; } else if (!strcmp(ftepp_tokval(ftepp), "elif")) { @@ -678,6 +1073,7 @@ static bool ftepp_hash(ftepp_t *ftepp) pc = &vec_last(ftepp->conditions); pc->on = !pc->was_on && cond.on; pc->was_on = pc->was_on || pc->on; + ftepp_update_output_condition(ftepp); break; } else if (!strcmp(ftepp_tokval(ftepp), "if")) { @@ -685,6 +1081,7 @@ static bool ftepp_hash(ftepp_t *ftepp) return false; cond.was_on = cond.on; vec_push(ftepp->conditions, cond); + ftepp->output_on = ftepp->output_on && cond.on; break; } else if (!strcmp(ftepp_tokval(ftepp), "else")) { @@ -694,6 +1091,7 @@ static bool ftepp_hash(ftepp_t *ftepp) pc->on = !pc->was_on; pc->had_else = true; ftepp_next(ftepp); + ftepp_update_output_condition(ftepp); break; } else if (!strcmp(ftepp_tokval(ftepp), "endif")) { @@ -703,6 +1101,14 @@ static bool ftepp_hash(ftepp_t *ftepp) } vec_pop(ftepp->conditions); ftepp_next(ftepp); + ftepp_update_output_condition(ftepp); + break; + } + else if (!strcmp(ftepp_tokval(ftepp), "include")) { + return ftepp_include(ftepp); + } + else if (!strcmp(ftepp_tokval(ftepp), "pragma")) { + ftepp_out(ftepp, "#", false); break; } else { @@ -719,6 +1125,12 @@ static bool ftepp_hash(ftepp_t *ftepp) case TOKEN_EOF: ftepp_error(ftepp, "missing newline at end of file", ftepp_tokval(ftepp)); return false; + + /* Builtins! Don't forget the builtins! */ + case TOKEN_INTCONST: + case TOKEN_FLOATCONST: + ftepp_out(ftepp, "#", false); + return true; } if (!ftepp_skipspace(ftepp)) return false; @@ -739,9 +1151,13 @@ static bool ftepp_preprocess(ftepp_t *ftepp) { if (ftepp->token >= TOKEN_EOF) break; - +#if 0 ftepp->newline = newline; newline = false; +#else + /* For the sake of FTE compatibility... FU, really */ + ftepp->newline = newline = true; +#endif switch (ftepp->token) { case TOKEN_KEYWORD: @@ -785,28 +1201,80 @@ static bool ftepp_preprocess(ftepp_t *ftepp) } while (!ftepp->errors && ftepp->token < TOKEN_EOF); newline = ftepp->token == TOKEN_EOF; - ftepp_delete(ftepp); return newline; } +/* Like in parser.c - files keep the previous state so we have one global + * preprocessor. Except here we will want to warn about dangling #ifs. + */ +static ftepp_t *ftepp; + +static bool ftepp_preprocess_done() +{ + bool retval = true; + lex_close(ftepp->lex); + ftepp->lex = NULL; + if (vec_size(ftepp->conditions)) { + if (ftepp_warn(ftepp, WARN_MULTIFILE_IF, "#if spanning multiple files, is this intended?")) + retval = false; + } + if (ftepp->itemname) { + mem_d(ftepp->itemname); + ftepp->itemname = NULL; + } + return retval; +} + bool ftepp_preprocess_file(const char *filename) { - ftepp_t *ftepp = ftepp_init(); ftepp->lex = lex_open(filename); + ftepp->itemname = util_strdup(filename); if (!ftepp->lex) { con_out("failed to open file \"%s\"\n", filename); return false; } - return ftepp_preprocess(ftepp); + if (!ftepp_preprocess(ftepp)) { + ftepp_delete(ftepp); + return false; + } + return ftepp_preprocess_done(); } bool ftepp_preprocess_string(const char *name, const char *str) { - ftepp_t *ftepp = ftepp_init(); ftepp->lex = lex_open_string(str, strlen(str), name); + ftepp->itemname = util_strdup(name); if (!ftepp->lex) { con_out("failed to create lexer for string \"%s\"\n", name); return false; } - return ftepp_preprocess(ftepp); + if (!ftepp_preprocess(ftepp)) { + ftepp_delete(ftepp); + return false; + } + return ftepp_preprocess_done(); +} + +bool ftepp_init() +{ + ftepp = ftepp_new(); + return !!ftepp; +} + +const char *ftepp_get() +{ + return ftepp->output_string; +} + +void ftepp_flush() +{ + vec_free(ftepp->output_string); +} + +void ftepp_finish() +{ + if (!ftepp) + return; + ftepp_delete(ftepp); + ftepp = NULL; }