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