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