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