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