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