]> git.xonotic.org Git - xonotic/gmqcc.git/blob - ftepp.c
9cd2903aaf985c56be297cf3733f3624f3bca7f7
[xonotic/gmqcc.git] / ftepp.c
1 /*
2  * Copyright (C) 2012
3  *     Wolfgang Bumiller
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a copy of
6  * this software and associated documentation files (the "Software"), to deal in
7  * the Software without restriction, including without limitation the rights to
8  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9  * of the Software, and to permit persons to whom the Software is furnished to do
10  * so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in all
13  * copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21  * SOFTWARE.
22  */
23 #include "gmqcc.h"
24 #include "lexer.h"
25
26 typedef struct {
27     bool on;
28     bool was_on;
29     bool had_else;
30 } ppcondition;
31
32 typedef struct {
33     int   token;
34     char *value;
35     /* a copy from the lexer */
36     union {
37         vector v;
38         int    i;
39         double f;
40         int    t; /* type */
41     } constval;
42 } pptoken;
43
44 typedef struct {
45     lex_ctx ctx;
46
47     char   *name;
48     char  **params;
49     /* yes we need an extra flag since `#define FOO x` is not the same as `#define FOO() x` */
50     bool    has_params;
51
52     pptoken **output;
53 } ppmacro;
54
55 typedef struct {
56     lex_file    *lex;
57     int          token;
58     bool         newline;
59     unsigned int errors;
60
61     bool         output_on;
62     ppcondition *conditions;
63     ppmacro    **macros;
64 } ftepp_t;
65
66 #define ftepp_tokval(f) ((f)->lex->tok.value)
67 #define ftepp_ctx(f)    ((f)->lex->tok.ctx)
68
69 static void ftepp_errorat(ftepp_t *ftepp, lex_ctx ctx, const char *fmt, ...)
70 {
71     va_list ap;
72
73     ftepp->errors++;
74
75     va_start(ap, fmt);
76     con_vprintmsg(LVL_ERROR, ctx.file, ctx.line, "error", fmt, ap);
77     va_end(ap);
78 }
79
80 static void ftepp_error(ftepp_t *ftepp, const char *fmt, ...)
81 {
82     va_list ap;
83
84     ftepp->errors++;
85
86     va_start(ap, fmt);
87     con_vprintmsg(LVL_ERROR, ftepp->lex->tok.ctx.file, ftepp->lex->tok.ctx.line, "error", fmt, ap);
88     va_end(ap);
89 }
90
91 static pptoken *pptoken_make(ftepp_t *ftepp)
92 {
93     pptoken *token = (pptoken*)mem_a(sizeof(pptoken));
94     token->token = ftepp->token;
95     if (token->token == TOKEN_WHITE)
96         token->value = util_strdup(" ");
97     else
98         token->value = util_strdup(ftepp_tokval(ftepp));
99     memcpy(&token->constval, &ftepp->lex->tok.constval, sizeof(token->constval));
100     return token;
101 }
102
103 static void pptoken_delete(pptoken *self)
104 {
105     mem_d(self->value);
106     mem_d(self);
107 }
108
109 static ppmacro *ppmacro_new(lex_ctx ctx, const char *name)
110 {
111     ppmacro *macro = (ppmacro*)mem_a(sizeof(ppmacro));
112     memset(macro, 0, sizeof(*macro));
113     macro->name = util_strdup(name);
114     return macro;
115 }
116
117 static void ppmacro_delete(ppmacro *self)
118 {
119     size_t i;
120     for (i = 0; i < vec_size(self->params); ++i)
121         mem_d(self->params[i]);
122     vec_free(self->params);
123     for (i = 0; i < vec_size(self->output); ++i)
124         pptoken_delete(self->output[i]);
125     vec_free(self->output);
126     mem_d(self->name);
127     mem_d(self);
128 }
129
130 static ftepp_t* ftepp_init()
131 {
132     ftepp_t *ftepp;
133
134     ftepp = (ftepp_t*)mem_a(sizeof(*ftepp));
135     memset(ftepp, 0, sizeof(*ftepp));
136
137     ftepp->output_on = true;
138
139     return ftepp;
140 }
141
142 static void ftepp_delete(ftepp_t *self)
143 {
144     size_t i;
145     for (i = 0; i < vec_size(self->macros); ++i)
146         ppmacro_delete(self->macros[i]);
147     vec_free(self->macros);
148     vec_free(self->conditions);
149     lex_close(self->lex);
150     mem_d(self);
151 }
152
153 static void ftepp_out(ftepp_t *ftepp, const char *str, bool ignore_cond)
154 {
155     if (ignore_cond || ftepp->output_on)
156     {
157         printf("%s", str);
158     }
159 }
160
161 static void ftepp_update_output_condition(ftepp_t *ftepp)
162 {
163     size_t i;
164     ftepp->output_on = true;
165     for (i = 0; i < vec_size(ftepp->conditions); ++i)
166         ftepp->output_on = ftepp->output_on && ftepp->conditions[i].on;
167 }
168
169 static ppmacro* ftepp_macro_find(ftepp_t *ftepp, const char *name)
170 {
171     size_t i;
172     for (i = 0; i < vec_size(ftepp->macros); ++i) {
173         if (!strcmp(name, ftepp->macros[i]->name))
174             return ftepp->macros[i];
175     }
176     return NULL;
177 }
178
179 static inline int ftepp_next(ftepp_t *ftepp)
180 {
181     return (ftepp->token = lex_do(ftepp->lex));
182 }
183
184 /* Important: this does not skip newlines! */
185 static bool ftepp_skipspace(ftepp_t *ftepp)
186 {
187     if (ftepp->token != TOKEN_WHITE)
188         return true;
189     while (ftepp_next(ftepp) == TOKEN_WHITE) {}
190     if (ftepp->token >= TOKEN_EOF) {
191         ftepp_error(ftepp, "unexpected end of preprocessor directive");
192         return false;
193     }
194     return true;
195 }
196
197 /* this one skips EOLs as well */
198 static bool ftepp_skipallwhite(ftepp_t *ftepp)
199 {
200     if (ftepp->token != TOKEN_WHITE && ftepp->token != TOKEN_EOL)
201         return true;
202     do {
203         ftepp_next(ftepp);
204     } while (ftepp->token == TOKEN_WHITE || ftepp->token == TOKEN_EOL);
205     if (ftepp->token >= TOKEN_EOF) {
206         ftepp_error(ftepp, "unexpected end of preprocessor directive");
207         return false;
208     }
209     return true;
210 }
211
212 /**
213  * The huge macro parsing code...
214  */
215 static bool ftepp_define_params(ftepp_t *ftepp, ppmacro *macro)
216 {
217     do {
218         ftepp_next(ftepp);
219         if (!ftepp_skipspace(ftepp))
220             return false;
221         if (ftepp->token == ')')
222             break;
223         switch (ftepp->token) {
224             case TOKEN_IDENT:
225             case TOKEN_TYPENAME:
226             case TOKEN_KEYWORD:
227                 break;
228             default:
229                 ftepp_error(ftepp, "unexpected token in parameter list");
230                 return false;
231         }
232         vec_push(macro->params, util_strdup(ftepp_tokval(ftepp)));
233         ftepp_next(ftepp);
234         if (!ftepp_skipspace(ftepp))
235             return false;
236     } while (ftepp->token == ',');
237     if (ftepp->token != ')') {
238         ftepp_error(ftepp, "expected closing paren after macro parameter list");
239         return false;
240     }
241     ftepp_next(ftepp);
242     /* skipspace happens in ftepp_define */
243     return true;
244 }
245
246 static bool ftepp_define_body(ftepp_t *ftepp, ppmacro *macro)
247 {
248     pptoken *ptok;
249     while (ftepp->token != TOKEN_EOL && ftepp->token < TOKEN_EOF) {
250         ptok = pptoken_make(ftepp);
251         vec_push(macro->output, ptok);
252         ftepp_next(ftepp);
253     }
254     if (ftepp->token != TOKEN_EOL) {
255         ftepp_error(ftepp, "unexpected junk after macro or unexpected end of file");
256         return false;
257     }
258     return true;
259 }
260
261 static bool ftepp_define(ftepp_t *ftepp)
262 {
263     ppmacro *macro;
264     (void)ftepp_next(ftepp);
265     if (!ftepp_skipspace(ftepp))
266         return false;
267
268     switch (ftepp->token) {
269         case TOKEN_IDENT:
270         case TOKEN_TYPENAME:
271         case TOKEN_KEYWORD:
272             macro = ppmacro_new(ftepp_ctx(ftepp), ftepp_tokval(ftepp));
273             break;
274         default:
275             ftepp_error(ftepp, "expected macro name");
276             return false;
277     }
278
279     (void)ftepp_next(ftepp);
280
281     if (ftepp->token == '(') {
282         macro->has_params = true;
283         if (!ftepp_define_params(ftepp, macro))
284             return false;
285     }
286
287     if (!ftepp_skipspace(ftepp))
288         return false;
289
290     if (!ftepp_define_body(ftepp, macro))
291         return false;
292
293     vec_push(ftepp->macros, macro);
294     return true;
295 }
296
297 /**
298  * When a macro is used we have to handle parameters as well
299  * as special-concatenation via ## or stringification via #
300  *
301  * Note: parenthesis can nest, so FOO((a),b) is valid, but only
302  * this kind of parens. Curly braces or [] don't count towards the
303  * paren-level.
304  */
305 typedef struct {
306     pptoken **tokens;
307 } macroparam;
308
309 static void macroparam_clean(macroparam *self)
310 {
311     size_t i;
312     for (i = 0; i < vec_size(self->tokens); ++i)
313         pptoken_delete(self->tokens[i]);
314     vec_free(self->tokens);
315 }
316
317 static bool ftepp_macro_call_params(ftepp_t *ftepp, macroparam **out_params)
318 {
319     macroparam *params = NULL;
320     pptoken    *ptok;
321     macroparam  mp;
322     size_t      parens = 0;
323     size_t      i;
324
325     while (ftepp->token != ')') {
326         mp.tokens = NULL;
327         while (parens || ftepp->token != ',') {
328             if (ftepp->token == '(')
329                 ++parens;
330             else if (ftepp->token == ')') {
331                 if (!parens)
332                     break;
333                 --parens;
334             }
335             ptok = pptoken_make(ftepp);
336             vec_push(mp.tokens, ptok);
337             if (ftepp_next(ftepp) >= TOKEN_EOF) {
338                 ftepp_error(ftepp, "unexpected EOF in macro call");
339                 goto on_error;
340             }
341         }
342         vec_push(params, mp);
343         mp.tokens = NULL;
344         if (ftepp->token == ')')
345             break;
346         if (ftepp->token != ',') {
347             ftepp_error(ftepp, "expected closing paren or comma in macro call");
348             goto on_error;
349         }
350         if (ftepp_next(ftepp) >= TOKEN_EOF) {
351             ftepp_error(ftepp, "unexpected EOF in macro call");
352             goto on_error;
353         }
354     }
355     if (ftepp_next(ftepp) >= TOKEN_EOF) {
356         ftepp_error(ftepp, "unexpected EOF in macro call");
357         goto on_error;
358     }
359     *out_params = params;
360     return true;
361
362 on_error:
363     if (mp.tokens)
364         macroparam_clean(&mp);
365     for (i = 0; i < vec_size(params); ++i)
366         macroparam_clean(&params[i]);
367     vec_free(params);
368     return false;
369 }
370
371 static bool ftepp_macro_expand(ftepp_t *ftepp, ppmacro *macro, macroparam *params)
372 {
373     return true;
374 }
375
376 static bool ftepp_macro_call(ftepp_t *ftepp, ppmacro *macro)
377 {
378     size_t     o;
379     macroparam *params = NULL;
380     bool        retval = true;
381
382     ftepp_next(ftepp);
383
384     if (!macro->has_params) {
385         for (o = 0; o < vec_size(macro->output); ++o) {
386             ftepp_out(ftepp, macro->output[o]->value, false);
387         }
388         return true;
389     }
390
391     if (!ftepp_skipallwhite(ftepp))
392         return false;
393
394     if (ftepp->token != '(') {
395         ftepp_error(ftepp, "expected macro parameters in parenthesis");
396         return false;
397     }
398
399     ftepp_next(ftepp);
400     if (!ftepp_macro_call_params(ftepp, &params))
401         return false;
402
403     if (vec_size(params) != vec_size(macro->params)) {
404         ftepp_error(ftepp, "macro %s expects %u paramteters, %u provided", macro->name,
405                     (unsigned int)vec_size(macro->params),
406                     (unsigned int)vec_size(params));
407         retval = false;
408         goto cleanup;
409     }
410
411     if (!ftepp_macro_expand(ftepp, macro, params))
412         retval = false;
413
414 cleanup:
415     for (o = 0; o < vec_size(params); ++o)
416         macroparam_clean(&params[o]);
417     vec_free(params);
418     return retval;
419 }
420
421 /**
422  * #if - the FTEQCC way:
423  *    defined(FOO) => true if FOO was #defined regardless of parameters or contents
424  *    <numbers>    => True if the number is not 0
425  *    !<factor>    => True if the factor yields false
426  *    !!<factor>   => ERROR on 2 or more unary nots
427  *    <macro>      => becomes the macro's FIRST token regardless of parameters
428  *    <e> && <e>   => True if both expressions are true
429  *    <e> || <e>   => True if either expression is true
430  *    <string>     => False
431  *    <ident>      => False (remember for macros the <macro> rule applies instead)
432  * Unary + and - are weird and wrong in fteqcc so we don't allow them
433  * parenthesis in expressions are allowed
434  * parameter lists on macros are errors
435  * No mathematical calculations are executed
436  */
437 static bool ftepp_if_expr(ftepp_t *ftepp, bool *out)
438 {
439     ppmacro *macro;
440     bool     wasnot = false;
441
442     if (!ftepp_skipspace(ftepp))
443         return false;
444
445     while (ftepp->token == '!') {
446         wasnot = true;
447         ftepp_next(ftepp);
448         if (!ftepp_skipspace(ftepp))
449             return false;
450     }
451
452     switch (ftepp->token) {
453         case TOKEN_IDENT:
454         case TOKEN_TYPENAME:
455         case TOKEN_KEYWORD:
456             if (!strcmp(ftepp_tokval(ftepp), "defined")) {
457                 ftepp_next(ftepp);
458                 if (!ftepp_skipspace(ftepp))
459                     return false;
460                 if (ftepp->token != '(') {
461                     ftepp_error(ftepp, "`defined` keyword in #if requires a macro name in parenthesis");
462                     return false;
463                 }
464                 ftepp_next(ftepp);
465                 if (!ftepp_skipspace(ftepp))
466                     return false;
467                 if (ftepp->token != TOKEN_IDENT &&
468                     ftepp->token != TOKEN_TYPENAME &&
469                     ftepp->token != TOKEN_KEYWORD)
470                 {
471                     ftepp_error(ftepp, "defined() used on an unexpected token type");
472                     return false;
473                 }
474                 macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
475                 *out = !!macro;
476                 ftepp_next(ftepp);
477                 if (!ftepp_skipspace(ftepp))
478                     return false;
479                 if (ftepp->token != ')') {
480                     ftepp_error(ftepp, "expected closing paren");
481                     return false;
482                 }
483                 break;
484             }
485
486             macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
487             if (!macro || !vec_size(macro->output)) {
488                 *out = false;
489             } else {
490                 /* This does not expand recursively! */
491                 switch (macro->output[0]->token) {
492                     case TOKEN_INTCONST:
493                         *out = !!(macro->output[0]->constval.f);
494                         break;
495                     case TOKEN_FLOATCONST:
496                         *out = !!(macro->output[0]->constval.f);
497                         break;
498                     default:
499                         *out = false;
500                         break;
501                 }
502             }
503             break;
504         case TOKEN_STRINGCONST:
505             *out = false;
506             break;
507         case TOKEN_INTCONST:
508             *out = !!(ftepp->lex->tok.constval.i);
509             break;
510         case TOKEN_FLOATCONST:
511             *out = !!(ftepp->lex->tok.constval.f);
512             break;
513
514         case '(':
515             ftepp_next(ftepp);
516             if (!ftepp_if_expr(ftepp, out))
517                 return false;
518             if (ftepp->token != ')') {
519                 ftepp_error(ftepp, "expected closing paren in #if expression");
520                 return false;
521             }
522             break;
523
524         default:
525             ftepp_error(ftepp, "junk in #if");
526             return false;
527     }
528     if (wasnot)
529         *out = !*out;
530
531     ftepp->lex->flags.noops = false;
532     ftepp_next(ftepp);
533     if (!ftepp_skipspace(ftepp))
534         return false;
535     ftepp->lex->flags.noops = true;
536
537     if (ftepp->token == ')')
538         return true;
539
540     if (ftepp->token != TOKEN_OPERATOR)
541         return true;
542
543     if (!strcmp(ftepp_tokval(ftepp), "&&") ||
544         !strcmp(ftepp_tokval(ftepp), "||"))
545     {
546         bool next = false;
547         char opc  = ftepp_tokval(ftepp)[0];
548
549         ftepp_next(ftepp);
550         if (!ftepp_if_expr(ftepp, &next))
551             return false;
552
553         if (opc == '&')
554             *out = *out && next;
555         else
556             *out = *out || next;
557         return true;
558     }
559     else {
560         ftepp_error(ftepp, "junk after #if");
561         return false;
562     }
563 }
564
565 static bool ftepp_if(ftepp_t *ftepp, ppcondition *cond)
566 {
567     bool result = false;
568
569     memset(cond, 0, sizeof(*cond));
570     (void)ftepp_next(ftepp);
571
572     if (!ftepp_skipspace(ftepp))
573         return false;
574     if (ftepp->token == TOKEN_EOL) {
575         ftepp_error(ftepp, "expected expression for #if-directive");
576         return false;
577     }
578
579     if (!ftepp_if_expr(ftepp, &result))
580         return false;
581
582     cond->on = result;
583     return true;
584 }
585
586 /**
587  * ifdef is rather simple
588  */
589 static bool ftepp_ifdef(ftepp_t *ftepp, ppcondition *cond)
590 {
591     ppmacro *macro;
592     memset(cond, 0, sizeof(*cond));
593     (void)ftepp_next(ftepp);
594     if (!ftepp_skipspace(ftepp))
595         return false;
596
597     switch (ftepp->token) {
598         case TOKEN_IDENT:
599         case TOKEN_TYPENAME:
600         case TOKEN_KEYWORD:
601             macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
602             break;
603         default:
604             ftepp_error(ftepp, "expected macro name");
605             return false;
606     }
607
608     (void)ftepp_next(ftepp);
609     if (!ftepp_skipspace(ftepp))
610         return false;
611     if (ftepp->token != TOKEN_EOL) {
612         ftepp_error(ftepp, "stray tokens after #ifdef");
613         return false;
614     }
615     cond->on = !!macro;
616     return true;
617 }
618
619 /* Basic structure handlers */
620 static bool ftepp_else_allowed(ftepp_t *ftepp)
621 {
622     if (!vec_size(ftepp->conditions)) {
623         ftepp_error(ftepp, "#else without #if");
624         return false;
625     }
626     if (vec_last(ftepp->conditions).had_else) {
627         ftepp_error(ftepp, "multiple #else for a single #if");
628         return false;
629     }
630     return true;
631 }
632
633 static bool ftepp_hash(ftepp_t *ftepp)
634 {
635     ppcondition cond;
636     ppcondition *pc;
637
638     lex_ctx ctx = ftepp_ctx(ftepp);
639
640     if (!ftepp_skipspace(ftepp))
641         return false;
642
643     switch (ftepp->token) {
644         case TOKEN_KEYWORD:
645         case TOKEN_IDENT:
646         case TOKEN_TYPENAME:
647             if (!strcmp(ftepp_tokval(ftepp), "define")) {
648                 return ftepp_define(ftepp);
649             }
650             else if (!strcmp(ftepp_tokval(ftepp), "ifdef")) {
651                 if (!ftepp_ifdef(ftepp, &cond))
652                     return false;
653                 cond.was_on = cond.on;
654                 vec_push(ftepp->conditions, cond);
655                 ftepp->output_on = ftepp->output_on && cond.on;
656                 break;
657             }
658             else if (!strcmp(ftepp_tokval(ftepp), "ifndef")) {
659                 if (!ftepp_ifdef(ftepp, &cond))
660                     return false;
661                 cond.on = !cond.on;
662                 cond.was_on = cond.on;
663                 vec_push(ftepp->conditions, cond);
664                 ftepp->output_on = ftepp->output_on && cond.on;
665                 break;
666             }
667             else if (!strcmp(ftepp_tokval(ftepp), "elifdef")) {
668                 if (!ftepp_else_allowed(ftepp))
669                     return false;
670                 if (!ftepp_ifdef(ftepp, &cond))
671                     return false;
672                 pc = &vec_last(ftepp->conditions);
673                 pc->on     = !pc->was_on && cond.on;
674                 pc->was_on = pc->was_on || pc->on;
675                 ftepp_update_output_condition(ftepp);
676                 break;
677             }
678             else if (!strcmp(ftepp_tokval(ftepp), "elifndef")) {
679                 if (!ftepp_else_allowed(ftepp))
680                     return false;
681                 if (!ftepp_ifdef(ftepp, &cond))
682                     return false;
683                 cond.on = !cond.on;
684                 pc = &vec_last(ftepp->conditions);
685                 pc->on     = !pc->was_on && cond.on;
686                 pc->was_on = pc->was_on || pc->on;
687                 ftepp_update_output_condition(ftepp);
688                 break;
689             }
690             else if (!strcmp(ftepp_tokval(ftepp), "elif")) {
691                 if (!ftepp_else_allowed(ftepp))
692                     return false;
693                 if (!ftepp_if(ftepp, &cond))
694                     return false;
695                 pc = &vec_last(ftepp->conditions);
696                 pc->on     = !pc->was_on && cond.on;
697                 pc->was_on = pc->was_on  || pc->on;
698                 ftepp_update_output_condition(ftepp);
699                 break;
700             }
701             else if (!strcmp(ftepp_tokval(ftepp), "if")) {
702                 if (!ftepp_if(ftepp, &cond))
703                     return false;
704                 cond.was_on = cond.on;
705                 vec_push(ftepp->conditions, cond);
706                 ftepp->output_on = ftepp->output_on && cond.on;
707                 break;
708             }
709             else if (!strcmp(ftepp_tokval(ftepp), "else")) {
710                 if (!ftepp_else_allowed(ftepp))
711                     return false;
712                 pc = &vec_last(ftepp->conditions);
713                 pc->on = !pc->was_on;
714                 pc->had_else = true;
715                 ftepp_next(ftepp);
716                 ftepp_update_output_condition(ftepp);
717                 break;
718             }
719             else if (!strcmp(ftepp_tokval(ftepp), "endif")) {
720                 if (!vec_size(ftepp->conditions)) {
721                     ftepp_error(ftepp, "#endif without #if");
722                     return false;
723                 }
724                 vec_pop(ftepp->conditions);
725                 ftepp_next(ftepp);
726                 ftepp_update_output_condition(ftepp);
727                 break;
728             }
729             else {
730                 ftepp_error(ftepp, "unrecognized preprocessor directive: `%s`", ftepp_tokval(ftepp));
731                 return false;
732             }
733             break;
734         default:
735             ftepp_error(ftepp, "unexpected preprocessor token: `%s`", ftepp_tokval(ftepp));
736             return false;
737         case TOKEN_EOL:
738             ftepp_errorat(ftepp, ctx, "empty preprocessor directive");
739             return false;
740         case TOKEN_EOF:
741             ftepp_error(ftepp, "missing newline at end of file", ftepp_tokval(ftepp));
742             return false;
743
744         /* Builtins! Don't forget the builtins! */
745         case TOKEN_INTCONST:
746         case TOKEN_FLOATCONST:
747             ftepp_out(ftepp, "#", false);
748             return true;
749     }
750     if (!ftepp_skipspace(ftepp))
751         return false;
752     return true;
753 }
754
755 static bool ftepp_preprocess(ftepp_t *ftepp)
756 {
757     ppmacro *macro;
758     bool     newline = true;
759
760     ftepp->lex->flags.preprocessing = true;
761     ftepp->lex->flags.mergelines    = false;
762     ftepp->lex->flags.noops         = true;
763
764     ftepp_next(ftepp);
765     do
766     {
767         if (ftepp->token >= TOKEN_EOF)
768             break;
769 #if 0
770         ftepp->newline = newline;
771         newline = false;
772 #else
773         /* For the sake of FTE compatibility... FU, really */
774         ftepp->newline = newline = true;
775 #endif
776
777         switch (ftepp->token) {
778             case TOKEN_KEYWORD:
779             case TOKEN_IDENT:
780             case TOKEN_TYPENAME:
781                 macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
782                 if (!macro) {
783                     ftepp_out(ftepp, ftepp_tokval(ftepp), false);
784                     ftepp_next(ftepp);
785                     break;
786                 }
787                 if (!ftepp_macro_call(ftepp, macro))
788                     ftepp->token = TOKEN_ERROR;
789                 break;
790             case '#':
791                 if (!ftepp->newline) {
792                     ftepp_out(ftepp, ftepp_tokval(ftepp), false);
793                     ftepp_next(ftepp);
794                     break;
795                 }
796                 ftepp->lex->flags.mergelines = true;
797                 if (ftepp_next(ftepp) >= TOKEN_EOF) {
798                     ftepp_error(ftepp, "error in preprocessor directive");
799                     ftepp->token = TOKEN_ERROR;
800                     break;
801                 }
802                 if (!ftepp_hash(ftepp))
803                     ftepp->token = TOKEN_ERROR;
804                 ftepp->lex->flags.mergelines = false;
805                 break;
806             case TOKEN_EOL:
807                 newline = true;
808                 ftepp_out(ftepp, "\n", true);
809                 ftepp_next(ftepp);
810                 break;
811             default:
812                 ftepp_out(ftepp, ftepp_tokval(ftepp), false);
813                 ftepp_next(ftepp);
814                 break;
815         }
816     } while (!ftepp->errors && ftepp->token < TOKEN_EOF);
817
818     newline = ftepp->token == TOKEN_EOF;
819     ftepp_delete(ftepp);
820     return newline;
821 }
822
823 bool ftepp_preprocess_file(const char *filename)
824 {
825     ftepp_t *ftepp = ftepp_init();
826     ftepp->lex = lex_open(filename);
827     if (!ftepp->lex) {
828         con_out("failed to open file \"%s\"\n", filename);
829         return false;
830     }
831     return ftepp_preprocess(ftepp);
832 }
833
834 bool ftepp_preprocess_string(const char *name, const char *str)
835 {
836     ftepp_t *ftepp = ftepp_init();
837     ftepp->lex = lex_open_string(str, strlen(str), name);
838     if (!ftepp->lex) {
839         con_out("failed to create lexer for string \"%s\"\n", name);
840         return false;
841     }
842     return ftepp_preprocess(ftepp);
843 }