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