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