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