X-Git-Url: https://git.xonotic.org/?p=xonotic%2Fgmqcc.git;a=blobdiff_plain;f=ftepp.c;h=1d734305406602d4c48e0312554f2b5384e2f70f;hp=7b31a601f0a2d6f44ae9ff01c1c41273a23eb245;hb=2652353f507345d485aef3b4e5fba5b2267d9a60;hpb=cc1c197fc6c80721ae7c12e316cd06bcbc1a57a6 diff --git a/ftepp.c b/ftepp.c index 7b31a60..1d73430 100644 --- a/ftepp.c +++ b/ftepp.c @@ -24,35 +24,47 @@ #include "lexer.h" typedef struct { - bool on; - bool was_on; - bool had_else; + bool on; + bool was_on; + bool had_else; } ppcondition; typedef struct { - int token; - char *value; + int token; + char *value; + /* a copy from the lexer */ + union { + vector v; + int i; + double f; + int t; /* type */ + } constval; } pptoken; typedef struct { - lex_ctx ctx; + lex_ctx ctx; - char *name; - char **params; - /* yes we need an extra flag since `#define FOO x` is not the same as `#define FOO() x` */ - bool has_params; + char *name; + char **params; + /* yes we need an extra flag since `#define FOO x` is not the same as `#define FOO() x` */ + bool has_params; - pptoken *output; + pptoken **output; } ppmacro; typedef struct { - lex_file *lex; - int token; - bool newline; - unsigned int errors; + lex_file *lex; + int token; + bool newline; + unsigned int errors; - ppcondition *conditions; - ppmacro **macros; + bool output_on; + ppcondition *conditions; + ppmacro **macros; + + char *output_string; + + char *itemname; } ftepp_t; #define ftepp_tokval(f) ((f)->lex->tok.value) @@ -60,325 +72,1209 @@ typedef struct { static void ftepp_errorat(ftepp_t *ftepp, lex_ctx ctx, const char *fmt, ...) { - va_list ap; + va_list ap; - ftepp->errors++; + ftepp->errors++; - va_start(ap, fmt); + va_start(ap, fmt); con_vprintmsg(LVL_ERROR, ctx.file, ctx.line, "error", fmt, ap); - va_end(ap); + va_end(ap); } static void ftepp_error(ftepp_t *ftepp, const char *fmt, ...) { - va_list ap; + va_list ap; - ftepp->errors++; + ftepp->errors++; - va_start(ap, fmt); + va_start(ap, fmt); con_vprintmsg(LVL_ERROR, ftepp->lex->tok.ctx.file, ftepp->lex->tok.ctx.line, "error", fmt, ap); - va_end(ap); + va_end(ap); } -ppmacro *ppmacro_new(lex_ctx ctx, const char *name) +static bool GMQCC_WARN ftepp_warn(ftepp_t *ftepp, int warntype, const char *fmt, ...) { - ppmacro *macro = (ppmacro*)mem_a(sizeof(ppmacro)); - memset(macro, 0, sizeof(*macro)); - macro->name = util_strdup(name); - return macro; + 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; } -void ppmacro_delete(ppmacro *self) +static pptoken *pptoken_make(ftepp_t *ftepp) { - vec_free(self->params); - vec_free(self->output); - mem_d(self->name); - mem_d(self); + 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; } -ftepp_t* ftepp_init() +static void pptoken_delete(pptoken *self) { - ftepp_t *ftepp; + mem_d(self->value); + mem_d(self); +} - ftepp = (ftepp_t*)mem_a(sizeof(*ftepp)); - memset(ftepp, 0, sizeof(*ftepp)); +static ppmacro *ppmacro_new(lex_ctx ctx, const char *name) +{ + ppmacro *macro = (ppmacro*)mem_a(sizeof(ppmacro)); + memset(macro, 0, sizeof(*macro)); + macro->name = util_strdup(name); + return macro; +} - return ftepp; +static void ppmacro_delete(ppmacro *self) +{ + size_t i; + for (i = 0; i < vec_size(self->params); ++i) + mem_d(self->params[i]); + vec_free(self->params); + for (i = 0; i < vec_size(self->output); ++i) + pptoken_delete(self->output[i]); + vec_free(self->output); + mem_d(self->name); + mem_d(self); } -void ftepp_delete(ftepp_t *self) +static ftepp_t* ftepp_new() { - vec_free(self->macros); - vec_free(self->conditions); - mem_d(self); + ftepp_t *ftepp; + + ftepp = (ftepp_t*)mem_a(sizeof(*ftepp)); + memset(ftepp, 0, sizeof(*ftepp)); + + ftepp->output_on = true; + + return ftepp; } -ppmacro* ftepp_macro_find(ftepp_t *ftepp, const char *name) +static void ftepp_delete(ftepp_t *self) { - size_t i; - for (i = 0; i < vec_size(ftepp->macros); ++i) { - if (!strcmp(name, ftepp->macros[i]->name)) - return ftepp->macros[i]; - } - return NULL; + 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); + 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 || ftepp->output_on) + { + size_t len; + char *data; + len = strlen(str); + data = vec_add(ftepp->output_string, len); + memcpy(data, str, len); + } +} + +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) { + if (!strcmp(name, ftepp->macros[i]->name)) + return ftepp->macros[i]; + } + 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)); + return (ftepp->token = lex_do(ftepp->lex)); } /* Important: this does not skip newlines! */ static bool ftepp_skipspace(ftepp_t *ftepp) { - if (ftepp->token != TOKEN_WHITE) - return true; - while (ftepp_next(ftepp) == TOKEN_WHITE) {} - return (ftepp->token < TOKEN_EOF); + if (ftepp->token != TOKEN_WHITE) + return true; + while (ftepp_next(ftepp) == TOKEN_WHITE) {} + if (ftepp->token >= TOKEN_EOF) { + ftepp_error(ftepp, "unexpected end of preprocessor directive"); + return false; + } + return true; } -/* if/ifdef/define handlers */ +/* this one skips EOLs as well */ +static bool ftepp_skipallwhite(ftepp_t *ftepp) +{ + if (ftepp->token != TOKEN_WHITE && ftepp->token != TOKEN_EOL) + return true; + do { + ftepp_next(ftepp); + } while (ftepp->token == TOKEN_WHITE || ftepp->token == TOKEN_EOL); + if (ftepp->token >= TOKEN_EOF) { + ftepp_error(ftepp, "unexpected end of preprocessor directive"); + return false; + } + return true; +} + +/** + * The huge macro parsing code... + */ +static bool ftepp_define_params(ftepp_t *ftepp, ppmacro *macro) +{ + do { + ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + if (ftepp->token == ')') + break; + switch (ftepp->token) { + case TOKEN_IDENT: + case TOKEN_TYPENAME: + case TOKEN_KEYWORD: + break; + default: + ftepp_error(ftepp, "unexpected token in parameter list"); + return false; + } + vec_push(macro->params, util_strdup(ftepp_tokval(ftepp))); + ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + } while (ftepp->token == ','); + if (ftepp->token != ')') { + ftepp_error(ftepp, "expected closing paren after macro parameter list"); + return false; + } + ftepp_next(ftepp); + /* skipspace happens in ftepp_define */ + return true; +} + +static bool ftepp_define_body(ftepp_t *ftepp, ppmacro *macro) +{ + pptoken *ptok; + while (ftepp->token != TOKEN_EOL && ftepp->token < TOKEN_EOF) { + ptok = pptoken_make(ftepp); + vec_push(macro->output, ptok); + ftepp_next(ftepp); + } + /* 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; + } + return true; +} + +static bool ftepp_define(ftepp_t *ftepp) +{ + ppmacro *macro; + (void)ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + + switch (ftepp->token) { + 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: + ftepp_error(ftepp, "expected macro name"); + return false; + } + + (void)ftepp_next(ftepp); + + if (ftepp->token == '(') { + macro->has_params = true; + if (!ftepp_define_params(ftepp, macro)) + return false; + } + + if (!ftepp_skipspace(ftepp)) + return false; + + if (!ftepp_define_body(ftepp, macro)) + return false; + + if (ftepp->output_on) + vec_push(ftepp->macros, macro); + else { + ppmacro_delete(macro); + } + return true; +} + +/** + * When a macro is used we have to handle parameters as well + * as special-concatenation via ## or stringification via # + * + * Note: parenthesis can nest, so FOO((a),b) is valid, but only + * this kind of parens. Curly braces or [] don't count towards the + * paren-level. + */ +typedef struct { + pptoken **tokens; +} macroparam; + +static void macroparam_clean(macroparam *self) +{ + size_t i; + for (i = 0; i < vec_size(self->tokens); ++i) + pptoken_delete(self->tokens[i]); + 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; + pptoken *ptok; + macroparam mp; + 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; + else if (ftepp->token == ')') { + if (!parens) + break; + --parens; + } + ptok = pptoken_make(ftepp); + vec_push(mp.tokens, ptok); + if (ftepp_next(ftepp) >= TOKEN_EOF) { + ftepp_error(ftepp, "unexpected EOF in macro call"); + goto on_error; + } + } + vec_push(params, mp); + mp.tokens = NULL; + if (ftepp->token == ')') + break; + if (ftepp->token != ',') { + ftepp_error(ftepp, "expected closing paren or comma in macro call"); + goto on_error; + } + if (ftepp_next(ftepp) >= TOKEN_EOF) { + ftepp_error(ftepp, "unexpected EOF in macro call"); + 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; + +on_error: + if (mp.tokens) + macroparam_clean(&mp); + for (i = 0; i < vec_size(params); ++i) + macroparam_clean(¶ms[i]); + vec_free(params); + 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; + + if (!macro->has_params) { + if (!ftepp_macro_expand(ftepp, macro, NULL)) + return false; + ftepp_next(ftepp); + return true; + } + ftepp_next(ftepp); + + if (!ftepp_skipallwhite(ftepp)) + return false; + + if (ftepp->token != '(') { + ftepp_error(ftepp, "expected macro parameters in parenthesis"); + return false; + } + + ftepp_next(ftepp); + if (!ftepp_macro_call_params(ftepp, ¶ms)) + return false; + + if (vec_size(params) != vec_size(macro->params)) { + ftepp_error(ftepp, "macro %s expects %u paramteters, %u provided", macro->name, + (unsigned int)vec_size(macro->params), + (unsigned int)vec_size(params)); + retval = false; + goto cleanup; + } + + if (!ftepp_macro_expand(ftepp, macro, params)) + retval = false; + ftepp_next(ftepp); + +cleanup: + for (o = 0; o < vec_size(params); ++o) + macroparam_clean(¶ms[o]); + vec_free(params); + return retval; +} + +/** + * #if - the FTEQCC way: + * defined(FOO) => true if FOO was #defined regardless of parameters or contents + * => True if the number is not 0 + * ! => True if the factor yields false + * !! => ERROR on 2 or more unary nots + * => becomes the macro's FIRST token regardless of parameters + * && => True if both expressions are true + * || => True if either expression is true + * => False + * => False (remember for macros the rule applies instead) + * Unary + and - are weird and wrong in fteqcc so we don't allow them + * parenthesis in expressions are allowed + * parameter lists on macros are errors + * No mathematical calculations are executed + */ +static bool ftepp_if_expr(ftepp_t *ftepp, bool *out) +{ + ppmacro *macro; + bool wasnot = false; + + if (!ftepp_skipspace(ftepp)) + return false; + + while (ftepp->token == '!') { + wasnot = true; + ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + } + + switch (ftepp->token) { + case TOKEN_IDENT: + case TOKEN_TYPENAME: + case TOKEN_KEYWORD: + if (!strcmp(ftepp_tokval(ftepp), "defined")) { + ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + if (ftepp->token != '(') { + ftepp_error(ftepp, "`defined` keyword in #if requires a macro name in parenthesis"); + return false; + } + ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + if (ftepp->token != TOKEN_IDENT && + ftepp->token != TOKEN_TYPENAME && + ftepp->token != TOKEN_KEYWORD) + { + ftepp_error(ftepp, "defined() used on an unexpected token type"); + return false; + } + macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp)); + *out = !!macro; + ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + if (ftepp->token != ')') { + ftepp_error(ftepp, "expected closing paren"); + return false; + } + break; + } + + macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp)); + if (!macro || !vec_size(macro->output)) { + *out = false; + } else { + /* This does not expand recursively! */ + switch (macro->output[0]->token) { + case TOKEN_INTCONST: + *out = !!(macro->output[0]->constval.f); + break; + case TOKEN_FLOATCONST: + *out = !!(macro->output[0]->constval.f); + break; + default: + *out = false; + break; + } + } + break; + case TOKEN_STRINGCONST: + *out = false; + break; + case TOKEN_INTCONST: + *out = !!(ftepp->lex->tok.constval.i); + break; + case TOKEN_FLOATCONST: + *out = !!(ftepp->lex->tok.constval.f); + break; + + case '(': + ftepp_next(ftepp); + if (!ftepp_if_expr(ftepp, out)) + return false; + if (ftepp->token != ')') { + ftepp_error(ftepp, "expected closing paren in #if expression"); + return false; + } + break; + + default: + ftepp_error(ftepp, "junk in #if"); + return false; + } + if (wasnot) + *out = !*out; + + ftepp->lex->flags.noops = false; + ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + ftepp->lex->flags.noops = true; + + if (ftepp->token == ')') + return true; + + if (ftepp->token != TOKEN_OPERATOR) + return true; + + if (!strcmp(ftepp_tokval(ftepp), "&&") || + !strcmp(ftepp_tokval(ftepp), "||")) + { + bool next = false; + char opc = ftepp_tokval(ftepp)[0]; + + ftepp_next(ftepp); + if (!ftepp_if_expr(ftepp, &next)) + return false; + + if (opc == '&') + *out = *out && next; + else + *out = *out || next; + return true; + } + else { + ftepp_error(ftepp, "junk after #if"); + return false; + } +} static bool ftepp_if(ftepp_t *ftepp, ppcondition *cond) { - ftepp_error(ftepp, "TODO: #if"); - return false; + bool result = false; + + memset(cond, 0, sizeof(*cond)); + (void)ftepp_next(ftepp); + + if (!ftepp_skipspace(ftepp)) + return false; + if (ftepp->token == TOKEN_EOL) { + ftepp_error(ftepp, "expected expression for #if-directive"); + return false; + } + + if (!ftepp_if_expr(ftepp, &result)) + return false; + + cond->on = result; + return true; } +/** + * ifdef is rather simple + */ static bool ftepp_ifdef(ftepp_t *ftepp, ppcondition *cond) { - ppmacro *macro; - (void)ftepp_next(ftepp); - if (!ftepp_skipspace(ftepp)) - return false; - - switch (ftepp->token) { - case TOKEN_IDENT: - case TOKEN_KEYWORD: - macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp)); - break; - default: - ftepp_error(ftepp, "expected macro name"); - return false; - } - - (void)ftepp_next(ftepp); - if (!ftepp_skipspace(ftepp)) - return false; - if (ftepp->token != TOKEN_EOL) { - ftepp_error(ftepp, "stray tokens after #ifdef"); - return false; - } - cond->on = !!macro; - return true; + ppmacro *macro; + memset(cond, 0, sizeof(*cond)); + (void)ftepp_next(ftepp); + if (!ftepp_skipspace(ftepp)) + return false; + + switch (ftepp->token) { + case TOKEN_IDENT: + case TOKEN_TYPENAME: + case TOKEN_KEYWORD: + macro = ftepp_macro_find(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; + } + */ + cond->on = !!macro; + return true; } -static bool ftepp_define(ftepp_t *ftepp) +/** + * 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) { - ppmacro *macro; - (void)ftepp_next(ftepp); - if (!ftepp_skipspace(ftepp)) - return false; - - switch (ftepp->token) { - case TOKEN_IDENT: - case TOKEN_KEYWORD: - macro = ppmacro_new(ftepp_ctx(ftepp), ftepp_tokval(ftepp)); - break; - default: - ftepp_error(ftepp, "expected macro name"); - return false; - } - - (void)ftepp_next(ftepp); - if (!ftepp_skipspace(ftepp)) - return false; - if (ftepp->token != TOKEN_EOL) { - ftepp_error(ftepp, "stray tokens after macro"); - return false; - } - vec_push(ftepp->macros, macro); - return true; + 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) { - if (!vec_size(ftepp->conditions)) { - ftepp_error(ftepp, "#else without #if"); - return false; - } - if (vec_last(ftepp->conditions).had_else) { - ftepp_error(ftepp, "multiple #else for a single #if"); - return false; - } - return true; + if (!vec_size(ftepp->conditions)) { + ftepp_error(ftepp, "#else without #if"); + return false; + } + if (vec_last(ftepp->conditions).had_else) { + ftepp_error(ftepp, "multiple #else for a single #if"); + return false; + } + return true; } static bool ftepp_hash(ftepp_t *ftepp) { - ppcondition cond; - ppcondition *pc; - - lex_ctx ctx = ftepp_ctx(ftepp); - - if (!ftepp_skipspace(ftepp)) - return false; - - switch (ftepp->token) { - case TOKEN_KEYWORD: - case TOKEN_IDENT: - if (!strcmp(ftepp_tokval(ftepp), "define")) { - return ftepp_define(ftepp); - } - else if (!strcmp(ftepp_tokval(ftepp), "ifdef")) { - if (!ftepp_ifdef(ftepp, &cond)) - return false; - vec_push(ftepp->conditions, cond); - return true; - } - else if (!strcmp(ftepp_tokval(ftepp), "ifndef")) { - if (!ftepp_ifdef(ftepp, &cond)) - return false; - cond.on = !cond.on; - vec_push(ftepp->conditions, cond); - return true; - } - else if (!strcmp(ftepp_tokval(ftepp), "elifdef")) { - if (!ftepp_else_allowed(ftepp)) - return false; - if (!ftepp_ifdef(ftepp, &cond)) - return false; - pc = &vec_last(ftepp->conditions); - pc->on = !pc->was_on && cond.on; - pc->was_on = pc->was_on || pc->on; - return true; - } - else if (!strcmp(ftepp_tokval(ftepp), "elifndef")) { - if (!ftepp_else_allowed(ftepp)) - return false; - if (!ftepp_ifdef(ftepp, &cond)) - return false; - cond.on = !cond.on; - pc = &vec_last(ftepp->conditions); - pc->on = !pc->was_on && cond.on; - pc->was_on = pc->was_on || pc->on; - return true; - } - else if (!strcmp(ftepp_tokval(ftepp), "elif")) { - if (!ftepp_else_allowed(ftepp)) - return false; - if (!ftepp_if(ftepp, &cond)) - return false; - pc = &vec_last(ftepp->conditions); - pc->on = !pc->was_on && cond.on; - pc->was_on = pc->was_on || pc->on; - return true; - } - else if (!strcmp(ftepp_tokval(ftepp), "if")) { - if (!ftepp_if(ftepp, &cond)) - return false; - vec_push(ftepp->conditions, cond); - return true; - } - else if (!strcmp(ftepp_tokval(ftepp), "else")) { - if (!ftepp_else_allowed(ftepp)) - return false; - pc = &vec_last(ftepp->conditions); - pc->on = !pc->was_on; - pc->had_else = true; - return true; - } - else if (!strcmp(ftepp_tokval(ftepp), "endif")) { - if (!vec_size(ftepp->conditions)) { - ftepp_error(ftepp, "#endif without #if"); - return false; - } - vec_pop(ftepp->conditions); - break; - } - else { - ftepp_error(ftepp, "unrecognized preprocessor directive: `%s`", ftepp_tokval(ftepp)); - return false; - } - break; - default: - ftepp_error(ftepp, "unexpected preprocessor token: `%s`", ftepp_tokval(ftepp)); - return false; - case TOKEN_EOL: - ftepp_errorat(ftepp, ctx, "empty preprocessor directive"); - return false; - case TOKEN_EOF: - ftepp_error(ftepp, "missing newline at end of file", ftepp_tokval(ftepp)); - return false; - } - return true; + ppcondition cond; + ppcondition *pc; + + lex_ctx ctx = ftepp_ctx(ftepp); + + if (!ftepp_skipspace(ftepp)) + return false; + + switch (ftepp->token) { + case TOKEN_KEYWORD: + case TOKEN_IDENT: + case TOKEN_TYPENAME: + 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")) { + if (!ftepp_ifdef(ftepp, &cond)) + return false; + 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")) { + if (!ftepp_else_allowed(ftepp)) + return false; + if (!ftepp_ifdef(ftepp, &cond)) + return false; + 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")) { + if (!ftepp_else_allowed(ftepp)) + return false; + if (!ftepp_ifdef(ftepp, &cond)) + return false; + cond.on = !cond.on; + 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")) { + if (!ftepp_else_allowed(ftepp)) + return false; + if (!ftepp_if(ftepp, &cond)) + return false; + 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")) { + if (!ftepp_if(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), "else")) { + if (!ftepp_else_allowed(ftepp)) + return false; + pc = &vec_last(ftepp->conditions); + 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")) { + if (!vec_size(ftepp->conditions)) { + ftepp_error(ftepp, "#endif without #if"); + return false; + } + 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 { + ftepp_error(ftepp, "unrecognized preprocessor directive: `%s`", ftepp_tokval(ftepp)); + return false; + } + break; + default: + ftepp_error(ftepp, "unexpected preprocessor token: `%s`", ftepp_tokval(ftepp)); + return false; + case TOKEN_EOL: + ftepp_errorat(ftepp, ctx, "empty preprocessor directive"); + return false; + 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; + return true; } static bool ftepp_preprocess(ftepp_t *ftepp) { - bool newline = true; - - ftepp->lex->flags.preprocessing = true; - - ftepp_next(ftepp); - do - { - if (ftepp->token >= TOKEN_EOF) - break; - - ftepp->newline = newline; - newline = false; - - switch (ftepp->token) { - case '#': - if (!ftepp->newline) { - printf("%s", ftepp_tokval(ftepp)); - ftepp_next(ftepp); - break; - } - if (ftepp_next(ftepp) >= TOKEN_EOF) { - ftepp_error(ftepp, "error in preprocessor directive"); - ftepp->token = TOKEN_ERROR; - break; - } - if (!ftepp_hash(ftepp)) - ftepp->token = TOKEN_ERROR; - break; - case TOKEN_EOL: - newline = true; - printf("\n"); - ftepp_next(ftepp); - break; - default: - printf("%s", ftepp_tokval(ftepp)); - ftepp_next(ftepp); - break; - } - } while (!ftepp->errors && ftepp->token < TOKEN_EOF); - - ftepp_delete(ftepp); - return (ftepp->token == TOKEN_EOF); + ppmacro *macro; + bool newline = true; + + ftepp->lex->flags.preprocessing = true; + ftepp->lex->flags.mergelines = false; + ftepp->lex->flags.noops = true; + + ftepp_next(ftepp); + do + { + 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: + case TOKEN_IDENT: + case TOKEN_TYPENAME: + macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp)); + if (!macro) { + ftepp_out(ftepp, ftepp_tokval(ftepp), false); + ftepp_next(ftepp); + break; + } + if (!ftepp_macro_call(ftepp, macro)) + ftepp->token = TOKEN_ERROR; + break; + case '#': + if (!ftepp->newline) { + ftepp_out(ftepp, ftepp_tokval(ftepp), false); + ftepp_next(ftepp); + break; + } + ftepp->lex->flags.mergelines = true; + if (ftepp_next(ftepp) >= TOKEN_EOF) { + ftepp_error(ftepp, "error in preprocessor directive"); + ftepp->token = TOKEN_ERROR; + break; + } + if (!ftepp_hash(ftepp)) + ftepp->token = TOKEN_ERROR; + ftepp->lex->flags.mergelines = false; + break; + case TOKEN_EOL: + newline = true; + ftepp_out(ftepp, "\n", true); + ftepp_next(ftepp); + break; + default: + ftepp_out(ftepp, ftepp_tokval(ftepp), false); + ftepp_next(ftepp); + break; + } + } while (!ftepp->errors && ftepp->token < TOKEN_EOF); + + newline = ftepp->token == TOKEN_EOF; + 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; }