]> de.git.xonotic.org Git - xonotic/gmqcc.git/blob - ftepp.c
ftepp_macro_call/ftepp_skipallwhite
[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 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 void pptoken_delete(pptoken *self)
103 {
104     mem_d(self->value);
105     mem_d(self);
106 }
107
108 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 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 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 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 ppmacro* ftepp_macro_find(ftepp_t *ftepp, const char *name)
151 {
152     size_t i;
153     for (i = 0; i < vec_size(ftepp->macros); ++i) {
154         if (!strcmp(name, ftepp->macros[i]->name))
155             return ftepp->macros[i];
156     }
157     return NULL;
158 }
159
160 static inline int ftepp_next(ftepp_t *ftepp)
161 {
162     return (ftepp->token = lex_do(ftepp->lex));
163 }
164
165 /* Important: this does not skip newlines! */
166 static bool ftepp_skipspace(ftepp_t *ftepp)
167 {
168     if (ftepp->token != TOKEN_WHITE)
169         return true;
170     while (ftepp_next(ftepp) == TOKEN_WHITE) {}
171     if (ftepp->token >= TOKEN_EOF) {
172         ftepp_error(ftepp, "unexpected end of preprocessor directive");
173         return false;
174     }
175     return true;
176 }
177
178 /* this one skips EOLs as well */
179 static bool ftepp_skipallwhite(ftepp_t *ftepp)
180 {
181     if (ftepp->token != TOKEN_WHITE && ftepp->token != TOKEN_EOL)
182         return true;
183     do {
184         ftepp_next(ftepp);
185     } while (ftepp->token == TOKEN_WHITE || ftepp->token == TOKEN_EOL);
186     if (ftepp->token >= TOKEN_EOF) {
187         ftepp_error(ftepp, "unexpected end of preprocessor directive");
188         return false;
189     }
190     return true;
191 }
192
193 /**
194  * The huge macro parsing code...
195  */
196 static bool ftepp_define_params(ftepp_t *ftepp, ppmacro *macro)
197 {
198     do {
199         ftepp_next(ftepp);
200         if (!ftepp_skipspace(ftepp))
201             return false;
202         if (ftepp->token == ')')
203             break;
204         switch (ftepp->token) {
205             case TOKEN_IDENT:
206             case TOKEN_TYPENAME:
207             case TOKEN_KEYWORD:
208                 break;
209             default:
210                 ftepp_error(ftepp, "unexpected token in parameter list");
211                 return false;
212         }
213         vec_push(macro->params, util_strdup(ftepp_tokval(ftepp)));
214         ftepp_next(ftepp);
215         if (!ftepp_skipspace(ftepp))
216             return false;
217     } while (ftepp->token == ',');
218     if (ftepp->token != ')') {
219         ftepp_error(ftepp, "expected closing paren after macro parameter list");
220         return false;
221     }
222     ftepp_next(ftepp);
223     /* skipspace happens in ftepp_define */
224     return true;
225 }
226
227 static bool ftepp_define_body(ftepp_t *ftepp, ppmacro *macro)
228 {
229     pptoken *ptok;
230     while (ftepp->token != TOKEN_EOL && ftepp->token < TOKEN_EOF) {
231         ptok = pptoken_make(ftepp);
232         vec_push(macro->output, ptok);
233
234         ftepp_next(ftepp);
235         if (!ftepp_skipspace(ftepp))
236             return false;
237     }
238     if (ftepp->token != TOKEN_EOL) {
239         ftepp_error(ftepp, "unexpected junk after macro or unexpected end of file");
240         return false;
241     }
242     return true;
243 }
244
245 static bool ftepp_define(ftepp_t *ftepp)
246 {
247     ppmacro *macro;
248     (void)ftepp_next(ftepp);
249     if (!ftepp_skipspace(ftepp))
250         return false;
251
252     switch (ftepp->token) {
253         case TOKEN_IDENT:
254         case TOKEN_TYPENAME:
255         case TOKEN_KEYWORD:
256             macro = ppmacro_new(ftepp_ctx(ftepp), ftepp_tokval(ftepp));
257             break;
258         default:
259             ftepp_error(ftepp, "expected macro name");
260             return false;
261     }
262
263     (void)ftepp_next(ftepp);
264
265     if (ftepp->token == '(') {
266         macro->has_params = true;
267         if (!ftepp_define_params(ftepp, macro))
268             return false;
269     }
270
271     if (!ftepp_skipspace(ftepp))
272         return false;
273
274     if (!ftepp_define_body(ftepp, macro))
275         return false;
276
277     vec_push(ftepp->macros, macro);
278     return true;
279 }
280
281 static bool ftepp_macro_call(ftepp_t *ftepp, ppmacro *macro)
282 {
283     ftepp_next(ftepp);
284     if (!ftepp_skipallwhite(ftepp))
285         return false;
286     return true;
287 }
288
289 /**
290  * When a macro is used we have to handle parameters as well
291  * as special-concatenation via ## or stringification via #
292  */
293
294 /**
295  * #if - the FTEQCC way:
296  *    defined(FOO) => true if FOO was #defined regardless of parameters or contents
297  *    <numbers>    => True if the number is not 0
298  *    !<factor>    => True if the factor yields false
299  *    !!<factor>   => ERROR on 2 or more unary nots
300  *    <macro>      => becomes the macro's FIRST token regardless of parameters
301  *    <e> && <e>   => True if both expressions are true
302  *    <e> || <e>   => True if either expression is true
303  *    <string>     => False
304  *    <ident>      => False (remember for macros the <macro> rule applies instead)
305  * Unary + and - are weird and wrong in fteqcc so we don't allow them
306  * parenthesis in expressions are allowed
307  * parameter lists on macros are errors
308  * No mathematical calculations are executed
309  */
310 static bool ftepp_if_expr(ftepp_t *ftepp, bool *out)
311 {
312     ppmacro *macro;
313     bool     wasnot = false;
314
315     if (!ftepp_skipspace(ftepp))
316         return false;
317
318     while (ftepp->token == '!') {
319         wasnot = true;
320         ftepp_next(ftepp);
321         if (!ftepp_skipspace(ftepp))
322             return false;
323     }
324
325     switch (ftepp->token) {
326         case TOKEN_IDENT:
327         case TOKEN_TYPENAME:
328         case TOKEN_KEYWORD:
329             if (!strcmp(ftepp_tokval(ftepp), "defined")) {
330                 ftepp_next(ftepp);
331                 if (!ftepp_skipspace(ftepp))
332                     return false;
333                 if (ftepp->token != '(') {
334                     ftepp_error(ftepp, "`defined` keyword in #if requires a macro name in parenthesis");
335                     return false;
336                 }
337                 ftepp_next(ftepp);
338                 if (!ftepp_skipspace(ftepp))
339                     return false;
340                 if (ftepp->token != TOKEN_IDENT &&
341                     ftepp->token != TOKEN_TYPENAME &&
342                     ftepp->token != TOKEN_KEYWORD)
343                 {
344                     ftepp_error(ftepp, "defined() used on an unexpected token type");
345                     return false;
346                 }
347                 macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
348                 *out = !!macro;
349                 ftepp_next(ftepp);
350                 if (!ftepp_skipspace(ftepp))
351                     return false;
352                 if (ftepp->token != ')') {
353                     ftepp_error(ftepp, "expected closing paren");
354                     return false;
355                 }
356                 break;
357             }
358
359             macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
360             if (!macro || !vec_size(macro->output)) {
361                 *out = false;
362             } else {
363                 /* This does not expand recursively! */
364                 switch (macro->output[0]->token) {
365                     case TOKEN_INTCONST:
366                         *out = !!(macro->output[0]->constval.f);
367                         break;
368                     case TOKEN_FLOATCONST:
369                         *out = !!(macro->output[0]->constval.f);
370                         break;
371                     default:
372                         *out = false;
373                         break;
374                 }
375             }
376             break;
377         case TOKEN_STRINGCONST:
378             *out = false;
379             break;
380         case TOKEN_INTCONST:
381             *out = !!(ftepp->lex->tok.constval.i);
382             break;
383         case TOKEN_FLOATCONST:
384             *out = !!(ftepp->lex->tok.constval.f);
385             break;
386
387         case '(':
388             ftepp_next(ftepp);
389             if (!ftepp_if_expr(ftepp, out))
390                 return false;
391             if (ftepp->token != ')') {
392                 ftepp_error(ftepp, "expected closing paren in #if expression");
393                 return false;
394             }
395             break;
396
397         default:
398             ftepp_error(ftepp, "junk in #if");
399             return false;
400     }
401     if (wasnot)
402         *out = !*out;
403
404     ftepp->lex->flags.noops = false;
405     ftepp_next(ftepp);
406     if (!ftepp_skipspace(ftepp))
407         return false;
408     ftepp->lex->flags.noops = true;
409
410     if (ftepp->token == ')')
411         return true;
412
413     if (ftepp->token != TOKEN_OPERATOR)
414         return true;
415
416     if (!strcmp(ftepp_tokval(ftepp), "&&") ||
417         !strcmp(ftepp_tokval(ftepp), "||"))
418     {
419         bool next = false;
420         char opc  = ftepp_tokval(ftepp)[0];
421
422         ftepp_next(ftepp);
423         if (!ftepp_if_expr(ftepp, &next))
424             return false;
425
426         if (opc == '&')
427             *out = *out && next;
428         else
429             *out = *out || next;
430         return true;
431     }
432     else {
433         ftepp_error(ftepp, "junk after #if");
434         return false;
435     }
436 }
437
438 static bool ftepp_if(ftepp_t *ftepp, ppcondition *cond)
439 {
440     bool result = false;
441
442     memset(cond, 0, sizeof(*cond));
443     (void)ftepp_next(ftepp);
444
445     if (!ftepp_skipspace(ftepp))
446         return false;
447     if (ftepp->token == TOKEN_EOL) {
448         ftepp_error(ftepp, "expected expression for #if-directive");
449         return false;
450     }
451
452     if (!ftepp_if_expr(ftepp, &result))
453         return false;
454
455     cond->on = result;
456     return true;
457 }
458
459 /**
460  * ifdef is rather simple
461  */
462 static bool ftepp_ifdef(ftepp_t *ftepp, ppcondition *cond)
463 {
464     ppmacro *macro;
465     memset(cond, 0, sizeof(*cond));
466     (void)ftepp_next(ftepp);
467     if (!ftepp_skipspace(ftepp))
468         return false;
469
470     switch (ftepp->token) {
471         case TOKEN_IDENT:
472         case TOKEN_TYPENAME:
473         case TOKEN_KEYWORD:
474             macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
475             break;
476         default:
477             ftepp_error(ftepp, "expected macro name");
478             return false;
479     }
480
481     (void)ftepp_next(ftepp);
482     if (!ftepp_skipspace(ftepp))
483         return false;
484     if (ftepp->token != TOKEN_EOL) {
485         ftepp_error(ftepp, "stray tokens after #ifdef");
486         return false;
487     }
488     cond->on = !!macro;
489     return true;
490 }
491
492 /* Basic structure handlers */
493 static bool ftepp_else_allowed(ftepp_t *ftepp)
494 {
495     if (!vec_size(ftepp->conditions)) {
496         ftepp_error(ftepp, "#else without #if");
497         return false;
498     }
499     if (vec_last(ftepp->conditions).had_else) {
500         ftepp_error(ftepp, "multiple #else for a single #if");
501         return false;
502     }
503     return true;
504 }
505
506 static bool ftepp_hash(ftepp_t *ftepp)
507 {
508     ppcondition cond;
509     ppcondition *pc;
510
511     lex_ctx ctx = ftepp_ctx(ftepp);
512
513     if (!ftepp_skipspace(ftepp))
514         return false;
515
516     switch (ftepp->token) {
517         case TOKEN_KEYWORD:
518         case TOKEN_IDENT:
519         case TOKEN_TYPENAME:
520             if (!strcmp(ftepp_tokval(ftepp), "define")) {
521                 return ftepp_define(ftepp);
522             }
523             else if (!strcmp(ftepp_tokval(ftepp), "ifdef")) {
524                 if (!ftepp_ifdef(ftepp, &cond))
525                     return false;
526                 cond.was_on = cond.on;
527                 vec_push(ftepp->conditions, cond);
528                 break;
529             }
530             else if (!strcmp(ftepp_tokval(ftepp), "ifndef")) {
531                 if (!ftepp_ifdef(ftepp, &cond))
532                     return false;
533                 cond.on = !cond.on;
534                 cond.was_on = cond.on;
535                 vec_push(ftepp->conditions, cond);
536                 break;
537             }
538             else if (!strcmp(ftepp_tokval(ftepp), "elifdef")) {
539                 if (!ftepp_else_allowed(ftepp))
540                     return false;
541                 if (!ftepp_ifdef(ftepp, &cond))
542                     return false;
543                 pc = &vec_last(ftepp->conditions);
544                 pc->on     = !pc->was_on && cond.on;
545                 pc->was_on = pc->was_on || pc->on;
546                 break;
547             }
548             else if (!strcmp(ftepp_tokval(ftepp), "elifndef")) {
549                 if (!ftepp_else_allowed(ftepp))
550                     return false;
551                 if (!ftepp_ifdef(ftepp, &cond))
552                     return false;
553                 cond.on = !cond.on;
554                 pc = &vec_last(ftepp->conditions);
555                 pc->on     = !pc->was_on && cond.on;
556                 pc->was_on = pc->was_on || pc->on;
557                 break;
558             }
559             else if (!strcmp(ftepp_tokval(ftepp), "elif")) {
560                 if (!ftepp_else_allowed(ftepp))
561                     return false;
562                 if (!ftepp_if(ftepp, &cond))
563                     return false;
564                 pc = &vec_last(ftepp->conditions);
565                 pc->on     = !pc->was_on && cond.on;
566                 pc->was_on = pc->was_on  || pc->on;
567                 break;
568             }
569             else if (!strcmp(ftepp_tokval(ftepp), "if")) {
570                 if (!ftepp_if(ftepp, &cond))
571                     return false;
572                 cond.was_on = cond.on;
573                 vec_push(ftepp->conditions, cond);
574                 break;
575             }
576             else if (!strcmp(ftepp_tokval(ftepp), "else")) {
577                 if (!ftepp_else_allowed(ftepp))
578                     return false;
579                 pc = &vec_last(ftepp->conditions);
580                 pc->on = !pc->was_on;
581                 pc->had_else = true;
582                 ftepp_next(ftepp);
583                 break;
584             }
585             else if (!strcmp(ftepp_tokval(ftepp), "endif")) {
586                 if (!vec_size(ftepp->conditions)) {
587                     ftepp_error(ftepp, "#endif without #if");
588                     return false;
589                 }
590                 vec_pop(ftepp->conditions);
591                 ftepp_next(ftepp);
592                 break;
593             }
594             else {
595                 ftepp_error(ftepp, "unrecognized preprocessor directive: `%s`", ftepp_tokval(ftepp));
596                 return false;
597             }
598             break;
599         default:
600             ftepp_error(ftepp, "unexpected preprocessor token: `%s`", ftepp_tokval(ftepp));
601             return false;
602         case TOKEN_EOL:
603             ftepp_errorat(ftepp, ctx, "empty preprocessor directive");
604             return false;
605         case TOKEN_EOF:
606             ftepp_error(ftepp, "missing newline at end of file", ftepp_tokval(ftepp));
607             return false;
608     }
609     if (!ftepp_skipspace(ftepp))
610         return false;
611     return true;
612 }
613
614 static void ftepp_out(ftepp_t *ftepp, const char *str, bool ignore_cond)
615 {
616     if (ignore_cond ||
617         !vec_size(ftepp->conditions) ||
618         vec_last(ftepp->conditions).on)
619     {
620         printf("%s", str);
621     }
622 }
623
624 static bool ftepp_preprocess(ftepp_t *ftepp)
625 {
626     ppmacro *macro;
627     bool     newline = true;
628
629     ftepp->lex->flags.preprocessing = true;
630     ftepp->lex->flags.mergelines    = false;
631     ftepp->lex->flags.noops         = true;
632
633     ftepp_next(ftepp);
634     do
635     {
636         if (ftepp->token >= TOKEN_EOF)
637             break;
638
639         ftepp->newline = newline;
640         newline = false;
641
642         switch (ftepp->token) {
643             case TOKEN_KEYWORD:
644             case TOKEN_IDENT:
645             case TOKEN_TYPENAME:
646                 macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
647                 if (!macro) {
648                     ftepp_out(ftepp, ftepp_tokval(ftepp), false);
649                     ftepp_next(ftepp);
650                     break;
651                 }
652                 if (!ftepp_macro_call(ftepp, macro))
653                     ftepp->token = TOKEN_ERROR;
654                 break;
655             case '#':
656                 if (!ftepp->newline) {
657                     ftepp_out(ftepp, ftepp_tokval(ftepp), false);
658                     ftepp_next(ftepp);
659                     break;
660                 }
661                 ftepp->lex->flags.mergelines = true;
662                 if (ftepp_next(ftepp) >= TOKEN_EOF) {
663                     ftepp_error(ftepp, "error in preprocessor directive");
664                     ftepp->token = TOKEN_ERROR;
665                     break;
666                 }
667                 if (!ftepp_hash(ftepp))
668                     ftepp->token = TOKEN_ERROR;
669                 ftepp->lex->flags.mergelines = false;
670                 break;
671             case TOKEN_EOL:
672                 newline = true;
673                 ftepp_out(ftepp, "\n", true);
674                 ftepp_next(ftepp);
675                 break;
676             default:
677                 ftepp_out(ftepp, ftepp_tokval(ftepp), false);
678                 ftepp_next(ftepp);
679                 break;
680         }
681     } while (!ftepp->errors && ftepp->token < TOKEN_EOF);
682
683     newline = ftepp->token == TOKEN_EOF;
684     ftepp_delete(ftepp);
685     return newline;
686 }
687
688 bool ftepp_preprocess_file(const char *filename)
689 {
690     ftepp_t *ftepp = ftepp_init();
691     ftepp->lex = lex_open(filename);
692     if (!ftepp->lex) {
693         con_out("failed to open file \"%s\"\n", filename);
694         return false;
695     }
696     return ftepp_preprocess(ftepp);
697 }
698
699 bool ftepp_preprocess_string(const char *name, const char *str)
700 {
701     ftepp_t *ftepp = ftepp_init();
702     ftepp->lex = lex_open_string(str, strlen(str), name);
703     if (!ftepp->lex) {
704         con_out("failed to create lexer for string \"%s\"\n", name);
705         return false;
706     }
707     return ftepp_preprocess(ftepp);
708 }