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