]> de.git.xonotic.org Git - xonotic/gmqcc.git/blob - ftepp.c
don't error on non-EOL EOF at the end of a macro line
[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     /* recursive expansion can cause EOFs here */
266     if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) {
267         ftepp_error(ftepp, "unexpected junk after macro or unexpected end of file");
268         return false;
269     }
270     return true;
271 }
272
273 static bool ftepp_define(ftepp_t *ftepp)
274 {
275     ppmacro *macro;
276     (void)ftepp_next(ftepp);
277     if (!ftepp_skipspace(ftepp))
278         return false;
279
280     switch (ftepp->token) {
281         case TOKEN_IDENT:
282         case TOKEN_TYPENAME:
283         case TOKEN_KEYWORD:
284             macro = ppmacro_new(ftepp_ctx(ftepp), ftepp_tokval(ftepp));
285             break;
286         default:
287             ftepp_error(ftepp, "expected macro name");
288             return false;
289     }
290
291     (void)ftepp_next(ftepp);
292
293     if (ftepp->token == '(') {
294         macro->has_params = true;
295         if (!ftepp_define_params(ftepp, macro))
296             return false;
297     }
298
299     if (!ftepp_skipspace(ftepp))
300         return false;
301
302     if (!ftepp_define_body(ftepp, macro))
303         return false;
304
305     vec_push(ftepp->macros, macro);
306     return true;
307 }
308
309 /**
310  * When a macro is used we have to handle parameters as well
311  * as special-concatenation via ## or stringification via #
312  *
313  * Note: parenthesis can nest, so FOO((a),b) is valid, but only
314  * this kind of parens. Curly braces or [] don't count towards the
315  * paren-level.
316  */
317 typedef struct {
318     pptoken **tokens;
319 } macroparam;
320
321 static void macroparam_clean(macroparam *self)
322 {
323     size_t i;
324     for (i = 0; i < vec_size(self->tokens); ++i)
325         pptoken_delete(self->tokens[i]);
326     vec_free(self->tokens);
327 }
328
329 /* need to leave the last token up */
330 static bool ftepp_macro_call_params(ftepp_t *ftepp, macroparam **out_params)
331 {
332     macroparam *params = NULL;
333     pptoken    *ptok;
334     macroparam  mp;
335     size_t      parens = 0;
336     size_t      i;
337
338     if (!ftepp_skipallwhite(ftepp))
339         return false;
340     while (ftepp->token != ')') {
341         mp.tokens = NULL;
342         if (!ftepp_skipallwhite(ftepp))
343             return false;
344         while (parens || ftepp->token != ',') {
345             if (ftepp->token == '(')
346                 ++parens;
347             else if (ftepp->token == ')') {
348                 if (!parens)
349                     break;
350                 --parens;
351             }
352             ptok = pptoken_make(ftepp);
353             vec_push(mp.tokens, ptok);
354             if (ftepp_next(ftepp) >= TOKEN_EOF) {
355                 ftepp_error(ftepp, "unexpected EOF in macro call");
356                 goto on_error;
357             }
358         }
359         vec_push(params, mp);
360         mp.tokens = NULL;
361         if (ftepp->token == ')')
362             break;
363         if (ftepp->token != ',') {
364             ftepp_error(ftepp, "expected closing paren or comma in macro call");
365             goto on_error;
366         }
367         if (ftepp_next(ftepp) >= TOKEN_EOF) {
368             ftepp_error(ftepp, "unexpected EOF in macro call");
369             goto on_error;
370         }
371     }
372     /* need to leave that up
373     if (ftepp_next(ftepp) >= TOKEN_EOF) {
374         ftepp_error(ftepp, "unexpected EOF in macro call");
375         goto on_error;
376     }
377     */
378     *out_params = params;
379     return true;
380
381 on_error:
382     if (mp.tokens)
383         macroparam_clean(&mp);
384     for (i = 0; i < vec_size(params); ++i)
385         macroparam_clean(&params[i]);
386     vec_free(params);
387     return false;
388 }
389
390 static bool macro_params_find(ppmacro *macro, const char *name, size_t *idx)
391 {
392     size_t i;
393     for (i = 0; i < vec_size(macro->params); ++i) {
394         if (!strcmp(macro->params[i], name)) {
395             *idx = i;
396             return true;
397         }
398     }
399     return false;
400 }
401
402 static bool ftepp_preprocess(ftepp_t *ftepp);
403 static bool ftepp_macro_expand(ftepp_t *ftepp, ppmacro *macro, macroparam *params)
404 {
405     char     *old_string = ftepp->output;
406     bool      old_string_flag = ftepp->output_string;
407     lex_file *old_lexer = ftepp->lex;
408     bool retval = true;
409
410     size_t    o, pi, pv;
411     lex_file *inlex;
412
413     /* really ... */
414     if (!vec_size(macro->output))
415         return true;
416
417     ftepp->output = NULL;
418     ftepp->output_string = true;
419     for (o = 0; o < vec_size(macro->output); ++o) {
420         pptoken *out = macro->output[o];
421         switch (out->token) {
422             case TOKEN_IDENT:
423             case TOKEN_TYPENAME:
424             case TOKEN_KEYWORD:
425                 if (!macro_params_find(macro, out->value, &pi)) {
426                     ftepp_out(ftepp, out->value, false);
427                     break;
428                 } else {
429                     for (pv = 0; pv < vec_size(params[pi].tokens); ++pv) {
430                         out = params[pi].tokens[pv];
431                         if (out->token == TOKEN_EOL)
432                             ftepp_out(ftepp, "\n", false);
433                         else
434                             ftepp_out(ftepp, out->value, false);
435                     }
436                 }
437                 break;
438             case TOKEN_EOL:
439                 ftepp_out(ftepp, "\n", false);
440                 break;
441             default:
442                 ftepp_out(ftepp, out->value, false);
443                 break;
444         }
445     }
446     vec_push(ftepp->output, 0);
447     /* Now run the preprocessor recursively on this string buffer */
448     /*
449     printf("__________\n%s\n=========\n", ftepp->output);
450     */
451     inlex = lex_open_string(ftepp->output, vec_size(ftepp->output)-1, ftepp->lex->name);
452     if (!inlex) {
453         ftepp_error(ftepp, "internal error: failed to instantiate lexer");
454         retval = false;
455         goto cleanup;
456     }
457     ftepp->output        = old_string;
458     ftepp->output_string = old_string_flag;
459     ftepp->lex = inlex;
460     if (!ftepp_preprocess(ftepp)) {
461         retval = false;
462         goto cleanup;
463     }
464
465 cleanup:
466     ftepp->lex           = old_lexer;
467     ftepp->output        = old_string;
468     ftepp->output_string = old_string_flag;
469     return retval;
470 }
471
472 static bool ftepp_macro_call(ftepp_t *ftepp, ppmacro *macro)
473 {
474     size_t     o;
475     macroparam *params = NULL;
476     bool        retval = true;
477
478     if (!macro->has_params) {
479         if (!ftepp_macro_expand(ftepp, macro, NULL))
480             return false;
481         ftepp_next(ftepp);
482         return true;
483     }
484     ftepp_next(ftepp);
485
486     if (!ftepp_skipallwhite(ftepp))
487         return false;
488
489     if (ftepp->token != '(') {
490         ftepp_error(ftepp, "expected macro parameters in parenthesis");
491         return false;
492     }
493
494     ftepp_next(ftepp);
495     if (!ftepp_macro_call_params(ftepp, &params))
496         return false;
497
498     if (vec_size(params) != vec_size(macro->params)) {
499         ftepp_error(ftepp, "macro %s expects %u paramteters, %u provided", macro->name,
500                     (unsigned int)vec_size(macro->params),
501                     (unsigned int)vec_size(params));
502         retval = false;
503         goto cleanup;
504     }
505
506     if (!ftepp_macro_expand(ftepp, macro, params))
507         retval = false;
508     ftepp_next(ftepp);
509
510 cleanup:
511     for (o = 0; o < vec_size(params); ++o)
512         macroparam_clean(&params[o]);
513     vec_free(params);
514     return retval;
515 }
516
517 /**
518  * #if - the FTEQCC way:
519  *    defined(FOO) => true if FOO was #defined regardless of parameters or contents
520  *    <numbers>    => True if the number is not 0
521  *    !<factor>    => True if the factor yields false
522  *    !!<factor>   => ERROR on 2 or more unary nots
523  *    <macro>      => becomes the macro's FIRST token regardless of parameters
524  *    <e> && <e>   => True if both expressions are true
525  *    <e> || <e>   => True if either expression is true
526  *    <string>     => False
527  *    <ident>      => False (remember for macros the <macro> rule applies instead)
528  * Unary + and - are weird and wrong in fteqcc so we don't allow them
529  * parenthesis in expressions are allowed
530  * parameter lists on macros are errors
531  * No mathematical calculations are executed
532  */
533 static bool ftepp_if_expr(ftepp_t *ftepp, bool *out)
534 {
535     ppmacro *macro;
536     bool     wasnot = false;
537
538     if (!ftepp_skipspace(ftepp))
539         return false;
540
541     while (ftepp->token == '!') {
542         wasnot = true;
543         ftepp_next(ftepp);
544         if (!ftepp_skipspace(ftepp))
545             return false;
546     }
547
548     switch (ftepp->token) {
549         case TOKEN_IDENT:
550         case TOKEN_TYPENAME:
551         case TOKEN_KEYWORD:
552             if (!strcmp(ftepp_tokval(ftepp), "defined")) {
553                 ftepp_next(ftepp);
554                 if (!ftepp_skipspace(ftepp))
555                     return false;
556                 if (ftepp->token != '(') {
557                     ftepp_error(ftepp, "`defined` keyword in #if requires a macro name in parenthesis");
558                     return false;
559                 }
560                 ftepp_next(ftepp);
561                 if (!ftepp_skipspace(ftepp))
562                     return false;
563                 if (ftepp->token != TOKEN_IDENT &&
564                     ftepp->token != TOKEN_TYPENAME &&
565                     ftepp->token != TOKEN_KEYWORD)
566                 {
567                     ftepp_error(ftepp, "defined() used on an unexpected token type");
568                     return false;
569                 }
570                 macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
571                 *out = !!macro;
572                 ftepp_next(ftepp);
573                 if (!ftepp_skipspace(ftepp))
574                     return false;
575                 if (ftepp->token != ')') {
576                     ftepp_error(ftepp, "expected closing paren");
577                     return false;
578                 }
579                 break;
580             }
581
582             macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
583             if (!macro || !vec_size(macro->output)) {
584                 *out = false;
585             } else {
586                 /* This does not expand recursively! */
587                 switch (macro->output[0]->token) {
588                     case TOKEN_INTCONST:
589                         *out = !!(macro->output[0]->constval.f);
590                         break;
591                     case TOKEN_FLOATCONST:
592                         *out = !!(macro->output[0]->constval.f);
593                         break;
594                     default:
595                         *out = false;
596                         break;
597                 }
598             }
599             break;
600         case TOKEN_STRINGCONST:
601             *out = false;
602             break;
603         case TOKEN_INTCONST:
604             *out = !!(ftepp->lex->tok.constval.i);
605             break;
606         case TOKEN_FLOATCONST:
607             *out = !!(ftepp->lex->tok.constval.f);
608             break;
609
610         case '(':
611             ftepp_next(ftepp);
612             if (!ftepp_if_expr(ftepp, out))
613                 return false;
614             if (ftepp->token != ')') {
615                 ftepp_error(ftepp, "expected closing paren in #if expression");
616                 return false;
617             }
618             break;
619
620         default:
621             ftepp_error(ftepp, "junk in #if");
622             return false;
623     }
624     if (wasnot)
625         *out = !*out;
626
627     ftepp->lex->flags.noops = false;
628     ftepp_next(ftepp);
629     if (!ftepp_skipspace(ftepp))
630         return false;
631     ftepp->lex->flags.noops = true;
632
633     if (ftepp->token == ')')
634         return true;
635
636     if (ftepp->token != TOKEN_OPERATOR)
637         return true;
638
639     if (!strcmp(ftepp_tokval(ftepp), "&&") ||
640         !strcmp(ftepp_tokval(ftepp), "||"))
641     {
642         bool next = false;
643         char opc  = ftepp_tokval(ftepp)[0];
644
645         ftepp_next(ftepp);
646         if (!ftepp_if_expr(ftepp, &next))
647             return false;
648
649         if (opc == '&')
650             *out = *out && next;
651         else
652             *out = *out || next;
653         return true;
654     }
655     else {
656         ftepp_error(ftepp, "junk after #if");
657         return false;
658     }
659 }
660
661 static bool ftepp_if(ftepp_t *ftepp, ppcondition *cond)
662 {
663     bool result = false;
664
665     memset(cond, 0, sizeof(*cond));
666     (void)ftepp_next(ftepp);
667
668     if (!ftepp_skipspace(ftepp))
669         return false;
670     if (ftepp->token == TOKEN_EOL) {
671         ftepp_error(ftepp, "expected expression for #if-directive");
672         return false;
673     }
674
675     if (!ftepp_if_expr(ftepp, &result))
676         return false;
677
678     cond->on = result;
679     return true;
680 }
681
682 /**
683  * ifdef is rather simple
684  */
685 static bool ftepp_ifdef(ftepp_t *ftepp, ppcondition *cond)
686 {
687     ppmacro *macro;
688     memset(cond, 0, sizeof(*cond));
689     (void)ftepp_next(ftepp);
690     if (!ftepp_skipspace(ftepp))
691         return false;
692
693     switch (ftepp->token) {
694         case TOKEN_IDENT:
695         case TOKEN_TYPENAME:
696         case TOKEN_KEYWORD:
697             macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
698             break;
699         default:
700             ftepp_error(ftepp, "expected macro name");
701             return false;
702     }
703
704     (void)ftepp_next(ftepp);
705     if (!ftepp_skipspace(ftepp))
706         return false;
707     if (ftepp->token != TOKEN_EOL) {
708         ftepp_error(ftepp, "stray tokens after #ifdef");
709         return false;
710     }
711     cond->on = !!macro;
712     return true;
713 }
714
715 /* Basic structure handlers */
716 static bool ftepp_else_allowed(ftepp_t *ftepp)
717 {
718     if (!vec_size(ftepp->conditions)) {
719         ftepp_error(ftepp, "#else without #if");
720         return false;
721     }
722     if (vec_last(ftepp->conditions).had_else) {
723         ftepp_error(ftepp, "multiple #else for a single #if");
724         return false;
725     }
726     return true;
727 }
728
729 static bool ftepp_hash(ftepp_t *ftepp)
730 {
731     ppcondition cond;
732     ppcondition *pc;
733
734     lex_ctx ctx = ftepp_ctx(ftepp);
735
736     if (!ftepp_skipspace(ftepp))
737         return false;
738
739     switch (ftepp->token) {
740         case TOKEN_KEYWORD:
741         case TOKEN_IDENT:
742         case TOKEN_TYPENAME:
743             if (!strcmp(ftepp_tokval(ftepp), "define")) {
744                 return ftepp_define(ftepp);
745             }
746             else if (!strcmp(ftepp_tokval(ftepp), "ifdef")) {
747                 if (!ftepp_ifdef(ftepp, &cond))
748                     return false;
749                 cond.was_on = cond.on;
750                 vec_push(ftepp->conditions, cond);
751                 ftepp->output_on = ftepp->output_on && cond.on;
752                 break;
753             }
754             else if (!strcmp(ftepp_tokval(ftepp), "ifndef")) {
755                 if (!ftepp_ifdef(ftepp, &cond))
756                     return false;
757                 cond.on = !cond.on;
758                 cond.was_on = cond.on;
759                 vec_push(ftepp->conditions, cond);
760                 ftepp->output_on = ftepp->output_on && cond.on;
761                 break;
762             }
763             else if (!strcmp(ftepp_tokval(ftepp), "elifdef")) {
764                 if (!ftepp_else_allowed(ftepp))
765                     return false;
766                 if (!ftepp_ifdef(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), "elifndef")) {
775                 if (!ftepp_else_allowed(ftepp))
776                     return false;
777                 if (!ftepp_ifdef(ftepp, &cond))
778                     return false;
779                 cond.on = !cond.on;
780                 pc = &vec_last(ftepp->conditions);
781                 pc->on     = !pc->was_on && cond.on;
782                 pc->was_on = pc->was_on || pc->on;
783                 ftepp_update_output_condition(ftepp);
784                 break;
785             }
786             else if (!strcmp(ftepp_tokval(ftepp), "elif")) {
787                 if (!ftepp_else_allowed(ftepp))
788                     return false;
789                 if (!ftepp_if(ftepp, &cond))
790                     return false;
791                 pc = &vec_last(ftepp->conditions);
792                 pc->on     = !pc->was_on && cond.on;
793                 pc->was_on = pc->was_on  || pc->on;
794                 ftepp_update_output_condition(ftepp);
795                 break;
796             }
797             else if (!strcmp(ftepp_tokval(ftepp), "if")) {
798                 if (!ftepp_if(ftepp, &cond))
799                     return false;
800                 cond.was_on = cond.on;
801                 vec_push(ftepp->conditions, cond);
802                 ftepp->output_on = ftepp->output_on && cond.on;
803                 break;
804             }
805             else if (!strcmp(ftepp_tokval(ftepp), "else")) {
806                 if (!ftepp_else_allowed(ftepp))
807                     return false;
808                 pc = &vec_last(ftepp->conditions);
809                 pc->on = !pc->was_on;
810                 pc->had_else = true;
811                 ftepp_next(ftepp);
812                 ftepp_update_output_condition(ftepp);
813                 break;
814             }
815             else if (!strcmp(ftepp_tokval(ftepp), "endif")) {
816                 if (!vec_size(ftepp->conditions)) {
817                     ftepp_error(ftepp, "#endif without #if");
818                     return false;
819                 }
820                 vec_pop(ftepp->conditions);
821                 ftepp_next(ftepp);
822                 ftepp_update_output_condition(ftepp);
823                 break;
824             }
825             else {
826                 ftepp_error(ftepp, "unrecognized preprocessor directive: `%s`", ftepp_tokval(ftepp));
827                 return false;
828             }
829             break;
830         default:
831             ftepp_error(ftepp, "unexpected preprocessor token: `%s`", ftepp_tokval(ftepp));
832             return false;
833         case TOKEN_EOL:
834             ftepp_errorat(ftepp, ctx, "empty preprocessor directive");
835             return false;
836         case TOKEN_EOF:
837             ftepp_error(ftepp, "missing newline at end of file", ftepp_tokval(ftepp));
838             return false;
839
840         /* Builtins! Don't forget the builtins! */
841         case TOKEN_INTCONST:
842         case TOKEN_FLOATCONST:
843             ftepp_out(ftepp, "#", false);
844             return true;
845     }
846     if (!ftepp_skipspace(ftepp))
847         return false;
848     return true;
849 }
850
851 static bool ftepp_preprocess(ftepp_t *ftepp)
852 {
853     ppmacro *macro;
854     bool     newline = true;
855
856     ftepp->lex->flags.preprocessing = true;
857     ftepp->lex->flags.mergelines    = false;
858     ftepp->lex->flags.noops         = true;
859
860     ftepp_next(ftepp);
861     do
862     {
863         if (ftepp->token >= TOKEN_EOF)
864             break;
865 #if 0
866         ftepp->newline = newline;
867         newline = false;
868 #else
869         /* For the sake of FTE compatibility... FU, really */
870         ftepp->newline = newline = true;
871 #endif
872
873         switch (ftepp->token) {
874             case TOKEN_KEYWORD:
875             case TOKEN_IDENT:
876             case TOKEN_TYPENAME:
877                 macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
878                 if (!macro) {
879                     ftepp_out(ftepp, ftepp_tokval(ftepp), false);
880                     ftepp_next(ftepp);
881                     break;
882                 }
883                 if (!ftepp_macro_call(ftepp, macro))
884                     ftepp->token = TOKEN_ERROR;
885                 break;
886             case '#':
887                 if (!ftepp->newline) {
888                     ftepp_out(ftepp, ftepp_tokval(ftepp), false);
889                     ftepp_next(ftepp);
890                     break;
891                 }
892                 ftepp->lex->flags.mergelines = true;
893                 if (ftepp_next(ftepp) >= TOKEN_EOF) {
894                     ftepp_error(ftepp, "error in preprocessor directive");
895                     ftepp->token = TOKEN_ERROR;
896                     break;
897                 }
898                 if (!ftepp_hash(ftepp))
899                     ftepp->token = TOKEN_ERROR;
900                 ftepp->lex->flags.mergelines = false;
901                 break;
902             case TOKEN_EOL:
903                 newline = true;
904                 ftepp_out(ftepp, "\n", true);
905                 ftepp_next(ftepp);
906                 break;
907             default:
908                 ftepp_out(ftepp, ftepp_tokval(ftepp), false);
909                 ftepp_next(ftepp);
910                 break;
911         }
912     } while (!ftepp->errors && ftepp->token < TOKEN_EOF);
913
914     newline = ftepp->token == TOKEN_EOF;
915     return newline;
916 }
917
918 bool ftepp_preprocess_file(const char *filename)
919 {
920     ftepp_t *ftepp = ftepp_init();
921     ftepp->lex = lex_open(filename);
922     if (!ftepp->lex) {
923         con_out("failed to open file \"%s\"\n", filename);
924         return false;
925     }
926     if (!ftepp_preprocess(ftepp)) {
927         ftepp_delete(ftepp);
928         return false;
929     }
930     ftepp_delete(ftepp);
931     return true;
932 }
933
934 bool ftepp_preprocess_string(const char *name, const char *str)
935 {
936     ftepp_t *ftepp = ftepp_init();
937     ftepp->lex = lex_open_string(str, strlen(str), name);
938     if (!ftepp->lex) {
939         con_out("failed to create lexer for string \"%s\"\n", name);
940         return false;
941     }
942     if (!ftepp_preprocess(ftepp)) {
943         ftepp_delete(ftepp);
944         return false;
945     }
946     ftepp_delete(ftepp);
947     return true;
948 }