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