]> git.xonotic.org Git - xonotic/gmqcc.git/blob - ftepp.cpp
ast_unary::make: safer double-negation optimization check
[xonotic/gmqcc.git] / ftepp.cpp
1 #include <string.h>
2 #include <stdlib.h>
3 #include <sys/stat.h>
4
5 #include "gmqcc.h"
6 #include "lexer.h"
7
8 #define HT_MACROS 1024
9
10 struct ppcondition {
11     bool on;
12     bool was_on;
13     bool had_else;
14 };
15
16 struct pptoken {
17     int token;
18     char *value;
19     /* a copy from the lexer */
20     union {
21         vec3_t v;
22         int i;
23         double f;
24         int t; /* type */
25     } constval;
26 };
27
28 struct ppmacro {
29     lex_ctx_t ctx;
30     char *name;
31     char **params;
32     /* yes we need an extra flag since `#define FOO x` is not the same as `#define FOO() x` */
33     bool has_params;
34     bool variadic;
35     pptoken **output;
36 };
37
38 struct ftepp_t {
39     lex_file *lex;
40     int token;
41     unsigned int errors;
42     bool output_on;
43     ppcondition *conditions;
44     ht macros;  /* hashtable<string, ppmacro*> */
45     char *output_string;
46     char *itemname;
47     char *includename;
48     bool in_macro;
49     uint32_t predef_countval;
50     uint32_t predef_randval;
51 };
52
53 /* __DATE__ */
54 static char *ftepp_predef_date(ftepp_t *context) {
55     const struct tm *itime = nullptr;
56     char            *value = (char*)mem_a(82);
57     time_t           rtime;
58
59     (void)context;
60
61     time (&rtime);
62     itime = util_localtime(&rtime);
63     strftime(value, 82, "\"%b %d %Y\"", itime);
64
65     return value;
66 }
67
68 /* __TIME__ */
69 static char *ftepp_predef_time(ftepp_t *context) {
70     const struct tm *itime = nullptr;
71     char            *value = (char*)mem_a(82);
72     time_t           rtime;
73
74     (void)context;
75
76     time (&rtime);
77     itime = util_localtime(&rtime);
78     strftime(value, 82, "\"%X\"", itime);
79
80     return value;
81 }
82
83 /* __LINE__ */
84 static char *ftepp_predef_line(ftepp_t *context) {
85     char *value;
86
87     util_asprintf(&value, "%d", (int)context->lex->line);
88     return value;
89 }
90 /* __FILE__ */
91 static char *ftepp_predef_file(ftepp_t *context) {
92     size_t length = strlen(context->lex->name) + 3; /* two quotes and a terminator */
93     char  *value  = (char*)mem_a(length);
94
95     util_snprintf(value, length, "\"%s\"", context->lex->name);
96     return value;
97 }
98 /* __COUNTER_LAST__ */
99 static char *ftepp_predef_counterlast(ftepp_t *context) {
100     char *value;
101     util_asprintf(&value, "%u", context->predef_countval);
102     return value;
103 }
104 /* __COUNTER__ */
105 static char *ftepp_predef_counter(ftepp_t *context) {
106     char *value;
107
108     context->predef_countval ++;
109     util_asprintf(&value, "%u", context->predef_countval);
110
111     return value;
112 }
113 /* __RANDOM__ */
114 static char *ftepp_predef_random(ftepp_t *context) {
115     char *value;
116
117     context->predef_randval = (util_rand() % 0xFF) + 1;
118     util_asprintf(&value, "%u", context->predef_randval);
119     return value;
120 }
121 /* __RANDOM_LAST__ */
122 static char *ftepp_predef_randomlast(ftepp_t *context) {
123     char *value;
124
125     util_asprintf(&value, "%u", context->predef_randval);
126     return value;
127 }
128 /* __TIMESTAMP__ */
129 static char *ftepp_predef_timestamp(ftepp_t *context) {
130     struct stat finfo;
131     const char *find;
132     char       *value;
133     size_t      size;
134
135     if (stat(context->lex->name, &finfo))
136         return util_strdup("\"<failed to determine timestamp>\"");
137
138     find = util_ctime(&finfo.st_mtime);
139     value = (char*)mem_a(strlen(find) + 1);
140     memcpy(&value[1], find, (size = strlen(find)) - 1);
141
142     value[0]    = '"';
143     value[size] = '"';
144
145     return value;
146 }
147
148 struct ftepp_predef_t {
149     const char *name;
150     char *(*func)(ftepp_t *);
151 };
152
153 static const ftepp_predef_t ftepp_predefs[] = {
154     { "__LINE__",         &ftepp_predef_line        },
155     { "__FILE__",         &ftepp_predef_file        },
156     { "__COUNTER__",      &ftepp_predef_counter     },
157     { "__COUNTER_LAST__", &ftepp_predef_counterlast },
158     { "__RANDOM__",       &ftepp_predef_random      },
159     { "__RANDOM_LAST__",  &ftepp_predef_randomlast  },
160     { "__DATE__",         &ftepp_predef_date        },
161     { "__TIME__",         &ftepp_predef_time        },
162     { "__TIME_STAMP__",   &ftepp_predef_timestamp   }
163 };
164
165 static GMQCC_INLINE size_t ftepp_predef_index(const char *name) {
166     /* no hashtable here, we simply check for one to exist the naive way */
167     size_t i;
168     for(i = 1; i < GMQCC_ARRAY_COUNT(ftepp_predefs) + 1; i++)
169         if (!strcmp(ftepp_predefs[i-1].name, name))
170             return i;
171     return 0;
172 }
173
174 bool ftepp_predef_exists(const char *name);
175 bool ftepp_predef_exists(const char *name) {
176     return ftepp_predef_index(name) != 0;
177 }
178
179 /* singleton because we're allowed */
180 static GMQCC_INLINE char *(*ftepp_predef(const char *name))(ftepp_t *context) {
181     size_t i = ftepp_predef_index(name);
182     return (i != 0) ? ftepp_predefs[i-1].func : nullptr;
183 }
184
185 #define ftepp_tokval(f) ((f)->lex->tok.value)
186 #define ftepp_ctx(f)    ((f)->lex->tok.ctx)
187
188 static void ftepp_errorat(ftepp_t *ftepp, lex_ctx_t ctx, const char *fmt, ...)
189 {
190     va_list ap;
191
192     ftepp->errors++;
193
194     va_start(ap, fmt);
195     con_cvprintmsg(ctx, LVL_ERROR, "error", fmt, ap);
196     va_end(ap);
197 }
198
199 static void ftepp_error(ftepp_t *ftepp, const char *fmt, ...)
200 {
201     va_list ap;
202
203     ftepp->errors++;
204
205     va_start(ap, fmt);
206     con_cvprintmsg(ftepp->lex->tok.ctx, LVL_ERROR, "error", fmt, ap);
207     va_end(ap);
208 }
209
210 static bool GMQCC_WARN ftepp_warn(ftepp_t *ftepp, int warntype, const char *fmt, ...)
211 {
212     bool    r;
213     va_list ap;
214
215     va_start(ap, fmt);
216     r = vcompile_warning(ftepp->lex->tok.ctx, warntype, fmt, ap);
217     va_end(ap);
218     return r;
219 }
220
221 static pptoken *pptoken_make(ftepp_t *ftepp)
222 {
223     pptoken *token = (pptoken*)mem_a(sizeof(pptoken));
224     token->token = ftepp->token;
225     token->value = util_strdup(ftepp_tokval(ftepp));
226     memcpy(&token->constval, &ftepp->lex->tok.constval, sizeof(token->constval));
227     return token;
228 }
229
230 static GMQCC_INLINE void pptoken_delete(pptoken *self)
231 {
232     mem_d(self->value);
233     mem_d(self);
234 }
235
236 static ppmacro *ppmacro_new(lex_ctx_t ctx, const char *name)
237 {
238     ppmacro *macro = (ppmacro*)mem_a(sizeof(ppmacro));
239
240     (void)ctx;
241     memset(macro, 0, sizeof(*macro));
242     macro->name = util_strdup(name);
243     return macro;
244 }
245
246 static void ppmacro_delete(ppmacro *self)
247 {
248     size_t i;
249     for (i = 0; i < vec_size(self->params); ++i)
250         mem_d(self->params[i]);
251     vec_free(self->params);
252     for (i = 0; i < vec_size(self->output); ++i)
253         pptoken_delete(self->output[i]);
254     vec_free(self->output);
255     mem_d(self->name);
256     mem_d(self);
257 }
258
259 static ftepp_t* ftepp_new(void)
260 {
261     ftepp_t *ftepp;
262
263     ftepp = (ftepp_t*)mem_a(sizeof(*ftepp));
264     memset(ftepp, 0, sizeof(*ftepp));
265
266     ftepp->macros          = util_htnew(HT_MACROS);
267     ftepp->output_on       = true;
268     ftepp->predef_countval = 0;
269     ftepp->predef_randval  = 0;
270
271     return ftepp;
272 }
273
274 static GMQCC_INLINE void ftepp_flush_do(ftepp_t *self)
275 {
276     vec_free(self->output_string);
277 }
278
279 static void ftepp_delete(ftepp_t *self)
280 {
281     ftepp_flush_do(self);
282     if (self->itemname)
283         mem_d(self->itemname);
284     if (self->includename)
285         vec_free(self->includename);
286
287     util_htrem(self->macros, (void (*)(void*))&ppmacro_delete);
288
289     vec_free(self->conditions);
290     if (self->lex)
291         lex_close(self->lex);
292     mem_d(self);
293 }
294
295 static void ftepp_out(ftepp_t *ftepp, const char *str, bool ignore_cond)
296 {
297     if (ignore_cond || ftepp->output_on)
298     {
299         size_t len;
300         char  *data;
301         len = strlen(str);
302         data = vec_add(ftepp->output_string, len);
303         memcpy(data, str, len);
304     }
305 }
306
307 static GMQCC_INLINE void ftepp_update_output_condition(ftepp_t *ftepp)
308 {
309     size_t i;
310     ftepp->output_on = true;
311     for (i = 0; i < vec_size(ftepp->conditions); ++i)
312         ftepp->output_on = ftepp->output_on && ftepp->conditions[i].on;
313 }
314
315 static GMQCC_INLINE ppmacro* ftepp_macro_find(ftepp_t *ftepp, const char *name)
316 {
317     return (ppmacro*)util_htget(ftepp->macros, name);
318 }
319
320 static GMQCC_INLINE void ftepp_macro_delete(ftepp_t *ftepp, const char *name)
321 {
322     util_htrm(ftepp->macros, name, (void (*)(void*))&ppmacro_delete);
323 }
324
325 static GMQCC_INLINE int ftepp_next(ftepp_t *ftepp)
326 {
327     return (ftepp->token = lex_do(ftepp->lex));
328 }
329
330 /* Important: this does not skip newlines! */
331 static bool ftepp_skipspace(ftepp_t *ftepp)
332 {
333     if (ftepp->token != TOKEN_WHITE)
334         return true;
335     while (ftepp_next(ftepp) == TOKEN_WHITE) {}
336     if (ftepp->token >= TOKEN_EOF) {
337         ftepp_error(ftepp, "unexpected end of preprocessor directive");
338         return false;
339     }
340     return true;
341 }
342
343 /* this one skips EOLs as well */
344 static bool ftepp_skipallwhite(ftepp_t *ftepp)
345 {
346     if (ftepp->token != TOKEN_WHITE && ftepp->token != TOKEN_EOL)
347         return true;
348     do {
349         ftepp_next(ftepp);
350     } while (ftepp->token == TOKEN_WHITE || ftepp->token == TOKEN_EOL);
351     if (ftepp->token >= TOKEN_EOF) {
352         ftepp_error(ftepp, "unexpected end of preprocessor directive");
353         return false;
354     }
355     return true;
356 }
357
358 /**
359  * The huge macro parsing code...
360  */
361 static bool ftepp_define_params(ftepp_t *ftepp, ppmacro *macro)
362 {
363     do {
364         ftepp_next(ftepp);
365         if (!ftepp_skipspace(ftepp))
366             return false;
367         if (ftepp->token == ')')
368             break;
369         switch (ftepp->token) {
370             case TOKEN_IDENT:
371             case TOKEN_TYPENAME:
372             case TOKEN_KEYWORD:
373                 vec_push(macro->params, util_strdup(ftepp_tokval(ftepp)));
374                 break;
375             case TOKEN_DOTS:
376                 macro->variadic = true;
377                 break;
378             default:
379                 ftepp_error(ftepp, "unexpected token in parameter list");
380                 return false;
381         }
382         ftepp_next(ftepp);
383         if (!ftepp_skipspace(ftepp))
384             return false;
385         if (macro->variadic && ftepp->token != ')') {
386             ftepp_error(ftepp, "cannot have parameters after the variadic parameters");
387             return false;
388         }
389     } while (ftepp->token == ',');
390
391     if (ftepp->token != ')') {
392         ftepp_error(ftepp, "expected closing paren after macro parameter list");
393         return false;
394     }
395     ftepp_next(ftepp);
396     /* skipspace happens in ftepp_define */
397     return true;
398 }
399
400 static bool ftepp_define_body(ftepp_t *ftepp, ppmacro *macro)
401 {
402     pptoken *ptok;
403     while (ftepp->token != TOKEN_EOL && ftepp->token < TOKEN_EOF) {
404         bool   subscript = false;
405         size_t index     = 0;
406         if (macro->variadic && !strcmp(ftepp_tokval(ftepp), "__VA_ARGS__")) {
407             subscript = !!(ftepp_next(ftepp) == '#');
408
409             if (subscript && ftepp_next(ftepp) != '#') {
410                 ftepp_error(ftepp, "expected `##` in __VA_ARGS__ for subscripting");
411                 return false;
412             } else if (subscript) {
413                 if (ftepp_next(ftepp) == '[') {
414                     if (ftepp_next(ftepp) != TOKEN_INTCONST) {
415                         ftepp_error(ftepp, "expected index for __VA_ARGS__ subscript");
416                         return false;
417                     }
418
419                     index = (int)strtol(ftepp_tokval(ftepp), nullptr, 10);
420
421                     if (ftepp_next(ftepp) != ']') {
422                         ftepp_error(ftepp, "expected `]` in __VA_ARGS__ subscript");
423                         return false;
424                     }
425
426                     /*
427                      * mark it as an array to be handled later as such and not
428                      * as traditional __VA_ARGS__
429                      */
430                     ftepp->token = TOKEN_VA_ARGS_ARRAY;
431                     ptok = pptoken_make(ftepp);
432                     ptok->constval.i = index;
433                     vec_push(macro->output, ptok);
434                     ftepp_next(ftepp);
435                 } else {
436                     ftepp_error(ftepp, "expected `[` for subscripting of __VA_ARGS__");
437                     return false;
438                 }
439             } else {
440                 int old = ftepp->token;
441                 ftepp->token = TOKEN_VA_ARGS;
442                 ptok = pptoken_make(ftepp);
443                 vec_push(macro->output, ptok);
444                 ftepp->token = old;
445             }
446         }
447         else if (macro->variadic && !strcmp(ftepp_tokval(ftepp), "__VA_COUNT__")) {
448             ftepp->token = TOKEN_VA_COUNT;
449             ptok         = pptoken_make(ftepp);
450             vec_push(macro->output, ptok);
451             ftepp_next(ftepp);
452         } else {
453             ptok = pptoken_make(ftepp);
454             vec_push(macro->output, ptok);
455             ftepp_next(ftepp);
456         }
457     }
458     /* recursive expansion can cause EOFs here */
459     if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) {
460         ftepp_error(ftepp, "unexpected junk after macro or unexpected end of file");
461         return false;
462     }
463     return true;
464 }
465
466 static const char *ftepp_math_constants[][2] = {
467     { "M_E",        "2.7182818284590452354"  }, /* e          */
468     { "M_LOG2E",    "1.4426950408889634074"  }, /* log_2 e    */
469     { "M_LOG10E",   "0.43429448190325182765" }, /* log_10 e   */
470     { "M_LN2",      "0.69314718055994530942" }, /* log_e 2    */
471     { "M_LN10",     "2.30258509299404568402" }, /* log_e 10   */
472     { "M_PI",       "3.14159265358979323846" }, /* pi         */
473     { "M_PI_2",     "1.57079632679489661923" }, /* pi/2       */
474     { "M_PI_4",     "0.78539816339744830962" }, /* pi/4       */
475     { "M_1_PI",     "0.31830988618379067154" }, /* 1/pi       */
476     { "M_2_PI",     "0.63661977236758134308" }, /* 2/pi       */
477     { "M_2_SQRTPI", "1.12837916709551257390" }, /* 2/sqrt(pi) */
478     { "M_SQRT2",    "1.41421356237309504880" }, /* sqrt(2)    */
479     { "M_SQRT1_2",  "0.70710678118654752440" }, /* 1/sqrt(2)  */
480     { "M_TAU",      "6.28318530717958647692" }  /* pi*2       */
481 };
482
483 static bool ftepp_define(ftepp_t *ftepp)
484 {
485     ppmacro *macro = nullptr;
486     size_t l = ftepp_ctx(ftepp).line;
487     size_t i;
488     bool   mathconstant = false;
489
490     (void)ftepp_next(ftepp);
491     if (!ftepp_skipspace(ftepp))
492         return false;
493
494     switch (ftepp->token) {
495         case TOKEN_IDENT:
496         case TOKEN_TYPENAME:
497         case TOKEN_KEYWORD:
498             if (OPTS_FLAG(FTEPP_MATHDEFS)) {
499                 for (i = 0; i < GMQCC_ARRAY_COUNT(ftepp_math_constants); i++) {
500                     if (!strcmp(ftepp_math_constants[i][0], ftepp_tokval(ftepp))) {
501                         mathconstant = true;
502                         break;
503                     }
504                 }
505             }
506
507             macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
508
509             if (OPTS_FLAG(FTEPP_MATHDEFS)) {
510                 /* user defined ones take precedence */
511                 if (macro && mathconstant) {
512                     ftepp_macro_delete(ftepp, ftepp_tokval(ftepp));
513                     macro = nullptr;
514                 }
515             }
516
517             if (macro && ftepp->output_on) {
518                 if (ftepp_warn(ftepp, WARN_CPP, "redefining `%s`", ftepp_tokval(ftepp)))
519                     return false;
520                 ftepp_macro_delete(ftepp, ftepp_tokval(ftepp));
521             }
522             macro = ppmacro_new(ftepp_ctx(ftepp), ftepp_tokval(ftepp));
523             break;
524         default:
525             ftepp_error(ftepp, "expected macro name");
526             return false;
527     }
528
529     (void)ftepp_next(ftepp);
530
531     if (ftepp->token == '(') {
532         macro->has_params = true;
533         if (!ftepp_define_params(ftepp, macro)) {
534             ppmacro_delete(macro);
535             return false;
536         }
537     }
538
539     if (!ftepp_skipspace(ftepp)) {
540         ppmacro_delete(macro);
541         return false;
542     }
543
544     if (!ftepp_define_body(ftepp, macro)) {
545         ppmacro_delete(macro);
546         return false;
547     }
548
549     if (ftepp->output_on)
550         util_htset(ftepp->macros, macro->name, (void*)macro);
551     else {
552         ppmacro_delete(macro);
553     }
554
555     for (; l < ftepp_ctx(ftepp).line; ++l)
556         ftepp_out(ftepp, "\n", true);
557     return true;
558 }
559
560 /**
561  * When a macro is used we have to handle parameters as well
562  * as special-concatenation via ## or stringification via #
563  *
564  * Note: parenthesis can nest, so FOO((a),b) is valid, but only
565  * this kind of parens. Curly braces or [] don't count towards the
566  * paren-level.
567  */
568 struct macroparam {
569     pptoken **tokens;
570 };
571
572 static void macroparam_clean(macroparam *self)
573 {
574     size_t i;
575     for (i = 0; i < vec_size(self->tokens); ++i)
576         pptoken_delete(self->tokens[i]);
577     vec_free(self->tokens);
578 }
579
580 /* need to leave the last token up */
581 static bool ftepp_macro_call_params(ftepp_t *ftepp, macroparam **out_params)
582 {
583     macroparam *params = nullptr;
584     pptoken    *ptok;
585     macroparam  mp;
586     size_t      parens = 0;
587     size_t      i;
588
589     if (!ftepp_skipallwhite(ftepp))
590         return false;
591     while (ftepp->token != ')') {
592         mp.tokens = nullptr;
593         if (!ftepp_skipallwhite(ftepp))
594             return false;
595         while (parens || ftepp->token != ',') {
596             if (ftepp->token == '(')
597                 ++parens;
598             else if (ftepp->token == ')') {
599                 if (!parens)
600                     break;
601                 --parens;
602             }
603             ptok = pptoken_make(ftepp);
604             vec_push(mp.tokens, ptok);
605             if (ftepp_next(ftepp) >= TOKEN_EOF) {
606                 ftepp_error(ftepp, "unexpected end of file in macro call");
607                 goto on_error;
608             }
609         }
610         vec_push(params, mp);
611         mp.tokens = nullptr;
612         if (ftepp->token == ')')
613             break;
614         if (ftepp->token != ',') {
615             ftepp_error(ftepp, "expected closing paren or comma in macro call");
616             goto on_error;
617         }
618         if (ftepp_next(ftepp) >= TOKEN_EOF) {
619             ftepp_error(ftepp, "unexpected end of file in macro call");
620             goto on_error;
621         }
622     }
623     *out_params = params;
624     return true;
625
626 on_error:
627     if (mp.tokens)
628         macroparam_clean(&mp);
629     for (i = 0; i < vec_size(params); ++i)
630         macroparam_clean(&params[i]);
631     vec_free(params);
632     return false;
633 }
634
635 static bool macro_params_find(ppmacro *macro, const char *name, size_t *idx)
636 {
637     size_t i;
638     for (i = 0; i < vec_size(macro->params); ++i) {
639         if (!strcmp(macro->params[i], name)) {
640             *idx = i;
641             return true;
642         }
643     }
644     return false;
645 }
646
647 static void ftepp_stringify_token(ftepp_t *ftepp, pptoken *token)
648 {
649     char        chs[2];
650     const char *ch;
651     chs[1] = 0;
652     switch (token->token) {
653         case TOKEN_STRINGCONST:
654             ch = token->value;
655             while (*ch) {
656                 /* in preprocessor mode strings already are string,
657                  * so we don't get actual newline bytes here.
658                  * Still need to escape backslashes and quotes.
659                  */
660                 switch (*ch) {
661                     case '\\': ftepp_out(ftepp, "\\\\", false); break;
662                     case '"':  ftepp_out(ftepp, "\\\"", false); break;
663                     default:
664                         chs[0] = *ch;
665                         ftepp_out(ftepp, chs, false);
666                         break;
667                 }
668                 ++ch;
669             }
670             break;
671         /*case TOKEN_WHITE:
672             ftepp_out(ftepp, " ", false);
673             break;*/
674         case TOKEN_EOL:
675             ftepp_out(ftepp, "\\n", false);
676             break;
677         default:
678             ftepp_out(ftepp, token->value, false);
679             break;
680     }
681 }
682
683 static void ftepp_stringify(ftepp_t *ftepp, macroparam *param)
684 {
685     size_t i;
686     ftepp_out(ftepp, "\"", false);
687     for (i = 0; i < vec_size(param->tokens); ++i)
688         ftepp_stringify_token(ftepp, param->tokens[i]);
689     ftepp_out(ftepp, "\"", false);
690 }
691
692 static void ftepp_recursion_header(ftepp_t *ftepp)
693 {
694     ftepp_out(ftepp, "\n#pragma push(line)\n", false);
695 }
696
697 static void ftepp_recursion_footer(ftepp_t *ftepp)
698 {
699     ftepp_out(ftepp, "\n#pragma pop(line)\n", false);
700 }
701
702 static bool ftepp_macro_expand(ftepp_t *ftepp, ppmacro *macro, macroparam *params, bool resetline);
703 static void ftepp_param_out(ftepp_t *ftepp, macroparam *param)
704 {
705     size_t   i;
706     pptoken *out;
707     for (i = 0; i < vec_size(param->tokens); ++i) {
708         out = param->tokens[i];
709         if (out->token == TOKEN_EOL)
710             ftepp_out(ftepp, "\n", false);
711         else {
712             ppmacro *find = ftepp_macro_find(ftepp, out->value);
713             if (OPTS_FLAG(FTEPP_INDIRECT_EXPANSION) && find && !find->has_params)
714                 ftepp_macro_expand(ftepp, find, nullptr, false);
715             else
716                 ftepp_out(ftepp, out->value, false);
717         }
718     }
719 }
720
721 static bool ftepp_preprocess(ftepp_t *ftepp);
722 static bool ftepp_macro_expand(ftepp_t *ftepp, ppmacro *macro, macroparam *params, bool resetline)
723 {
724     char     *buffer       = nullptr;
725     char     *old_string   = ftepp->output_string;
726     char     *inner_string;
727     lex_file *old_lexer    = ftepp->lex;
728     size_t    vararg_start = vec_size(macro->params);
729     bool      retval       = true;
730     bool      has_newlines;
731     size_t    varargs;
732
733     size_t    o, pi;
734     lex_file *inlex;
735
736     bool      old_inmacro;
737     bool      strip = false;
738
739     int nextok;
740
741     if (vararg_start < vec_size(params))
742         varargs = vec_size(params) - vararg_start;
743     else
744         varargs = 0;
745
746     /* really ... */
747     if (!vec_size(macro->output))
748         return true;
749
750     ftepp->output_string = nullptr;
751     for (o = 0; o < vec_size(macro->output); ++o) {
752         pptoken *out = macro->output[o];
753         switch (out->token) {
754             case TOKEN_VA_ARGS:
755                 if (!macro->variadic) {
756                     ftepp_error(ftepp, "internal preprocessor error: TOKEN_VA_ARGS in non-variadic macro");
757                     vec_free(old_string);
758                     return false;
759                 }
760                 if (!varargs)
761                     break;
762
763                 pi = 0;
764                 ftepp_param_out(ftepp, &params[pi + vararg_start]);
765                 for (++pi; pi < varargs; ++pi) {
766                     ftepp_out(ftepp, ", ", false);
767                     ftepp_param_out(ftepp, &params[pi + vararg_start]);
768                 }
769                 break;
770
771             case TOKEN_VA_ARGS_ARRAY:
772                 if ((size_t)out->constval.i >= varargs) {
773                     ftepp_error(ftepp, "subscript of `[%u]` is out of bounds for `__VA_ARGS__`", out->constval.i);
774                     vec_free(old_string);
775                     return false;
776                 }
777
778                 ftepp_param_out(ftepp, &params[out->constval.i + vararg_start]);
779                 break;
780
781             case TOKEN_VA_COUNT:
782                 util_asprintf(&buffer, "%d", varargs);
783                 ftepp_out(ftepp, buffer, false);
784                 mem_d(buffer);
785                 break;
786
787             case TOKEN_IDENT:
788             case TOKEN_TYPENAME:
789             case TOKEN_KEYWORD:
790                 if (!macro_params_find(macro, out->value, &pi)) {
791                     ftepp_out(ftepp, out->value, false);
792                     break;
793                 } else
794                     ftepp_param_out(ftepp, &params[pi]);
795                 break;
796             case '#':
797                 if (o + 1 < vec_size(macro->output)) {
798                     nextok = macro->output[o+1]->token;
799                     if (nextok == '#') {
800                         /* raw concatenation */
801                         ++o;
802                         strip = true;
803                         break;
804                     }
805                     if ( (nextok == TOKEN_IDENT    ||
806                           nextok == TOKEN_KEYWORD  ||
807                           nextok == TOKEN_TYPENAME) &&
808                         macro_params_find(macro, macro->output[o+1]->value, &pi))
809                     {
810                         ++o;
811
812                         ftepp_stringify(ftepp, &params[pi]);
813                         break;
814                     }
815                 }
816                 ftepp_out(ftepp, "#", false);
817                 break;
818             case TOKEN_EOL:
819                 ftepp_out(ftepp, "\n", false);
820                 break;
821             default:
822                 buffer = out->value;
823                 #define buffer_stripable(X) ((X) == ' ' || (X) == '\t')
824                 if (vec_size(macro->output) > o + 1 && macro->output[o+1]->token == '#' && buffer_stripable(*buffer))
825                     buffer++;
826                 if (strip) {
827                     while (buffer_stripable(*buffer)) buffer++;
828                     strip = false;
829                 }
830                 ftepp_out(ftepp, buffer, false);
831                 break;
832         }
833     }
834     vec_push(ftepp->output_string, 0);
835     /* Now run the preprocessor recursively on this string buffer */
836     /*
837     printf("__________\n%s\n=========\n", ftepp->output_string);
838     */
839     inlex = lex_open_string(ftepp->output_string, vec_size(ftepp->output_string)-1, ftepp->lex->name);
840     if (!inlex) {
841         ftepp_error(ftepp, "internal error: failed to instantiate lexer");
842         retval = false;
843         goto cleanup;
844     }
845
846     inlex->line  = ftepp->lex->line;
847     inlex->sline = ftepp->lex->sline;
848     ftepp->lex   = inlex;
849
850     old_inmacro     = ftepp->in_macro;
851     ftepp->in_macro = true;
852     ftepp->output_string = nullptr;
853     if (!ftepp_preprocess(ftepp)) {
854         ftepp->in_macro = old_inmacro;
855         vec_free(ftepp->lex->open_string);
856         vec_free(ftepp->output_string);
857         lex_close(ftepp->lex);
858         retval = false;
859         goto cleanup;
860     }
861     ftepp->in_macro = old_inmacro;
862     vec_free(ftepp->lex->open_string);
863     lex_close(ftepp->lex);
864
865     inner_string = ftepp->output_string;
866     ftepp->output_string = old_string;
867
868     has_newlines = (strchr(inner_string, '\n') != nullptr);
869
870     if (has_newlines && !old_inmacro)
871         ftepp_recursion_header(ftepp);
872
873     vec_append(ftepp->output_string, vec_size(inner_string), inner_string);
874     vec_free(inner_string);
875
876     if (has_newlines && !old_inmacro)
877         ftepp_recursion_footer(ftepp);
878
879     if (resetline && !ftepp->in_macro) {
880         char lineno[128];
881         util_snprintf(lineno, 128, "\n#pragma line(%lu)\n", (unsigned long)(old_lexer->sline));
882         ftepp_out(ftepp, lineno, false);
883     }
884
885     old_string = ftepp->output_string;
886 cleanup:
887     ftepp->lex           = old_lexer;
888     ftepp->output_string = old_string;
889     return retval;
890 }
891
892 static bool ftepp_macro_call(ftepp_t *ftepp, ppmacro *macro)
893 {
894     size_t     o;
895     macroparam *params = nullptr;
896     bool        retval = true;
897     size_t      paramline;
898
899     if (!macro->has_params) {
900         if (!ftepp_macro_expand(ftepp, macro, nullptr, false))
901             return false;
902         ftepp_next(ftepp);
903         return true;
904     }
905     ftepp_next(ftepp);
906
907     if (!ftepp_skipallwhite(ftepp))
908         return false;
909
910     if (ftepp->token != '(') {
911         ftepp_error(ftepp, "expected macro parameters in parenthesis");
912         return false;
913     }
914
915     ftepp_next(ftepp);
916     paramline = ftepp->lex->sline;
917     if (!ftepp_macro_call_params(ftepp, &params))
918         return false;
919
920     if ( vec_size(params) < vec_size(macro->params) ||
921         (vec_size(params) > vec_size(macro->params) && !macro->variadic) )
922     {
923         ftepp_error(ftepp, "macro %s expects%s %u paramteters, %u provided", macro->name,
924                     (macro->variadic ? " at least" : ""),
925                     (unsigned int)vec_size(macro->params),
926                     (unsigned int)vec_size(params));
927         retval = false;
928         goto cleanup;
929     }
930
931     if (!ftepp_macro_expand(ftepp, macro, params, (paramline != ftepp->lex->sline)))
932         retval = false;
933     ftepp_next(ftepp);
934
935 cleanup:
936     for (o = 0; o < vec_size(params); ++o)
937         macroparam_clean(&params[o]);
938     vec_free(params);
939     return retval;
940 }
941
942 /**
943  * #if - the FTEQCC way:
944  *    defined(FOO) => true if FOO was #defined regardless of parameters or contents
945  *    <numbers>    => True if the number is not 0
946  *    !<factor>    => True if the factor yields false
947  *    !!<factor>   => ERROR on 2 or more unary nots
948  *    <macro>      => becomes the macro's FIRST token regardless of parameters
949  *    <e> && <e>   => True if both expressions are true
950  *    <e> || <e>   => True if either expression is true
951  *    <string>     => False
952  *    <ident>      => False (remember for macros the <macro> rule applies instead)
953  * Unary + and - are weird and wrong in fteqcc so we don't allow them
954  * parenthesis in expressions are allowed
955  * parameter lists on macros are errors
956  * No mathematical calculations are executed
957  */
958 static bool ftepp_if_expr(ftepp_t *ftepp, bool *out, double *value_out);
959 static bool ftepp_if_op(ftepp_t *ftepp)
960 {
961     ftepp->lex->flags.noops = false;
962     ftepp_next(ftepp);
963     if (!ftepp_skipspace(ftepp))
964         return false;
965     ftepp->lex->flags.noops = true;
966     return true;
967 }
968 static bool ftepp_if_value(ftepp_t *ftepp, bool *out, double *value_out)
969 {
970     ppmacro *macro;
971     bool     wasnot = false;
972     bool     wasneg = false;
973
974     if (!ftepp_skipspace(ftepp))
975         return false;
976
977     while (ftepp->token == '!') {
978         wasnot = true;
979         ftepp_next(ftepp);
980         if (!ftepp_skipspace(ftepp))
981             return false;
982     }
983
984     if (ftepp->token == TOKEN_OPERATOR && !strcmp(ftepp_tokval(ftepp), "-"))
985     {
986         wasneg = true;
987         ftepp_next(ftepp);
988         if (!ftepp_skipspace(ftepp))
989             return false;
990     }
991
992     switch (ftepp->token) {
993         case TOKEN_IDENT:
994         case TOKEN_TYPENAME:
995         case TOKEN_KEYWORD:
996             if (!strcmp(ftepp_tokval(ftepp), "defined")) {
997                 ftepp_next(ftepp);
998                 if (!ftepp_skipspace(ftepp))
999                     return false;
1000                 if (ftepp->token != '(') {
1001                     ftepp_error(ftepp, "`defined` keyword in #if requires a macro name in parenthesis");
1002                     return false;
1003                 }
1004                 ftepp_next(ftepp);
1005                 if (!ftepp_skipspace(ftepp))
1006                     return false;
1007                 if (ftepp->token != TOKEN_IDENT &&
1008                     ftepp->token != TOKEN_TYPENAME &&
1009                     ftepp->token != TOKEN_KEYWORD)
1010                 {
1011                     ftepp_error(ftepp, "defined() used on an unexpected token type");
1012                     return false;
1013                 }
1014                 macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
1015                 *out = !!macro;
1016                 ftepp_next(ftepp);
1017                 if (!ftepp_skipspace(ftepp))
1018                     return false;
1019                 if (ftepp->token != ')') {
1020                     ftepp_error(ftepp, "expected closing paren");
1021                     return false;
1022                 }
1023                 break;
1024             }
1025
1026             macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
1027             if (!macro || !vec_size(macro->output)) {
1028                 *out = false;
1029                 *value_out = 0;
1030             } else {
1031                 /* This does not expand recursively! */
1032                 switch (macro->output[0]->token) {
1033                     case TOKEN_INTCONST:
1034                         *value_out = macro->output[0]->constval.i;
1035                         *out = !!(macro->output[0]->constval.i);
1036                         break;
1037                     case TOKEN_FLOATCONST:
1038                         *value_out = macro->output[0]->constval.f;
1039                         *out = !!(macro->output[0]->constval.f);
1040                         break;
1041                     default:
1042                         *out = false;
1043                         break;
1044                 }
1045             }
1046             break;
1047         case TOKEN_STRINGCONST:
1048             *value_out = 0;
1049             *out = false;
1050             break;
1051         case TOKEN_INTCONST:
1052             *value_out = ftepp->lex->tok.constval.i;
1053             *out = !!(ftepp->lex->tok.constval.i);
1054             break;
1055         case TOKEN_FLOATCONST:
1056             *value_out = ftepp->lex->tok.constval.f;
1057             *out = !!(ftepp->lex->tok.constval.f);
1058             break;
1059
1060         case '(':
1061             ftepp_next(ftepp);
1062             if (!ftepp_if_expr(ftepp, out, value_out))
1063                 return false;
1064             if (ftepp->token != ')') {
1065                 ftepp_error(ftepp, "expected closing paren in #if expression");
1066                 return false;
1067             }
1068             break;
1069
1070         default:
1071             ftepp_error(ftepp, "junk in #if: `%s` ...", ftepp_tokval(ftepp));
1072             if (OPTS_OPTION_BOOL(OPTION_DEBUG))
1073                 ftepp_error(ftepp, "internal: token %i\n", ftepp->token);
1074             return false;
1075     }
1076     if (wasneg)
1077         *value_out = -*value_out;
1078     if (wasnot) {
1079         *out = !*out;
1080         *value_out = (*out ? 1 : 0);
1081     }
1082     return true;
1083 }
1084
1085 /*
1086 static bool ftepp_if_nextvalue(ftepp_t *ftepp, bool *out, double *value_out)
1087 {
1088     if (!ftepp_next(ftepp))
1089         return false;
1090     return ftepp_if_value(ftepp, out, value_out);
1091 }
1092 */
1093
1094 static bool ftepp_if_expr(ftepp_t *ftepp, bool *out, double *value_out)
1095 {
1096     if (!ftepp_if_value(ftepp, out, value_out))
1097         return false;
1098
1099     if (!ftepp_if_op(ftepp))
1100         return false;
1101
1102     if (ftepp->token == ')' || ftepp->token != TOKEN_OPERATOR)
1103         return true;
1104
1105     /* FTEQCC is all right-associative and no precedence here */
1106     if (!strcmp(ftepp_tokval(ftepp), "&&") ||
1107         !strcmp(ftepp_tokval(ftepp), "||"))
1108     {
1109         bool next = false;
1110         char opc  = ftepp_tokval(ftepp)[0];
1111         double nextvalue;
1112
1113         (void)nextvalue;
1114         if (!ftepp_next(ftepp))
1115             return false;
1116         if (!ftepp_if_expr(ftepp, &next, &nextvalue))
1117             return false;
1118
1119         if (opc == '&')
1120             *out = *out && next;
1121         else
1122             *out = *out || next;
1123
1124         *value_out = (*out ? 1 : 0);
1125         return true;
1126     }
1127     else if (!strcmp(ftepp_tokval(ftepp), "==") ||
1128              !strcmp(ftepp_tokval(ftepp), "!=") ||
1129              !strcmp(ftepp_tokval(ftepp), ">=") ||
1130              !strcmp(ftepp_tokval(ftepp), "<=") ||
1131              !strcmp(ftepp_tokval(ftepp), ">") ||
1132              !strcmp(ftepp_tokval(ftepp), "<"))
1133     {
1134         bool next = false;
1135         const char opc0 = ftepp_tokval(ftepp)[0];
1136         const char opc1 = ftepp_tokval(ftepp)[1];
1137         double other;
1138
1139         if (!ftepp_next(ftepp))
1140             return false;
1141         if (!ftepp_if_expr(ftepp, &next, &other))
1142             return false;
1143
1144         if (opc0 == '=')
1145             *out = (*value_out == other);
1146         else if (opc0 == '!')
1147             *out = (*value_out != other);
1148         else if (opc0 == '>') {
1149             if (opc1 == '=') *out = (*value_out >= other);
1150             else             *out = (*value_out > other);
1151         }
1152         else if (opc0 == '<') {
1153             if (opc1 == '=') *out = (*value_out <= other);
1154             else             *out = (*value_out < other);
1155         }
1156         *value_out = (*out ? 1 : 0);
1157
1158         return true;
1159     }
1160     else {
1161         ftepp_error(ftepp, "junk after #if");
1162         return false;
1163     }
1164 }
1165
1166 static bool ftepp_if(ftepp_t *ftepp, ppcondition *cond)
1167 {
1168     bool result = false;
1169     double dummy = 0;
1170
1171     memset(cond, 0, sizeof(*cond));
1172     (void)ftepp_next(ftepp);
1173
1174     if (!ftepp_skipspace(ftepp))
1175         return false;
1176     if (ftepp->token == TOKEN_EOL) {
1177         ftepp_error(ftepp, "expected expression for #if-directive");
1178         return false;
1179     }
1180
1181     if (!ftepp_if_expr(ftepp, &result, &dummy))
1182         return false;
1183
1184     cond->on = result;
1185     return true;
1186 }
1187
1188 /**
1189  * ifdef is rather simple
1190  */
1191 static bool ftepp_ifdef(ftepp_t *ftepp, ppcondition *cond)
1192 {
1193     ppmacro *macro;
1194     memset(cond, 0, sizeof(*cond));
1195     (void)ftepp_next(ftepp);
1196     if (!ftepp_skipspace(ftepp))
1197         return false;
1198
1199     switch (ftepp->token) {
1200         case TOKEN_IDENT:
1201         case TOKEN_TYPENAME:
1202         case TOKEN_KEYWORD:
1203             macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
1204             break;
1205         default:
1206             ftepp_error(ftepp, "expected macro name");
1207             return false;
1208     }
1209
1210     (void)ftepp_next(ftepp);
1211     if (!ftepp_skipspace(ftepp))
1212         return false;
1213     /* relaxing this condition
1214     if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) {
1215         ftepp_error(ftepp, "stray tokens after #ifdef");
1216         return false;
1217     }
1218     */
1219     cond->on = !!macro;
1220     return true;
1221 }
1222
1223 /**
1224  * undef is also simple
1225  */
1226 static bool ftepp_undef(ftepp_t *ftepp)
1227 {
1228     (void)ftepp_next(ftepp);
1229     if (!ftepp_skipspace(ftepp))
1230         return false;
1231
1232     if (ftepp->output_on) {
1233         switch (ftepp->token) {
1234             case TOKEN_IDENT:
1235             case TOKEN_TYPENAME:
1236             case TOKEN_KEYWORD:
1237                 ftepp_macro_delete(ftepp, ftepp_tokval(ftepp));
1238                 break;
1239             default:
1240                 ftepp_error(ftepp, "expected macro name");
1241                 return false;
1242         }
1243     }
1244
1245     (void)ftepp_next(ftepp);
1246     if (!ftepp_skipspace(ftepp))
1247         return false;
1248     /* relaxing this condition
1249     if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) {
1250         ftepp_error(ftepp, "stray tokens after #ifdef");
1251         return false;
1252     }
1253     */
1254     return true;
1255 }
1256
1257 /* Special unescape-string function which skips a leading quote
1258  * and stops at a quote, not just at \0
1259  */
1260 static void unescape(const char *str, char *out) {
1261     ++str;
1262     while (*str && *str != '"') {
1263         if (*str == '\\') {
1264             ++str;
1265             switch (*str) {
1266                 case '\\': *out++ = *str; break;
1267                 case '"':  *out++ = *str; break;
1268                 case 'a':  *out++ = '\a'; break;
1269                 case 'b':  *out++ = '\b'; break;
1270                 case 'r':  *out++ = '\r'; break;
1271                 case 'n':  *out++ = '\n'; break;
1272                 case 't':  *out++ = '\t'; break;
1273                 case 'f':  *out++ = '\f'; break;
1274                 case 'v':  *out++ = '\v'; break;
1275                 default:
1276                     *out++ = '\\';
1277                     *out++ = *str;
1278                     break;
1279             }
1280             ++str;
1281             continue;
1282         }
1283
1284         *out++ = *str++;
1285     }
1286     *out = 0;
1287 }
1288
1289 static char *ftepp_include_find_path(const char *file, const char *pathfile)
1290 {
1291     FILE *fp;
1292     char       *filename = nullptr;
1293     const char *last_slash;
1294     size_t      len;
1295
1296     if (!pathfile)
1297         return nullptr;
1298
1299     last_slash = strrchr(pathfile, '/');
1300
1301     if (last_slash) {
1302         len = last_slash - pathfile;
1303         memcpy(vec_add(filename, len), pathfile, len);
1304         vec_push(filename, '/');
1305     }
1306
1307     len = strlen(file);
1308     memcpy(vec_add(filename, len+1), file, len);
1309     vec_last(filename) = 0;
1310
1311     fp = fopen(filename, "rb");
1312     if (fp) {
1313         fclose(fp);
1314         return filename;
1315     }
1316     vec_free(filename);
1317     return nullptr;
1318 }
1319
1320 static char *ftepp_include_find(ftepp_t *ftepp, const char *file)
1321 {
1322     char *filename = nullptr;
1323
1324     filename = ftepp_include_find_path(file, ftepp->includename);
1325     if (!filename)
1326         filename = ftepp_include_find_path(file, ftepp->itemname);
1327     return filename;
1328 }
1329
1330 static bool ftepp_directive_warning(ftepp_t *ftepp) {
1331     char *message = nullptr;
1332
1333     if (!ftepp_skipspace(ftepp))
1334         return false;
1335
1336     /* handle the odd non string constant case so it works like C */
1337     if (ftepp->token != TOKEN_STRINGCONST) {
1338         bool  store   = false;
1339         vec_append(message, 8, "#warning");
1340         ftepp_next(ftepp);
1341         while (ftepp->token != TOKEN_EOL) {
1342             vec_append(message, strlen(ftepp_tokval(ftepp)), ftepp_tokval(ftepp));
1343             ftepp_next(ftepp);
1344         }
1345         vec_push(message, '\0');
1346         if (ftepp->output_on)
1347             store = ftepp_warn(ftepp, WARN_CPP, message);
1348         else
1349             store = false;
1350         vec_free(message);
1351         return store;
1352     }
1353
1354     if (!ftepp->output_on)
1355         return false;
1356
1357     unescape  (ftepp_tokval(ftepp), ftepp_tokval(ftepp));
1358     return ftepp_warn(ftepp, WARN_CPP, "#warning %s", ftepp_tokval(ftepp));
1359 }
1360
1361 static void ftepp_directive_error(ftepp_t *ftepp) {
1362     char *message = nullptr;
1363
1364     if (!ftepp_skipspace(ftepp))
1365         return;
1366
1367     /* handle the odd non string constant case so it works like C */
1368     if (ftepp->token != TOKEN_STRINGCONST) {
1369         vec_append(message, 6, "#error");
1370         ftepp_next(ftepp);
1371         while (ftepp->token != TOKEN_EOL) {
1372             vec_append(message, strlen(ftepp_tokval(ftepp)), ftepp_tokval(ftepp));
1373             ftepp_next(ftepp);
1374         }
1375         vec_push(message, '\0');
1376         if (ftepp->output_on)
1377             ftepp_error(ftepp, message);
1378         vec_free(message);
1379         return;
1380     }
1381
1382     if (!ftepp->output_on)
1383         return;
1384
1385     unescape  (ftepp_tokval(ftepp), ftepp_tokval(ftepp));
1386     ftepp_error(ftepp, "#error %s", ftepp_tokval(ftepp));
1387 }
1388
1389 static void ftepp_directive_message(ftepp_t *ftepp) {
1390     char *message = nullptr;
1391
1392     if (!ftepp_skipspace(ftepp))
1393         return;
1394
1395     /* handle the odd non string constant case so it works like C */
1396     if (ftepp->token != TOKEN_STRINGCONST) {
1397         vec_append(message, 8, "#message");
1398         ftepp_next(ftepp);
1399         while (ftepp->token != TOKEN_EOL) {
1400             vec_append(message, strlen(ftepp_tokval(ftepp)), ftepp_tokval(ftepp));
1401             ftepp_next(ftepp);
1402         }
1403         vec_push(message, '\0');
1404         if (ftepp->output_on)
1405             con_cprintmsg(ftepp->lex->tok.ctx, LVL_MSG, "message", message);
1406         vec_free(message);
1407         return;
1408     }
1409
1410     if (!ftepp->output_on)
1411         return;
1412
1413     unescape     (ftepp_tokval(ftepp), ftepp_tokval(ftepp));
1414     con_cprintmsg(ftepp->lex->tok.ctx, LVL_MSG, "message",  ftepp_tokval(ftepp));
1415 }
1416
1417 /**
1418  * Include a file.
1419  * FIXME: do we need/want a -I option?
1420  * FIXME: what about when dealing with files in subdirectories coming from a progs.src?
1421  */
1422 static bool ftepp_include(ftepp_t *ftepp)
1423 {
1424     lex_file *old_lexer = ftepp->lex;
1425     lex_file *inlex;
1426     lex_ctx_t ctx;
1427     char     lineno[128];
1428     char     *filename;
1429     char     *parsename = nullptr;
1430     char     *old_includename;
1431
1432     (void)ftepp_next(ftepp);
1433     if (!ftepp_skipspace(ftepp))
1434         return false;
1435
1436     if (ftepp->token != TOKEN_STRINGCONST) {
1437         ppmacro *macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
1438         if (macro) {
1439             char *backup = ftepp->output_string;
1440             ftepp->output_string = nullptr;
1441             if (ftepp_macro_expand(ftepp, macro, nullptr, true)) {
1442                 parsename = util_strdup(ftepp->output_string);
1443                 vec_free(ftepp->output_string);
1444                 ftepp->output_string = backup;
1445             } else {
1446                 ftepp->output_string = backup;
1447                 ftepp_error(ftepp, "expected filename to include");
1448                 return false;
1449             }
1450         } else if (OPTS_FLAG(FTEPP_PREDEFS)) {
1451             /* Well it could be a predefine like __LINE__ */
1452             char *(*predef)(ftepp_t*) = ftepp_predef(ftepp_tokval(ftepp));
1453             if (predef) {
1454                 parsename = predef(ftepp);
1455             } else {
1456                 ftepp_error(ftepp, "expected filename to include");
1457                 return false;
1458             }
1459         }
1460     }
1461
1462     if (!ftepp->output_on) {
1463         (void)ftepp_next(ftepp);
1464         return true;
1465     }
1466
1467     if (parsename)
1468         unescape(parsename, parsename);
1469     else {
1470         char *tokval = ftepp_tokval(ftepp);
1471         unescape(tokval, tokval);
1472         parsename = util_strdup(tokval);
1473     }
1474
1475     ctx = ftepp_ctx(ftepp);
1476     ftepp_out(ftepp, "\n#pragma file(", false);
1477     ftepp_out(ftepp, parsename, false);
1478     ftepp_out(ftepp, ")\n#pragma line(1)\n", false);
1479
1480     filename = ftepp_include_find(ftepp, parsename);
1481     if (!filename) {
1482         ftepp_error(ftepp, "failed to open include file `%s`", parsename);
1483         mem_d(parsename);
1484         return false;
1485     }
1486     mem_d(parsename);
1487     inlex = lex_open(filename);
1488     if (!inlex) {
1489         ftepp_error(ftepp, "open failed on include file `%s`", filename);
1490         vec_free(filename);
1491         return false;
1492     }
1493     ftepp->lex = inlex;
1494     old_includename = ftepp->includename;
1495     ftepp->includename = filename;
1496     if (!ftepp_preprocess(ftepp)) {
1497         vec_free(ftepp->includename);
1498         ftepp->includename = old_includename;
1499         lex_close(ftepp->lex);
1500         ftepp->lex = old_lexer;
1501         return false;
1502     }
1503     vec_free(ftepp->includename);
1504     ftepp->includename = old_includename;
1505     lex_close(ftepp->lex);
1506     ftepp->lex = old_lexer;
1507
1508     ftepp_out(ftepp, "\n#pragma file(", false);
1509     ftepp_out(ftepp, ctx.file, false);
1510     util_snprintf(lineno, sizeof(lineno), ")\n#pragma line(%lu)\n", (unsigned long)(ctx.line+1));
1511     ftepp_out(ftepp, lineno, false);
1512
1513     /* skip the line */
1514     (void)ftepp_next(ftepp);
1515     if (!ftepp_skipspace(ftepp))
1516         return false;
1517     if (ftepp->token != TOKEN_EOL) {
1518         ftepp_error(ftepp, "stray tokens after #include");
1519         return false;
1520     }
1521     (void)ftepp_next(ftepp);
1522
1523     return true;
1524 }
1525
1526 /* Basic structure handlers */
1527 static bool ftepp_else_allowed(ftepp_t *ftepp)
1528 {
1529     if (!vec_size(ftepp->conditions)) {
1530         ftepp_error(ftepp, "#else without #if");
1531         return false;
1532     }
1533     if (vec_last(ftepp->conditions).had_else) {
1534         ftepp_error(ftepp, "multiple #else for a single #if");
1535         return false;
1536     }
1537     return true;
1538 }
1539
1540 static GMQCC_INLINE void ftepp_inmacro(ftepp_t *ftepp, const char *hash) {
1541     if (ftepp->in_macro)
1542         (void)!ftepp_warn(ftepp, WARN_DIRECTIVE_INMACRO, "`#%s` directive in macro", hash);
1543 }
1544
1545 static bool ftepp_hash(ftepp_t *ftepp)
1546 {
1547     ppcondition cond;
1548     ppcondition *pc;
1549
1550     lex_ctx_t ctx = ftepp_ctx(ftepp);
1551
1552     if (!ftepp_skipspace(ftepp))
1553         return false;
1554
1555     switch (ftepp->token) {
1556         case TOKEN_KEYWORD:
1557         case TOKEN_IDENT:
1558         case TOKEN_TYPENAME:
1559             if (!strcmp(ftepp_tokval(ftepp), "define")) {
1560                 ftepp_inmacro(ftepp, "define");
1561                 return ftepp_define(ftepp);
1562             }
1563             else if (!strcmp(ftepp_tokval(ftepp), "undef")) {
1564                 ftepp_inmacro(ftepp, "undef");
1565                 return ftepp_undef(ftepp);
1566             }
1567             else if (!strcmp(ftepp_tokval(ftepp), "ifdef")) {
1568                 ftepp_inmacro(ftepp, "ifdef");
1569                 if (!ftepp_ifdef(ftepp, &cond))
1570                     return false;
1571                 cond.was_on = cond.on;
1572                 vec_push(ftepp->conditions, cond);
1573                 ftepp->output_on = ftepp->output_on && cond.on;
1574                 break;
1575             }
1576             else if (!strcmp(ftepp_tokval(ftepp), "ifndef")) {
1577                 ftepp_inmacro(ftepp, "ifndef");
1578                 if (!ftepp_ifdef(ftepp, &cond))
1579                     return false;
1580                 cond.on = !cond.on;
1581                 cond.was_on = cond.on;
1582                 vec_push(ftepp->conditions, cond);
1583                 ftepp->output_on = ftepp->output_on && cond.on;
1584                 break;
1585             }
1586             else if (!strcmp(ftepp_tokval(ftepp), "elifdef")) {
1587                 ftepp_inmacro(ftepp, "elifdef");
1588                 if (!ftepp_else_allowed(ftepp))
1589                     return false;
1590                 if (!ftepp_ifdef(ftepp, &cond))
1591                     return false;
1592                 pc = &vec_last(ftepp->conditions);
1593                 pc->on     = !pc->was_on && cond.on;
1594                 pc->was_on = pc->was_on || pc->on;
1595                 ftepp_update_output_condition(ftepp);
1596                 break;
1597             }
1598             else if (!strcmp(ftepp_tokval(ftepp), "elifndef")) {
1599                 ftepp_inmacro(ftepp, "elifndef");
1600                 if (!ftepp_else_allowed(ftepp))
1601                     return false;
1602                 if (!ftepp_ifdef(ftepp, &cond))
1603                     return false;
1604                 cond.on = !cond.on;
1605                 pc = &vec_last(ftepp->conditions);
1606                 pc->on     = !pc->was_on && cond.on;
1607                 pc->was_on = pc->was_on || pc->on;
1608                 ftepp_update_output_condition(ftepp);
1609                 break;
1610             }
1611             else if (!strcmp(ftepp_tokval(ftepp), "elif")) {
1612                 ftepp_inmacro(ftepp, "elif");
1613                 if (!ftepp_else_allowed(ftepp))
1614                     return false;
1615                 if (!ftepp_if(ftepp, &cond))
1616                     return false;
1617                 pc = &vec_last(ftepp->conditions);
1618                 pc->on     = !pc->was_on && cond.on;
1619                 pc->was_on = pc->was_on  || pc->on;
1620                 ftepp_update_output_condition(ftepp);
1621                 break;
1622             }
1623             else if (!strcmp(ftepp_tokval(ftepp), "if")) {
1624                 ftepp_inmacro(ftepp, "if");
1625                 if (!ftepp_if(ftepp, &cond))
1626                     return false;
1627                 cond.was_on = cond.on;
1628                 vec_push(ftepp->conditions, cond);
1629                 ftepp->output_on = ftepp->output_on && cond.on;
1630                 break;
1631             }
1632             else if (!strcmp(ftepp_tokval(ftepp), "else")) {
1633                 ftepp_inmacro(ftepp, "else");
1634                 if (!ftepp_else_allowed(ftepp))
1635                     return false;
1636                 pc = &vec_last(ftepp->conditions);
1637                 pc->on = !pc->was_on;
1638                 pc->had_else = true;
1639                 ftepp_next(ftepp);
1640                 ftepp_update_output_condition(ftepp);
1641                 break;
1642             }
1643             else if (!strcmp(ftepp_tokval(ftepp), "endif")) {
1644                 ftepp_inmacro(ftepp, "endif");
1645                 if (!vec_size(ftepp->conditions)) {
1646                     ftepp_error(ftepp, "#endif without #if");
1647                     return false;
1648                 }
1649                 vec_pop(ftepp->conditions);
1650                 ftepp_next(ftepp);
1651                 ftepp_update_output_condition(ftepp);
1652                 break;
1653             }
1654             else if (!strcmp(ftepp_tokval(ftepp), "include")) {
1655                 ftepp_inmacro(ftepp, "include");
1656                 return ftepp_include(ftepp);
1657             }
1658             else if (!strcmp(ftepp_tokval(ftepp), "pragma")) {
1659                 ftepp_out(ftepp, "#", false);
1660                 break;
1661             }
1662             else if (!strcmp(ftepp_tokval(ftepp), "warning")) {
1663                 ftepp_directive_warning(ftepp);
1664                 break;
1665             }
1666             else if (!strcmp(ftepp_tokval(ftepp), "error")) {
1667                 ftepp_directive_error(ftepp);
1668                 break;
1669             }
1670             else if (!strcmp(ftepp_tokval(ftepp), "message")) {
1671                 ftepp_directive_message(ftepp);
1672                 break;
1673             }
1674             else {
1675                 if (ftepp->output_on) {
1676                     ftepp_error(ftepp, "unrecognized preprocessor directive: `%s`", ftepp_tokval(ftepp));
1677                     return false;
1678                 } else {
1679                     ftepp_next(ftepp);
1680                     break;
1681                 }
1682             }
1683             /* break; never reached */
1684         default:
1685             ftepp_error(ftepp, "unexpected preprocessor token: `%s`", ftepp_tokval(ftepp));
1686             return false;
1687         case TOKEN_EOL:
1688             ftepp_errorat(ftepp, ctx, "empty preprocessor directive");
1689             return false;
1690         case TOKEN_EOF:
1691             ftepp_error(ftepp, "missing newline at end of file", ftepp_tokval(ftepp));
1692             return false;
1693
1694         /* Builtins! Don't forget the builtins! */
1695         case TOKEN_INTCONST:
1696         case TOKEN_FLOATCONST:
1697             ftepp_out(ftepp, "#", false);
1698             return true;
1699     }
1700     if (!ftepp_skipspace(ftepp))
1701         return false;
1702     return true;
1703 }
1704
1705 static bool ftepp_preprocess(ftepp_t *ftepp)
1706 {
1707     ppmacro *macro;
1708     bool     newline = true;
1709
1710     /* predef stuff */
1711     char    *expand  = nullptr;
1712
1713     ftepp->lex->flags.preprocessing = true;
1714     ftepp->lex->flags.mergelines    = false;
1715     ftepp->lex->flags.noops         = true;
1716
1717     ftepp_next(ftepp);
1718     do
1719     {
1720         if (ftepp->token >= TOKEN_EOF)
1721             break;
1722         switch (ftepp->token) {
1723             case TOKEN_KEYWORD:
1724             case TOKEN_IDENT:
1725             case TOKEN_TYPENAME:
1726                 /* is it a predef? */
1727                 if (OPTS_FLAG(FTEPP_PREDEFS)) {
1728                     char *(*predef)(ftepp_t*) = ftepp_predef(ftepp_tokval(ftepp));
1729                     if (predef) {
1730                         expand = predef(ftepp);
1731                         ftepp_out (ftepp, expand, false);
1732                         ftepp_next(ftepp);
1733
1734                         mem_d(expand);
1735                         break;
1736                     }
1737                 }
1738
1739                 if (ftepp->output_on)
1740                     macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
1741                 else
1742                     macro = nullptr;
1743
1744                 if (!macro) {
1745                     ftepp_out(ftepp, ftepp_tokval(ftepp), false);
1746                     ftepp_next(ftepp);
1747                     break;
1748                 }
1749                 if (!ftepp_macro_call(ftepp, macro))
1750                     ftepp->token = TOKEN_ERROR;
1751                 break;
1752             case '#':
1753                 if (!newline) {
1754                     ftepp_out(ftepp, ftepp_tokval(ftepp), false);
1755                     ftepp_next(ftepp);
1756                     break;
1757                 }
1758                 ftepp->lex->flags.mergelines = true;
1759                 if (ftepp_next(ftepp) >= TOKEN_EOF) {
1760                     ftepp_error(ftepp, "error in preprocessor directive");
1761                     ftepp->token = TOKEN_ERROR;
1762                     break;
1763                 }
1764                 if (!ftepp_hash(ftepp))
1765                     ftepp->token = TOKEN_ERROR;
1766                 ftepp->lex->flags.mergelines = false;
1767                 break;
1768             case TOKEN_EOL:
1769                 newline = true;
1770                 ftepp_out(ftepp, "\n", true);
1771                 ftepp_next(ftepp);
1772                 break;
1773             case TOKEN_WHITE:
1774                 /* same as default but don't set newline=false */
1775                 ftepp_out(ftepp, ftepp_tokval(ftepp), true);
1776                 ftepp_next(ftepp);
1777                 break;
1778             default:
1779                 newline = false;
1780                 ftepp_out(ftepp, ftepp_tokval(ftepp), false);
1781                 ftepp_next(ftepp);
1782                 break;
1783         }
1784     } while (!ftepp->errors && ftepp->token < TOKEN_EOF);
1785
1786     /* force a 0 at the end but don't count it as added to the output */
1787     vec_push(ftepp->output_string, 0);
1788     vec_shrinkby(ftepp->output_string, 1);
1789
1790     return (ftepp->token == TOKEN_EOF);
1791 }
1792
1793 /* Like in parser.c - files keep the previous state so we have one global
1794  * preprocessor. Except here we will want to warn about dangling #ifs.
1795  */
1796 static bool ftepp_preprocess_done(ftepp_t *ftepp)
1797 {
1798     bool retval = true;
1799     if (vec_size(ftepp->conditions)) {
1800         if (ftepp_warn(ftepp, WARN_MULTIFILE_IF, "#if spanning multiple files, is this intended?"))
1801             retval = false;
1802     }
1803     lex_close(ftepp->lex);
1804     ftepp->lex = nullptr;
1805     if (ftepp->itemname) {
1806         mem_d(ftepp->itemname);
1807         ftepp->itemname = nullptr;
1808     }
1809     return retval;
1810 }
1811
1812 bool ftepp_preprocess_file(ftepp_t *ftepp, const char *filename)
1813 {
1814     ftepp->lex = lex_open(filename);
1815     ftepp->itemname = util_strdup(filename);
1816     if (!ftepp->lex) {
1817         con_out("failed to open file \"%s\"\n", filename);
1818         return false;
1819     }
1820     if (!ftepp_preprocess(ftepp))
1821         return false;
1822     return ftepp_preprocess_done(ftepp);
1823 }
1824
1825 bool ftepp_preprocess_string(ftepp_t *ftepp, const char *name, const char *str)
1826 {
1827     ftepp->lex = lex_open_string(str, strlen(str), name);
1828     ftepp->itemname = util_strdup(name);
1829     if (!ftepp->lex) {
1830         con_out("failed to create lexer for string \"%s\"\n", name);
1831         return false;
1832     }
1833     if (!ftepp_preprocess(ftepp))
1834         return false;
1835     return ftepp_preprocess_done(ftepp);
1836 }
1837
1838
1839 void ftepp_add_macro(ftepp_t *ftepp, const char *name, const char *value) {
1840     char *create = nullptr;
1841
1842     /* use saner path for empty macros */
1843     if (!value) {
1844         ftepp_add_define(ftepp, "__builtin__", name);
1845         return;
1846     }
1847
1848     vec_append(create, 8,           "#define ");
1849     vec_append(create, strlen(name), name);
1850     vec_push  (create, ' ');
1851     vec_append(create, strlen(value), value);
1852     vec_push  (create, 0);
1853
1854     ftepp_preprocess_string(ftepp, "__builtin__", create);
1855     vec_free  (create);
1856 }
1857
1858 ftepp_t *ftepp_create()
1859 {
1860     ftepp_t *ftepp;
1861     char minor[32];
1862     char major[32];
1863     size_t i;
1864
1865     ftepp = ftepp_new();
1866     if (!ftepp)
1867         return nullptr;
1868
1869     memset(minor, 0, sizeof(minor));
1870     memset(major, 0, sizeof(major));
1871
1872     /* set the right macro based on the selected standard */
1873     ftepp_add_define(ftepp, nullptr, "GMQCC");
1874     if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_FTEQCC) {
1875         ftepp_add_define(ftepp, nullptr, "__STD_FTEQCC__");
1876         /* 1.00 */
1877         major[0] = '"';
1878         major[1] = '1';
1879         major[2] = '"';
1880
1881         minor[0] = '"';
1882         minor[1] = '0';
1883         minor[2] = '"';
1884     } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_GMQCC) {
1885         ftepp_add_define(ftepp, nullptr, "__STD_GMQCC__");
1886         util_snprintf(major, 32, "\"%d\"", GMQCC_VERSION_MAJOR);
1887         util_snprintf(minor, 32, "\"%d\"", GMQCC_VERSION_MINOR);
1888     } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCCX) {
1889         ftepp_add_define(ftepp, nullptr, "__STD_QCCX__");
1890         util_snprintf(major, 32, "\"%d\"", GMQCC_VERSION_MAJOR);
1891         util_snprintf(minor, 32, "\"%d\"", GMQCC_VERSION_MINOR);
1892     } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) {
1893         ftepp_add_define(ftepp, nullptr, "__STD_QCC__");
1894         /* 1.0 */
1895         major[0] = '"';
1896         major[1] = '1';
1897         major[2] = '"';
1898
1899         minor[0] = '"';
1900         minor[1] = '0';
1901         minor[2] = '"';
1902     }
1903
1904     ftepp_add_macro(ftepp, "__STD_VERSION_MINOR__", minor);
1905     ftepp_add_macro(ftepp, "__STD_VERSION_MAJOR__", major);
1906
1907     /*
1908      * We're going to just make __NULL__ nil, which works for 60% of the
1909      * cases of __NULL_ for fteqcc.
1910      */
1911     ftepp_add_macro(ftepp, "__NULL__", "nil");
1912
1913     /* add all the math constants if they can be */
1914     if (OPTS_FLAG(FTEPP_MATHDEFS)) {
1915         for (i = 0; i < GMQCC_ARRAY_COUNT(ftepp_math_constants); i++)
1916             if (!ftepp_macro_find(ftepp, ftepp_math_constants[i][0]))
1917                 ftepp_add_macro(ftepp, ftepp_math_constants[i][0], ftepp_math_constants[i][1]);
1918     }
1919
1920     return ftepp;
1921 }
1922
1923 void ftepp_add_define(ftepp_t *ftepp, const char *source, const char *name)
1924 {
1925     ppmacro *macro;
1926     lex_ctx_t ctx = { "__builtin__", 0, 0 };
1927     ctx.file = source;
1928     macro = ppmacro_new(ctx, name);
1929     util_htset(ftepp->macros, name, macro);
1930 }
1931
1932 const char *ftepp_get(ftepp_t *ftepp)
1933 {
1934     return ftepp->output_string;
1935 }
1936
1937 void ftepp_flush(ftepp_t *ftepp)
1938 {
1939     ftepp_flush_do(ftepp);
1940 }
1941
1942 void ftepp_finish(ftepp_t *ftepp)
1943 {
1944     if (!ftepp)
1945         return;
1946     ftepp_delete(ftepp);
1947 }