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