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