]> git.xonotic.org Git - xonotic/gmqcc.git/blob - ftepp.c
Happy new years!
[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     ftepp->lex = inlex;
651     ftepp_recursion_header(ftepp);
652     if (!ftepp_preprocess(ftepp)) {
653         lex_close(ftepp->lex);
654         retval = false;
655         goto cleanup;
656     }
657     ftepp_recursion_footer(ftepp);
658     old_string = ftepp->output_string;
659
660 cleanup:
661     ftepp->lex           = old_lexer;
662     ftepp->output_string = old_string;
663     return retval;
664 }
665
666 static bool ftepp_macro_call(ftepp_t *ftepp, ppmacro *macro)
667 {
668     size_t     o;
669     macroparam *params = NULL;
670     bool        retval = true;
671
672     if (!macro->has_params) {
673         if (!ftepp_macro_expand(ftepp, macro, NULL))
674             return false;
675         ftepp_next(ftepp);
676         return true;
677     }
678     ftepp_next(ftepp);
679
680     if (!ftepp_skipallwhite(ftepp))
681         return false;
682
683     if (ftepp->token != '(') {
684         ftepp_error(ftepp, "expected macro parameters in parenthesis");
685         return false;
686     }
687
688     ftepp_next(ftepp);
689     if (!ftepp_macro_call_params(ftepp, &params))
690         return false;
691
692     if (vec_size(params) != vec_size(macro->params)) {
693         ftepp_error(ftepp, "macro %s expects %u paramteters, %u provided", macro->name,
694                     (unsigned int)vec_size(macro->params),
695                     (unsigned int)vec_size(params));
696         retval = false;
697         goto cleanup;
698     }
699
700     if (!ftepp_macro_expand(ftepp, macro, params))
701         retval = false;
702     ftepp_next(ftepp);
703
704 cleanup:
705     for (o = 0; o < vec_size(params); ++o)
706         macroparam_clean(&params[o]);
707     vec_free(params);
708     return retval;
709 }
710
711 /**
712  * #if - the FTEQCC way:
713  *    defined(FOO) => true if FOO was #defined regardless of parameters or contents
714  *    <numbers>    => True if the number is not 0
715  *    !<factor>    => True if the factor yields false
716  *    !!<factor>   => ERROR on 2 or more unary nots
717  *    <macro>      => becomes the macro's FIRST token regardless of parameters
718  *    <e> && <e>   => True if both expressions are true
719  *    <e> || <e>   => True if either expression is true
720  *    <string>     => False
721  *    <ident>      => False (remember for macros the <macro> rule applies instead)
722  * Unary + and - are weird and wrong in fteqcc so we don't allow them
723  * parenthesis in expressions are allowed
724  * parameter lists on macros are errors
725  * No mathematical calculations are executed
726  */
727 static bool ftepp_if_expr(ftepp_t *ftepp, bool *out, double *value_out);
728 static bool ftepp_if_op(ftepp_t *ftepp)
729 {
730     ftepp->lex->flags.noops = false;
731     ftepp_next(ftepp);
732     if (!ftepp_skipspace(ftepp))
733         return false;
734     ftepp->lex->flags.noops = true;
735     return true;
736 }
737 static bool ftepp_if_value(ftepp_t *ftepp, bool *out, double *value_out)
738 {
739     ppmacro *macro;
740     bool     wasnot = false;
741
742     if (!ftepp_skipspace(ftepp))
743         return false;
744
745     while (ftepp->token == '!') {
746         wasnot = true;
747         ftepp_next(ftepp);
748         if (!ftepp_skipspace(ftepp))
749             return false;
750     }
751
752     switch (ftepp->token) {
753         case TOKEN_IDENT:
754         case TOKEN_TYPENAME:
755         case TOKEN_KEYWORD:
756             if (!strcmp(ftepp_tokval(ftepp), "defined")) {
757                 ftepp_next(ftepp);
758                 if (!ftepp_skipspace(ftepp))
759                     return false;
760                 if (ftepp->token != '(') {
761                     ftepp_error(ftepp, "`defined` keyword in #if requires a macro name in parenthesis");
762                     return false;
763                 }
764                 ftepp_next(ftepp);
765                 if (!ftepp_skipspace(ftepp))
766                     return false;
767                 if (ftepp->token != TOKEN_IDENT &&
768                     ftepp->token != TOKEN_TYPENAME &&
769                     ftepp->token != TOKEN_KEYWORD)
770                 {
771                     ftepp_error(ftepp, "defined() used on an unexpected token type");
772                     return false;
773                 }
774                 macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
775                 *out = !!macro;
776                 ftepp_next(ftepp);
777                 if (!ftepp_skipspace(ftepp))
778                     return false;
779                 if (ftepp->token != ')') {
780                     ftepp_error(ftepp, "expected closing paren");
781                     return false;
782                 }
783                 break;
784             }
785
786             macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
787             if (!macro || !vec_size(macro->output)) {
788                 *out = false;
789                 *value_out = 0;
790             } else {
791                 /* This does not expand recursively! */
792                 switch (macro->output[0]->token) {
793                     case TOKEN_INTCONST:
794                         *value_out = macro->output[0]->constval.i;
795                         *out = !!(macro->output[0]->constval.i);
796                         break;
797                     case TOKEN_FLOATCONST:
798                         *value_out = macro->output[0]->constval.f;
799                         *out = !!(macro->output[0]->constval.f);
800                         break;
801                     default:
802                         *out = false;
803                         break;
804                 }
805             }
806             break;
807         case TOKEN_STRINGCONST:
808             *out = false;
809             break;
810         case TOKEN_INTCONST:
811             *value_out = ftepp->lex->tok.constval.i;
812             *out = !!(ftepp->lex->tok.constval.i);
813             break;
814         case TOKEN_FLOATCONST:
815             *value_out = ftepp->lex->tok.constval.f;
816             *out = !!(ftepp->lex->tok.constval.f);
817             break;
818
819         case '(':
820             ftepp_next(ftepp);
821             if (!ftepp_if_expr(ftepp, out, value_out))
822                 return false;
823             if (ftepp->token != ')') {
824                 ftepp_error(ftepp, "expected closing paren in #if expression");
825                 return false;
826             }
827             break;
828
829         default:
830             ftepp_error(ftepp, "junk in #if: `%s` ...", ftepp_tokval(ftepp));
831             return false;
832     }
833     if (wasnot) {
834         *out = !*out;
835         *value_out = (*out ? 1 : 0);
836     }
837     return true;
838 }
839
840 /*
841 static bool ftepp_if_nextvalue(ftepp_t *ftepp, bool *out, double *value_out)
842 {
843     if (!ftepp_next(ftepp))
844         return false;
845     return ftepp_if_value(ftepp, out, value_out);
846 }
847 */
848
849 static bool ftepp_if_expr(ftepp_t *ftepp, bool *out, double *value_out)
850 {
851     if (!ftepp_if_value(ftepp, out, value_out))
852         return false;
853
854     if (!ftepp_if_op(ftepp))
855         return false;
856
857     if (ftepp->token == ')' || ftepp->token != TOKEN_OPERATOR)
858         return true;
859
860     /* FTEQCC is all right-associative and no precedence here */
861     if (!strcmp(ftepp_tokval(ftepp), "&&") ||
862         !strcmp(ftepp_tokval(ftepp), "||"))
863     {
864         bool next = false;
865         char opc  = ftepp_tokval(ftepp)[0];
866         double nextvalue;
867
868         (void)nextvalue;
869         if (!ftepp_next(ftepp))
870             return false;
871         if (!ftepp_if_expr(ftepp, &next, &nextvalue))
872             return false;
873
874         if (opc == '&')
875             *out = *out && next;
876         else
877             *out = *out || next;
878
879         *value_out = (*out ? 1 : 0);
880         return true;
881     }
882     else if (!strcmp(ftepp_tokval(ftepp), "==") ||
883              !strcmp(ftepp_tokval(ftepp), "!=") ||
884              !strcmp(ftepp_tokval(ftepp), ">=") ||
885              !strcmp(ftepp_tokval(ftepp), "<=") ||
886              !strcmp(ftepp_tokval(ftepp), ">") ||
887              !strcmp(ftepp_tokval(ftepp), "<"))
888     {
889         bool next = false;
890         const char opc0 = ftepp_tokval(ftepp)[0];
891         const char opc1 = ftepp_tokval(ftepp)[1];
892         double other;
893
894         if (!ftepp_next(ftepp))
895             return false;
896         if (!ftepp_if_expr(ftepp, &next, &other))
897             return false;
898
899         if (opc0 == '=')
900             *out = (*value_out == other);
901         else if (opc0 == '!')
902             *out = (*value_out != other);
903         else if (opc0 == '>') {
904             if (opc1 == '=') *out = (*value_out >= other);
905             else             *out = (*value_out > other);
906         }
907         else if (opc0 == '<') {
908             if (opc1 == '=') *out = (*value_out <= other);
909             else             *out = (*value_out < other);
910         }
911         *value_out = (*out ? 1 : 0);
912
913         return true;
914     }
915     else {
916         ftepp_error(ftepp, "junk after #if");
917         return false;
918     }
919 }
920
921 static bool ftepp_if(ftepp_t *ftepp, ppcondition *cond)
922 {
923     bool result = false;
924     double dummy = 0;
925
926     memset(cond, 0, sizeof(*cond));
927     (void)ftepp_next(ftepp);
928
929     if (!ftepp_skipspace(ftepp))
930         return false;
931     if (ftepp->token == TOKEN_EOL) {
932         ftepp_error(ftepp, "expected expression for #if-directive");
933         return false;
934     }
935
936     if (!ftepp_if_expr(ftepp, &result, &dummy))
937         return false;
938
939     cond->on = result;
940     return true;
941 }
942
943 /**
944  * ifdef is rather simple
945  */
946 static bool ftepp_ifdef(ftepp_t *ftepp, ppcondition *cond)
947 {
948     ppmacro *macro;
949     memset(cond, 0, sizeof(*cond));
950     (void)ftepp_next(ftepp);
951     if (!ftepp_skipspace(ftepp))
952         return false;
953
954     switch (ftepp->token) {
955         case TOKEN_IDENT:
956         case TOKEN_TYPENAME:
957         case TOKEN_KEYWORD:
958             macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
959             break;
960         default:
961             ftepp_error(ftepp, "expected macro name");
962             return false;
963     }
964
965     (void)ftepp_next(ftepp);
966     if (!ftepp_skipspace(ftepp))
967         return false;
968     /* relaxing this condition
969     if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) {
970         ftepp_error(ftepp, "stray tokens after #ifdef");
971         return false;
972     }
973     */
974     cond->on = !!macro;
975     return true;
976 }
977
978 /**
979  * undef is also simple
980  */
981 static bool ftepp_undef(ftepp_t *ftepp)
982 {
983     (void)ftepp_next(ftepp);
984     if (!ftepp_skipspace(ftepp))
985         return false;
986
987     if (ftepp->output_on) {
988         switch (ftepp->token) {
989             case TOKEN_IDENT:
990             case TOKEN_TYPENAME:
991             case TOKEN_KEYWORD:
992                 ftepp_macro_delete(ftepp, ftepp_tokval(ftepp));
993                 break;
994             default:
995                 ftepp_error(ftepp, "expected macro name");
996                 return false;
997         }
998     }
999
1000     (void)ftepp_next(ftepp);
1001     if (!ftepp_skipspace(ftepp))
1002         return false;
1003     /* relaxing this condition
1004     if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) {
1005         ftepp_error(ftepp, "stray tokens after #ifdef");
1006         return false;
1007     }
1008     */
1009     return true;
1010 }
1011
1012 /* Special unescape-string function which skips a leading quote
1013  * and stops at a quote, not just at \0
1014  */
1015 static void unescape(const char *str, char *out) {
1016     ++str;
1017     while (*str && *str != '"') {
1018         if (*str == '\\') {
1019             ++str;
1020             switch (*str) {
1021                 case '\\': *out++ = *str; break;
1022                 case '"':  *out++ = *str; break;
1023                 case 'a':  *out++ = '\a'; break;
1024                 case 'b':  *out++ = '\b'; break;
1025                 case 'r':  *out++ = '\r'; break;
1026                 case 'n':  *out++ = '\n'; break;
1027                 case 't':  *out++ = '\t'; break;
1028                 case 'f':  *out++ = '\f'; break;
1029                 case 'v':  *out++ = '\v'; break;
1030                 default:
1031                     *out++ = '\\';
1032                     *out++ = *str;
1033                     break;
1034             }
1035             ++str;
1036             continue;
1037         }
1038
1039         *out++ = *str++;
1040     }
1041     *out = 0;
1042 }
1043
1044 static char *ftepp_include_find_path(const char *file, const char *pathfile)
1045 {
1046     FILE       *fp;
1047     char       *filename = NULL;
1048     const char *last_slash;
1049     size_t      len;
1050
1051     if (!pathfile)
1052         return NULL;
1053
1054     last_slash = strrchr(pathfile, '/');
1055
1056     if (last_slash) {
1057         len = last_slash - pathfile;
1058         memcpy(vec_add(filename, len), pathfile, len);
1059         vec_push(filename, '/');
1060     }
1061
1062     len = strlen(file);
1063     memcpy(vec_add(filename, len+1), file, len);
1064     vec_last(filename) = 0;
1065
1066     fp = file_open(filename, "rb");
1067     if (fp) {
1068         file_close(fp);
1069         return filename;
1070     }
1071     vec_free(filename);
1072     return NULL;
1073 }
1074
1075 static char *ftepp_include_find(ftepp_t *ftepp, const char *file)
1076 {
1077     char *filename = NULL;
1078
1079     filename = ftepp_include_find_path(file, ftepp->includename);
1080     if (!filename)
1081         filename = ftepp_include_find_path(file, ftepp->itemname);
1082     return filename;
1083 }
1084
1085 static bool ftepp_directive_warning(ftepp_t *ftepp) {
1086     char *message = NULL;
1087
1088     if (!ftepp_skipspace(ftepp))
1089         return false;
1090
1091     /* handle the odd non string constant case so it works like C */
1092     if (ftepp->token != TOKEN_STRINGCONST) {
1093         bool  store   = false;
1094         vec_upload(message, "#warning", 8);
1095         ftepp_next(ftepp);
1096         while (ftepp->token != TOKEN_EOL) {
1097             vec_upload(message, ftepp_tokval(ftepp), strlen(ftepp_tokval(ftepp)));
1098             ftepp_next(ftepp);
1099         }
1100         vec_push(message, '\0');
1101         store = ftepp_warn(ftepp, WARN_CPP, message);
1102         vec_free(message);
1103         return store;
1104     }
1105
1106     unescape  (ftepp_tokval(ftepp), ftepp_tokval(ftepp));
1107     return ftepp_warn(ftepp, WARN_CPP, "#warning %s", ftepp_tokval(ftepp));
1108 }
1109
1110 static void ftepp_directive_error(ftepp_t *ftepp) {
1111     char *message = NULL;
1112
1113     if (!ftepp_skipspace(ftepp))
1114         return;
1115
1116     /* handle the odd non string constant case so it works like C */
1117     if (ftepp->token != TOKEN_STRINGCONST) {
1118         vec_upload(message, "#error", 6);
1119         ftepp_next(ftepp);
1120         while (ftepp->token != TOKEN_EOL) {
1121             vec_upload(message, ftepp_tokval(ftepp), strlen(ftepp_tokval(ftepp)));
1122             ftepp_next(ftepp);
1123         }
1124         vec_push(message, '\0');
1125         ftepp_error(ftepp, message);
1126         vec_free(message);
1127         return;
1128     }
1129
1130     unescape  (ftepp_tokval(ftepp), ftepp_tokval(ftepp));
1131     ftepp_error(ftepp, "#error %s", ftepp_tokval(ftepp));
1132 }
1133
1134 /**
1135  * Include a file.
1136  * FIXME: do we need/want a -I option?
1137  * FIXME: what about when dealing with files in subdirectories coming from a progs.src?
1138  */
1139 static bool ftepp_include(ftepp_t *ftepp)
1140 {
1141     lex_file *old_lexer = ftepp->lex;
1142     lex_file *inlex;
1143     lex_ctx  ctx;
1144     char     lineno[128];
1145     char     *filename;
1146     char     *old_includename;
1147
1148     (void)ftepp_next(ftepp);
1149     if (!ftepp_skipspace(ftepp))
1150         return false;
1151
1152     if (ftepp->token != TOKEN_STRINGCONST) {
1153         ftepp_error(ftepp, "expected filename to include");
1154         return false;
1155     }
1156
1157     ctx = ftepp_ctx(ftepp);
1158
1159     unescape(ftepp_tokval(ftepp), ftepp_tokval(ftepp));
1160
1161     ftepp_out(ftepp, "\n#pragma file(", false);
1162     ftepp_out(ftepp, ftepp_tokval(ftepp), false);
1163     ftepp_out(ftepp, ")\n#pragma line(1)\n", false);
1164
1165     filename = ftepp_include_find(ftepp, ftepp_tokval(ftepp));
1166     if (!filename) {
1167         ftepp_error(ftepp, "failed to open include file `%s`", ftepp_tokval(ftepp));
1168         return false;
1169     }
1170     inlex = lex_open(filename);
1171     if (!inlex) {
1172         ftepp_error(ftepp, "open failed on include file `%s`", filename);
1173         vec_free(filename);
1174         return false;
1175     }
1176     ftepp->lex = inlex;
1177     old_includename = ftepp->includename;
1178     ftepp->includename = filename;
1179     if (!ftepp_preprocess(ftepp)) {
1180         vec_free(ftepp->includename);
1181         ftepp->includename = old_includename;
1182         lex_close(ftepp->lex);
1183         ftepp->lex = old_lexer;
1184         return false;
1185     }
1186     vec_free(ftepp->includename);
1187     ftepp->includename = old_includename;
1188     lex_close(ftepp->lex);
1189     ftepp->lex = old_lexer;
1190
1191     ftepp_out(ftepp, "\n#pragma file(", false);
1192     ftepp_out(ftepp, ctx.file, false);
1193     snprintf(lineno, sizeof(lineno), ")\n#pragma line(%lu)\n", (unsigned long)(ctx.line+1));
1194     ftepp_out(ftepp, lineno, false);
1195
1196     /* skip the line */
1197     (void)ftepp_next(ftepp);
1198     if (!ftepp_skipspace(ftepp))
1199         return false;
1200     if (ftepp->token != TOKEN_EOL) {
1201         ftepp_error(ftepp, "stray tokens after #include");
1202         return false;
1203     }
1204     (void)ftepp_next(ftepp);
1205
1206     return true;
1207 }
1208
1209 /* Basic structure handlers */
1210 static bool ftepp_else_allowed(ftepp_t *ftepp)
1211 {
1212     if (!vec_size(ftepp->conditions)) {
1213         ftepp_error(ftepp, "#else without #if");
1214         return false;
1215     }
1216     if (vec_last(ftepp->conditions).had_else) {
1217         ftepp_error(ftepp, "multiple #else for a single #if");
1218         return false;
1219     }
1220     return true;
1221 }
1222
1223 static bool ftepp_hash(ftepp_t *ftepp)
1224 {
1225     ppcondition cond;
1226     ppcondition *pc;
1227
1228     lex_ctx ctx = ftepp_ctx(ftepp);
1229
1230     if (!ftepp_skipspace(ftepp))
1231         return false;
1232
1233     switch (ftepp->token) {
1234         case TOKEN_KEYWORD:
1235         case TOKEN_IDENT:
1236         case TOKEN_TYPENAME:
1237             if (!strcmp(ftepp_tokval(ftepp), "define")) {
1238                 return ftepp_define(ftepp);
1239             }
1240             else if (!strcmp(ftepp_tokval(ftepp), "undef")) {
1241                 return ftepp_undef(ftepp);
1242             }
1243             else if (!strcmp(ftepp_tokval(ftepp), "ifdef")) {
1244                 if (!ftepp_ifdef(ftepp, &cond))
1245                     return false;
1246                 cond.was_on = cond.on;
1247                 vec_push(ftepp->conditions, cond);
1248                 ftepp->output_on = ftepp->output_on && cond.on;
1249                 break;
1250             }
1251             else if (!strcmp(ftepp_tokval(ftepp), "ifndef")) {
1252                 if (!ftepp_ifdef(ftepp, &cond))
1253                     return false;
1254                 cond.on = !cond.on;
1255                 cond.was_on = cond.on;
1256                 vec_push(ftepp->conditions, cond);
1257                 ftepp->output_on = ftepp->output_on && cond.on;
1258                 break;
1259             }
1260             else if (!strcmp(ftepp_tokval(ftepp), "elifdef")) {
1261                 if (!ftepp_else_allowed(ftepp))
1262                     return false;
1263                 if (!ftepp_ifdef(ftepp, &cond))
1264                     return false;
1265                 pc = &vec_last(ftepp->conditions);
1266                 pc->on     = !pc->was_on && cond.on;
1267                 pc->was_on = pc->was_on || pc->on;
1268                 ftepp_update_output_condition(ftepp);
1269                 break;
1270             }
1271             else if (!strcmp(ftepp_tokval(ftepp), "elifndef")) {
1272                 if (!ftepp_else_allowed(ftepp))
1273                     return false;
1274                 if (!ftepp_ifdef(ftepp, &cond))
1275                     return false;
1276                 cond.on = !cond.on;
1277                 pc = &vec_last(ftepp->conditions);
1278                 pc->on     = !pc->was_on && cond.on;
1279                 pc->was_on = pc->was_on || pc->on;
1280                 ftepp_update_output_condition(ftepp);
1281                 break;
1282             }
1283             else if (!strcmp(ftepp_tokval(ftepp), "elif")) {
1284                 if (!ftepp_else_allowed(ftepp))
1285                     return false;
1286                 if (!ftepp_if(ftepp, &cond))
1287                     return false;
1288                 pc = &vec_last(ftepp->conditions);
1289                 pc->on     = !pc->was_on && cond.on;
1290                 pc->was_on = pc->was_on  || pc->on;
1291                 ftepp_update_output_condition(ftepp);
1292                 break;
1293             }
1294             else if (!strcmp(ftepp_tokval(ftepp), "if")) {
1295                 if (!ftepp_if(ftepp, &cond))
1296                     return false;
1297                 cond.was_on = cond.on;
1298                 vec_push(ftepp->conditions, cond);
1299                 ftepp->output_on = ftepp->output_on && cond.on;
1300                 break;
1301             }
1302             else if (!strcmp(ftepp_tokval(ftepp), "else")) {
1303                 if (!ftepp_else_allowed(ftepp))
1304                     return false;
1305                 pc = &vec_last(ftepp->conditions);
1306                 pc->on = !pc->was_on;
1307                 pc->had_else = true;
1308                 ftepp_next(ftepp);
1309                 ftepp_update_output_condition(ftepp);
1310                 break;
1311             }
1312             else if (!strcmp(ftepp_tokval(ftepp), "endif")) {
1313                 if (!vec_size(ftepp->conditions)) {
1314                     ftepp_error(ftepp, "#endif without #if");
1315                     return false;
1316                 }
1317                 vec_pop(ftepp->conditions);
1318                 ftepp_next(ftepp);
1319                 ftepp_update_output_condition(ftepp);
1320                 break;
1321             }
1322             else if (!strcmp(ftepp_tokval(ftepp), "include")) {
1323                 return ftepp_include(ftepp);
1324             }
1325             else if (!strcmp(ftepp_tokval(ftepp), "pragma")) {
1326                 ftepp_out(ftepp, "#", false);
1327                 break;
1328             }
1329             else if (!strcmp(ftepp_tokval(ftepp), "warning")) {
1330                 ftepp_directive_warning(ftepp);
1331                 break;
1332             }
1333             else if (!strcmp(ftepp_tokval(ftepp), "error")) {
1334                 ftepp_directive_error(ftepp);
1335                 break;
1336             }
1337             else {
1338                 if (ftepp->output_on) {
1339                     ftepp_error(ftepp, "unrecognized preprocessor directive: `%s`", ftepp_tokval(ftepp));
1340                     return false;
1341                 } else {
1342                     ftepp_next(ftepp);
1343                     break;
1344                 }
1345             }
1346             /* break; never reached */
1347         default:
1348             ftepp_error(ftepp, "unexpected preprocessor token: `%s`", ftepp_tokval(ftepp));
1349             return false;
1350         case TOKEN_EOL:
1351             ftepp_errorat(ftepp, ctx, "empty preprocessor directive");
1352             return false;
1353         case TOKEN_EOF:
1354             ftepp_error(ftepp, "missing newline at end of file", ftepp_tokval(ftepp));
1355             return false;
1356
1357         /* Builtins! Don't forget the builtins! */
1358         case TOKEN_INTCONST:
1359         case TOKEN_FLOATCONST:
1360             ftepp_out(ftepp, "#", false);
1361             return true;
1362     }
1363     if (!ftepp_skipspace(ftepp))
1364         return false;
1365     return true;
1366 }
1367
1368 static bool ftepp_preprocess(ftepp_t *ftepp)
1369 {
1370     ppmacro *macro;
1371     bool     newline = true;
1372
1373     /* predef stuff */
1374     char    *expand  = NULL;
1375     size_t   i;
1376
1377     ftepp->lex->flags.preprocessing = true;
1378     ftepp->lex->flags.mergelines    = false;
1379     ftepp->lex->flags.noops         = true;
1380
1381     ftepp_next(ftepp);
1382     do
1383     {
1384         if (ftepp->token >= TOKEN_EOF)
1385             break;
1386 #if 0
1387         newline = true;
1388 #endif
1389
1390         switch (ftepp->token) {
1391             case TOKEN_KEYWORD:
1392             case TOKEN_IDENT:
1393             case TOKEN_TYPENAME:
1394                 /* is it a predef? */
1395                 if (OPTS_FLAG(FTEPP_PREDEFS)) {
1396                     for (i = 0; i < sizeof(ftepp_predefs) / sizeof (*ftepp_predefs); i++) {
1397                         if (!strcmp(ftepp_predefs[i].name, ftepp_tokval(ftepp))) {
1398                             expand = ftepp_predefs[i].func(ftepp->lex);
1399                             ftepp_out(ftepp, expand, false);
1400                             ftepp_next(ftepp); /* skip */
1401
1402                             mem_d(expand); /* free memory */
1403                             break;
1404                         }
1405                     }
1406                 }
1407
1408                 if (ftepp->output_on)
1409                     macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
1410                 else
1411                     macro = NULL;
1412
1413                 if (!macro) {
1414                     ftepp_out(ftepp, ftepp_tokval(ftepp), false);
1415                     ftepp_next(ftepp);
1416                     break;
1417                 }
1418                 if (!ftepp_macro_call(ftepp, macro))
1419                     ftepp->token = TOKEN_ERROR;
1420                 break;
1421             case '#':
1422                 if (!newline) {
1423                     ftepp_out(ftepp, ftepp_tokval(ftepp), false);
1424                     ftepp_next(ftepp);
1425                     break;
1426                 }
1427                 ftepp->lex->flags.mergelines = true;
1428                 if (ftepp_next(ftepp) >= TOKEN_EOF) {
1429                     ftepp_error(ftepp, "error in preprocessor directive");
1430                     ftepp->token = TOKEN_ERROR;
1431                     break;
1432                 }
1433                 if (!ftepp_hash(ftepp))
1434                     ftepp->token = TOKEN_ERROR;
1435                 ftepp->lex->flags.mergelines = false;
1436                 break;
1437             case TOKEN_EOL:
1438                 newline = true;
1439                 ftepp_out(ftepp, "\n", true);
1440                 ftepp_next(ftepp);
1441                 break;
1442             case TOKEN_WHITE:
1443                 /* same as default but don't set newline=false */
1444                 ftepp_out(ftepp, ftepp_tokval(ftepp), false);
1445                 ftepp_next(ftepp);
1446                 break;
1447             default:
1448                 newline = false;
1449                 ftepp_out(ftepp, ftepp_tokval(ftepp), false);
1450                 ftepp_next(ftepp);
1451                 break;
1452         }
1453     } while (!ftepp->errors && ftepp->token < TOKEN_EOF);
1454
1455     /* force a 0 at the end but don't count it as added to the output */
1456     vec_push(ftepp->output_string, 0);
1457     vec_shrinkby(ftepp->output_string, 1);
1458
1459     return (ftepp->token == TOKEN_EOF);
1460 }
1461
1462 /* Like in parser.c - files keep the previous state so we have one global
1463  * preprocessor. Except here we will want to warn about dangling #ifs.
1464  */
1465 static ftepp_t *ftepp;
1466
1467 static bool ftepp_preprocess_done()
1468 {
1469     bool retval = true;
1470     if (vec_size(ftepp->conditions)) {
1471         if (ftepp_warn(ftepp, WARN_MULTIFILE_IF, "#if spanning multiple files, is this intended?"))
1472             retval = false;
1473     }
1474     lex_close(ftepp->lex);
1475     ftepp->lex = NULL;
1476     if (ftepp->itemname) {
1477         mem_d(ftepp->itemname);
1478         ftepp->itemname = NULL;
1479     }
1480     return retval;
1481 }
1482
1483 bool ftepp_preprocess_file(const char *filename)
1484 {
1485     ftepp->lex = lex_open(filename);
1486     ftepp->itemname = util_strdup(filename);
1487     if (!ftepp->lex) {
1488         con_out("failed to open file \"%s\"\n", filename);
1489         return false;
1490     }
1491     if (!ftepp_preprocess(ftepp))
1492         return false;
1493     return ftepp_preprocess_done();
1494 }
1495
1496 bool ftepp_preprocess_string(const char *name, const char *str)
1497 {
1498     ftepp->lex = lex_open_string(str, strlen(str), name);
1499     ftepp->itemname = util_strdup(name);
1500     if (!ftepp->lex) {
1501         con_out("failed to create lexer for string \"%s\"\n", name);
1502         return false;
1503     }
1504     if (!ftepp_preprocess(ftepp))
1505         return false;
1506     return ftepp_preprocess_done();
1507 }
1508
1509
1510 void ftepp_add_macro(const char *name, const char *value) {
1511     char *create = NULL;
1512
1513     /* use saner path for empty macros */
1514     if (!value) {
1515         ftepp_add_define("__builtin__", name);
1516         return;
1517     }
1518
1519     vec_upload(create, "#define ", 8);
1520     vec_upload(create, name,  strlen(name));
1521     vec_push  (create, ' ');
1522     vec_upload(create, value, strlen(value));
1523     vec_push  (create, 0);
1524
1525     ftepp_preprocess_string("__builtin__", create);
1526     vec_free  (create);
1527 }
1528
1529 bool ftepp_init()
1530 {
1531     char minor[32];
1532     char major[32];
1533
1534     ftepp = ftepp_new();
1535     if (!ftepp)
1536         return false;
1537
1538     memset(minor, 0, sizeof(minor));
1539     memset(major, 0, sizeof(major));
1540
1541     /* set the right macro based on the selected standard */
1542     ftepp_add_define(NULL, "GMQCC");
1543     if (opts.standard == COMPILER_FTEQCC) {
1544         ftepp_add_define(NULL, "__STD_FTEQCC__");
1545         /* 1.00 */
1546         major[0] = '"';
1547         major[1] = '1';
1548         major[2] = '"';
1549
1550         minor[0] = '"';
1551         minor[1] = '0';
1552         minor[2] = '"';
1553     } else if (opts.standard == COMPILER_GMQCC) {
1554         ftepp_add_define(NULL, "__STD_GMQCC__");
1555         sprintf(major, "\"%d\"", GMQCC_VERSION_MAJOR);
1556         sprintf(minor, "\"%d\"", GMQCC_VERSION_MINOR);
1557     } else if (opts.standard == COMPILER_QCC) {
1558         ftepp_add_define(NULL, "__STD_QCC__");
1559         /* 1.0 */
1560         major[0] = '"';
1561         major[1] = '1';
1562         major[2] = '"';
1563
1564         minor[0] = '"';
1565         minor[1] = '0';
1566         minor[2] = '"';
1567     }
1568
1569     ftepp_add_macro("__STD_VERSION_MINOR__", minor);
1570     ftepp_add_macro("__STD_VERSION_MAJOR__", major);
1571
1572     return true;
1573 }
1574
1575 void ftepp_add_define(const char *source, const char *name)
1576 {
1577     ppmacro *macro;
1578     lex_ctx ctx = { "__builtin__", 0 };
1579     ctx.file = source;
1580     macro = ppmacro_new(ctx, name);
1581     vec_push(ftepp->macros, macro);
1582 }
1583
1584 const char *ftepp_get()
1585 {
1586     return ftepp->output_string;
1587 }
1588
1589 void ftepp_flush()
1590 {
1591     vec_free(ftepp->output_string);
1592 }
1593
1594 void ftepp_finish()
1595 {
1596     if (!ftepp)
1597         return;
1598     ftepp_delete(ftepp);
1599     ftepp = NULL;
1600 }