]> de.git.xonotic.org Git - xonotic/gmqcc.git/blob - ftepp.c
The ftepp now always outputs into a string buffer, with -E the buffer will be dumped...
[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     char        *output_string;
66 } ftepp_t;
67
68 #define ftepp_tokval(f) ((f)->lex->tok.value)
69 #define ftepp_ctx(f)    ((f)->lex->tok.ctx)
70
71 static void ftepp_errorat(ftepp_t *ftepp, lex_ctx ctx, const char *fmt, ...)
72 {
73     va_list ap;
74
75     ftepp->errors++;
76
77     va_start(ap, fmt);
78     con_vprintmsg(LVL_ERROR, ctx.file, ctx.line, "error", fmt, ap);
79     va_end(ap);
80 }
81
82 static void ftepp_error(ftepp_t *ftepp, const char *fmt, ...)
83 {
84     va_list ap;
85
86     ftepp->errors++;
87
88     va_start(ap, fmt);
89     con_vprintmsg(LVL_ERROR, ftepp->lex->tok.ctx.file, ftepp->lex->tok.ctx.line, "error", fmt, ap);
90     va_end(ap);
91 }
92
93 static bool GMQCC_WARN ftepp_warn(ftepp_t *ftepp, int warntype, const char *fmt, ...)
94 {
95     va_list ap;
96     int lvl = LVL_WARNING;
97
98     if (!OPTS_WARN(warntype))
99         return false;
100
101     if (opts_werror) {
102             lvl = LVL_ERROR;
103         ftepp->errors++;
104     }
105
106     va_start(ap, fmt);
107     con_vprintmsg(lvl, ftepp->lex->tok.ctx.file, ftepp->lex->tok.ctx.line, "error", fmt, ap);
108     va_end(ap);
109     return opts_werror;
110 }
111
112 static pptoken *pptoken_make(ftepp_t *ftepp)
113 {
114     pptoken *token = (pptoken*)mem_a(sizeof(pptoken));
115     token->token = ftepp->token;
116 #if 0
117     if (token->token == TOKEN_WHITE)
118         token->value = util_strdup(" ");
119     else
120 #else
121         token->value = util_strdup(ftepp_tokval(ftepp));
122 #endif
123     memcpy(&token->constval, &ftepp->lex->tok.constval, sizeof(token->constval));
124     return token;
125 }
126
127 static void pptoken_delete(pptoken *self)
128 {
129     mem_d(self->value);
130     mem_d(self);
131 }
132
133 static ppmacro *ppmacro_new(lex_ctx ctx, const char *name)
134 {
135     ppmacro *macro = (ppmacro*)mem_a(sizeof(ppmacro));
136     memset(macro, 0, sizeof(*macro));
137     macro->name = util_strdup(name);
138     return macro;
139 }
140
141 static void ppmacro_delete(ppmacro *self)
142 {
143     size_t i;
144     for (i = 0; i < vec_size(self->params); ++i)
145         mem_d(self->params[i]);
146     vec_free(self->params);
147     for (i = 0; i < vec_size(self->output); ++i)
148         pptoken_delete(self->output[i]);
149     vec_free(self->output);
150     mem_d(self->name);
151     mem_d(self);
152 }
153
154 static ftepp_t* ftepp_new()
155 {
156     ftepp_t *ftepp;
157
158     ftepp = (ftepp_t*)mem_a(sizeof(*ftepp));
159     memset(ftepp, 0, sizeof(*ftepp));
160
161     ftepp->output_on = true;
162
163     return ftepp;
164 }
165
166 static void ftepp_delete(ftepp_t *self)
167 {
168     size_t i;
169     for (i = 0; i < vec_size(self->macros); ++i)
170         ppmacro_delete(self->macros[i]);
171     vec_free(self->macros);
172     vec_free(self->conditions);
173     if (self->lex)
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         len = strlen(str);
185         data = vec_add(ftepp->output_string, len);
186         memcpy(data, str, len);
187     }
188 }
189
190 static void ftepp_update_output_condition(ftepp_t *ftepp)
191 {
192     size_t i;
193     ftepp->output_on = true;
194     for (i = 0; i < vec_size(ftepp->conditions); ++i)
195         ftepp->output_on = ftepp->output_on && ftepp->conditions[i].on;
196 }
197
198 static ppmacro* ftepp_macro_find(ftepp_t *ftepp, const char *name)
199 {
200     size_t i;
201     for (i = 0; i < vec_size(ftepp->macros); ++i) {
202         if (!strcmp(name, ftepp->macros[i]->name))
203             return ftepp->macros[i];
204     }
205     return NULL;
206 }
207
208 static void ftepp_macro_delete(ftepp_t *ftepp, const char *name)
209 {
210     size_t i;
211     for (i = 0; i < vec_size(ftepp->macros); ++i) {
212         if (!strcmp(name, ftepp->macros[i]->name)) {
213             vec_remove(ftepp->macros, i, 1);
214             return;
215         }
216     }
217 }
218
219 static inline int ftepp_next(ftepp_t *ftepp)
220 {
221     return (ftepp->token = lex_do(ftepp->lex));
222 }
223
224 /* Important: this does not skip newlines! */
225 static bool ftepp_skipspace(ftepp_t *ftepp)
226 {
227     if (ftepp->token != TOKEN_WHITE)
228         return true;
229     while (ftepp_next(ftepp) == TOKEN_WHITE) {}
230     if (ftepp->token >= TOKEN_EOF) {
231         ftepp_error(ftepp, "unexpected end of preprocessor directive");
232         return false;
233     }
234     return true;
235 }
236
237 /* this one skips EOLs as well */
238 static bool ftepp_skipallwhite(ftepp_t *ftepp)
239 {
240     if (ftepp->token != TOKEN_WHITE && ftepp->token != TOKEN_EOL)
241         return true;
242     do {
243         ftepp_next(ftepp);
244     } while (ftepp->token == TOKEN_WHITE || ftepp->token == TOKEN_EOL);
245     if (ftepp->token >= TOKEN_EOF) {
246         ftepp_error(ftepp, "unexpected end of preprocessor directive");
247         return false;
248     }
249     return true;
250 }
251
252 /**
253  * The huge macro parsing code...
254  */
255 static bool ftepp_define_params(ftepp_t *ftepp, ppmacro *macro)
256 {
257     do {
258         ftepp_next(ftepp);
259         if (!ftepp_skipspace(ftepp))
260             return false;
261         if (ftepp->token == ')')
262             break;
263         switch (ftepp->token) {
264             case TOKEN_IDENT:
265             case TOKEN_TYPENAME:
266             case TOKEN_KEYWORD:
267                 break;
268             default:
269                 ftepp_error(ftepp, "unexpected token in parameter list");
270                 return false;
271         }
272         vec_push(macro->params, util_strdup(ftepp_tokval(ftepp)));
273         ftepp_next(ftepp);
274         if (!ftepp_skipspace(ftepp))
275             return false;
276     } while (ftepp->token == ',');
277     if (ftepp->token != ')') {
278         ftepp_error(ftepp, "expected closing paren after macro parameter list");
279         return false;
280     }
281     ftepp_next(ftepp);
282     /* skipspace happens in ftepp_define */
283     return true;
284 }
285
286 static bool ftepp_define_body(ftepp_t *ftepp, ppmacro *macro)
287 {
288     pptoken *ptok;
289     while (ftepp->token != TOKEN_EOL && ftepp->token < TOKEN_EOF) {
290         ptok = pptoken_make(ftepp);
291         vec_push(macro->output, ptok);
292         ftepp_next(ftepp);
293     }
294     /* recursive expansion can cause EOFs here */
295     if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) {
296         ftepp_error(ftepp, "unexpected junk after macro or unexpected end of file");
297         return false;
298     }
299     return true;
300 }
301
302 static bool ftepp_define(ftepp_t *ftepp)
303 {
304     ppmacro *macro;
305     (void)ftepp_next(ftepp);
306     if (!ftepp_skipspace(ftepp))
307         return false;
308
309     switch (ftepp->token) {
310         case TOKEN_IDENT:
311         case TOKEN_TYPENAME:
312         case TOKEN_KEYWORD:
313             macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
314             if (macro && ftepp->output_on) {
315                 if (ftepp_warn(ftepp, WARN_PREPROCESSOR, "redefining `%s`", ftepp_tokval(ftepp)))
316                     return false;
317                 ftepp_macro_delete(ftepp, ftepp_tokval(ftepp));
318             }
319             macro = ppmacro_new(ftepp_ctx(ftepp), ftepp_tokval(ftepp));
320             break;
321         default:
322             ftepp_error(ftepp, "expected macro name");
323             return false;
324     }
325
326     (void)ftepp_next(ftepp);
327
328     if (ftepp->token == '(') {
329         macro->has_params = true;
330         if (!ftepp_define_params(ftepp, macro))
331             return false;
332     }
333
334     if (!ftepp_skipspace(ftepp))
335         return false;
336
337     if (!ftepp_define_body(ftepp, macro))
338         return false;
339
340     if (ftepp->output_on)
341         vec_push(ftepp->macros, macro);
342     else {
343         ppmacro_delete(macro);
344     }
345     return true;
346 }
347
348 /**
349  * When a macro is used we have to handle parameters as well
350  * as special-concatenation via ## or stringification via #
351  *
352  * Note: parenthesis can nest, so FOO((a),b) is valid, but only
353  * this kind of parens. Curly braces or [] don't count towards the
354  * paren-level.
355  */
356 typedef struct {
357     pptoken **tokens;
358 } macroparam;
359
360 static void macroparam_clean(macroparam *self)
361 {
362     size_t i;
363     for (i = 0; i < vec_size(self->tokens); ++i)
364         pptoken_delete(self->tokens[i]);
365     vec_free(self->tokens);
366 }
367
368 /* need to leave the last token up */
369 static bool ftepp_macro_call_params(ftepp_t *ftepp, macroparam **out_params)
370 {
371     macroparam *params = NULL;
372     pptoken    *ptok;
373     macroparam  mp;
374     size_t      parens = 0;
375     size_t      i;
376
377     if (!ftepp_skipallwhite(ftepp))
378         return false;
379     while (ftepp->token != ')') {
380         mp.tokens = NULL;
381         if (!ftepp_skipallwhite(ftepp))
382             return false;
383         while (parens || ftepp->token != ',') {
384             if (ftepp->token == '(')
385                 ++parens;
386             else if (ftepp->token == ')') {
387                 if (!parens)
388                     break;
389                 --parens;
390             }
391             ptok = pptoken_make(ftepp);
392             vec_push(mp.tokens, ptok);
393             if (ftepp_next(ftepp) >= TOKEN_EOF) {
394                 ftepp_error(ftepp, "unexpected EOF in macro call");
395                 goto on_error;
396             }
397         }
398         vec_push(params, mp);
399         mp.tokens = NULL;
400         if (ftepp->token == ')')
401             break;
402         if (ftepp->token != ',') {
403             ftepp_error(ftepp, "expected closing paren or comma in macro call");
404             goto on_error;
405         }
406         if (ftepp_next(ftepp) >= TOKEN_EOF) {
407             ftepp_error(ftepp, "unexpected EOF in macro call");
408             goto on_error;
409         }
410     }
411     /* need to leave that up
412     if (ftepp_next(ftepp) >= TOKEN_EOF) {
413         ftepp_error(ftepp, "unexpected EOF in macro call");
414         goto on_error;
415     }
416     */
417     *out_params = params;
418     return true;
419
420 on_error:
421     if (mp.tokens)
422         macroparam_clean(&mp);
423     for (i = 0; i < vec_size(params); ++i)
424         macroparam_clean(&params[i]);
425     vec_free(params);
426     return false;
427 }
428
429 static bool macro_params_find(ppmacro *macro, const char *name, size_t *idx)
430 {
431     size_t i;
432     for (i = 0; i < vec_size(macro->params); ++i) {
433         if (!strcmp(macro->params[i], name)) {
434             *idx = i;
435             return true;
436         }
437     }
438     return false;
439 }
440
441 static bool ftepp_preprocess(ftepp_t *ftepp);
442 static bool ftepp_macro_expand(ftepp_t *ftepp, ppmacro *macro, macroparam *params)
443 {
444     char     *old_string = ftepp->output_string;
445     lex_file *old_lexer = ftepp->lex;
446     bool retval = true;
447
448     size_t    o, pi, pv;
449     lex_file *inlex;
450
451     /* really ... */
452     if (!vec_size(macro->output))
453         return true;
454
455     ftepp->output_string = NULL;
456     for (o = 0; o < vec_size(macro->output); ++o) {
457         pptoken *out = macro->output[o];
458         switch (out->token) {
459             case TOKEN_IDENT:
460             case TOKEN_TYPENAME:
461             case TOKEN_KEYWORD:
462                 if (!macro_params_find(macro, out->value, &pi)) {
463                     ftepp_out(ftepp, out->value, false);
464                     break;
465                 } else {
466                     for (pv = 0; pv < vec_size(params[pi].tokens); ++pv) {
467                         out = params[pi].tokens[pv];
468                         if (out->token == TOKEN_EOL)
469                             ftepp_out(ftepp, "\n", false);
470                         else
471                             ftepp_out(ftepp, out->value, false);
472                     }
473                 }
474                 break;
475             case '#':
476                 if (o + 1 < vec_size(macro->output) && macro->output[o+1]->token == '#') {
477                     /* raw concatenation */
478                     ++o;
479                     break;
480                 }
481                 ftepp_out(ftepp, "#", false);
482                 break;
483             case TOKEN_EOL:
484                 ftepp_out(ftepp, "\n", false);
485                 break;
486             default:
487                 ftepp_out(ftepp, out->value, false);
488                 break;
489         }
490     }
491     vec_push(ftepp->output_string, 0);
492     /* Now run the preprocessor recursively on this string buffer */
493     /*
494     printf("__________\n%s\n=========\n", ftepp->output_string);
495     */
496     inlex = lex_open_string(ftepp->output_string, vec_size(ftepp->output_string)-1, ftepp->lex->name);
497     if (!inlex) {
498         ftepp_error(ftepp, "internal error: failed to instantiate lexer");
499         retval = false;
500         goto cleanup;
501     }
502     ftepp->output_string = old_string;
503     ftepp->lex = inlex;
504     if (!ftepp_preprocess(ftepp)) {
505         lex_close(ftepp->lex);
506         retval = false;
507         goto cleanup;
508     }
509
510 cleanup:
511     ftepp->lex           = old_lexer;
512     ftepp->output_string = old_string;
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     if (ftepp->output_on) {
771         switch (ftepp->token) {
772             case TOKEN_IDENT:
773             case TOKEN_TYPENAME:
774             case TOKEN_KEYWORD:
775                 ftepp_macro_delete(ftepp, ftepp_tokval(ftepp));
776                 break;
777             default:
778                 ftepp_error(ftepp, "expected macro name");
779                 return false;
780         }
781     }
782
783     (void)ftepp_next(ftepp);
784     if (!ftepp_skipspace(ftepp))
785         return false;
786     /* relaxing this condition
787     if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) {
788         ftepp_error(ftepp, "stray tokens after #ifdef");
789         return false;
790     }
791     */
792     return true;
793 }
794
795 /* Basic structure handlers */
796 static bool ftepp_else_allowed(ftepp_t *ftepp)
797 {
798     if (!vec_size(ftepp->conditions)) {
799         ftepp_error(ftepp, "#else without #if");
800         return false;
801     }
802     if (vec_last(ftepp->conditions).had_else) {
803         ftepp_error(ftepp, "multiple #else for a single #if");
804         return false;
805     }
806     return true;
807 }
808
809 static bool ftepp_hash(ftepp_t *ftepp)
810 {
811     ppcondition cond;
812     ppcondition *pc;
813
814     lex_ctx ctx = ftepp_ctx(ftepp);
815
816     if (!ftepp_skipspace(ftepp))
817         return false;
818
819     switch (ftepp->token) {
820         case TOKEN_KEYWORD:
821         case TOKEN_IDENT:
822         case TOKEN_TYPENAME:
823             if (!strcmp(ftepp_tokval(ftepp), "define")) {
824                 return ftepp_define(ftepp);
825             }
826             else if (!strcmp(ftepp_tokval(ftepp), "undef")) {
827                 return ftepp_undef(ftepp);
828             }
829             else if (!strcmp(ftepp_tokval(ftepp), "ifdef")) {
830                 if (!ftepp_ifdef(ftepp, &cond))
831                     return false;
832                 cond.was_on = cond.on;
833                 vec_push(ftepp->conditions, cond);
834                 ftepp->output_on = ftepp->output_on && cond.on;
835                 break;
836             }
837             else if (!strcmp(ftepp_tokval(ftepp), "ifndef")) {
838                 if (!ftepp_ifdef(ftepp, &cond))
839                     return false;
840                 cond.on = !cond.on;
841                 cond.was_on = cond.on;
842                 vec_push(ftepp->conditions, cond);
843                 ftepp->output_on = ftepp->output_on && cond.on;
844                 break;
845             }
846             else if (!strcmp(ftepp_tokval(ftepp), "elifdef")) {
847                 if (!ftepp_else_allowed(ftepp))
848                     return false;
849                 if (!ftepp_ifdef(ftepp, &cond))
850                     return false;
851                 pc = &vec_last(ftepp->conditions);
852                 pc->on     = !pc->was_on && cond.on;
853                 pc->was_on = pc->was_on || pc->on;
854                 ftepp_update_output_condition(ftepp);
855                 break;
856             }
857             else if (!strcmp(ftepp_tokval(ftepp), "elifndef")) {
858                 if (!ftepp_else_allowed(ftepp))
859                     return false;
860                 if (!ftepp_ifdef(ftepp, &cond))
861                     return false;
862                 cond.on = !cond.on;
863                 pc = &vec_last(ftepp->conditions);
864                 pc->on     = !pc->was_on && cond.on;
865                 pc->was_on = pc->was_on || pc->on;
866                 ftepp_update_output_condition(ftepp);
867                 break;
868             }
869             else if (!strcmp(ftepp_tokval(ftepp), "elif")) {
870                 if (!ftepp_else_allowed(ftepp))
871                     return false;
872                 if (!ftepp_if(ftepp, &cond))
873                     return false;
874                 pc = &vec_last(ftepp->conditions);
875                 pc->on     = !pc->was_on && cond.on;
876                 pc->was_on = pc->was_on  || pc->on;
877                 ftepp_update_output_condition(ftepp);
878                 break;
879             }
880             else if (!strcmp(ftepp_tokval(ftepp), "if")) {
881                 if (!ftepp_if(ftepp, &cond))
882                     return false;
883                 cond.was_on = cond.on;
884                 vec_push(ftepp->conditions, cond);
885                 ftepp->output_on = ftepp->output_on && cond.on;
886                 break;
887             }
888             else if (!strcmp(ftepp_tokval(ftepp), "else")) {
889                 if (!ftepp_else_allowed(ftepp))
890                     return false;
891                 pc = &vec_last(ftepp->conditions);
892                 pc->on = !pc->was_on;
893                 pc->had_else = true;
894                 ftepp_next(ftepp);
895                 ftepp_update_output_condition(ftepp);
896                 break;
897             }
898             else if (!strcmp(ftepp_tokval(ftepp), "endif")) {
899                 if (!vec_size(ftepp->conditions)) {
900                     ftepp_error(ftepp, "#endif without #if");
901                     return false;
902                 }
903                 vec_pop(ftepp->conditions);
904                 ftepp_next(ftepp);
905                 ftepp_update_output_condition(ftepp);
906                 break;
907             }
908             else {
909                 ftepp_error(ftepp, "unrecognized preprocessor directive: `%s`", ftepp_tokval(ftepp));
910                 return false;
911             }
912             break;
913         default:
914             ftepp_error(ftepp, "unexpected preprocessor token: `%s`", ftepp_tokval(ftepp));
915             return false;
916         case TOKEN_EOL:
917             ftepp_errorat(ftepp, ctx, "empty preprocessor directive");
918             return false;
919         case TOKEN_EOF:
920             ftepp_error(ftepp, "missing newline at end of file", ftepp_tokval(ftepp));
921             return false;
922
923         /* Builtins! Don't forget the builtins! */
924         case TOKEN_INTCONST:
925         case TOKEN_FLOATCONST:
926             ftepp_out(ftepp, "#", false);
927             return true;
928     }
929     if (!ftepp_skipspace(ftepp))
930         return false;
931     return true;
932 }
933
934 static bool ftepp_preprocess(ftepp_t *ftepp)
935 {
936     ppmacro *macro;
937     bool     newline = true;
938
939     ftepp->lex->flags.preprocessing = true;
940     ftepp->lex->flags.mergelines    = false;
941     ftepp->lex->flags.noops         = true;
942
943     ftepp_next(ftepp);
944     do
945     {
946         if (ftepp->token >= TOKEN_EOF)
947             break;
948 #if 0
949         ftepp->newline = newline;
950         newline = false;
951 #else
952         /* For the sake of FTE compatibility... FU, really */
953         ftepp->newline = newline = true;
954 #endif
955
956         switch (ftepp->token) {
957             case TOKEN_KEYWORD:
958             case TOKEN_IDENT:
959             case TOKEN_TYPENAME:
960                 macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
961                 if (!macro) {
962                     ftepp_out(ftepp, ftepp_tokval(ftepp), false);
963                     ftepp_next(ftepp);
964                     break;
965                 }
966                 if (!ftepp_macro_call(ftepp, macro))
967                     ftepp->token = TOKEN_ERROR;
968                 break;
969             case '#':
970                 if (!ftepp->newline) {
971                     ftepp_out(ftepp, ftepp_tokval(ftepp), false);
972                     ftepp_next(ftepp);
973                     break;
974                 }
975                 ftepp->lex->flags.mergelines = true;
976                 if (ftepp_next(ftepp) >= TOKEN_EOF) {
977                     ftepp_error(ftepp, "error in preprocessor directive");
978                     ftepp->token = TOKEN_ERROR;
979                     break;
980                 }
981                 if (!ftepp_hash(ftepp))
982                     ftepp->token = TOKEN_ERROR;
983                 ftepp->lex->flags.mergelines = false;
984                 break;
985             case TOKEN_EOL:
986                 newline = true;
987                 ftepp_out(ftepp, "\n", true);
988                 ftepp_next(ftepp);
989                 break;
990             default:
991                 ftepp_out(ftepp, ftepp_tokval(ftepp), false);
992                 ftepp_next(ftepp);
993                 break;
994         }
995     } while (!ftepp->errors && ftepp->token < TOKEN_EOF);
996
997     newline = ftepp->token == TOKEN_EOF;
998     return newline;
999 }
1000
1001 /* Like in parser.c - files keep the previous state so we have one global
1002  * preprocessor. Except here we will want to warn about dangling #ifs.
1003  */
1004 static ftepp_t *ftepp;
1005
1006 static bool ftepp_preprocess_done()
1007 {
1008     bool retval = true;
1009     lex_close(ftepp->lex);
1010     ftepp->lex = NULL;
1011     if (vec_size(ftepp->conditions)) {
1012         if (ftepp_warn(ftepp, WARN_MULTIFILE_IF, "#if spanning multiple files, is this intended?"))
1013             retval = false;
1014     }
1015     return retval;
1016 }
1017
1018 bool ftepp_preprocess_file(const char *filename)
1019 {
1020     ftepp->lex = lex_open(filename);
1021     if (!ftepp->lex) {
1022         con_out("failed to open file \"%s\"\n", filename);
1023         return false;
1024     }
1025     if (!ftepp_preprocess(ftepp)) {
1026         ftepp_delete(ftepp);
1027         return false;
1028     }
1029     return ftepp_preprocess_done();
1030 }
1031
1032 bool ftepp_preprocess_string(const char *name, const char *str)
1033 {
1034     ftepp_t *ftepp = ftepp_new();
1035     ftepp->lex = lex_open_string(str, strlen(str), name);
1036     if (!ftepp->lex) {
1037         con_out("failed to create lexer for string \"%s\"\n", name);
1038         return false;
1039     }
1040     if (!ftepp_preprocess(ftepp)) {
1041         ftepp_delete(ftepp);
1042         return false;
1043     }
1044     return ftepp_preprocess_done();
1045 }
1046
1047 bool ftepp_init()
1048 {
1049     ftepp = ftepp_new();
1050     return !!ftepp;
1051 }
1052
1053 const char *ftepp_get()
1054 {
1055     return ftepp->output_string;
1056 }
1057
1058 void ftepp_flush()
1059 {
1060     vec_free(ftepp->output_string);
1061 }
1062
1063 void ftepp_finish()
1064 {
1065     if (!ftepp)
1066         return;
1067     ftepp_delete(ftepp);
1068     ftepp = NULL;
1069 }