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