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