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