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