]> git.xonotic.org Git - xonotic/gmqcc.git/blob - ftepp.c
unsed params in ftepp.c
[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     (void)ctx;
138     ppmacro *macro = (ppmacro*)mem_a(sizeof(ppmacro));
139     memset(macro, 0, sizeof(*macro));
140     macro->name = util_strdup(name);
141     return macro;
142 }
143
144 static void ppmacro_delete(ppmacro *self)
145 {
146     size_t i;
147     for (i = 0; i < vec_size(self->params); ++i)
148         mem_d(self->params[i]);
149     vec_free(self->params);
150     for (i = 0; i < vec_size(self->output); ++i)
151         pptoken_delete(self->output[i]);
152     vec_free(self->output);
153     mem_d(self->name);
154     mem_d(self);
155 }
156
157 static ftepp_t* ftepp_new()
158 {
159     ftepp_t *ftepp;
160
161     ftepp = (ftepp_t*)mem_a(sizeof(*ftepp));
162     memset(ftepp, 0, sizeof(*ftepp));
163
164     ftepp->output_on = true;
165
166     return ftepp;
167 }
168
169 static void ftepp_delete(ftepp_t *self)
170 {
171     size_t i;
172     if (self->itemname)
173         mem_d(self->itemname);
174     for (i = 0; i < vec_size(self->macros); ++i)
175         ppmacro_delete(self->macros[i]);
176     vec_free(self->macros);
177     vec_free(self->conditions);
178     if (self->lex)
179         lex_close(self->lex);
180     mem_d(self);
181 }
182
183 static void ftepp_out(ftepp_t *ftepp, const char *str, bool ignore_cond)
184 {
185     if (ignore_cond || ftepp->output_on)
186     {
187         size_t len;
188         char  *data;
189         len = strlen(str);
190         data = vec_add(ftepp->output_string, len);
191         memcpy(data, str, len);
192     }
193 }
194
195 static void ftepp_update_output_condition(ftepp_t *ftepp)
196 {
197     size_t i;
198     ftepp->output_on = true;
199     for (i = 0; i < vec_size(ftepp->conditions); ++i)
200         ftepp->output_on = ftepp->output_on && ftepp->conditions[i].on;
201 }
202
203 static ppmacro* ftepp_macro_find(ftepp_t *ftepp, const char *name)
204 {
205     size_t i;
206     for (i = 0; i < vec_size(ftepp->macros); ++i) {
207         if (!strcmp(name, ftepp->macros[i]->name))
208             return ftepp->macros[i];
209     }
210     return NULL;
211 }
212
213 static void ftepp_macro_delete(ftepp_t *ftepp, const char *name)
214 {
215     size_t i;
216     for (i = 0; i < vec_size(ftepp->macros); ++i) {
217         if (!strcmp(name, ftepp->macros[i]->name)) {
218             vec_remove(ftepp->macros, i, 1);
219             return;
220         }
221     }
222 }
223
224 static inline int ftepp_next(ftepp_t *ftepp)
225 {
226     return (ftepp->token = lex_do(ftepp->lex));
227 }
228
229 /* Important: this does not skip newlines! */
230 static bool ftepp_skipspace(ftepp_t *ftepp)
231 {
232     if (ftepp->token != TOKEN_WHITE)
233         return true;
234     while (ftepp_next(ftepp) == TOKEN_WHITE) {}
235     if (ftepp->token >= TOKEN_EOF) {
236         ftepp_error(ftepp, "unexpected end of preprocessor directive");
237         return false;
238     }
239     return true;
240 }
241
242 /* this one skips EOLs as well */
243 static bool ftepp_skipallwhite(ftepp_t *ftepp)
244 {
245     if (ftepp->token != TOKEN_WHITE && ftepp->token != TOKEN_EOL)
246         return true;
247     do {
248         ftepp_next(ftepp);
249     } while (ftepp->token == TOKEN_WHITE || ftepp->token == TOKEN_EOL);
250     if (ftepp->token >= TOKEN_EOF) {
251         ftepp_error(ftepp, "unexpected end of preprocessor directive");
252         return false;
253     }
254     return true;
255 }
256
257 /**
258  * The huge macro parsing code...
259  */
260 static bool ftepp_define_params(ftepp_t *ftepp, ppmacro *macro)
261 {
262     do {
263         ftepp_next(ftepp);
264         if (!ftepp_skipspace(ftepp))
265             return false;
266         if (ftepp->token == ')')
267             break;
268         switch (ftepp->token) {
269             case TOKEN_IDENT:
270             case TOKEN_TYPENAME:
271             case TOKEN_KEYWORD:
272                 break;
273             default:
274                 ftepp_error(ftepp, "unexpected token in parameter list");
275                 return false;
276         }
277         vec_push(macro->params, util_strdup(ftepp_tokval(ftepp)));
278         ftepp_next(ftepp);
279         if (!ftepp_skipspace(ftepp))
280             return false;
281     } while (ftepp->token == ',');
282     if (ftepp->token != ')') {
283         ftepp_error(ftepp, "expected closing paren after macro parameter list");
284         return false;
285     }
286     ftepp_next(ftepp);
287     /* skipspace happens in ftepp_define */
288     return true;
289 }
290
291 static bool ftepp_define_body(ftepp_t *ftepp, ppmacro *macro)
292 {
293     pptoken *ptok;
294     while (ftepp->token != TOKEN_EOL && ftepp->token < TOKEN_EOF) {
295         ptok = pptoken_make(ftepp);
296         vec_push(macro->output, ptok);
297         ftepp_next(ftepp);
298     }
299     /* recursive expansion can cause EOFs here */
300     if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) {
301         ftepp_error(ftepp, "unexpected junk after macro or unexpected end of file");
302         return false;
303     }
304     return true;
305 }
306
307 static bool ftepp_define(ftepp_t *ftepp)
308 {
309     ppmacro *macro;
310     (void)ftepp_next(ftepp);
311     if (!ftepp_skipspace(ftepp))
312         return false;
313
314     switch (ftepp->token) {
315         case TOKEN_IDENT:
316         case TOKEN_TYPENAME:
317         case TOKEN_KEYWORD:
318             macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
319             if (macro && ftepp->output_on) {
320                 if (ftepp_warn(ftepp, WARN_PREPROCESSOR, "redefining `%s`", ftepp_tokval(ftepp)))
321                     return false;
322                 ftepp_macro_delete(ftepp, ftepp_tokval(ftepp));
323             }
324             macro = ppmacro_new(ftepp_ctx(ftepp), ftepp_tokval(ftepp));
325             break;
326         default:
327             ftepp_error(ftepp, "expected macro name");
328             return false;
329     }
330
331     (void)ftepp_next(ftepp);
332
333     if (ftepp->token == '(') {
334         macro->has_params = true;
335         if (!ftepp_define_params(ftepp, macro))
336             return false;
337     }
338
339     if (!ftepp_skipspace(ftepp))
340         return false;
341
342     if (!ftepp_define_body(ftepp, macro))
343         return false;
344
345     if (ftepp->output_on)
346         vec_push(ftepp->macros, macro);
347     else {
348         ppmacro_delete(macro);
349     }
350     return true;
351 }
352
353 /**
354  * When a macro is used we have to handle parameters as well
355  * as special-concatenation via ## or stringification via #
356  *
357  * Note: parenthesis can nest, so FOO((a),b) is valid, but only
358  * this kind of parens. Curly braces or [] don't count towards the
359  * paren-level.
360  */
361 typedef struct {
362     pptoken **tokens;
363 } macroparam;
364
365 static void macroparam_clean(macroparam *self)
366 {
367     size_t i;
368     for (i = 0; i < vec_size(self->tokens); ++i)
369         pptoken_delete(self->tokens[i]);
370     vec_free(self->tokens);
371 }
372
373 /* need to leave the last token up */
374 static bool ftepp_macro_call_params(ftepp_t *ftepp, macroparam **out_params)
375 {
376     macroparam *params = NULL;
377     pptoken    *ptok;
378     macroparam  mp;
379     size_t      parens = 0;
380     size_t      i;
381
382     if (!ftepp_skipallwhite(ftepp))
383         return false;
384     while (ftepp->token != ')') {
385         mp.tokens = NULL;
386         if (!ftepp_skipallwhite(ftepp))
387             return false;
388         while (parens || ftepp->token != ',') {
389             if (ftepp->token == '(')
390                 ++parens;
391             else if (ftepp->token == ')') {
392                 if (!parens)
393                     break;
394                 --parens;
395             }
396             ptok = pptoken_make(ftepp);
397             vec_push(mp.tokens, ptok);
398             if (ftepp_next(ftepp) >= TOKEN_EOF) {
399                 ftepp_error(ftepp, "unexpected EOF in macro call");
400                 goto on_error;
401             }
402         }
403         vec_push(params, mp);
404         mp.tokens = NULL;
405         if (ftepp->token == ')')
406             break;
407         if (ftepp->token != ',') {
408             ftepp_error(ftepp, "expected closing paren or comma in macro call");
409             goto on_error;
410         }
411         if (ftepp_next(ftepp) >= TOKEN_EOF) {
412             ftepp_error(ftepp, "unexpected EOF in macro call");
413             goto on_error;
414         }
415     }
416     /* need to leave that up
417     if (ftepp_next(ftepp) >= TOKEN_EOF) {
418         ftepp_error(ftepp, "unexpected EOF in macro call");
419         goto on_error;
420     }
421     */
422     *out_params = params;
423     return true;
424
425 on_error:
426     if (mp.tokens)
427         macroparam_clean(&mp);
428     for (i = 0; i < vec_size(params); ++i)
429         macroparam_clean(&params[i]);
430     vec_free(params);
431     return false;
432 }
433
434 static bool macro_params_find(ppmacro *macro, const char *name, size_t *idx)
435 {
436     size_t i;
437     for (i = 0; i < vec_size(macro->params); ++i) {
438         if (!strcmp(macro->params[i], name)) {
439             *idx = i;
440             return true;
441         }
442     }
443     return false;
444 }
445
446 static void ftepp_stringify_token(ftepp_t *ftepp, pptoken *token)
447 {
448     char        chs[2];
449     const char *ch;
450     chs[1] = 0;
451     switch (token->token) {
452         case TOKEN_STRINGCONST:
453             ch = token->value;
454             while (*ch) {
455                 /* in preprocessor mode strings already are string,
456                  * so we don't get actual newline bytes here.
457                  * Still need to escape backslashes and quotes.
458                  */
459                 switch (*ch) {
460                     case '\\': ftepp_out(ftepp, "\\\\", false); break;
461                     case '"':  ftepp_out(ftepp, "\\\"", false); break;
462                     default:
463                         chs[0] = *ch;
464                         ftepp_out(ftepp, chs, false);
465                         break;
466                 }
467                 ++ch;
468             }
469             break;
470         case TOKEN_WHITE:
471             ftepp_out(ftepp, " ", false);
472             break;
473         case TOKEN_EOL:
474             ftepp_out(ftepp, "\\n", false);
475             break;
476         default:
477             ftepp_out(ftepp, token->value, false);
478             break;
479     }
480 }
481
482 static void ftepp_stringify(ftepp_t *ftepp, macroparam *param)
483 {
484     size_t i;
485     ftepp_out(ftepp, "\"", false);
486     for (i = 0; i < vec_size(param->tokens); ++i)
487         ftepp_stringify_token(ftepp, param->tokens[i]);
488     ftepp_out(ftepp, "\"", false);
489 }
490
491 static void ftepp_recursion_header(ftepp_t *ftepp)
492 {
493     ftepp_out(ftepp, "\n#pragma push(line)\n", false);
494 }
495
496 static void ftepp_recursion_footer(ftepp_t *ftepp)
497 {
498     ftepp_out(ftepp, "\n#pragma pop(line)\n", false);
499 }
500
501 static bool ftepp_preprocess(ftepp_t *ftepp);
502 static bool ftepp_macro_expand(ftepp_t *ftepp, ppmacro *macro, macroparam *params)
503 {
504     char     *old_string = ftepp->output_string;
505     lex_file *old_lexer = ftepp->lex;
506     bool retval = true;
507
508     size_t    o, pi, pv;
509     lex_file *inlex;
510
511     int nextok;
512
513     /* really ... */
514     if (!vec_size(macro->output))
515         return true;
516
517     ftepp->output_string = NULL;
518     for (o = 0; o < vec_size(macro->output); ++o) {
519         pptoken *out = macro->output[o];
520         switch (out->token) {
521             case TOKEN_IDENT:
522             case TOKEN_TYPENAME:
523             case TOKEN_KEYWORD:
524                 if (!macro_params_find(macro, out->value, &pi)) {
525                     ftepp_out(ftepp, out->value, false);
526                     break;
527                 } else {
528                     for (pv = 0; pv < vec_size(params[pi].tokens); ++pv) {
529                         out = params[pi].tokens[pv];
530                         if (out->token == TOKEN_EOL)
531                             ftepp_out(ftepp, "\n", false);
532                         else
533                             ftepp_out(ftepp, out->value, false);
534                     }
535                 }
536                 break;
537             case '#':
538                 if (o + 1 < vec_size(macro->output)) {
539                     nextok = macro->output[o+1]->token;
540                     if (nextok == '#') {
541                         /* raw concatenation */
542                         ++o;
543                         break;
544                     }
545                     if ( (nextok == TOKEN_IDENT    ||
546                           nextok == TOKEN_KEYWORD  ||
547                           nextok == TOKEN_TYPENAME) &&
548                         macro_params_find(macro, macro->output[o+1]->value, &pi))
549                     {
550                         ++o;
551                         ftepp_stringify(ftepp, &params[pi]);
552                         break;
553                     }
554                 }
555                 ftepp_out(ftepp, "#", false);
556                 break;
557             case TOKEN_EOL:
558                 ftepp_out(ftepp, "\n", false);
559                 break;
560             default:
561                 ftepp_out(ftepp, out->value, false);
562                 break;
563         }
564     }
565     vec_push(ftepp->output_string, 0);
566     /* Now run the preprocessor recursively on this string buffer */
567     /*
568     printf("__________\n%s\n=========\n", ftepp->output_string);
569     */
570     inlex = lex_open_string(ftepp->output_string, vec_size(ftepp->output_string)-1, ftepp->lex->name);
571     if (!inlex) {
572         ftepp_error(ftepp, "internal error: failed to instantiate lexer");
573         retval = false;
574         goto cleanup;
575     }
576     ftepp->output_string = old_string;
577     ftepp->lex = inlex;
578     ftepp_recursion_header(ftepp);
579     if (!ftepp_preprocess(ftepp)) {
580         lex_close(ftepp->lex);
581         retval = false;
582         goto cleanup;
583     }
584     ftepp_recursion_footer(ftepp);
585     old_string = ftepp->output_string;
586
587 cleanup:
588     ftepp->lex           = old_lexer;
589     ftepp->output_string = old_string;
590     return retval;
591 }
592
593 static bool ftepp_macro_call(ftepp_t *ftepp, ppmacro *macro)
594 {
595     size_t     o;
596     macroparam *params = NULL;
597     bool        retval = true;
598
599     if (!macro->has_params) {
600         if (!ftepp_macro_expand(ftepp, macro, NULL))
601             return false;
602         ftepp_next(ftepp);
603         return true;
604     }
605     ftepp_next(ftepp);
606
607     if (!ftepp_skipallwhite(ftepp))
608         return false;
609
610     if (ftepp->token != '(') {
611         ftepp_error(ftepp, "expected macro parameters in parenthesis");
612         return false;
613     }
614
615     ftepp_next(ftepp);
616     if (!ftepp_macro_call_params(ftepp, &params))
617         return false;
618
619     if (vec_size(params) != vec_size(macro->params)) {
620         ftepp_error(ftepp, "macro %s expects %u paramteters, %u provided", macro->name,
621                     (unsigned int)vec_size(macro->params),
622                     (unsigned int)vec_size(params));
623         retval = false;
624         goto cleanup;
625     }
626
627     if (!ftepp_macro_expand(ftepp, macro, params))
628         retval = false;
629     ftepp_next(ftepp);
630
631 cleanup:
632     for (o = 0; o < vec_size(params); ++o)
633         macroparam_clean(&params[o]);
634     vec_free(params);
635     return retval;
636 }
637
638 /**
639  * #if - the FTEQCC way:
640  *    defined(FOO) => true if FOO was #defined regardless of parameters or contents
641  *    <numbers>    => True if the number is not 0
642  *    !<factor>    => True if the factor yields false
643  *    !!<factor>   => ERROR on 2 or more unary nots
644  *    <macro>      => becomes the macro's FIRST token regardless of parameters
645  *    <e> && <e>   => True if both expressions are true
646  *    <e> || <e>   => True if either expression is true
647  *    <string>     => False
648  *    <ident>      => False (remember for macros the <macro> rule applies instead)
649  * Unary + and - are weird and wrong in fteqcc so we don't allow them
650  * parenthesis in expressions are allowed
651  * parameter lists on macros are errors
652  * No mathematical calculations are executed
653  */
654 static bool ftepp_if_expr(ftepp_t *ftepp, bool *out)
655 {
656     ppmacro *macro;
657     bool     wasnot = false;
658
659     if (!ftepp_skipspace(ftepp))
660         return false;
661
662     while (ftepp->token == '!') {
663         wasnot = true;
664         ftepp_next(ftepp);
665         if (!ftepp_skipspace(ftepp))
666             return false;
667     }
668
669     switch (ftepp->token) {
670         case TOKEN_IDENT:
671         case TOKEN_TYPENAME:
672         case TOKEN_KEYWORD:
673             if (!strcmp(ftepp_tokval(ftepp), "defined")) {
674                 ftepp_next(ftepp);
675                 if (!ftepp_skipspace(ftepp))
676                     return false;
677                 if (ftepp->token != '(') {
678                     ftepp_error(ftepp, "`defined` keyword in #if requires a macro name in parenthesis");
679                     return false;
680                 }
681                 ftepp_next(ftepp);
682                 if (!ftepp_skipspace(ftepp))
683                     return false;
684                 if (ftepp->token != TOKEN_IDENT &&
685                     ftepp->token != TOKEN_TYPENAME &&
686                     ftepp->token != TOKEN_KEYWORD)
687                 {
688                     ftepp_error(ftepp, "defined() used on an unexpected token type");
689                     return false;
690                 }
691                 macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
692                 *out = !!macro;
693                 ftepp_next(ftepp);
694                 if (!ftepp_skipspace(ftepp))
695                     return false;
696                 if (ftepp->token != ')') {
697                     ftepp_error(ftepp, "expected closing paren");
698                     return false;
699                 }
700                 break;
701             }
702
703             macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
704             if (!macro || !vec_size(macro->output)) {
705                 *out = false;
706             } else {
707                 /* This does not expand recursively! */
708                 switch (macro->output[0]->token) {
709                     case TOKEN_INTCONST:
710                         *out = !!(macro->output[0]->constval.f);
711                         break;
712                     case TOKEN_FLOATCONST:
713                         *out = !!(macro->output[0]->constval.f);
714                         break;
715                     default:
716                         *out = false;
717                         break;
718                 }
719             }
720             break;
721         case TOKEN_STRINGCONST:
722             *out = false;
723             break;
724         case TOKEN_INTCONST:
725             *out = !!(ftepp->lex->tok.constval.i);
726             break;
727         case TOKEN_FLOATCONST:
728             *out = !!(ftepp->lex->tok.constval.f);
729             break;
730
731         case '(':
732             ftepp_next(ftepp);
733             if (!ftepp_if_expr(ftepp, out))
734                 return false;
735             if (ftepp->token != ')') {
736                 ftepp_error(ftepp, "expected closing paren in #if expression");
737                 return false;
738             }
739             break;
740
741         default:
742             ftepp_error(ftepp, "junk in #if");
743             return false;
744     }
745     if (wasnot)
746         *out = !*out;
747
748     ftepp->lex->flags.noops = false;
749     ftepp_next(ftepp);
750     if (!ftepp_skipspace(ftepp))
751         return false;
752     ftepp->lex->flags.noops = true;
753
754     if (ftepp->token == ')')
755         return true;
756
757     if (ftepp->token != TOKEN_OPERATOR)
758         return true;
759
760     if (!strcmp(ftepp_tokval(ftepp), "&&") ||
761         !strcmp(ftepp_tokval(ftepp), "||"))
762     {
763         bool next = false;
764         char opc  = ftepp_tokval(ftepp)[0];
765
766         ftepp_next(ftepp);
767         if (!ftepp_if_expr(ftepp, &next))
768             return false;
769
770         if (opc == '&')
771             *out = *out && next;
772         else
773             *out = *out || next;
774         return true;
775     }
776     else {
777         ftepp_error(ftepp, "junk after #if");
778         return false;
779     }
780 }
781
782 static bool ftepp_if(ftepp_t *ftepp, ppcondition *cond)
783 {
784     bool result = false;
785
786     memset(cond, 0, sizeof(*cond));
787     (void)ftepp_next(ftepp);
788
789     if (!ftepp_skipspace(ftepp))
790         return false;
791     if (ftepp->token == TOKEN_EOL) {
792         ftepp_error(ftepp, "expected expression for #if-directive");
793         return false;
794     }
795
796     if (!ftepp_if_expr(ftepp, &result))
797         return false;
798
799     cond->on = result;
800     return true;
801 }
802
803 /**
804  * ifdef is rather simple
805  */
806 static bool ftepp_ifdef(ftepp_t *ftepp, ppcondition *cond)
807 {
808     ppmacro *macro;
809     memset(cond, 0, sizeof(*cond));
810     (void)ftepp_next(ftepp);
811     if (!ftepp_skipspace(ftepp))
812         return false;
813
814     switch (ftepp->token) {
815         case TOKEN_IDENT:
816         case TOKEN_TYPENAME:
817         case TOKEN_KEYWORD:
818             macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
819             break;
820         default:
821             ftepp_error(ftepp, "expected macro name");
822             return false;
823     }
824
825     (void)ftepp_next(ftepp);
826     if (!ftepp_skipspace(ftepp))
827         return false;
828     /* relaxing this condition
829     if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) {
830         ftepp_error(ftepp, "stray tokens after #ifdef");
831         return false;
832     }
833     */
834     cond->on = !!macro;
835     return true;
836 }
837
838 /**
839  * undef is also simple
840  */
841 static bool ftepp_undef(ftepp_t *ftepp)
842 {
843     (void)ftepp_next(ftepp);
844     if (!ftepp_skipspace(ftepp))
845         return false;
846
847     if (ftepp->output_on) {
848         switch (ftepp->token) {
849             case TOKEN_IDENT:
850             case TOKEN_TYPENAME:
851             case TOKEN_KEYWORD:
852                 ftepp_macro_delete(ftepp, ftepp_tokval(ftepp));
853                 break;
854             default:
855                 ftepp_error(ftepp, "expected macro name");
856                 return false;
857         }
858     }
859
860     (void)ftepp_next(ftepp);
861     if (!ftepp_skipspace(ftepp))
862         return false;
863     /* relaxing this condition
864     if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) {
865         ftepp_error(ftepp, "stray tokens after #ifdef");
866         return false;
867     }
868     */
869     return true;
870 }
871
872 /* Special unescape-string function which skips a leading quote
873  * and stops at a quote, not just at \0
874  */
875 static void unescape(const char *str, char *out) {
876     ++str;
877     while (*str && *str != '"') {
878         if (*str == '\\') {
879             ++str;
880             switch (*str) {
881                 case '\\': *out++ = *str; break;
882                 case '"':  *out++ = *str; break;
883                 case 'a':  *out++ = '\a'; break;
884                 case 'b':  *out++ = '\b'; break;
885                 case 'r':  *out++ = '\r'; break;
886                 case 'n':  *out++ = '\n'; break;
887                 case 't':  *out++ = '\t'; break;
888                 case 'f':  *out++ = '\f'; break;
889                 case 'v':  *out++ = '\v'; break;
890                 default:
891                     *out++ = '\\';
892                     *out++ = *str;
893                     break;
894             }
895             ++str;
896             continue;
897         }
898
899         *out++ = *str++;
900     }
901     *out = 0;
902 }
903
904 static char *ftepp_include_find(ftepp_t *ftepp, const char *file)
905 {
906     char *filename = NULL;
907     size_t len;
908
909     if (ftepp->itemname) {
910         const char *last_slash;
911         last_slash = strrchr(ftepp->itemname, '/');
912         if (last_slash) {
913             len = last_slash - ftepp->itemname;
914             memcpy(vec_add(filename, len), ftepp->itemname, len);
915             vec_push(filename, '/');
916         }
917         else {
918             len = strlen(ftepp->itemname);
919             memcpy(vec_add(filename, len), ftepp->itemname, len);
920             if (vec_last(filename) != '/')
921                 vec_push(filename, '/');
922         }
923     }
924     len = strlen(file);
925     memcpy(vec_add(filename, len), file, len);
926     vec_push(filename, 0);
927     return filename;
928 }
929
930 /**
931  * Include a file.
932  * FIXME: do we need/want a -I option?
933  * FIXME: what about when dealing with files in subdirectories coming from a progs.src?
934  */
935 static bool ftepp_include(ftepp_t *ftepp)
936 {
937     lex_file *old_lexer = ftepp->lex;
938     lex_file *inlex;
939     lex_ctx  ctx;
940     char     lineno[128];
941     char     *filename;
942
943     (void)ftepp_next(ftepp);
944     if (!ftepp_skipspace(ftepp))
945         return false;
946
947     if (ftepp->token != TOKEN_STRINGCONST) {
948         ftepp_error(ftepp, "expected filename to include");
949         return false;
950     }
951
952     ctx = ftepp_ctx(ftepp);
953
954     unescape(ftepp_tokval(ftepp), ftepp_tokval(ftepp));
955
956     ftepp_out(ftepp, "\n#pragma file(", false);
957     ftepp_out(ftepp, ftepp_tokval(ftepp), false);
958     ftepp_out(ftepp, ")\n#pragma line(1)\n", false);
959
960     filename = ftepp_include_find(ftepp, ftepp_tokval(ftepp));
961     inlex = lex_open(filename);
962     if (!inlex) {
963         ftepp_error(ftepp, "failed to open include file `%s`", filename);
964         vec_free(filename);
965         return false;
966     }
967     vec_free(filename);
968     ftepp->lex = inlex;
969     if (!ftepp_preprocess(ftepp)) {
970         lex_close(ftepp->lex);
971         ftepp->lex = old_lexer;
972         return false;
973     }
974     lex_close(ftepp->lex);
975     ftepp->lex = old_lexer;
976
977     ftepp_out(ftepp, "\n#pragma file(", false);
978     ftepp_out(ftepp, ctx.file, false);
979     snprintf(lineno, sizeof(lineno), ")\n#pragma line(%lu)\n", (unsigned long)(ctx.line+1));
980     ftepp_out(ftepp, lineno, false);
981
982     /* skip the line */
983     (void)ftepp_next(ftepp);
984     if (!ftepp_skipspace(ftepp))
985         return false;
986     if (ftepp->token != TOKEN_EOL) {
987         ftepp_error(ftepp, "stray tokens after #include");
988         return false;
989     }
990     (void)ftepp_next(ftepp);
991
992     return true;
993 }
994
995 /* Basic structure handlers */
996 static bool ftepp_else_allowed(ftepp_t *ftepp)
997 {
998     if (!vec_size(ftepp->conditions)) {
999         ftepp_error(ftepp, "#else without #if");
1000         return false;
1001     }
1002     if (vec_last(ftepp->conditions).had_else) {
1003         ftepp_error(ftepp, "multiple #else for a single #if");
1004         return false;
1005     }
1006     return true;
1007 }
1008
1009 static bool ftepp_hash(ftepp_t *ftepp)
1010 {
1011     ppcondition cond;
1012     ppcondition *pc;
1013
1014     lex_ctx ctx = ftepp_ctx(ftepp);
1015
1016     if (!ftepp_skipspace(ftepp))
1017         return false;
1018
1019     switch (ftepp->token) {
1020         case TOKEN_KEYWORD:
1021         case TOKEN_IDENT:
1022         case TOKEN_TYPENAME:
1023             if (!strcmp(ftepp_tokval(ftepp), "define")) {
1024                 return ftepp_define(ftepp);
1025             }
1026             else if (!strcmp(ftepp_tokval(ftepp), "undef")) {
1027                 return ftepp_undef(ftepp);
1028             }
1029             else if (!strcmp(ftepp_tokval(ftepp), "ifdef")) {
1030                 if (!ftepp_ifdef(ftepp, &cond))
1031                     return false;
1032                 cond.was_on = cond.on;
1033                 vec_push(ftepp->conditions, cond);
1034                 ftepp->output_on = ftepp->output_on && cond.on;
1035                 break;
1036             }
1037             else if (!strcmp(ftepp_tokval(ftepp), "ifndef")) {
1038                 if (!ftepp_ifdef(ftepp, &cond))
1039                     return false;
1040                 cond.on = !cond.on;
1041                 cond.was_on = cond.on;
1042                 vec_push(ftepp->conditions, cond);
1043                 ftepp->output_on = ftepp->output_on && cond.on;
1044                 break;
1045             }
1046             else if (!strcmp(ftepp_tokval(ftepp), "elifdef")) {
1047                 if (!ftepp_else_allowed(ftepp))
1048                     return false;
1049                 if (!ftepp_ifdef(ftepp, &cond))
1050                     return false;
1051                 pc = &vec_last(ftepp->conditions);
1052                 pc->on     = !pc->was_on && cond.on;
1053                 pc->was_on = pc->was_on || pc->on;
1054                 ftepp_update_output_condition(ftepp);
1055                 break;
1056             }
1057             else if (!strcmp(ftepp_tokval(ftepp), "elifndef")) {
1058                 if (!ftepp_else_allowed(ftepp))
1059                     return false;
1060                 if (!ftepp_ifdef(ftepp, &cond))
1061                     return false;
1062                 cond.on = !cond.on;
1063                 pc = &vec_last(ftepp->conditions);
1064                 pc->on     = !pc->was_on && cond.on;
1065                 pc->was_on = pc->was_on || pc->on;
1066                 ftepp_update_output_condition(ftepp);
1067                 break;
1068             }
1069             else if (!strcmp(ftepp_tokval(ftepp), "elif")) {
1070                 if (!ftepp_else_allowed(ftepp))
1071                     return false;
1072                 if (!ftepp_if(ftepp, &cond))
1073                     return false;
1074                 pc = &vec_last(ftepp->conditions);
1075                 pc->on     = !pc->was_on && cond.on;
1076                 pc->was_on = pc->was_on  || pc->on;
1077                 ftepp_update_output_condition(ftepp);
1078                 break;
1079             }
1080             else if (!strcmp(ftepp_tokval(ftepp), "if")) {
1081                 if (!ftepp_if(ftepp, &cond))
1082                     return false;
1083                 cond.was_on = cond.on;
1084                 vec_push(ftepp->conditions, cond);
1085                 ftepp->output_on = ftepp->output_on && cond.on;
1086                 break;
1087             }
1088             else if (!strcmp(ftepp_tokval(ftepp), "else")) {
1089                 if (!ftepp_else_allowed(ftepp))
1090                     return false;
1091                 pc = &vec_last(ftepp->conditions);
1092                 pc->on = !pc->was_on;
1093                 pc->had_else = true;
1094                 ftepp_next(ftepp);
1095                 ftepp_update_output_condition(ftepp);
1096                 break;
1097             }
1098             else if (!strcmp(ftepp_tokval(ftepp), "endif")) {
1099                 if (!vec_size(ftepp->conditions)) {
1100                     ftepp_error(ftepp, "#endif without #if");
1101                     return false;
1102                 }
1103                 vec_pop(ftepp->conditions);
1104                 ftepp_next(ftepp);
1105                 ftepp_update_output_condition(ftepp);
1106                 break;
1107             }
1108             else if (!strcmp(ftepp_tokval(ftepp), "include")) {
1109                 return ftepp_include(ftepp);
1110             }
1111             else if (!strcmp(ftepp_tokval(ftepp), "pragma")) {
1112                 ftepp_out(ftepp, "#", false);
1113                 break;
1114             }
1115             else {
1116                 ftepp_error(ftepp, "unrecognized preprocessor directive: `%s`", ftepp_tokval(ftepp));
1117                 return false;
1118             }
1119             /* break; never reached */
1120         default:
1121             ftepp_error(ftepp, "unexpected preprocessor token: `%s`", ftepp_tokval(ftepp));
1122             return false;
1123         case TOKEN_EOL:
1124             ftepp_errorat(ftepp, ctx, "empty preprocessor directive");
1125             return false;
1126         case TOKEN_EOF:
1127             ftepp_error(ftepp, "missing newline at end of file", ftepp_tokval(ftepp));
1128             return false;
1129
1130         /* Builtins! Don't forget the builtins! */
1131         case TOKEN_INTCONST:
1132         case TOKEN_FLOATCONST:
1133             ftepp_out(ftepp, "#", false);
1134             return true;
1135     }
1136     if (!ftepp_skipspace(ftepp))
1137         return false;
1138     return true;
1139 }
1140
1141 static bool ftepp_preprocess(ftepp_t *ftepp)
1142 {
1143     ppmacro *macro;
1144     bool     newline = true;
1145
1146     ftepp->lex->flags.preprocessing = true;
1147     ftepp->lex->flags.mergelines    = false;
1148     ftepp->lex->flags.noops         = true;
1149
1150     ftepp_next(ftepp);
1151     do
1152     {
1153         if (ftepp->token >= TOKEN_EOF)
1154             break;
1155 #if 0
1156         ftepp->newline = newline;
1157         newline = false;
1158 #else
1159         /* For the sake of FTE compatibility... FU, really */
1160         ftepp->newline = newline = true;
1161 #endif
1162
1163         switch (ftepp->token) {
1164             case TOKEN_KEYWORD:
1165             case TOKEN_IDENT:
1166             case TOKEN_TYPENAME:
1167                 macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
1168                 if (!macro) {
1169                     ftepp_out(ftepp, ftepp_tokval(ftepp), false);
1170                     ftepp_next(ftepp);
1171                     break;
1172                 }
1173                 if (!ftepp_macro_call(ftepp, macro))
1174                     ftepp->token = TOKEN_ERROR;
1175                 break;
1176             case '#':
1177                 if (!ftepp->newline) {
1178                     ftepp_out(ftepp, ftepp_tokval(ftepp), false);
1179                     ftepp_next(ftepp);
1180                     break;
1181                 }
1182                 ftepp->lex->flags.mergelines = true;
1183                 if (ftepp_next(ftepp) >= TOKEN_EOF) {
1184                     ftepp_error(ftepp, "error in preprocessor directive");
1185                     ftepp->token = TOKEN_ERROR;
1186                     break;
1187                 }
1188                 if (!ftepp_hash(ftepp))
1189                     ftepp->token = TOKEN_ERROR;
1190                 ftepp->lex->flags.mergelines = false;
1191                 break;
1192             case TOKEN_EOL:
1193                 newline = true;
1194                 ftepp_out(ftepp, "\n", true);
1195                 ftepp_next(ftepp);
1196                 break;
1197             default:
1198                 ftepp_out(ftepp, ftepp_tokval(ftepp), false);
1199                 ftepp_next(ftepp);
1200                 break;
1201         }
1202     } while (!ftepp->errors && ftepp->token < TOKEN_EOF);
1203
1204     newline = ftepp->token == TOKEN_EOF;
1205     return newline;
1206 }
1207
1208 /* Like in parser.c - files keep the previous state so we have one global
1209  * preprocessor. Except here we will want to warn about dangling #ifs.
1210  */
1211 static ftepp_t *ftepp;
1212
1213 static bool ftepp_preprocess_done()
1214 {
1215     bool retval = true;
1216     lex_close(ftepp->lex);
1217     ftepp->lex = NULL;
1218     if (vec_size(ftepp->conditions)) {
1219         if (ftepp_warn(ftepp, WARN_MULTIFILE_IF, "#if spanning multiple files, is this intended?"))
1220             retval = false;
1221     }
1222     if (ftepp->itemname) {
1223         mem_d(ftepp->itemname);
1224         ftepp->itemname = NULL;
1225     }
1226     return retval;
1227 }
1228
1229 bool ftepp_preprocess_file(const char *filename)
1230 {
1231     ftepp->lex = lex_open(filename);
1232     ftepp->itemname = util_strdup(filename);
1233     if (!ftepp->lex) {
1234         con_out("failed to open file \"%s\"\n", filename);
1235         return false;
1236     }
1237     if (!ftepp_preprocess(ftepp)) {
1238         ftepp_delete(ftepp);
1239         return false;
1240     }
1241     return ftepp_preprocess_done();
1242 }
1243
1244 bool ftepp_preprocess_string(const char *name, const char *str)
1245 {
1246     ftepp->lex = lex_open_string(str, strlen(str), name);
1247     ftepp->itemname = util_strdup(name);
1248     if (!ftepp->lex) {
1249         con_out("failed to create lexer for string \"%s\"\n", name);
1250         return false;
1251     }
1252     if (!ftepp_preprocess(ftepp)) {
1253         ftepp_delete(ftepp);
1254         return false;
1255     }
1256     return ftepp_preprocess_done();
1257 }
1258
1259 bool ftepp_init()
1260 {
1261     ftepp = ftepp_new();
1262     return !!ftepp;
1263 }
1264
1265 const char *ftepp_get()
1266 {
1267     return ftepp->output_string;
1268 }
1269
1270 void ftepp_flush()
1271 {
1272     vec_free(ftepp->output_string);
1273 }
1274
1275 void ftepp_finish()
1276 {
1277     if (!ftepp)
1278         return;
1279     ftepp_delete(ftepp);
1280     ftepp = NULL;
1281 }