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