]> git.xonotic.org Git - xonotic/gmqcc.git/blob - ftepp.c
Expand macros into a string buffer
[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     bool         output_string;
66     char        *output;
67 } ftepp_t;
68
69 #define ftepp_tokval(f) ((f)->lex->tok.value)
70 #define ftepp_ctx(f)    ((f)->lex->tok.ctx)
71
72 static void ftepp_errorat(ftepp_t *ftepp, lex_ctx ctx, const char *fmt, ...)
73 {
74     va_list ap;
75
76     ftepp->errors++;
77
78     va_start(ap, fmt);
79     con_vprintmsg(LVL_ERROR, ctx.file, ctx.line, "error", fmt, ap);
80     va_end(ap);
81 }
82
83 static void ftepp_error(ftepp_t *ftepp, const char *fmt, ...)
84 {
85     va_list ap;
86
87     ftepp->errors++;
88
89     va_start(ap, fmt);
90     con_vprintmsg(LVL_ERROR, ftepp->lex->tok.ctx.file, ftepp->lex->tok.ctx.line, "error", fmt, ap);
91     va_end(ap);
92 }
93
94 static pptoken *pptoken_make(ftepp_t *ftepp)
95 {
96     pptoken *token = (pptoken*)mem_a(sizeof(pptoken));
97     token->token = ftepp->token;
98     if (token->token == TOKEN_WHITE)
99         token->value = util_strdup(" ");
100     else
101         token->value = util_strdup(ftepp_tokval(ftepp));
102     memcpy(&token->constval, &ftepp->lex->tok.constval, sizeof(token->constval));
103     return token;
104 }
105
106 static void pptoken_delete(pptoken *self)
107 {
108     mem_d(self->value);
109     mem_d(self);
110 }
111
112 static ppmacro *ppmacro_new(lex_ctx ctx, const char *name)
113 {
114     ppmacro *macro = (ppmacro*)mem_a(sizeof(ppmacro));
115     memset(macro, 0, sizeof(*macro));
116     macro->name = util_strdup(name);
117     return macro;
118 }
119
120 static void ppmacro_delete(ppmacro *self)
121 {
122     size_t i;
123     for (i = 0; i < vec_size(self->params); ++i)
124         mem_d(self->params[i]);
125     vec_free(self->params);
126     for (i = 0; i < vec_size(self->output); ++i)
127         pptoken_delete(self->output[i]);
128     vec_free(self->output);
129     mem_d(self->name);
130     mem_d(self);
131 }
132
133 static ftepp_t* ftepp_init()
134 {
135     ftepp_t *ftepp;
136
137     ftepp = (ftepp_t*)mem_a(sizeof(*ftepp));
138     memset(ftepp, 0, sizeof(*ftepp));
139
140     ftepp->output_on = true;
141
142     return ftepp;
143 }
144
145 static void ftepp_delete(ftepp_t *self)
146 {
147     size_t i;
148     for (i = 0; i < vec_size(self->macros); ++i)
149         ppmacro_delete(self->macros[i]);
150     vec_free(self->macros);
151     vec_free(self->conditions);
152     lex_close(self->lex);
153     mem_d(self);
154 }
155
156 static void ftepp_out(ftepp_t *ftepp, const char *str, bool ignore_cond)
157 {
158     if (ignore_cond || ftepp->output_on)
159     {
160         size_t len;
161         char  *data;
162         if (!ftepp->output_string) {
163             printf("%s", str);
164             return;
165         }
166         len = strlen(str);
167         data = vec_add(ftepp->output, len);
168         memcpy(data, str, len);
169     }
170 }
171
172 static void ftepp_update_output_condition(ftepp_t *ftepp)
173 {
174     size_t i;
175     ftepp->output_on = true;
176     for (i = 0; i < vec_size(ftepp->conditions); ++i)
177         ftepp->output_on = ftepp->output_on && ftepp->conditions[i].on;
178 }
179
180 static ppmacro* ftepp_macro_find(ftepp_t *ftepp, const char *name)
181 {
182     size_t i;
183     for (i = 0; i < vec_size(ftepp->macros); ++i) {
184         if (!strcmp(name, ftepp->macros[i]->name))
185             return ftepp->macros[i];
186     }
187     return NULL;
188 }
189
190 static inline int ftepp_next(ftepp_t *ftepp)
191 {
192     return (ftepp->token = lex_do(ftepp->lex));
193 }
194
195 /* Important: this does not skip newlines! */
196 static bool ftepp_skipspace(ftepp_t *ftepp)
197 {
198     if (ftepp->token != TOKEN_WHITE)
199         return true;
200     while (ftepp_next(ftepp) == TOKEN_WHITE) {}
201     if (ftepp->token >= TOKEN_EOF) {
202         ftepp_error(ftepp, "unexpected end of preprocessor directive");
203         return false;
204     }
205     return true;
206 }
207
208 /* this one skips EOLs as well */
209 static bool ftepp_skipallwhite(ftepp_t *ftepp)
210 {
211     if (ftepp->token != TOKEN_WHITE && ftepp->token != TOKEN_EOL)
212         return true;
213     do {
214         ftepp_next(ftepp);
215     } while (ftepp->token == TOKEN_WHITE || ftepp->token == TOKEN_EOL);
216     if (ftepp->token >= TOKEN_EOF) {
217         ftepp_error(ftepp, "unexpected end of preprocessor directive");
218         return false;
219     }
220     return true;
221 }
222
223 /**
224  * The huge macro parsing code...
225  */
226 static bool ftepp_define_params(ftepp_t *ftepp, ppmacro *macro)
227 {
228     do {
229         ftepp_next(ftepp);
230         if (!ftepp_skipspace(ftepp))
231             return false;
232         if (ftepp->token == ')')
233             break;
234         switch (ftepp->token) {
235             case TOKEN_IDENT:
236             case TOKEN_TYPENAME:
237             case TOKEN_KEYWORD:
238                 break;
239             default:
240                 ftepp_error(ftepp, "unexpected token in parameter list");
241                 return false;
242         }
243         vec_push(macro->params, util_strdup(ftepp_tokval(ftepp)));
244         ftepp_next(ftepp);
245         if (!ftepp_skipspace(ftepp))
246             return false;
247     } while (ftepp->token == ',');
248     if (ftepp->token != ')') {
249         ftepp_error(ftepp, "expected closing paren after macro parameter list");
250         return false;
251     }
252     ftepp_next(ftepp);
253     /* skipspace happens in ftepp_define */
254     return true;
255 }
256
257 static bool ftepp_define_body(ftepp_t *ftepp, ppmacro *macro)
258 {
259     pptoken *ptok;
260     while (ftepp->token != TOKEN_EOL && ftepp->token < TOKEN_EOF) {
261         ptok = pptoken_make(ftepp);
262         vec_push(macro->output, ptok);
263         ftepp_next(ftepp);
264     }
265     if (ftepp->token != TOKEN_EOL) {
266         ftepp_error(ftepp, "unexpected junk after macro or unexpected end of file");
267         return false;
268     }
269     return true;
270 }
271
272 static bool ftepp_define(ftepp_t *ftepp)
273 {
274     ppmacro *macro;
275     (void)ftepp_next(ftepp);
276     if (!ftepp_skipspace(ftepp))
277         return false;
278
279     switch (ftepp->token) {
280         case TOKEN_IDENT:
281         case TOKEN_TYPENAME:
282         case TOKEN_KEYWORD:
283             macro = ppmacro_new(ftepp_ctx(ftepp), ftepp_tokval(ftepp));
284             break;
285         default:
286             ftepp_error(ftepp, "expected macro name");
287             return false;
288     }
289
290     (void)ftepp_next(ftepp);
291
292     if (ftepp->token == '(') {
293         macro->has_params = true;
294         if (!ftepp_define_params(ftepp, macro))
295             return false;
296     }
297
298     if (!ftepp_skipspace(ftepp))
299         return false;
300
301     if (!ftepp_define_body(ftepp, macro))
302         return false;
303
304     vec_push(ftepp->macros, macro);
305     return true;
306 }
307
308 /**
309  * When a macro is used we have to handle parameters as well
310  * as special-concatenation via ## or stringification via #
311  *
312  * Note: parenthesis can nest, so FOO((a),b) is valid, but only
313  * this kind of parens. Curly braces or [] don't count towards the
314  * paren-level.
315  */
316 typedef struct {
317     pptoken **tokens;
318 } macroparam;
319
320 static void macroparam_clean(macroparam *self)
321 {
322     size_t i;
323     for (i = 0; i < vec_size(self->tokens); ++i)
324         pptoken_delete(self->tokens[i]);
325     vec_free(self->tokens);
326 }
327
328 static bool ftepp_macro_call_params(ftepp_t *ftepp, macroparam **out_params)
329 {
330     macroparam *params = NULL;
331     pptoken    *ptok;
332     macroparam  mp;
333     size_t      parens = 0;
334     size_t      i;
335
336     if (!ftepp_skipallwhite(ftepp))
337         return false;
338     while (ftepp->token != ')') {
339         mp.tokens = NULL;
340         if (!ftepp_skipallwhite(ftepp))
341             return false;
342         while (parens || ftepp->token != ',') {
343             if (ftepp->token == '(')
344                 ++parens;
345             else if (ftepp->token == ')') {
346                 if (!parens)
347                     break;
348                 --parens;
349             }
350             ptok = pptoken_make(ftepp);
351             vec_push(mp.tokens, ptok);
352             if (ftepp_next(ftepp) >= TOKEN_EOF) {
353                 ftepp_error(ftepp, "unexpected EOF in macro call");
354                 goto on_error;
355             }
356         }
357         vec_push(params, mp);
358         mp.tokens = NULL;
359         if (ftepp->token == ')')
360             break;
361         if (ftepp->token != ',') {
362             ftepp_error(ftepp, "expected closing paren or comma in macro call");
363             goto on_error;
364         }
365         if (ftepp_next(ftepp) >= TOKEN_EOF) {
366             ftepp_error(ftepp, "unexpected EOF in macro call");
367             goto on_error;
368         }
369     }
370     if (ftepp_next(ftepp) >= TOKEN_EOF) {
371         ftepp_error(ftepp, "unexpected EOF in macro call");
372         goto on_error;
373     }
374     *out_params = params;
375     return true;
376
377 on_error:
378     if (mp.tokens)
379         macroparam_clean(&mp);
380     for (i = 0; i < vec_size(params); ++i)
381         macroparam_clean(&params[i]);
382     vec_free(params);
383     return false;
384 }
385
386 static bool macro_params_find(ppmacro *macro, const char *name, size_t *idx)
387 {
388     size_t i;
389     for (i = 0; i < vec_size(macro->params); ++i) {
390         if (!strcmp(macro->params[i], name)) {
391             *idx = i;
392             return true;
393         }
394     }
395     return false;
396 }
397
398 static bool ftepp_macro_expand(ftepp_t *ftepp, ppmacro *macro, macroparam *params)
399 {
400     char *old_string = ftepp->output;
401     bool old_string_flag = ftepp->output_string;
402     bool retval = true;
403
404     size_t o, pi, pv;
405
406     /* really ... */
407     if (!vec_size(macro->output))
408         return true;
409
410     ftepp->output = NULL;
411     ftepp->output_string = true;
412     for (o = 0; o < vec_size(macro->output); ++o) {
413         pptoken *out = macro->output[o];
414         switch (out->token) {
415             case TOKEN_IDENT:
416             case TOKEN_TYPENAME:
417             case TOKEN_KEYWORD:
418                 if (!macro_params_find(macro, out->value, &pi)) {
419                     ftepp_out(ftepp, out->value, false);
420                     break;
421                 } else {
422                     for (pv = 0; pv < vec_size(params[pi].tokens); ++pv) {
423                         out = params[pi].tokens[pv];
424                         if (out->token == TOKEN_EOL)
425                             ftepp_out(ftepp, "\n", false);
426                         else
427                             ftepp_out(ftepp, out->value, false);
428                     }
429                 }
430                 break;
431             case TOKEN_EOL:
432                 ftepp_out(ftepp, "\n", false);
433                 break;
434             default:
435                 ftepp_out(ftepp, out->value, false);
436                 break;
437         }
438     }
439     vec_push(ftepp->output, 0);
440     printf("_________________\n%s\n=================\n", ftepp->output);
441     goto cleanup;
442
443 cleanup:
444     ftepp->output = old_string;
445     ftepp->output_string = old_string_flag;
446     return retval;
447 }
448
449 static bool ftepp_macro_call(ftepp_t *ftepp, ppmacro *macro)
450 {
451     size_t     o;
452     macroparam *params = NULL;
453     bool        retval = true;
454
455     ftepp_next(ftepp);
456
457     if (!macro->has_params) {
458         for (o = 0; o < vec_size(macro->output); ++o) {
459             ftepp_out(ftepp, macro->output[o]->value, false);
460         }
461         return true;
462     }
463
464     if (!ftepp_skipallwhite(ftepp))
465         return false;
466
467     if (ftepp->token != '(') {
468         ftepp_error(ftepp, "expected macro parameters in parenthesis");
469         return false;
470     }
471
472     ftepp_next(ftepp);
473     if (!ftepp_macro_call_params(ftepp, &params))
474         return false;
475
476     if (vec_size(params) != vec_size(macro->params)) {
477         ftepp_error(ftepp, "macro %s expects %u paramteters, %u provided", macro->name,
478                     (unsigned int)vec_size(macro->params),
479                     (unsigned int)vec_size(params));
480         retval = false;
481         goto cleanup;
482     }
483
484     if (!ftepp_macro_expand(ftepp, macro, params))
485         retval = false;
486
487 cleanup:
488     for (o = 0; o < vec_size(params); ++o)
489         macroparam_clean(&params[o]);
490     vec_free(params);
491     return retval;
492 }
493
494 /**
495  * #if - the FTEQCC way:
496  *    defined(FOO) => true if FOO was #defined regardless of parameters or contents
497  *    <numbers>    => True if the number is not 0
498  *    !<factor>    => True if the factor yields false
499  *    !!<factor>   => ERROR on 2 or more unary nots
500  *    <macro>      => becomes the macro's FIRST token regardless of parameters
501  *    <e> && <e>   => True if both expressions are true
502  *    <e> || <e>   => True if either expression is true
503  *    <string>     => False
504  *    <ident>      => False (remember for macros the <macro> rule applies instead)
505  * Unary + and - are weird and wrong in fteqcc so we don't allow them
506  * parenthesis in expressions are allowed
507  * parameter lists on macros are errors
508  * No mathematical calculations are executed
509  */
510 static bool ftepp_if_expr(ftepp_t *ftepp, bool *out)
511 {
512     ppmacro *macro;
513     bool     wasnot = false;
514
515     if (!ftepp_skipspace(ftepp))
516         return false;
517
518     while (ftepp->token == '!') {
519         wasnot = true;
520         ftepp_next(ftepp);
521         if (!ftepp_skipspace(ftepp))
522             return false;
523     }
524
525     switch (ftepp->token) {
526         case TOKEN_IDENT:
527         case TOKEN_TYPENAME:
528         case TOKEN_KEYWORD:
529             if (!strcmp(ftepp_tokval(ftepp), "defined")) {
530                 ftepp_next(ftepp);
531                 if (!ftepp_skipspace(ftepp))
532                     return false;
533                 if (ftepp->token != '(') {
534                     ftepp_error(ftepp, "`defined` keyword in #if requires a macro name in parenthesis");
535                     return false;
536                 }
537                 ftepp_next(ftepp);
538                 if (!ftepp_skipspace(ftepp))
539                     return false;
540                 if (ftepp->token != TOKEN_IDENT &&
541                     ftepp->token != TOKEN_TYPENAME &&
542                     ftepp->token != TOKEN_KEYWORD)
543                 {
544                     ftepp_error(ftepp, "defined() used on an unexpected token type");
545                     return false;
546                 }
547                 macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
548                 *out = !!macro;
549                 ftepp_next(ftepp);
550                 if (!ftepp_skipspace(ftepp))
551                     return false;
552                 if (ftepp->token != ')') {
553                     ftepp_error(ftepp, "expected closing paren");
554                     return false;
555                 }
556                 break;
557             }
558
559             macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
560             if (!macro || !vec_size(macro->output)) {
561                 *out = false;
562             } else {
563                 /* This does not expand recursively! */
564                 switch (macro->output[0]->token) {
565                     case TOKEN_INTCONST:
566                         *out = !!(macro->output[0]->constval.f);
567                         break;
568                     case TOKEN_FLOATCONST:
569                         *out = !!(macro->output[0]->constval.f);
570                         break;
571                     default:
572                         *out = false;
573                         break;
574                 }
575             }
576             break;
577         case TOKEN_STRINGCONST:
578             *out = false;
579             break;
580         case TOKEN_INTCONST:
581             *out = !!(ftepp->lex->tok.constval.i);
582             break;
583         case TOKEN_FLOATCONST:
584             *out = !!(ftepp->lex->tok.constval.f);
585             break;
586
587         case '(':
588             ftepp_next(ftepp);
589             if (!ftepp_if_expr(ftepp, out))
590                 return false;
591             if (ftepp->token != ')') {
592                 ftepp_error(ftepp, "expected closing paren in #if expression");
593                 return false;
594             }
595             break;
596
597         default:
598             ftepp_error(ftepp, "junk in #if");
599             return false;
600     }
601     if (wasnot)
602         *out = !*out;
603
604     ftepp->lex->flags.noops = false;
605     ftepp_next(ftepp);
606     if (!ftepp_skipspace(ftepp))
607         return false;
608     ftepp->lex->flags.noops = true;
609
610     if (ftepp->token == ')')
611         return true;
612
613     if (ftepp->token != TOKEN_OPERATOR)
614         return true;
615
616     if (!strcmp(ftepp_tokval(ftepp), "&&") ||
617         !strcmp(ftepp_tokval(ftepp), "||"))
618     {
619         bool next = false;
620         char opc  = ftepp_tokval(ftepp)[0];
621
622         ftepp_next(ftepp);
623         if (!ftepp_if_expr(ftepp, &next))
624             return false;
625
626         if (opc == '&')
627             *out = *out && next;
628         else
629             *out = *out || next;
630         return true;
631     }
632     else {
633         ftepp_error(ftepp, "junk after #if");
634         return false;
635     }
636 }
637
638 static bool ftepp_if(ftepp_t *ftepp, ppcondition *cond)
639 {
640     bool result = false;
641
642     memset(cond, 0, sizeof(*cond));
643     (void)ftepp_next(ftepp);
644
645     if (!ftepp_skipspace(ftepp))
646         return false;
647     if (ftepp->token == TOKEN_EOL) {
648         ftepp_error(ftepp, "expected expression for #if-directive");
649         return false;
650     }
651
652     if (!ftepp_if_expr(ftepp, &result))
653         return false;
654
655     cond->on = result;
656     return true;
657 }
658
659 /**
660  * ifdef is rather simple
661  */
662 static bool ftepp_ifdef(ftepp_t *ftepp, ppcondition *cond)
663 {
664     ppmacro *macro;
665     memset(cond, 0, sizeof(*cond));
666     (void)ftepp_next(ftepp);
667     if (!ftepp_skipspace(ftepp))
668         return false;
669
670     switch (ftepp->token) {
671         case TOKEN_IDENT:
672         case TOKEN_TYPENAME:
673         case TOKEN_KEYWORD:
674             macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
675             break;
676         default:
677             ftepp_error(ftepp, "expected macro name");
678             return false;
679     }
680
681     (void)ftepp_next(ftepp);
682     if (!ftepp_skipspace(ftepp))
683         return false;
684     if (ftepp->token != TOKEN_EOL) {
685         ftepp_error(ftepp, "stray tokens after #ifdef");
686         return false;
687     }
688     cond->on = !!macro;
689     return true;
690 }
691
692 /* Basic structure handlers */
693 static bool ftepp_else_allowed(ftepp_t *ftepp)
694 {
695     if (!vec_size(ftepp->conditions)) {
696         ftepp_error(ftepp, "#else without #if");
697         return false;
698     }
699     if (vec_last(ftepp->conditions).had_else) {
700         ftepp_error(ftepp, "multiple #else for a single #if");
701         return false;
702     }
703     return true;
704 }
705
706 static bool ftepp_hash(ftepp_t *ftepp)
707 {
708     ppcondition cond;
709     ppcondition *pc;
710
711     lex_ctx ctx = ftepp_ctx(ftepp);
712
713     if (!ftepp_skipspace(ftepp))
714         return false;
715
716     switch (ftepp->token) {
717         case TOKEN_KEYWORD:
718         case TOKEN_IDENT:
719         case TOKEN_TYPENAME:
720             if (!strcmp(ftepp_tokval(ftepp), "define")) {
721                 return ftepp_define(ftepp);
722             }
723             else if (!strcmp(ftepp_tokval(ftepp), "ifdef")) {
724                 if (!ftepp_ifdef(ftepp, &cond))
725                     return false;
726                 cond.was_on = cond.on;
727                 vec_push(ftepp->conditions, cond);
728                 ftepp->output_on = ftepp->output_on && cond.on;
729                 break;
730             }
731             else if (!strcmp(ftepp_tokval(ftepp), "ifndef")) {
732                 if (!ftepp_ifdef(ftepp, &cond))
733                     return false;
734                 cond.on = !cond.on;
735                 cond.was_on = cond.on;
736                 vec_push(ftepp->conditions, cond);
737                 ftepp->output_on = ftepp->output_on && cond.on;
738                 break;
739             }
740             else if (!strcmp(ftepp_tokval(ftepp), "elifdef")) {
741                 if (!ftepp_else_allowed(ftepp))
742                     return false;
743                 if (!ftepp_ifdef(ftepp, &cond))
744                     return false;
745                 pc = &vec_last(ftepp->conditions);
746                 pc->on     = !pc->was_on && cond.on;
747                 pc->was_on = pc->was_on || pc->on;
748                 ftepp_update_output_condition(ftepp);
749                 break;
750             }
751             else if (!strcmp(ftepp_tokval(ftepp), "elifndef")) {
752                 if (!ftepp_else_allowed(ftepp))
753                     return false;
754                 if (!ftepp_ifdef(ftepp, &cond))
755                     return false;
756                 cond.on = !cond.on;
757                 pc = &vec_last(ftepp->conditions);
758                 pc->on     = !pc->was_on && cond.on;
759                 pc->was_on = pc->was_on || pc->on;
760                 ftepp_update_output_condition(ftepp);
761                 break;
762             }
763             else if (!strcmp(ftepp_tokval(ftepp), "elif")) {
764                 if (!ftepp_else_allowed(ftepp))
765                     return false;
766                 if (!ftepp_if(ftepp, &cond))
767                     return false;
768                 pc = &vec_last(ftepp->conditions);
769                 pc->on     = !pc->was_on && cond.on;
770                 pc->was_on = pc->was_on  || pc->on;
771                 ftepp_update_output_condition(ftepp);
772                 break;
773             }
774             else if (!strcmp(ftepp_tokval(ftepp), "if")) {
775                 if (!ftepp_if(ftepp, &cond))
776                     return false;
777                 cond.was_on = cond.on;
778                 vec_push(ftepp->conditions, cond);
779                 ftepp->output_on = ftepp->output_on && cond.on;
780                 break;
781             }
782             else if (!strcmp(ftepp_tokval(ftepp), "else")) {
783                 if (!ftepp_else_allowed(ftepp))
784                     return false;
785                 pc = &vec_last(ftepp->conditions);
786                 pc->on = !pc->was_on;
787                 pc->had_else = true;
788                 ftepp_next(ftepp);
789                 ftepp_update_output_condition(ftepp);
790                 break;
791             }
792             else if (!strcmp(ftepp_tokval(ftepp), "endif")) {
793                 if (!vec_size(ftepp->conditions)) {
794                     ftepp_error(ftepp, "#endif without #if");
795                     return false;
796                 }
797                 vec_pop(ftepp->conditions);
798                 ftepp_next(ftepp);
799                 ftepp_update_output_condition(ftepp);
800                 break;
801             }
802             else {
803                 ftepp_error(ftepp, "unrecognized preprocessor directive: `%s`", ftepp_tokval(ftepp));
804                 return false;
805             }
806             break;
807         default:
808             ftepp_error(ftepp, "unexpected preprocessor token: `%s`", ftepp_tokval(ftepp));
809             return false;
810         case TOKEN_EOL:
811             ftepp_errorat(ftepp, ctx, "empty preprocessor directive");
812             return false;
813         case TOKEN_EOF:
814             ftepp_error(ftepp, "missing newline at end of file", ftepp_tokval(ftepp));
815             return false;
816
817         /* Builtins! Don't forget the builtins! */
818         case TOKEN_INTCONST:
819         case TOKEN_FLOATCONST:
820             ftepp_out(ftepp, "#", false);
821             return true;
822     }
823     if (!ftepp_skipspace(ftepp))
824         return false;
825     return true;
826 }
827
828 static bool ftepp_preprocess(ftepp_t *ftepp)
829 {
830     ppmacro *macro;
831     bool     newline = true;
832
833     ftepp->lex->flags.preprocessing = true;
834     ftepp->lex->flags.mergelines    = false;
835     ftepp->lex->flags.noops         = true;
836
837     ftepp_next(ftepp);
838     do
839     {
840         if (ftepp->token >= TOKEN_EOF)
841             break;
842 #if 0
843         ftepp->newline = newline;
844         newline = false;
845 #else
846         /* For the sake of FTE compatibility... FU, really */
847         ftepp->newline = newline = true;
848 #endif
849
850         switch (ftepp->token) {
851             case TOKEN_KEYWORD:
852             case TOKEN_IDENT:
853             case TOKEN_TYPENAME:
854                 macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
855                 if (!macro) {
856                     ftepp_out(ftepp, ftepp_tokval(ftepp), false);
857                     ftepp_next(ftepp);
858                     break;
859                 }
860                 if (!ftepp_macro_call(ftepp, macro))
861                     ftepp->token = TOKEN_ERROR;
862                 break;
863             case '#':
864                 if (!ftepp->newline) {
865                     ftepp_out(ftepp, ftepp_tokval(ftepp), false);
866                     ftepp_next(ftepp);
867                     break;
868                 }
869                 ftepp->lex->flags.mergelines = true;
870                 if (ftepp_next(ftepp) >= TOKEN_EOF) {
871                     ftepp_error(ftepp, "error in preprocessor directive");
872                     ftepp->token = TOKEN_ERROR;
873                     break;
874                 }
875                 if (!ftepp_hash(ftepp))
876                     ftepp->token = TOKEN_ERROR;
877                 ftepp->lex->flags.mergelines = false;
878                 break;
879             case TOKEN_EOL:
880                 newline = true;
881                 ftepp_out(ftepp, "\n", true);
882                 ftepp_next(ftepp);
883                 break;
884             default:
885                 ftepp_out(ftepp, ftepp_tokval(ftepp), false);
886                 ftepp_next(ftepp);
887                 break;
888         }
889     } while (!ftepp->errors && ftepp->token < TOKEN_EOF);
890
891     newline = ftepp->token == TOKEN_EOF;
892     return newline;
893 }
894
895 bool ftepp_preprocess_file(const char *filename)
896 {
897     ftepp_t *ftepp = ftepp_init();
898     ftepp->lex = lex_open(filename);
899     if (!ftepp->lex) {
900         con_out("failed to open file \"%s\"\n", filename);
901         return false;
902     }
903     if (!ftepp_preprocess(ftepp)) {
904         ftepp_delete(ftepp);
905         return false;
906     }
907     ftepp_delete(ftepp);
908     return true;
909 }
910
911 bool ftepp_preprocess_string(const char *name, const char *str)
912 {
913     ftepp_t *ftepp = ftepp_init();
914     ftepp->lex = lex_open_string(str, strlen(str), name);
915     if (!ftepp->lex) {
916         con_out("failed to create lexer for string \"%s\"\n", name);
917         return false;
918     }
919     if (!ftepp_preprocess(ftepp)) {
920         ftepp_delete(ftepp);
921         return false;
922     }
923     ftepp_delete(ftepp);
924     return true;
925 }