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