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