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