]> 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         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_init()
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         retval = false;
513         goto cleanup;
514     }
515
516 cleanup:
517     ftepp->lex           = old_lexer;
518     ftepp->output        = old_string;
519     ftepp->output_string = old_string_flag;
520     return retval;
521 }
522
523 static bool ftepp_macro_call(ftepp_t *ftepp, ppmacro *macro)
524 {
525     size_t     o;
526     macroparam *params = NULL;
527     bool        retval = true;
528
529     if (!macro->has_params) {
530         if (!ftepp_macro_expand(ftepp, macro, NULL))
531             return false;
532         ftepp_next(ftepp);
533         return true;
534     }
535     ftepp_next(ftepp);
536
537     if (!ftepp_skipallwhite(ftepp))
538         return false;
539
540     if (ftepp->token != '(') {
541         ftepp_error(ftepp, "expected macro parameters in parenthesis");
542         return false;
543     }
544
545     ftepp_next(ftepp);
546     if (!ftepp_macro_call_params(ftepp, &params))
547         return false;
548
549     if (vec_size(params) != vec_size(macro->params)) {
550         ftepp_error(ftepp, "macro %s expects %u paramteters, %u provided", macro->name,
551                     (unsigned int)vec_size(macro->params),
552                     (unsigned int)vec_size(params));
553         retval = false;
554         goto cleanup;
555     }
556
557     if (!ftepp_macro_expand(ftepp, macro, params))
558         retval = false;
559     ftepp_next(ftepp);
560
561 cleanup:
562     for (o = 0; o < vec_size(params); ++o)
563         macroparam_clean(&params[o]);
564     vec_free(params);
565     return retval;
566 }
567
568 /**
569  * #if - the FTEQCC way:
570  *    defined(FOO) => true if FOO was #defined regardless of parameters or contents
571  *    <numbers>    => True if the number is not 0
572  *    !<factor>    => True if the factor yields false
573  *    !!<factor>   => ERROR on 2 or more unary nots
574  *    <macro>      => becomes the macro's FIRST token regardless of parameters
575  *    <e> && <e>   => True if both expressions are true
576  *    <e> || <e>   => True if either expression is true
577  *    <string>     => False
578  *    <ident>      => False (remember for macros the <macro> rule applies instead)
579  * Unary + and - are weird and wrong in fteqcc so we don't allow them
580  * parenthesis in expressions are allowed
581  * parameter lists on macros are errors
582  * No mathematical calculations are executed
583  */
584 static bool ftepp_if_expr(ftepp_t *ftepp, bool *out)
585 {
586     ppmacro *macro;
587     bool     wasnot = false;
588
589     if (!ftepp_skipspace(ftepp))
590         return false;
591
592     while (ftepp->token == '!') {
593         wasnot = true;
594         ftepp_next(ftepp);
595         if (!ftepp_skipspace(ftepp))
596             return false;
597     }
598
599     switch (ftepp->token) {
600         case TOKEN_IDENT:
601         case TOKEN_TYPENAME:
602         case TOKEN_KEYWORD:
603             if (!strcmp(ftepp_tokval(ftepp), "defined")) {
604                 ftepp_next(ftepp);
605                 if (!ftepp_skipspace(ftepp))
606                     return false;
607                 if (ftepp->token != '(') {
608                     ftepp_error(ftepp, "`defined` keyword in #if requires a macro name in parenthesis");
609                     return false;
610                 }
611                 ftepp_next(ftepp);
612                 if (!ftepp_skipspace(ftepp))
613                     return false;
614                 if (ftepp->token != TOKEN_IDENT &&
615                     ftepp->token != TOKEN_TYPENAME &&
616                     ftepp->token != TOKEN_KEYWORD)
617                 {
618                     ftepp_error(ftepp, "defined() used on an unexpected token type");
619                     return false;
620                 }
621                 macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
622                 *out = !!macro;
623                 ftepp_next(ftepp);
624                 if (!ftepp_skipspace(ftepp))
625                     return false;
626                 if (ftepp->token != ')') {
627                     ftepp_error(ftepp, "expected closing paren");
628                     return false;
629                 }
630                 break;
631             }
632
633             macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
634             if (!macro || !vec_size(macro->output)) {
635                 *out = false;
636             } else {
637                 /* This does not expand recursively! */
638                 switch (macro->output[0]->token) {
639                     case TOKEN_INTCONST:
640                         *out = !!(macro->output[0]->constval.f);
641                         break;
642                     case TOKEN_FLOATCONST:
643                         *out = !!(macro->output[0]->constval.f);
644                         break;
645                     default:
646                         *out = false;
647                         break;
648                 }
649             }
650             break;
651         case TOKEN_STRINGCONST:
652             *out = false;
653             break;
654         case TOKEN_INTCONST:
655             *out = !!(ftepp->lex->tok.constval.i);
656             break;
657         case TOKEN_FLOATCONST:
658             *out = !!(ftepp->lex->tok.constval.f);
659             break;
660
661         case '(':
662             ftepp_next(ftepp);
663             if (!ftepp_if_expr(ftepp, out))
664                 return false;
665             if (ftepp->token != ')') {
666                 ftepp_error(ftepp, "expected closing paren in #if expression");
667                 return false;
668             }
669             break;
670
671         default:
672             ftepp_error(ftepp, "junk in #if");
673             return false;
674     }
675     if (wasnot)
676         *out = !*out;
677
678     ftepp->lex->flags.noops = false;
679     ftepp_next(ftepp);
680     if (!ftepp_skipspace(ftepp))
681         return false;
682     ftepp->lex->flags.noops = true;
683
684     if (ftepp->token == ')')
685         return true;
686
687     if (ftepp->token != TOKEN_OPERATOR)
688         return true;
689
690     if (!strcmp(ftepp_tokval(ftepp), "&&") ||
691         !strcmp(ftepp_tokval(ftepp), "||"))
692     {
693         bool next = false;
694         char opc  = ftepp_tokval(ftepp)[0];
695
696         ftepp_next(ftepp);
697         if (!ftepp_if_expr(ftepp, &next))
698             return false;
699
700         if (opc == '&')
701             *out = *out && next;
702         else
703             *out = *out || next;
704         return true;
705     }
706     else {
707         ftepp_error(ftepp, "junk after #if");
708         return false;
709     }
710 }
711
712 static bool ftepp_if(ftepp_t *ftepp, ppcondition *cond)
713 {
714     bool result = false;
715
716     memset(cond, 0, sizeof(*cond));
717     (void)ftepp_next(ftepp);
718
719     if (!ftepp_skipspace(ftepp))
720         return false;
721     if (ftepp->token == TOKEN_EOL) {
722         ftepp_error(ftepp, "expected expression for #if-directive");
723         return false;
724     }
725
726     if (!ftepp_if_expr(ftepp, &result))
727         return false;
728
729     cond->on = result;
730     return true;
731 }
732
733 /**
734  * ifdef is rather simple
735  */
736 static bool ftepp_ifdef(ftepp_t *ftepp, ppcondition *cond)
737 {
738     ppmacro *macro;
739     memset(cond, 0, sizeof(*cond));
740     (void)ftepp_next(ftepp);
741     if (!ftepp_skipspace(ftepp))
742         return false;
743
744     switch (ftepp->token) {
745         case TOKEN_IDENT:
746         case TOKEN_TYPENAME:
747         case TOKEN_KEYWORD:
748             macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
749             break;
750         default:
751             ftepp_error(ftepp, "expected macro name");
752             return false;
753     }
754
755     (void)ftepp_next(ftepp);
756     if (!ftepp_skipspace(ftepp))
757         return false;
758     /* relaxing this condition
759     if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) {
760         ftepp_error(ftepp, "stray tokens after #ifdef");
761         return false;
762     }
763     */
764     cond->on = !!macro;
765     return true;
766 }
767
768 /**
769  * undef is also simple
770  */
771 static bool ftepp_undef(ftepp_t *ftepp)
772 {
773     (void)ftepp_next(ftepp);
774     if (!ftepp_skipspace(ftepp))
775         return false;
776
777     if (ftepp->output_on) {
778         switch (ftepp->token) {
779             case TOKEN_IDENT:
780             case TOKEN_TYPENAME:
781             case TOKEN_KEYWORD:
782                 ftepp_macro_delete(ftepp, ftepp_tokval(ftepp));
783                 break;
784             default:
785                 ftepp_error(ftepp, "expected macro name");
786                 return false;
787         }
788     }
789
790     (void)ftepp_next(ftepp);
791     if (!ftepp_skipspace(ftepp))
792         return false;
793     /* relaxing this condition
794     if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) {
795         ftepp_error(ftepp, "stray tokens after #ifdef");
796         return false;
797     }
798     */
799     return true;
800 }
801
802 /* Basic structure handlers */
803 static bool ftepp_else_allowed(ftepp_t *ftepp)
804 {
805     if (!vec_size(ftepp->conditions)) {
806         ftepp_error(ftepp, "#else without #if");
807         return false;
808     }
809     if (vec_last(ftepp->conditions).had_else) {
810         ftepp_error(ftepp, "multiple #else for a single #if");
811         return false;
812     }
813     return true;
814 }
815
816 static bool ftepp_hash(ftepp_t *ftepp)
817 {
818     ppcondition cond;
819     ppcondition *pc;
820
821     lex_ctx ctx = ftepp_ctx(ftepp);
822
823     if (!ftepp_skipspace(ftepp))
824         return false;
825
826     switch (ftepp->token) {
827         case TOKEN_KEYWORD:
828         case TOKEN_IDENT:
829         case TOKEN_TYPENAME:
830             if (!strcmp(ftepp_tokval(ftepp), "define")) {
831                 return ftepp_define(ftepp);
832             }
833             else if (!strcmp(ftepp_tokval(ftepp), "undef")) {
834                 return ftepp_undef(ftepp);
835             }
836             else if (!strcmp(ftepp_tokval(ftepp), "ifdef")) {
837                 if (!ftepp_ifdef(ftepp, &cond))
838                     return false;
839                 cond.was_on = cond.on;
840                 vec_push(ftepp->conditions, cond);
841                 ftepp->output_on = ftepp->output_on && cond.on;
842                 break;
843             }
844             else if (!strcmp(ftepp_tokval(ftepp), "ifndef")) {
845                 if (!ftepp_ifdef(ftepp, &cond))
846                     return false;
847                 cond.on = !cond.on;
848                 cond.was_on = cond.on;
849                 vec_push(ftepp->conditions, cond);
850                 ftepp->output_on = ftepp->output_on && cond.on;
851                 break;
852             }
853             else if (!strcmp(ftepp_tokval(ftepp), "elifdef")) {
854                 if (!ftepp_else_allowed(ftepp))
855                     return false;
856                 if (!ftepp_ifdef(ftepp, &cond))
857                     return false;
858                 pc = &vec_last(ftepp->conditions);
859                 pc->on     = !pc->was_on && cond.on;
860                 pc->was_on = pc->was_on || pc->on;
861                 ftepp_update_output_condition(ftepp);
862                 break;
863             }
864             else if (!strcmp(ftepp_tokval(ftepp), "elifndef")) {
865                 if (!ftepp_else_allowed(ftepp))
866                     return false;
867                 if (!ftepp_ifdef(ftepp, &cond))
868                     return false;
869                 cond.on = !cond.on;
870                 pc = &vec_last(ftepp->conditions);
871                 pc->on     = !pc->was_on && cond.on;
872                 pc->was_on = pc->was_on || pc->on;
873                 ftepp_update_output_condition(ftepp);
874                 break;
875             }
876             else if (!strcmp(ftepp_tokval(ftepp), "elif")) {
877                 if (!ftepp_else_allowed(ftepp))
878                     return false;
879                 if (!ftepp_if(ftepp, &cond))
880                     return false;
881                 pc = &vec_last(ftepp->conditions);
882                 pc->on     = !pc->was_on && cond.on;
883                 pc->was_on = pc->was_on  || pc->on;
884                 ftepp_update_output_condition(ftepp);
885                 break;
886             }
887             else if (!strcmp(ftepp_tokval(ftepp), "if")) {
888                 if (!ftepp_if(ftepp, &cond))
889                     return false;
890                 cond.was_on = cond.on;
891                 vec_push(ftepp->conditions, cond);
892                 ftepp->output_on = ftepp->output_on && cond.on;
893                 break;
894             }
895             else if (!strcmp(ftepp_tokval(ftepp), "else")) {
896                 if (!ftepp_else_allowed(ftepp))
897                     return false;
898                 pc = &vec_last(ftepp->conditions);
899                 pc->on = !pc->was_on;
900                 pc->had_else = true;
901                 ftepp_next(ftepp);
902                 ftepp_update_output_condition(ftepp);
903                 break;
904             }
905             else if (!strcmp(ftepp_tokval(ftepp), "endif")) {
906                 if (!vec_size(ftepp->conditions)) {
907                     ftepp_error(ftepp, "#endif without #if");
908                     return false;
909                 }
910                 vec_pop(ftepp->conditions);
911                 ftepp_next(ftepp);
912                 ftepp_update_output_condition(ftepp);
913                 break;
914             }
915             else {
916                 ftepp_error(ftepp, "unrecognized preprocessor directive: `%s`", ftepp_tokval(ftepp));
917                 return false;
918             }
919             break;
920         default:
921             ftepp_error(ftepp, "unexpected preprocessor token: `%s`", ftepp_tokval(ftepp));
922             return false;
923         case TOKEN_EOL:
924             ftepp_errorat(ftepp, ctx, "empty preprocessor directive");
925             return false;
926         case TOKEN_EOF:
927             ftepp_error(ftepp, "missing newline at end of file", ftepp_tokval(ftepp));
928             return false;
929
930         /* Builtins! Don't forget the builtins! */
931         case TOKEN_INTCONST:
932         case TOKEN_FLOATCONST:
933             ftepp_out(ftepp, "#", false);
934             return true;
935     }
936     if (!ftepp_skipspace(ftepp))
937         return false;
938     return true;
939 }
940
941 static bool ftepp_preprocess(ftepp_t *ftepp)
942 {
943     ppmacro *macro;
944     bool     newline = true;
945
946     ftepp->lex->flags.preprocessing = true;
947     ftepp->lex->flags.mergelines    = false;
948     ftepp->lex->flags.noops         = true;
949
950     ftepp_next(ftepp);
951     do
952     {
953         if (ftepp->token >= TOKEN_EOF)
954             break;
955 #if 0
956         ftepp->newline = newline;
957         newline = false;
958 #else
959         /* For the sake of FTE compatibility... FU, really */
960         ftepp->newline = newline = true;
961 #endif
962
963         switch (ftepp->token) {
964             case TOKEN_KEYWORD:
965             case TOKEN_IDENT:
966             case TOKEN_TYPENAME:
967                 macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
968                 if (!macro) {
969                     ftepp_out(ftepp, ftepp_tokval(ftepp), false);
970                     ftepp_next(ftepp);
971                     break;
972                 }
973                 if (!ftepp_macro_call(ftepp, macro))
974                     ftepp->token = TOKEN_ERROR;
975                 break;
976             case '#':
977                 if (!ftepp->newline) {
978                     ftepp_out(ftepp, ftepp_tokval(ftepp), false);
979                     ftepp_next(ftepp);
980                     break;
981                 }
982                 ftepp->lex->flags.mergelines = true;
983                 if (ftepp_next(ftepp) >= TOKEN_EOF) {
984                     ftepp_error(ftepp, "error in preprocessor directive");
985                     ftepp->token = TOKEN_ERROR;
986                     break;
987                 }
988                 if (!ftepp_hash(ftepp))
989                     ftepp->token = TOKEN_ERROR;
990                 ftepp->lex->flags.mergelines = false;
991                 break;
992             case TOKEN_EOL:
993                 newline = true;
994                 ftepp_out(ftepp, "\n", true);
995                 ftepp_next(ftepp);
996                 break;
997             default:
998                 ftepp_out(ftepp, ftepp_tokval(ftepp), false);
999                 ftepp_next(ftepp);
1000                 break;
1001         }
1002     } while (!ftepp->errors && ftepp->token < TOKEN_EOF);
1003
1004     newline = ftepp->token == TOKEN_EOF;
1005     return newline;
1006 }
1007
1008 bool ftepp_preprocess_file(const char *filename)
1009 {
1010     ftepp_t *ftepp = ftepp_init();
1011     ftepp->lex = lex_open(filename);
1012     if (!ftepp->lex) {
1013         con_out("failed to open file \"%s\"\n", filename);
1014         return false;
1015     }
1016     if (!ftepp_preprocess(ftepp)) {
1017         ftepp_delete(ftepp);
1018         return false;
1019     }
1020     ftepp_delete(ftepp);
1021     return true;
1022 }
1023
1024 bool ftepp_preprocess_string(const char *name, const char *str)
1025 {
1026     ftepp_t *ftepp = ftepp_init();
1027     ftepp->lex = lex_open_string(str, strlen(str), name);
1028     if (!ftepp->lex) {
1029         con_out("failed to create lexer for string \"%s\"\n", name);
1030         return false;
1031     }
1032     if (!ftepp_preprocess(ftepp)) {
1033         ftepp_delete(ftepp);
1034         return false;
1035     }
1036     ftepp_delete(ftepp);
1037     return true;
1038 }