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