]> git.xonotic.org Git - xonotic/gmqcc.git/blob - ftepp.c
Comment about why stringify is so short
[xonotic/gmqcc.git] / ftepp.c
1 /*
2  * Copyright (C) 2012
3  *     Wolfgang Bumiller
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a copy of
6  * this software and associated documentation files (the "Software"), to deal in
7  * the Software without restriction, including without limitation the rights to
8  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9  * of the Software, and to permit persons to whom the Software is furnished to do
10  * so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in all
13  * copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21  * SOFTWARE.
22  */
23 #include "gmqcc.h"
24 #include "lexer.h"
25
26 typedef struct {
27     bool on;
28     bool was_on;
29     bool had_else;
30 } ppcondition;
31
32 typedef struct {
33     int   token;
34     char *value;
35     /* a copy from the lexer */
36     union {
37         vector v;
38         int    i;
39         double f;
40         int    t; /* type */
41     } constval;
42 } pptoken;
43
44 typedef struct {
45     lex_ctx ctx;
46
47     char   *name;
48     char  **params;
49     /* yes we need an extra flag since `#define FOO x` is not the same as `#define FOO() x` */
50     bool    has_params;
51
52     pptoken **output;
53 } ppmacro;
54
55 typedef struct {
56     lex_file    *lex;
57     int          token;
58     bool         newline;
59     unsigned int errors;
60
61     bool         output_on;
62     ppcondition *conditions;
63     ppmacro    **macros;
64
65     char        *output_string;
66 } ftepp_t;
67
68 #define ftepp_tokval(f) ((f)->lex->tok.value)
69 #define ftepp_ctx(f)    ((f)->lex->tok.ctx)
70
71 static void ftepp_errorat(ftepp_t *ftepp, lex_ctx ctx, const char *fmt, ...)
72 {
73     va_list ap;
74
75     ftepp->errors++;
76
77     va_start(ap, fmt);
78     con_vprintmsg(LVL_ERROR, ctx.file, ctx.line, "error", fmt, ap);
79     va_end(ap);
80 }
81
82 static void ftepp_error(ftepp_t *ftepp, const char *fmt, ...)
83 {
84     va_list ap;
85
86     ftepp->errors++;
87
88     va_start(ap, fmt);
89     con_vprintmsg(LVL_ERROR, ftepp->lex->tok.ctx.file, ftepp->lex->tok.ctx.line, "error", fmt, ap);
90     va_end(ap);
91 }
92
93 static bool GMQCC_WARN ftepp_warn(ftepp_t *ftepp, int warntype, const char *fmt, ...)
94 {
95     va_list ap;
96     int lvl = LVL_WARNING;
97
98     if (!OPTS_WARN(warntype))
99         return false;
100
101     if (opts_werror) {
102             lvl = LVL_ERROR;
103         ftepp->errors++;
104     }
105
106     va_start(ap, fmt);
107     con_vprintmsg(lvl, ftepp->lex->tok.ctx.file, ftepp->lex->tok.ctx.line, "error", fmt, ap);
108     va_end(ap);
109     return opts_werror;
110 }
111
112 static pptoken *pptoken_make(ftepp_t *ftepp)
113 {
114     pptoken *token = (pptoken*)mem_a(sizeof(pptoken));
115     token->token = ftepp->token;
116 #if 0
117     if (token->token == TOKEN_WHITE)
118         token->value = util_strdup(" ");
119     else
120 #else
121         token->value = util_strdup(ftepp_tokval(ftepp));
122 #endif
123     memcpy(&token->constval, &ftepp->lex->tok.constval, sizeof(token->constval));
124     return token;
125 }
126
127 static void pptoken_delete(pptoken *self)
128 {
129     mem_d(self->value);
130     mem_d(self);
131 }
132
133 static ppmacro *ppmacro_new(lex_ctx ctx, const char *name)
134 {
135     ppmacro *macro = (ppmacro*)mem_a(sizeof(ppmacro));
136     memset(macro, 0, sizeof(*macro));
137     macro->name = util_strdup(name);
138     return macro;
139 }
140
141 static void ppmacro_delete(ppmacro *self)
142 {
143     size_t i;
144     for (i = 0; i < vec_size(self->params); ++i)
145         mem_d(self->params[i]);
146     vec_free(self->params);
147     for (i = 0; i < vec_size(self->output); ++i)
148         pptoken_delete(self->output[i]);
149     vec_free(self->output);
150     mem_d(self->name);
151     mem_d(self);
152 }
153
154 static ftepp_t* ftepp_new()
155 {
156     ftepp_t *ftepp;
157
158     ftepp = (ftepp_t*)mem_a(sizeof(*ftepp));
159     memset(ftepp, 0, sizeof(*ftepp));
160
161     ftepp->output_on = true;
162
163     return ftepp;
164 }
165
166 static void ftepp_delete(ftepp_t *self)
167 {
168     size_t i;
169     for (i = 0; i < vec_size(self->macros); ++i)
170         ppmacro_delete(self->macros[i]);
171     vec_free(self->macros);
172     vec_free(self->conditions);
173     if (self->lex)
174         lex_close(self->lex);
175     mem_d(self);
176 }
177
178 static void ftepp_out(ftepp_t *ftepp, const char *str, bool ignore_cond)
179 {
180     if (ignore_cond || ftepp->output_on)
181     {
182         size_t len;
183         char  *data;
184         len = strlen(str);
185         data = vec_add(ftepp->output_string, len);
186         memcpy(data, str, len);
187     }
188 }
189
190 static void ftepp_update_output_condition(ftepp_t *ftepp)
191 {
192     size_t i;
193     ftepp->output_on = true;
194     for (i = 0; i < vec_size(ftepp->conditions); ++i)
195         ftepp->output_on = ftepp->output_on && ftepp->conditions[i].on;
196 }
197
198 static ppmacro* ftepp_macro_find(ftepp_t *ftepp, const char *name)
199 {
200     size_t i;
201     for (i = 0; i < vec_size(ftepp->macros); ++i) {
202         if (!strcmp(name, ftepp->macros[i]->name))
203             return ftepp->macros[i];
204     }
205     return NULL;
206 }
207
208 static void ftepp_macro_delete(ftepp_t *ftepp, const char *name)
209 {
210     size_t i;
211     for (i = 0; i < vec_size(ftepp->macros); ++i) {
212         if (!strcmp(name, ftepp->macros[i]->name)) {
213             vec_remove(ftepp->macros, i, 1);
214             return;
215         }
216     }
217 }
218
219 static inline int ftepp_next(ftepp_t *ftepp)
220 {
221     return (ftepp->token = lex_do(ftepp->lex));
222 }
223
224 /* Important: this does not skip newlines! */
225 static bool ftepp_skipspace(ftepp_t *ftepp)
226 {
227     if (ftepp->token != TOKEN_WHITE)
228         return true;
229     while (ftepp_next(ftepp) == TOKEN_WHITE) {}
230     if (ftepp->token >= TOKEN_EOF) {
231         ftepp_error(ftepp, "unexpected end of preprocessor directive");
232         return false;
233     }
234     return true;
235 }
236
237 /* this one skips EOLs as well */
238 static bool ftepp_skipallwhite(ftepp_t *ftepp)
239 {
240     if (ftepp->token != TOKEN_WHITE && ftepp->token != TOKEN_EOL)
241         return true;
242     do {
243         ftepp_next(ftepp);
244     } while (ftepp->token == TOKEN_WHITE || ftepp->token == TOKEN_EOL);
245     if (ftepp->token >= TOKEN_EOF) {
246         ftepp_error(ftepp, "unexpected end of preprocessor directive");
247         return false;
248     }
249     return true;
250 }
251
252 /**
253  * The huge macro parsing code...
254  */
255 static bool ftepp_define_params(ftepp_t *ftepp, ppmacro *macro)
256 {
257     do {
258         ftepp_next(ftepp);
259         if (!ftepp_skipspace(ftepp))
260             return false;
261         if (ftepp->token == ')')
262             break;
263         switch (ftepp->token) {
264             case TOKEN_IDENT:
265             case TOKEN_TYPENAME:
266             case TOKEN_KEYWORD:
267                 break;
268             default:
269                 ftepp_error(ftepp, "unexpected token in parameter list");
270                 return false;
271         }
272         vec_push(macro->params, util_strdup(ftepp_tokval(ftepp)));
273         ftepp_next(ftepp);
274         if (!ftepp_skipspace(ftepp))
275             return false;
276     } while (ftepp->token == ',');
277     if (ftepp->token != ')') {
278         ftepp_error(ftepp, "expected closing paren after macro parameter list");
279         return false;
280     }
281     ftepp_next(ftepp);
282     /* skipspace happens in ftepp_define */
283     return true;
284 }
285
286 static bool ftepp_define_body(ftepp_t *ftepp, ppmacro *macro)
287 {
288     pptoken *ptok;
289     while (ftepp->token != TOKEN_EOL && ftepp->token < TOKEN_EOF) {
290         ptok = pptoken_make(ftepp);
291         vec_push(macro->output, ptok);
292         ftepp_next(ftepp);
293     }
294     /* recursive expansion can cause EOFs here */
295     if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) {
296         ftepp_error(ftepp, "unexpected junk after macro or unexpected end of file");
297         return false;
298     }
299     return true;
300 }
301
302 static bool ftepp_define(ftepp_t *ftepp)
303 {
304     ppmacro *macro;
305     (void)ftepp_next(ftepp);
306     if (!ftepp_skipspace(ftepp))
307         return false;
308
309     switch (ftepp->token) {
310         case TOKEN_IDENT:
311         case TOKEN_TYPENAME:
312         case TOKEN_KEYWORD:
313             macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
314             if (macro && ftepp->output_on) {
315                 if (ftepp_warn(ftepp, WARN_PREPROCESSOR, "redefining `%s`", ftepp_tokval(ftepp)))
316                     return false;
317                 ftepp_macro_delete(ftepp, ftepp_tokval(ftepp));
318             }
319             macro = ppmacro_new(ftepp_ctx(ftepp), ftepp_tokval(ftepp));
320             break;
321         default:
322             ftepp_error(ftepp, "expected macro name");
323             return false;
324     }
325
326     (void)ftepp_next(ftepp);
327
328     if (ftepp->token == '(') {
329         macro->has_params = true;
330         if (!ftepp_define_params(ftepp, macro))
331             return false;
332     }
333
334     if (!ftepp_skipspace(ftepp))
335         return false;
336
337     if (!ftepp_define_body(ftepp, macro))
338         return false;
339
340     if (ftepp->output_on)
341         vec_push(ftepp->macros, macro);
342     else {
343         ppmacro_delete(macro);
344     }
345     return true;
346 }
347
348 /**
349  * When a macro is used we have to handle parameters as well
350  * as special-concatenation via ## or stringification via #
351  *
352  * Note: parenthesis can nest, so FOO((a),b) is valid, but only
353  * this kind of parens. Curly braces or [] don't count towards the
354  * paren-level.
355  */
356 typedef struct {
357     pptoken **tokens;
358 } macroparam;
359
360 static void macroparam_clean(macroparam *self)
361 {
362     size_t i;
363     for (i = 0; i < vec_size(self->tokens); ++i)
364         pptoken_delete(self->tokens[i]);
365     vec_free(self->tokens);
366 }
367
368 /* need to leave the last token up */
369 static bool ftepp_macro_call_params(ftepp_t *ftepp, macroparam **out_params)
370 {
371     macroparam *params = NULL;
372     pptoken    *ptok;
373     macroparam  mp;
374     size_t      parens = 0;
375     size_t      i;
376
377     if (!ftepp_skipallwhite(ftepp))
378         return false;
379     while (ftepp->token != ')') {
380         mp.tokens = NULL;
381         if (!ftepp_skipallwhite(ftepp))
382             return false;
383         while (parens || ftepp->token != ',') {
384             if (ftepp->token == '(')
385                 ++parens;
386             else if (ftepp->token == ')') {
387                 if (!parens)
388                     break;
389                 --parens;
390             }
391             ptok = pptoken_make(ftepp);
392             vec_push(mp.tokens, ptok);
393             if (ftepp_next(ftepp) >= TOKEN_EOF) {
394                 ftepp_error(ftepp, "unexpected EOF in macro call");
395                 goto on_error;
396             }
397         }
398         vec_push(params, mp);
399         mp.tokens = NULL;
400         if (ftepp->token == ')')
401             break;
402         if (ftepp->token != ',') {
403             ftepp_error(ftepp, "expected closing paren or comma in macro call");
404             goto on_error;
405         }
406         if (ftepp_next(ftepp) >= TOKEN_EOF) {
407             ftepp_error(ftepp, "unexpected EOF in macro call");
408             goto on_error;
409         }
410     }
411     /* need to leave that up
412     if (ftepp_next(ftepp) >= TOKEN_EOF) {
413         ftepp_error(ftepp, "unexpected EOF in macro call");
414         goto on_error;
415     }
416     */
417     *out_params = params;
418     return true;
419
420 on_error:
421     if (mp.tokens)
422         macroparam_clean(&mp);
423     for (i = 0; i < vec_size(params); ++i)
424         macroparam_clean(&params[i]);
425     vec_free(params);
426     return false;
427 }
428
429 static bool macro_params_find(ppmacro *macro, const char *name, size_t *idx)
430 {
431     size_t i;
432     for (i = 0; i < vec_size(macro->params); ++i) {
433         if (!strcmp(macro->params[i], name)) {
434             *idx = i;
435             return true;
436         }
437     }
438     return false;
439 }
440
441 static void ftepp_stringify_token(ftepp_t *ftepp, pptoken *token)
442 {
443     char        chs[2];
444     const char *ch;
445     chs[1] = 0;
446     switch (token->token) {
447         case TOKEN_STRINGCONST:
448             ch = token->value;
449             while (*ch) {
450                 /* in preprocessor mode strings already are string,
451                  * so we don't get actual newline bytes here.
452                  * Still need to escape backslashes and quotes.
453                  */
454                 switch (*ch) {
455                     case '\\': ftepp_out(ftepp, "\\\\", false); break;
456                     case '"':  ftepp_out(ftepp, "\\\"", false); break;
457                     default:
458                         chs[0] = *ch;
459                         ftepp_out(ftepp, chs, false);
460                         break;
461                 }
462                 ++ch;
463             }
464             break;
465         case TOKEN_WHITE:
466             ftepp_out(ftepp, " ", false);
467             break;
468         case TOKEN_EOL:
469             ftepp_out(ftepp, "\\n", false);
470             break;
471         default:
472             ftepp_out(ftepp, token->value, false);
473             break;
474     }
475 }
476
477 static void ftepp_stringify(ftepp_t *ftepp, macroparam *param)
478 {
479     size_t i;
480     ftepp_out(ftepp, "\"", false);
481     for (i = 0; i < vec_size(param->tokens); ++i)
482         ftepp_stringify_token(ftepp, param->tokens[i]);
483     ftepp_out(ftepp, "\"", false);
484 }
485
486 static bool ftepp_preprocess(ftepp_t *ftepp);
487 static bool ftepp_macro_expand(ftepp_t *ftepp, ppmacro *macro, macroparam *params)
488 {
489     char     *old_string = ftepp->output_string;
490     lex_file *old_lexer = ftepp->lex;
491     bool retval = true;
492
493     size_t    o, pi, pv;
494     lex_file *inlex;
495
496     int nextok;
497
498     /* really ... */
499     if (!vec_size(macro->output))
500         return true;
501
502     ftepp->output_string = NULL;
503     for (o = 0; o < vec_size(macro->output); ++o) {
504         pptoken *out = macro->output[o];
505         switch (out->token) {
506             case TOKEN_IDENT:
507             case TOKEN_TYPENAME:
508             case TOKEN_KEYWORD:
509                 if (!macro_params_find(macro, out->value, &pi)) {
510                     ftepp_out(ftepp, out->value, false);
511                     break;
512                 } else {
513                     for (pv = 0; pv < vec_size(params[pi].tokens); ++pv) {
514                         out = params[pi].tokens[pv];
515                         if (out->token == TOKEN_EOL)
516                             ftepp_out(ftepp, "\n", false);
517                         else
518                             ftepp_out(ftepp, out->value, false);
519                     }
520                 }
521                 break;
522             case '#':
523                 if (o + 1 < vec_size(macro->output)) {
524                     nextok = macro->output[o+1]->token;
525                     if (nextok == '#') {
526                         /* raw concatenation */
527                         ++o;
528                         break;
529                     }
530                     if ( (nextok == TOKEN_IDENT    ||
531                           nextok == TOKEN_KEYWORD  ||
532                           nextok == TOKEN_TYPENAME) &&
533                         macro_params_find(macro, macro->output[o+1]->value, &pi))
534                     {
535                         ++o;
536                         ftepp_stringify(ftepp, &params[pi]);
537                         break;
538                     }
539                 }
540                 ftepp_out(ftepp, "#", false);
541                 break;
542             case TOKEN_EOL:
543                 ftepp_out(ftepp, "\n", false);
544                 break;
545             default:
546                 ftepp_out(ftepp, out->value, false);
547                 break;
548         }
549     }
550     vec_push(ftepp->output_string, 0);
551     /* Now run the preprocessor recursively on this string buffer */
552     /*
553     printf("__________\n%s\n=========\n", ftepp->output_string);
554     */
555     inlex = lex_open_string(ftepp->output_string, vec_size(ftepp->output_string)-1, ftepp->lex->name);
556     if (!inlex) {
557         ftepp_error(ftepp, "internal error: failed to instantiate lexer");
558         retval = false;
559         goto cleanup;
560     }
561     ftepp->output_string = old_string;
562     ftepp->lex = inlex;
563     if (!ftepp_preprocess(ftepp)) {
564         lex_close(ftepp->lex);
565         retval = false;
566         goto cleanup;
567     }
568
569 cleanup:
570     ftepp->lex           = old_lexer;
571     ftepp->output_string = old_string;
572     return retval;
573 }
574
575 static bool ftepp_macro_call(ftepp_t *ftepp, ppmacro *macro)
576 {
577     size_t     o;
578     macroparam *params = NULL;
579     bool        retval = true;
580
581     if (!macro->has_params) {
582         if (!ftepp_macro_expand(ftepp, macro, NULL))
583             return false;
584         ftepp_next(ftepp);
585         return true;
586     }
587     ftepp_next(ftepp);
588
589     if (!ftepp_skipallwhite(ftepp))
590         return false;
591
592     if (ftepp->token != '(') {
593         ftepp_error(ftepp, "expected macro parameters in parenthesis");
594         return false;
595     }
596
597     ftepp_next(ftepp);
598     if (!ftepp_macro_call_params(ftepp, &params))
599         return false;
600
601     if (vec_size(params) != vec_size(macro->params)) {
602         ftepp_error(ftepp, "macro %s expects %u paramteters, %u provided", macro->name,
603                     (unsigned int)vec_size(macro->params),
604                     (unsigned int)vec_size(params));
605         retval = false;
606         goto cleanup;
607     }
608
609     if (!ftepp_macro_expand(ftepp, macro, params))
610         retval = false;
611     ftepp_next(ftepp);
612
613 cleanup:
614     for (o = 0; o < vec_size(params); ++o)
615         macroparam_clean(&params[o]);
616     vec_free(params);
617     return retval;
618 }
619
620 /**
621  * #if - the FTEQCC way:
622  *    defined(FOO) => true if FOO was #defined regardless of parameters or contents
623  *    <numbers>    => True if the number is not 0
624  *    !<factor>    => True if the factor yields false
625  *    !!<factor>   => ERROR on 2 or more unary nots
626  *    <macro>      => becomes the macro's FIRST token regardless of parameters
627  *    <e> && <e>   => True if both expressions are true
628  *    <e> || <e>   => True if either expression is true
629  *    <string>     => False
630  *    <ident>      => False (remember for macros the <macro> rule applies instead)
631  * Unary + and - are weird and wrong in fteqcc so we don't allow them
632  * parenthesis in expressions are allowed
633  * parameter lists on macros are errors
634  * No mathematical calculations are executed
635  */
636 static bool ftepp_if_expr(ftepp_t *ftepp, bool *out)
637 {
638     ppmacro *macro;
639     bool     wasnot = false;
640
641     if (!ftepp_skipspace(ftepp))
642         return false;
643
644     while (ftepp->token == '!') {
645         wasnot = true;
646         ftepp_next(ftepp);
647         if (!ftepp_skipspace(ftepp))
648             return false;
649     }
650
651     switch (ftepp->token) {
652         case TOKEN_IDENT:
653         case TOKEN_TYPENAME:
654         case TOKEN_KEYWORD:
655             if (!strcmp(ftepp_tokval(ftepp), "defined")) {
656                 ftepp_next(ftepp);
657                 if (!ftepp_skipspace(ftepp))
658                     return false;
659                 if (ftepp->token != '(') {
660                     ftepp_error(ftepp, "`defined` keyword in #if requires a macro name in parenthesis");
661                     return false;
662                 }
663                 ftepp_next(ftepp);
664                 if (!ftepp_skipspace(ftepp))
665                     return false;
666                 if (ftepp->token != TOKEN_IDENT &&
667                     ftepp->token != TOKEN_TYPENAME &&
668                     ftepp->token != TOKEN_KEYWORD)
669                 {
670                     ftepp_error(ftepp, "defined() used on an unexpected token type");
671                     return false;
672                 }
673                 macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
674                 *out = !!macro;
675                 ftepp_next(ftepp);
676                 if (!ftepp_skipspace(ftepp))
677                     return false;
678                 if (ftepp->token != ')') {
679                     ftepp_error(ftepp, "expected closing paren");
680                     return false;
681                 }
682                 break;
683             }
684
685             macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
686             if (!macro || !vec_size(macro->output)) {
687                 *out = false;
688             } else {
689                 /* This does not expand recursively! */
690                 switch (macro->output[0]->token) {
691                     case TOKEN_INTCONST:
692                         *out = !!(macro->output[0]->constval.f);
693                         break;
694                     case TOKEN_FLOATCONST:
695                         *out = !!(macro->output[0]->constval.f);
696                         break;
697                     default:
698                         *out = false;
699                         break;
700                 }
701             }
702             break;
703         case TOKEN_STRINGCONST:
704             *out = false;
705             break;
706         case TOKEN_INTCONST:
707             *out = !!(ftepp->lex->tok.constval.i);
708             break;
709         case TOKEN_FLOATCONST:
710             *out = !!(ftepp->lex->tok.constval.f);
711             break;
712
713         case '(':
714             ftepp_next(ftepp);
715             if (!ftepp_if_expr(ftepp, out))
716                 return false;
717             if (ftepp->token != ')') {
718                 ftepp_error(ftepp, "expected closing paren in #if expression");
719                 return false;
720             }
721             break;
722
723         default:
724             ftepp_error(ftepp, "junk in #if");
725             return false;
726     }
727     if (wasnot)
728         *out = !*out;
729
730     ftepp->lex->flags.noops = false;
731     ftepp_next(ftepp);
732     if (!ftepp_skipspace(ftepp))
733         return false;
734     ftepp->lex->flags.noops = true;
735
736     if (ftepp->token == ')')
737         return true;
738
739     if (ftepp->token != TOKEN_OPERATOR)
740         return true;
741
742     if (!strcmp(ftepp_tokval(ftepp), "&&") ||
743         !strcmp(ftepp_tokval(ftepp), "||"))
744     {
745         bool next = false;
746         char opc  = ftepp_tokval(ftepp)[0];
747
748         ftepp_next(ftepp);
749         if (!ftepp_if_expr(ftepp, &next))
750             return false;
751
752         if (opc == '&')
753             *out = *out && next;
754         else
755             *out = *out || next;
756         return true;
757     }
758     else {
759         ftepp_error(ftepp, "junk after #if");
760         return false;
761     }
762 }
763
764 static bool ftepp_if(ftepp_t *ftepp, ppcondition *cond)
765 {
766     bool result = false;
767
768     memset(cond, 0, sizeof(*cond));
769     (void)ftepp_next(ftepp);
770
771     if (!ftepp_skipspace(ftepp))
772         return false;
773     if (ftepp->token == TOKEN_EOL) {
774         ftepp_error(ftepp, "expected expression for #if-directive");
775         return false;
776     }
777
778     if (!ftepp_if_expr(ftepp, &result))
779         return false;
780
781     cond->on = result;
782     return true;
783 }
784
785 /**
786  * ifdef is rather simple
787  */
788 static bool ftepp_ifdef(ftepp_t *ftepp, ppcondition *cond)
789 {
790     ppmacro *macro;
791     memset(cond, 0, sizeof(*cond));
792     (void)ftepp_next(ftepp);
793     if (!ftepp_skipspace(ftepp))
794         return false;
795
796     switch (ftepp->token) {
797         case TOKEN_IDENT:
798         case TOKEN_TYPENAME:
799         case TOKEN_KEYWORD:
800             macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
801             break;
802         default:
803             ftepp_error(ftepp, "expected macro name");
804             return false;
805     }
806
807     (void)ftepp_next(ftepp);
808     if (!ftepp_skipspace(ftepp))
809         return false;
810     /* relaxing this condition
811     if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) {
812         ftepp_error(ftepp, "stray tokens after #ifdef");
813         return false;
814     }
815     */
816     cond->on = !!macro;
817     return true;
818 }
819
820 /**
821  * undef is also simple
822  */
823 static bool ftepp_undef(ftepp_t *ftepp)
824 {
825     (void)ftepp_next(ftepp);
826     if (!ftepp_skipspace(ftepp))
827         return false;
828
829     if (ftepp->output_on) {
830         switch (ftepp->token) {
831             case TOKEN_IDENT:
832             case TOKEN_TYPENAME:
833             case TOKEN_KEYWORD:
834                 ftepp_macro_delete(ftepp, ftepp_tokval(ftepp));
835                 break;
836             default:
837                 ftepp_error(ftepp, "expected macro name");
838                 return false;
839         }
840     }
841
842     (void)ftepp_next(ftepp);
843     if (!ftepp_skipspace(ftepp))
844         return false;
845     /* relaxing this condition
846     if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) {
847         ftepp_error(ftepp, "stray tokens after #ifdef");
848         return false;
849     }
850     */
851     return true;
852 }
853
854 /* Basic structure handlers */
855 static bool ftepp_else_allowed(ftepp_t *ftepp)
856 {
857     if (!vec_size(ftepp->conditions)) {
858         ftepp_error(ftepp, "#else without #if");
859         return false;
860     }
861     if (vec_last(ftepp->conditions).had_else) {
862         ftepp_error(ftepp, "multiple #else for a single #if");
863         return false;
864     }
865     return true;
866 }
867
868 static bool ftepp_hash(ftepp_t *ftepp)
869 {
870     ppcondition cond;
871     ppcondition *pc;
872
873     lex_ctx ctx = ftepp_ctx(ftepp);
874
875     if (!ftepp_skipspace(ftepp))
876         return false;
877
878     switch (ftepp->token) {
879         case TOKEN_KEYWORD:
880         case TOKEN_IDENT:
881         case TOKEN_TYPENAME:
882             if (!strcmp(ftepp_tokval(ftepp), "define")) {
883                 return ftepp_define(ftepp);
884             }
885             else if (!strcmp(ftepp_tokval(ftepp), "undef")) {
886                 return ftepp_undef(ftepp);
887             }
888             else if (!strcmp(ftepp_tokval(ftepp), "ifdef")) {
889                 if (!ftepp_ifdef(ftepp, &cond))
890                     return false;
891                 cond.was_on = cond.on;
892                 vec_push(ftepp->conditions, cond);
893                 ftepp->output_on = ftepp->output_on && cond.on;
894                 break;
895             }
896             else if (!strcmp(ftepp_tokval(ftepp), "ifndef")) {
897                 if (!ftepp_ifdef(ftepp, &cond))
898                     return false;
899                 cond.on = !cond.on;
900                 cond.was_on = cond.on;
901                 vec_push(ftepp->conditions, cond);
902                 ftepp->output_on = ftepp->output_on && cond.on;
903                 break;
904             }
905             else if (!strcmp(ftepp_tokval(ftepp), "elifdef")) {
906                 if (!ftepp_else_allowed(ftepp))
907                     return false;
908                 if (!ftepp_ifdef(ftepp, &cond))
909                     return false;
910                 pc = &vec_last(ftepp->conditions);
911                 pc->on     = !pc->was_on && cond.on;
912                 pc->was_on = pc->was_on || pc->on;
913                 ftepp_update_output_condition(ftepp);
914                 break;
915             }
916             else if (!strcmp(ftepp_tokval(ftepp), "elifndef")) {
917                 if (!ftepp_else_allowed(ftepp))
918                     return false;
919                 if (!ftepp_ifdef(ftepp, &cond))
920                     return false;
921                 cond.on = !cond.on;
922                 pc = &vec_last(ftepp->conditions);
923                 pc->on     = !pc->was_on && cond.on;
924                 pc->was_on = pc->was_on || pc->on;
925                 ftepp_update_output_condition(ftepp);
926                 break;
927             }
928             else if (!strcmp(ftepp_tokval(ftepp), "elif")) {
929                 if (!ftepp_else_allowed(ftepp))
930                     return false;
931                 if (!ftepp_if(ftepp, &cond))
932                     return false;
933                 pc = &vec_last(ftepp->conditions);
934                 pc->on     = !pc->was_on && cond.on;
935                 pc->was_on = pc->was_on  || pc->on;
936                 ftepp_update_output_condition(ftepp);
937                 break;
938             }
939             else if (!strcmp(ftepp_tokval(ftepp), "if")) {
940                 if (!ftepp_if(ftepp, &cond))
941                     return false;
942                 cond.was_on = cond.on;
943                 vec_push(ftepp->conditions, cond);
944                 ftepp->output_on = ftepp->output_on && cond.on;
945                 break;
946             }
947             else if (!strcmp(ftepp_tokval(ftepp), "else")) {
948                 if (!ftepp_else_allowed(ftepp))
949                     return false;
950                 pc = &vec_last(ftepp->conditions);
951                 pc->on = !pc->was_on;
952                 pc->had_else = true;
953                 ftepp_next(ftepp);
954                 ftepp_update_output_condition(ftepp);
955                 break;
956             }
957             else if (!strcmp(ftepp_tokval(ftepp), "endif")) {
958                 if (!vec_size(ftepp->conditions)) {
959                     ftepp_error(ftepp, "#endif without #if");
960                     return false;
961                 }
962                 vec_pop(ftepp->conditions);
963                 ftepp_next(ftepp);
964                 ftepp_update_output_condition(ftepp);
965                 break;
966             }
967             else {
968                 ftepp_error(ftepp, "unrecognized preprocessor directive: `%s`", ftepp_tokval(ftepp));
969                 return false;
970             }
971             break;
972         default:
973             ftepp_error(ftepp, "unexpected preprocessor token: `%s`", ftepp_tokval(ftepp));
974             return false;
975         case TOKEN_EOL:
976             ftepp_errorat(ftepp, ctx, "empty preprocessor directive");
977             return false;
978         case TOKEN_EOF:
979             ftepp_error(ftepp, "missing newline at end of file", ftepp_tokval(ftepp));
980             return false;
981
982         /* Builtins! Don't forget the builtins! */
983         case TOKEN_INTCONST:
984         case TOKEN_FLOATCONST:
985             ftepp_out(ftepp, "#", false);
986             return true;
987     }
988     if (!ftepp_skipspace(ftepp))
989         return false;
990     return true;
991 }
992
993 static bool ftepp_preprocess(ftepp_t *ftepp)
994 {
995     ppmacro *macro;
996     bool     newline = true;
997
998     ftepp->lex->flags.preprocessing = true;
999     ftepp->lex->flags.mergelines    = false;
1000     ftepp->lex->flags.noops         = true;
1001
1002     ftepp_next(ftepp);
1003     do
1004     {
1005         if (ftepp->token >= TOKEN_EOF)
1006             break;
1007 #if 0
1008         ftepp->newline = newline;
1009         newline = false;
1010 #else
1011         /* For the sake of FTE compatibility... FU, really */
1012         ftepp->newline = newline = true;
1013 #endif
1014
1015         switch (ftepp->token) {
1016             case TOKEN_KEYWORD:
1017             case TOKEN_IDENT:
1018             case TOKEN_TYPENAME:
1019                 macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
1020                 if (!macro) {
1021                     ftepp_out(ftepp, ftepp_tokval(ftepp), false);
1022                     ftepp_next(ftepp);
1023                     break;
1024                 }
1025                 if (!ftepp_macro_call(ftepp, macro))
1026                     ftepp->token = TOKEN_ERROR;
1027                 break;
1028             case '#':
1029                 if (!ftepp->newline) {
1030                     ftepp_out(ftepp, ftepp_tokval(ftepp), false);
1031                     ftepp_next(ftepp);
1032                     break;
1033                 }
1034                 ftepp->lex->flags.mergelines = true;
1035                 if (ftepp_next(ftepp) >= TOKEN_EOF) {
1036                     ftepp_error(ftepp, "error in preprocessor directive");
1037                     ftepp->token = TOKEN_ERROR;
1038                     break;
1039                 }
1040                 if (!ftepp_hash(ftepp))
1041                     ftepp->token = TOKEN_ERROR;
1042                 ftepp->lex->flags.mergelines = false;
1043                 break;
1044             case TOKEN_EOL:
1045                 newline = true;
1046                 ftepp_out(ftepp, "\n", true);
1047                 ftepp_next(ftepp);
1048                 break;
1049             default:
1050                 ftepp_out(ftepp, ftepp_tokval(ftepp), false);
1051                 ftepp_next(ftepp);
1052                 break;
1053         }
1054     } while (!ftepp->errors && ftepp->token < TOKEN_EOF);
1055
1056     newline = ftepp->token == TOKEN_EOF;
1057     return newline;
1058 }
1059
1060 /* Like in parser.c - files keep the previous state so we have one global
1061  * preprocessor. Except here we will want to warn about dangling #ifs.
1062  */
1063 static ftepp_t *ftepp;
1064
1065 static bool ftepp_preprocess_done()
1066 {
1067     bool retval = true;
1068     lex_close(ftepp->lex);
1069     ftepp->lex = NULL;
1070     if (vec_size(ftepp->conditions)) {
1071         if (ftepp_warn(ftepp, WARN_MULTIFILE_IF, "#if spanning multiple files, is this intended?"))
1072             retval = false;
1073     }
1074     return retval;
1075 }
1076
1077 bool ftepp_preprocess_file(const char *filename)
1078 {
1079     ftepp->lex = lex_open(filename);
1080     if (!ftepp->lex) {
1081         con_out("failed to open file \"%s\"\n", filename);
1082         return false;
1083     }
1084     if (!ftepp_preprocess(ftepp)) {
1085         ftepp_delete(ftepp);
1086         return false;
1087     }
1088     return ftepp_preprocess_done();
1089 }
1090
1091 bool ftepp_preprocess_string(const char *name, const char *str)
1092 {
1093     ftepp_t *ftepp = ftepp_new();
1094     ftepp->lex = lex_open_string(str, strlen(str), name);
1095     if (!ftepp->lex) {
1096         con_out("failed to create lexer for string \"%s\"\n", name);
1097         return false;
1098     }
1099     if (!ftepp_preprocess(ftepp)) {
1100         ftepp_delete(ftepp);
1101         return false;
1102     }
1103     return ftepp_preprocess_done();
1104 }
1105
1106 bool ftepp_init()
1107 {
1108     ftepp = ftepp_new();
1109     return !!ftepp;
1110 }
1111
1112 const char *ftepp_get()
1113 {
1114     return ftepp->output_string;
1115 }
1116
1117 void ftepp_flush()
1118 {
1119     vec_free(ftepp->output_string);
1120 }
1121
1122 void ftepp_finish()
1123 {
1124     if (!ftepp)
1125         return;
1126     ftepp_delete(ftepp);
1127     ftepp = NULL;
1128 }