]> git.xonotic.org Git - xonotic/gmqcc.git/blob - ftepp.c
Importing a tiny README file
[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
67     char        *itemname;
68 } ftepp_t;
69
70 #define ftepp_tokval(f) ((f)->lex->tok.value)
71 #define ftepp_ctx(f)    ((f)->lex->tok.ctx)
72
73 static void ftepp_errorat(ftepp_t *ftepp, lex_ctx ctx, const char *fmt, ...)
74 {
75     va_list ap;
76
77     ftepp->errors++;
78
79     va_start(ap, fmt);
80     con_vprintmsg(LVL_ERROR, ctx.file, ctx.line, "error", fmt, ap);
81     va_end(ap);
82 }
83
84 static void ftepp_error(ftepp_t *ftepp, const char *fmt, ...)
85 {
86     va_list ap;
87
88     ftepp->errors++;
89
90     va_start(ap, fmt);
91     con_vprintmsg(LVL_ERROR, ftepp->lex->tok.ctx.file, ftepp->lex->tok.ctx.line, "error", fmt, ap);
92     va_end(ap);
93 }
94
95 static bool GMQCC_WARN ftepp_warn(ftepp_t *ftepp, int warntype, const char *fmt, ...)
96 {
97     va_list ap;
98     int lvl = LVL_WARNING;
99
100     if (!OPTS_WARN(warntype))
101         return false;
102
103     if (opts_werror) {
104             lvl = LVL_ERROR;
105         ftepp->errors++;
106     }
107
108     va_start(ap, fmt);
109     con_vprintmsg(lvl, ftepp->lex->tok.ctx.file, ftepp->lex->tok.ctx.line, "error", fmt, ap);
110     va_end(ap);
111     return opts_werror;
112 }
113
114 static pptoken *pptoken_make(ftepp_t *ftepp)
115 {
116     pptoken *token = (pptoken*)mem_a(sizeof(pptoken));
117     token->token = ftepp->token;
118 #if 0
119     if (token->token == TOKEN_WHITE)
120         token->value = util_strdup(" ");
121     else
122 #else
123         token->value = util_strdup(ftepp_tokval(ftepp));
124 #endif
125     memcpy(&token->constval, &ftepp->lex->tok.constval, sizeof(token->constval));
126     return token;
127 }
128
129 static void pptoken_delete(pptoken *self)
130 {
131     mem_d(self->value);
132     mem_d(self);
133 }
134
135 static ppmacro *ppmacro_new(lex_ctx ctx, const char *name)
136 {
137     ppmacro *macro = (ppmacro*)mem_a(sizeof(ppmacro));
138     memset(macro, 0, sizeof(*macro));
139     macro->name = util_strdup(name);
140     return macro;
141 }
142
143 static void ppmacro_delete(ppmacro *self)
144 {
145     size_t i;
146     for (i = 0; i < vec_size(self->params); ++i)
147         mem_d(self->params[i]);
148     vec_free(self->params);
149     for (i = 0; i < vec_size(self->output); ++i)
150         pptoken_delete(self->output[i]);
151     vec_free(self->output);
152     mem_d(self->name);
153     mem_d(self);
154 }
155
156 static ftepp_t* ftepp_new()
157 {
158     ftepp_t *ftepp;
159
160     ftepp = (ftepp_t*)mem_a(sizeof(*ftepp));
161     memset(ftepp, 0, sizeof(*ftepp));
162
163     ftepp->output_on = true;
164
165     return ftepp;
166 }
167
168 static void ftepp_delete(ftepp_t *self)
169 {
170     size_t i;
171     if (self->itemname)
172         mem_d(self->itemname);
173     for (i = 0; i < vec_size(self->macros); ++i)
174         ppmacro_delete(self->macros[i]);
175     vec_free(self->macros);
176     vec_free(self->conditions);
177     if (self->lex)
178         lex_close(self->lex);
179     mem_d(self);
180 }
181
182 static void ftepp_out(ftepp_t *ftepp, const char *str, bool ignore_cond)
183 {
184     if (ignore_cond || ftepp->output_on)
185     {
186         size_t len;
187         char  *data;
188         len = strlen(str);
189         data = vec_add(ftepp->output_string, 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 void ftepp_stringify_token(ftepp_t *ftepp, pptoken *token)
446 {
447     char        chs[2];
448     const char *ch;
449     chs[1] = 0;
450     switch (token->token) {
451         case TOKEN_STRINGCONST:
452             ch = token->value;
453             while (*ch) {
454                 /* in preprocessor mode strings already are string,
455                  * so we don't get actual newline bytes here.
456                  * Still need to escape backslashes and quotes.
457                  */
458                 switch (*ch) {
459                     case '\\': ftepp_out(ftepp, "\\\\", false); break;
460                     case '"':  ftepp_out(ftepp, "\\\"", false); break;
461                     default:
462                         chs[0] = *ch;
463                         ftepp_out(ftepp, chs, false);
464                         break;
465                 }
466                 ++ch;
467             }
468             break;
469         case TOKEN_WHITE:
470             ftepp_out(ftepp, " ", false);
471             break;
472         case TOKEN_EOL:
473             ftepp_out(ftepp, "\\n", false);
474             break;
475         default:
476             ftepp_out(ftepp, token->value, false);
477             break;
478     }
479 }
480
481 static void ftepp_stringify(ftepp_t *ftepp, macroparam *param)
482 {
483     size_t i;
484     ftepp_out(ftepp, "\"", false);
485     for (i = 0; i < vec_size(param->tokens); ++i)
486         ftepp_stringify_token(ftepp, param->tokens[i]);
487     ftepp_out(ftepp, "\"", false);
488 }
489
490 static void ftepp_recursion_header(ftepp_t *ftepp)
491 {
492     ftepp_out(ftepp, "\n#pragma push(line)\n", false);
493 }
494
495 static void ftepp_recursion_footer(ftepp_t *ftepp)
496 {
497     ftepp_out(ftepp, "\n#pragma pop(line)\n", false);
498 }
499
500 static bool ftepp_preprocess(ftepp_t *ftepp);
501 static bool ftepp_macro_expand(ftepp_t *ftepp, ppmacro *macro, macroparam *params)
502 {
503     char     *old_string = ftepp->output_string;
504     lex_file *old_lexer = ftepp->lex;
505     bool retval = true;
506
507     size_t    o, pi, pv;
508     lex_file *inlex;
509
510     int nextok;
511
512     /* really ... */
513     if (!vec_size(macro->output))
514         return true;
515
516     ftepp->output_string = NULL;
517     for (o = 0; o < vec_size(macro->output); ++o) {
518         pptoken *out = macro->output[o];
519         switch (out->token) {
520             case TOKEN_IDENT:
521             case TOKEN_TYPENAME:
522             case TOKEN_KEYWORD:
523                 if (!macro_params_find(macro, out->value, &pi)) {
524                     ftepp_out(ftepp, out->value, false);
525                     break;
526                 } else {
527                     for (pv = 0; pv < vec_size(params[pi].tokens); ++pv) {
528                         out = params[pi].tokens[pv];
529                         if (out->token == TOKEN_EOL)
530                             ftepp_out(ftepp, "\n", false);
531                         else
532                             ftepp_out(ftepp, out->value, false);
533                     }
534                 }
535                 break;
536             case '#':
537                 if (o + 1 < vec_size(macro->output)) {
538                     nextok = macro->output[o+1]->token;
539                     if (nextok == '#') {
540                         /* raw concatenation */
541                         ++o;
542                         break;
543                     }
544                     if ( (nextok == TOKEN_IDENT    ||
545                           nextok == TOKEN_KEYWORD  ||
546                           nextok == TOKEN_TYPENAME) &&
547                         macro_params_find(macro, macro->output[o+1]->value, &pi))
548                     {
549                         ++o;
550                         ftepp_stringify(ftepp, &params[pi]);
551                         break;
552                     }
553                 }
554                 ftepp_out(ftepp, "#", false);
555                 break;
556             case TOKEN_EOL:
557                 ftepp_out(ftepp, "\n", false);
558                 break;
559             default:
560                 ftepp_out(ftepp, out->value, false);
561                 break;
562         }
563     }
564     vec_push(ftepp->output_string, 0);
565     /* Now run the preprocessor recursively on this string buffer */
566     /*
567     printf("__________\n%s\n=========\n", ftepp->output_string);
568     */
569     inlex = lex_open_string(ftepp->output_string, vec_size(ftepp->output_string)-1, ftepp->lex->name);
570     if (!inlex) {
571         ftepp_error(ftepp, "internal error: failed to instantiate lexer");
572         retval = false;
573         goto cleanup;
574     }
575     ftepp->output_string = old_string;
576     ftepp->lex = inlex;
577     ftepp_recursion_header(ftepp);
578     if (!ftepp_preprocess(ftepp)) {
579         lex_close(ftepp->lex);
580         retval = false;
581         goto cleanup;
582     }
583     ftepp_recursion_footer(ftepp);
584     old_string = ftepp->output_string;
585
586 cleanup:
587     ftepp->lex           = old_lexer;
588     ftepp->output_string = old_string;
589     return retval;
590 }
591
592 static bool ftepp_macro_call(ftepp_t *ftepp, ppmacro *macro)
593 {
594     size_t     o;
595     macroparam *params = NULL;
596     bool        retval = true;
597
598     if (!macro->has_params) {
599         if (!ftepp_macro_expand(ftepp, macro, NULL))
600             return false;
601         ftepp_next(ftepp);
602         return true;
603     }
604     ftepp_next(ftepp);
605
606     if (!ftepp_skipallwhite(ftepp))
607         return false;
608
609     if (ftepp->token != '(') {
610         ftepp_error(ftepp, "expected macro parameters in parenthesis");
611         return false;
612     }
613
614     ftepp_next(ftepp);
615     if (!ftepp_macro_call_params(ftepp, &params))
616         return false;
617
618     if (vec_size(params) != vec_size(macro->params)) {
619         ftepp_error(ftepp, "macro %s expects %u paramteters, %u provided", macro->name,
620                     (unsigned int)vec_size(macro->params),
621                     (unsigned int)vec_size(params));
622         retval = false;
623         goto cleanup;
624     }
625
626     if (!ftepp_macro_expand(ftepp, macro, params))
627         retval = false;
628     ftepp_next(ftepp);
629
630 cleanup:
631     for (o = 0; o < vec_size(params); ++o)
632         macroparam_clean(&params[o]);
633     vec_free(params);
634     return retval;
635 }
636
637 /**
638  * #if - the FTEQCC way:
639  *    defined(FOO) => true if FOO was #defined regardless of parameters or contents
640  *    <numbers>    => True if the number is not 0
641  *    !<factor>    => True if the factor yields false
642  *    !!<factor>   => ERROR on 2 or more unary nots
643  *    <macro>      => becomes the macro's FIRST token regardless of parameters
644  *    <e> && <e>   => True if both expressions are true
645  *    <e> || <e>   => True if either expression is true
646  *    <string>     => False
647  *    <ident>      => False (remember for macros the <macro> rule applies instead)
648  * Unary + and - are weird and wrong in fteqcc so we don't allow them
649  * parenthesis in expressions are allowed
650  * parameter lists on macros are errors
651  * No mathematical calculations are executed
652  */
653 static bool ftepp_if_expr(ftepp_t *ftepp, bool *out)
654 {
655     ppmacro *macro;
656     bool     wasnot = false;
657
658     if (!ftepp_skipspace(ftepp))
659         return false;
660
661     while (ftepp->token == '!') {
662         wasnot = true;
663         ftepp_next(ftepp);
664         if (!ftepp_skipspace(ftepp))
665             return false;
666     }
667
668     switch (ftepp->token) {
669         case TOKEN_IDENT:
670         case TOKEN_TYPENAME:
671         case TOKEN_KEYWORD:
672             if (!strcmp(ftepp_tokval(ftepp), "defined")) {
673                 ftepp_next(ftepp);
674                 if (!ftepp_skipspace(ftepp))
675                     return false;
676                 if (ftepp->token != '(') {
677                     ftepp_error(ftepp, "`defined` keyword in #if requires a macro name in parenthesis");
678                     return false;
679                 }
680                 ftepp_next(ftepp);
681                 if (!ftepp_skipspace(ftepp))
682                     return false;
683                 if (ftepp->token != TOKEN_IDENT &&
684                     ftepp->token != TOKEN_TYPENAME &&
685                     ftepp->token != TOKEN_KEYWORD)
686                 {
687                     ftepp_error(ftepp, "defined() used on an unexpected token type");
688                     return false;
689                 }
690                 macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
691                 *out = !!macro;
692                 ftepp_next(ftepp);
693                 if (!ftepp_skipspace(ftepp))
694                     return false;
695                 if (ftepp->token != ')') {
696                     ftepp_error(ftepp, "expected closing paren");
697                     return false;
698                 }
699                 break;
700             }
701
702             macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
703             if (!macro || !vec_size(macro->output)) {
704                 *out = false;
705             } else {
706                 /* This does not expand recursively! */
707                 switch (macro->output[0]->token) {
708                     case TOKEN_INTCONST:
709                         *out = !!(macro->output[0]->constval.f);
710                         break;
711                     case TOKEN_FLOATCONST:
712                         *out = !!(macro->output[0]->constval.f);
713                         break;
714                     default:
715                         *out = false;
716                         break;
717                 }
718             }
719             break;
720         case TOKEN_STRINGCONST:
721             *out = false;
722             break;
723         case TOKEN_INTCONST:
724             *out = !!(ftepp->lex->tok.constval.i);
725             break;
726         case TOKEN_FLOATCONST:
727             *out = !!(ftepp->lex->tok.constval.f);
728             break;
729
730         case '(':
731             ftepp_next(ftepp);
732             if (!ftepp_if_expr(ftepp, out))
733                 return false;
734             if (ftepp->token != ')') {
735                 ftepp_error(ftepp, "expected closing paren in #if expression");
736                 return false;
737             }
738             break;
739
740         default:
741             ftepp_error(ftepp, "junk in #if");
742             return false;
743     }
744     if (wasnot)
745         *out = !*out;
746
747     ftepp->lex->flags.noops = false;
748     ftepp_next(ftepp);
749     if (!ftepp_skipspace(ftepp))
750         return false;
751     ftepp->lex->flags.noops = true;
752
753     if (ftepp->token == ')')
754         return true;
755
756     if (ftepp->token != TOKEN_OPERATOR)
757         return true;
758
759     if (!strcmp(ftepp_tokval(ftepp), "&&") ||
760         !strcmp(ftepp_tokval(ftepp), "||"))
761     {
762         bool next = false;
763         char opc  = ftepp_tokval(ftepp)[0];
764
765         ftepp_next(ftepp);
766         if (!ftepp_if_expr(ftepp, &next))
767             return false;
768
769         if (opc == '&')
770             *out = *out && next;
771         else
772             *out = *out || next;
773         return true;
774     }
775     else {
776         ftepp_error(ftepp, "junk after #if");
777         return false;
778     }
779 }
780
781 static bool ftepp_if(ftepp_t *ftepp, ppcondition *cond)
782 {
783     bool result = false;
784
785     memset(cond, 0, sizeof(*cond));
786     (void)ftepp_next(ftepp);
787
788     if (!ftepp_skipspace(ftepp))
789         return false;
790     if (ftepp->token == TOKEN_EOL) {
791         ftepp_error(ftepp, "expected expression for #if-directive");
792         return false;
793     }
794
795     if (!ftepp_if_expr(ftepp, &result))
796         return false;
797
798     cond->on = result;
799     return true;
800 }
801
802 /**
803  * ifdef is rather simple
804  */
805 static bool ftepp_ifdef(ftepp_t *ftepp, ppcondition *cond)
806 {
807     ppmacro *macro;
808     memset(cond, 0, sizeof(*cond));
809     (void)ftepp_next(ftepp);
810     if (!ftepp_skipspace(ftepp))
811         return false;
812
813     switch (ftepp->token) {
814         case TOKEN_IDENT:
815         case TOKEN_TYPENAME:
816         case TOKEN_KEYWORD:
817             macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
818             break;
819         default:
820             ftepp_error(ftepp, "expected macro name");
821             return false;
822     }
823
824     (void)ftepp_next(ftepp);
825     if (!ftepp_skipspace(ftepp))
826         return false;
827     /* relaxing this condition
828     if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) {
829         ftepp_error(ftepp, "stray tokens after #ifdef");
830         return false;
831     }
832     */
833     cond->on = !!macro;
834     return true;
835 }
836
837 /**
838  * undef is also simple
839  */
840 static bool ftepp_undef(ftepp_t *ftepp)
841 {
842     (void)ftepp_next(ftepp);
843     if (!ftepp_skipspace(ftepp))
844         return false;
845
846     if (ftepp->output_on) {
847         switch (ftepp->token) {
848             case TOKEN_IDENT:
849             case TOKEN_TYPENAME:
850             case TOKEN_KEYWORD:
851                 ftepp_macro_delete(ftepp, ftepp_tokval(ftepp));
852                 break;
853             default:
854                 ftepp_error(ftepp, "expected macro name");
855                 return false;
856         }
857     }
858
859     (void)ftepp_next(ftepp);
860     if (!ftepp_skipspace(ftepp))
861         return false;
862     /* relaxing this condition
863     if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) {
864         ftepp_error(ftepp, "stray tokens after #ifdef");
865         return false;
866     }
867     */
868     return true;
869 }
870
871 /* Special unescape-string function which skips a leading quote
872  * and stops at a quote, not just at \0
873  */
874 static void unescape(const char *str, char *out) {
875     ++str;
876     while (*str && *str != '"') {
877         if (*str == '\\') {
878             ++str;
879             switch (*str) {
880                 case '\\': *out++ = *str; break;
881                 case '"':  *out++ = *str; break;
882                 case 'a':  *out++ = '\a'; break;
883                 case 'b':  *out++ = '\b'; break;
884                 case 'r':  *out++ = '\r'; break;
885                 case 'n':  *out++ = '\n'; break;
886                 case 't':  *out++ = '\t'; break;
887                 case 'f':  *out++ = '\f'; break;
888                 case 'v':  *out++ = '\v'; break;
889                 default:
890                     *out++ = '\\';
891                     *out++ = *str;
892                     break;
893             }
894             ++str;
895             continue;
896         }
897
898         *out++ = *str++;
899     }
900     *out = 0;
901 }
902
903 static char *ftepp_include_find(ftepp_t *ftepp, const char *file)
904 {
905     char *filename = NULL;
906     size_t len;
907
908     if (ftepp->itemname) {
909         const char *last_slash;
910         last_slash = strrchr(ftepp->itemname, '/');
911         if (last_slash) {
912             len = last_slash - ftepp->itemname;
913             memcpy(vec_add(filename, len), ftepp->itemname, len);
914             vec_push(filename, '/');
915         }
916         else {
917             len = strlen(ftepp->itemname);
918             memcpy(vec_add(filename, len), ftepp->itemname, len);
919             if (vec_last(filename) != '/')
920                 vec_push(filename, '/');
921         }
922     }
923     len = strlen(file);
924     memcpy(vec_add(filename, len), file, len);
925     vec_push(filename, 0);
926     return filename;
927 }
928
929 /**
930  * Include a file.
931  * FIXME: do we need/want a -I option?
932  * FIXME: what about when dealing with files in subdirectories coming from a progs.src?
933  */
934 static bool ftepp_include(ftepp_t *ftepp)
935 {
936     lex_file *old_lexer = ftepp->lex;
937     lex_file *inlex;
938     lex_ctx  ctx;
939     char     lineno[128];
940     char     *filename;
941
942     (void)ftepp_next(ftepp);
943     if (!ftepp_skipspace(ftepp))
944         return false;
945
946     if (ftepp->token != TOKEN_STRINGCONST) {
947         ftepp_error(ftepp, "expected filename to include");
948         return false;
949     }
950
951     ctx = ftepp_ctx(ftepp);
952
953     unescape(ftepp_tokval(ftepp), ftepp_tokval(ftepp));
954
955     ftepp_out(ftepp, "\n#pragma file(", false);
956     ftepp_out(ftepp, ftepp_tokval(ftepp), false);
957     ftepp_out(ftepp, ")\n#pragma line(1)\n", false);
958
959     filename = ftepp_include_find(ftepp, ftepp_tokval(ftepp));
960     inlex = lex_open(filename);
961     if (!inlex) {
962         ftepp_error(ftepp, "failed to open include file `%s`", filename);
963         vec_free(filename);
964         return false;
965     }
966     vec_free(filename);
967     ftepp->lex = inlex;
968     if (!ftepp_preprocess(ftepp)) {
969         lex_close(ftepp->lex);
970         ftepp->lex = old_lexer;
971         return false;
972     }
973     lex_close(ftepp->lex);
974     ftepp->lex = old_lexer;
975
976     ftepp_out(ftepp, "\n#pragma file(", false);
977     ftepp_out(ftepp, ctx.file, false);
978     snprintf(lineno, sizeof(lineno), ")\n#pragma line(%lu)\n", (unsigned long)(ctx.line+1));
979     ftepp_out(ftepp, lineno, false);
980
981     /* skip the line */
982     (void)ftepp_next(ftepp);
983     if (!ftepp_skipspace(ftepp))
984         return false;
985     if (ftepp->token != TOKEN_EOL) {
986         ftepp_error(ftepp, "stray tokens after #include");
987         return false;
988     }
989     (void)ftepp_next(ftepp);
990
991     return true;
992 }
993
994 /* Basic structure handlers */
995 static bool ftepp_else_allowed(ftepp_t *ftepp)
996 {
997     if (!vec_size(ftepp->conditions)) {
998         ftepp_error(ftepp, "#else without #if");
999         return false;
1000     }
1001     if (vec_last(ftepp->conditions).had_else) {
1002         ftepp_error(ftepp, "multiple #else for a single #if");
1003         return false;
1004     }
1005     return true;
1006 }
1007
1008 static bool ftepp_hash(ftepp_t *ftepp)
1009 {
1010     ppcondition cond;
1011     ppcondition *pc;
1012
1013     lex_ctx ctx = ftepp_ctx(ftepp);
1014
1015     if (!ftepp_skipspace(ftepp))
1016         return false;
1017
1018     switch (ftepp->token) {
1019         case TOKEN_KEYWORD:
1020         case TOKEN_IDENT:
1021         case TOKEN_TYPENAME:
1022             if (!strcmp(ftepp_tokval(ftepp), "define")) {
1023                 return ftepp_define(ftepp);
1024             }
1025             else if (!strcmp(ftepp_tokval(ftepp), "undef")) {
1026                 return ftepp_undef(ftepp);
1027             }
1028             else if (!strcmp(ftepp_tokval(ftepp), "ifdef")) {
1029                 if (!ftepp_ifdef(ftepp, &cond))
1030                     return false;
1031                 cond.was_on = cond.on;
1032                 vec_push(ftepp->conditions, cond);
1033                 ftepp->output_on = ftepp->output_on && cond.on;
1034                 break;
1035             }
1036             else if (!strcmp(ftepp_tokval(ftepp), "ifndef")) {
1037                 if (!ftepp_ifdef(ftepp, &cond))
1038                     return false;
1039                 cond.on = !cond.on;
1040                 cond.was_on = cond.on;
1041                 vec_push(ftepp->conditions, cond);
1042                 ftepp->output_on = ftepp->output_on && cond.on;
1043                 break;
1044             }
1045             else if (!strcmp(ftepp_tokval(ftepp), "elifdef")) {
1046                 if (!ftepp_else_allowed(ftepp))
1047                     return false;
1048                 if (!ftepp_ifdef(ftepp, &cond))
1049                     return false;
1050                 pc = &vec_last(ftepp->conditions);
1051                 pc->on     = !pc->was_on && cond.on;
1052                 pc->was_on = pc->was_on || pc->on;
1053                 ftepp_update_output_condition(ftepp);
1054                 break;
1055             }
1056             else if (!strcmp(ftepp_tokval(ftepp), "elifndef")) {
1057                 if (!ftepp_else_allowed(ftepp))
1058                     return false;
1059                 if (!ftepp_ifdef(ftepp, &cond))
1060                     return false;
1061                 cond.on = !cond.on;
1062                 pc = &vec_last(ftepp->conditions);
1063                 pc->on     = !pc->was_on && cond.on;
1064                 pc->was_on = pc->was_on || pc->on;
1065                 ftepp_update_output_condition(ftepp);
1066                 break;
1067             }
1068             else if (!strcmp(ftepp_tokval(ftepp), "elif")) {
1069                 if (!ftepp_else_allowed(ftepp))
1070                     return false;
1071                 if (!ftepp_if(ftepp, &cond))
1072                     return false;
1073                 pc = &vec_last(ftepp->conditions);
1074                 pc->on     = !pc->was_on && cond.on;
1075                 pc->was_on = pc->was_on  || pc->on;
1076                 ftepp_update_output_condition(ftepp);
1077                 break;
1078             }
1079             else if (!strcmp(ftepp_tokval(ftepp), "if")) {
1080                 if (!ftepp_if(ftepp, &cond))
1081                     return false;
1082                 cond.was_on = cond.on;
1083                 vec_push(ftepp->conditions, cond);
1084                 ftepp->output_on = ftepp->output_on && cond.on;
1085                 break;
1086             }
1087             else if (!strcmp(ftepp_tokval(ftepp), "else")) {
1088                 if (!ftepp_else_allowed(ftepp))
1089                     return false;
1090                 pc = &vec_last(ftepp->conditions);
1091                 pc->on = !pc->was_on;
1092                 pc->had_else = true;
1093                 ftepp_next(ftepp);
1094                 ftepp_update_output_condition(ftepp);
1095                 break;
1096             }
1097             else if (!strcmp(ftepp_tokval(ftepp), "endif")) {
1098                 if (!vec_size(ftepp->conditions)) {
1099                     ftepp_error(ftepp, "#endif without #if");
1100                     return false;
1101                 }
1102                 vec_pop(ftepp->conditions);
1103                 ftepp_next(ftepp);
1104                 ftepp_update_output_condition(ftepp);
1105                 break;
1106             }
1107             else if (!strcmp(ftepp_tokval(ftepp), "include")) {
1108                 return ftepp_include(ftepp);
1109             }
1110             else if (!strcmp(ftepp_tokval(ftepp), "pragma")) {
1111                 ftepp_out(ftepp, "#", false);
1112                 break;
1113             }
1114             else {
1115                 ftepp_error(ftepp, "unrecognized preprocessor directive: `%s`", ftepp_tokval(ftepp));
1116                 return false;
1117             }
1118             break;
1119         default:
1120             ftepp_error(ftepp, "unexpected preprocessor token: `%s`", ftepp_tokval(ftepp));
1121             return false;
1122         case TOKEN_EOL:
1123             ftepp_errorat(ftepp, ctx, "empty preprocessor directive");
1124             return false;
1125         case TOKEN_EOF:
1126             ftepp_error(ftepp, "missing newline at end of file", ftepp_tokval(ftepp));
1127             return false;
1128
1129         /* Builtins! Don't forget the builtins! */
1130         case TOKEN_INTCONST:
1131         case TOKEN_FLOATCONST:
1132             ftepp_out(ftepp, "#", false);
1133             return true;
1134     }
1135     if (!ftepp_skipspace(ftepp))
1136         return false;
1137     return true;
1138 }
1139
1140 static bool ftepp_preprocess(ftepp_t *ftepp)
1141 {
1142     ppmacro *macro;
1143     bool     newline = true;
1144
1145     ftepp->lex->flags.preprocessing = true;
1146     ftepp->lex->flags.mergelines    = false;
1147     ftepp->lex->flags.noops         = true;
1148
1149     ftepp_next(ftepp);
1150     do
1151     {
1152         if (ftepp->token >= TOKEN_EOF)
1153             break;
1154 #if 0
1155         ftepp->newline = newline;
1156         newline = false;
1157 #else
1158         /* For the sake of FTE compatibility... FU, really */
1159         ftepp->newline = newline = true;
1160 #endif
1161
1162         switch (ftepp->token) {
1163             case TOKEN_KEYWORD:
1164             case TOKEN_IDENT:
1165             case TOKEN_TYPENAME:
1166                 macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
1167                 if (!macro) {
1168                     ftepp_out(ftepp, ftepp_tokval(ftepp), false);
1169                     ftepp_next(ftepp);
1170                     break;
1171                 }
1172                 if (!ftepp_macro_call(ftepp, macro))
1173                     ftepp->token = TOKEN_ERROR;
1174                 break;
1175             case '#':
1176                 if (!ftepp->newline) {
1177                     ftepp_out(ftepp, ftepp_tokval(ftepp), false);
1178                     ftepp_next(ftepp);
1179                     break;
1180                 }
1181                 ftepp->lex->flags.mergelines = true;
1182                 if (ftepp_next(ftepp) >= TOKEN_EOF) {
1183                     ftepp_error(ftepp, "error in preprocessor directive");
1184                     ftepp->token = TOKEN_ERROR;
1185                     break;
1186                 }
1187                 if (!ftepp_hash(ftepp))
1188                     ftepp->token = TOKEN_ERROR;
1189                 ftepp->lex->flags.mergelines = false;
1190                 break;
1191             case TOKEN_EOL:
1192                 newline = true;
1193                 ftepp_out(ftepp, "\n", true);
1194                 ftepp_next(ftepp);
1195                 break;
1196             default:
1197                 ftepp_out(ftepp, ftepp_tokval(ftepp), false);
1198                 ftepp_next(ftepp);
1199                 break;
1200         }
1201     } while (!ftepp->errors && ftepp->token < TOKEN_EOF);
1202
1203     newline = ftepp->token == TOKEN_EOF;
1204     return newline;
1205 }
1206
1207 /* Like in parser.c - files keep the previous state so we have one global
1208  * preprocessor. Except here we will want to warn about dangling #ifs.
1209  */
1210 static ftepp_t *ftepp;
1211
1212 static bool ftepp_preprocess_done()
1213 {
1214     bool retval = true;
1215     lex_close(ftepp->lex);
1216     ftepp->lex = NULL;
1217     if (vec_size(ftepp->conditions)) {
1218         if (ftepp_warn(ftepp, WARN_MULTIFILE_IF, "#if spanning multiple files, is this intended?"))
1219             retval = false;
1220     }
1221     if (ftepp->itemname) {
1222         mem_d(ftepp->itemname);
1223         ftepp->itemname = NULL;
1224     }
1225     return retval;
1226 }
1227
1228 bool ftepp_preprocess_file(const char *filename)
1229 {
1230     ftepp->lex = lex_open(filename);
1231     ftepp->itemname = util_strdup(filename);
1232     if (!ftepp->lex) {
1233         con_out("failed to open file \"%s\"\n", filename);
1234         return false;
1235     }
1236     if (!ftepp_preprocess(ftepp)) {
1237         ftepp_delete(ftepp);
1238         return false;
1239     }
1240     return ftepp_preprocess_done();
1241 }
1242
1243 bool ftepp_preprocess_string(const char *name, const char *str)
1244 {
1245     ftepp->lex = lex_open_string(str, strlen(str), name);
1246     ftepp->itemname = util_strdup(name);
1247     if (!ftepp->lex) {
1248         con_out("failed to create lexer for string \"%s\"\n", name);
1249         return false;
1250     }
1251     if (!ftepp_preprocess(ftepp)) {
1252         ftepp_delete(ftepp);
1253         return false;
1254     }
1255     return ftepp_preprocess_done();
1256 }
1257
1258 bool ftepp_init()
1259 {
1260     ftepp = ftepp_new();
1261     return !!ftepp;
1262 }
1263
1264 const char *ftepp_get()
1265 {
1266     return ftepp->output_string;
1267 }
1268
1269 void ftepp_flush()
1270 {
1271     vec_free(ftepp->output_string);
1272 }
1273
1274 void ftepp_finish()
1275 {
1276     if (!ftepp)
1277         return;
1278     ftepp_delete(ftepp);
1279     ftepp = NULL;
1280 }