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