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