]> git.xonotic.org Git - xonotic/gmqcc.git/blob - ftepp.c
using ftepp_out which will honor conditions
[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 } pptoken;
36
37 typedef struct {
38         lex_ctx ctx;
39
40         char   *name;
41         char  **params;
42         /* yes we need an extra flag since `#define FOO x` is not the same as `#define FOO() x` */
43         bool    has_params;
44
45         pptoken *output;
46 } ppmacro;
47
48 typedef struct {
49         lex_file    *lex;
50         int          token;
51         bool         newline;
52         unsigned int errors;
53
54         ppcondition *conditions;
55         ppmacro    **macros;
56 } ftepp_t;
57
58 #define ftepp_tokval(f) ((f)->lex->tok.value)
59 #define ftepp_ctx(f)    ((f)->lex->tok.ctx)
60
61 static void ftepp_errorat(ftepp_t *ftepp, lex_ctx ctx, const char *fmt, ...)
62 {
63         va_list ap;
64
65         ftepp->errors++;
66
67         va_start(ap, fmt);
68     con_vprintmsg(LVL_ERROR, ctx.file, ctx.line, "error", fmt, ap);
69         va_end(ap);
70 }
71
72 static void ftepp_error(ftepp_t *ftepp, const char *fmt, ...)
73 {
74         va_list ap;
75
76         ftepp->errors++;
77
78         va_start(ap, fmt);
79     con_vprintmsg(LVL_ERROR, ftepp->lex->tok.ctx.file, ftepp->lex->tok.ctx.line, "error", fmt, ap);
80         va_end(ap);
81 }
82
83 ppmacro *ppmacro_new(lex_ctx ctx, const char *name)
84 {
85         ppmacro *macro = (ppmacro*)mem_a(sizeof(ppmacro));
86         memset(macro, 0, sizeof(*macro));
87         macro->name = util_strdup(name);
88         return macro;
89 }
90
91 void ppmacro_delete(ppmacro *self)
92 {
93         vec_free(self->params);
94         vec_free(self->output);
95         mem_d(self->name);
96         mem_d(self);
97 }
98
99 ftepp_t* ftepp_init()
100 {
101         ftepp_t *ftepp;
102
103         ftepp = (ftepp_t*)mem_a(sizeof(*ftepp));
104         memset(ftepp, 0, sizeof(*ftepp));
105
106         return ftepp;
107 }
108
109 void ftepp_delete(ftepp_t *self)
110 {
111         vec_free(self->macros);
112         vec_free(self->conditions);
113         mem_d(self);
114 }
115
116 ppmacro* ftepp_macro_find(ftepp_t *ftepp, const char *name)
117 {
118         size_t i;
119         for (i = 0; i < vec_size(ftepp->macros); ++i) {
120                 if (!strcmp(name, ftepp->macros[i]->name))
121                         return ftepp->macros[i];
122         }
123         return NULL;
124 }
125
126 static inline int ftepp_next(ftepp_t *ftepp)
127 {
128         return (ftepp->token = lex_do(ftepp->lex));
129 }
130
131 /* Important: this does not skip newlines! */
132 static bool ftepp_skipspace(ftepp_t *ftepp)
133 {
134         if (ftepp->token != TOKEN_WHITE)
135                 return true;
136         while (ftepp_next(ftepp) == TOKEN_WHITE) {}
137         return (ftepp->token < TOKEN_EOF);
138 }
139
140 /* if/ifdef/define handlers */
141
142 static bool ftepp_if(ftepp_t *ftepp, ppcondition *cond)
143 {
144         ftepp_error(ftepp, "TODO: #if");
145         return false;
146 }
147
148 static bool ftepp_ifdef(ftepp_t *ftepp, ppcondition *cond)
149 {
150         ppmacro *macro;
151         (void)ftepp_next(ftepp);
152         if (!ftepp_skipspace(ftepp))
153                 return false;
154
155         switch (ftepp->token) {
156                 case TOKEN_IDENT:
157                 case TOKEN_KEYWORD:
158                         macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
159                         break;
160                 default:
161                         ftepp_error(ftepp, "expected macro name");
162                         return false;
163         }
164
165         (void)ftepp_next(ftepp);
166         if (!ftepp_skipspace(ftepp))
167                 return false;
168         if (ftepp->token != TOKEN_EOL) {
169                 ftepp_error(ftepp, "stray tokens after #ifdef");
170                 return false;
171         }
172         cond->on = !!macro;
173         return true;
174 }
175
176 static bool ftepp_define(ftepp_t *ftepp)
177 {
178         ppmacro *macro;
179         (void)ftepp_next(ftepp);
180         if (!ftepp_skipspace(ftepp))
181                 return false;
182
183         switch (ftepp->token) {
184                 case TOKEN_IDENT:
185                 case TOKEN_KEYWORD:
186                         macro = ppmacro_new(ftepp_ctx(ftepp), ftepp_tokval(ftepp));
187                         break;
188                 default:
189                         ftepp_error(ftepp, "expected macro name");
190                         return false;
191         }
192
193         (void)ftepp_next(ftepp);
194         if (!ftepp_skipspace(ftepp))
195                 return false;
196         if (ftepp->token != TOKEN_EOL) {
197                 ftepp_error(ftepp, "stray tokens after macro");
198                 return false;
199         }
200         vec_push(ftepp->macros, macro);
201         return true;
202 }
203
204 /* Basic structure handlers */
205 static bool ftepp_else_allowed(ftepp_t *ftepp)
206 {
207         if (!vec_size(ftepp->conditions)) {
208                 ftepp_error(ftepp, "#else without #if");
209                 return false;
210         }
211         if (vec_last(ftepp->conditions).had_else) {
212                 ftepp_error(ftepp, "multiple #else for a single #if");
213                 return false;
214         }
215         return true;
216 }
217
218 static bool ftepp_hash(ftepp_t *ftepp)
219 {
220         ppcondition cond;
221         ppcondition *pc;
222
223         lex_ctx ctx = ftepp_ctx(ftepp);
224
225         if (!ftepp_skipspace(ftepp))
226                 return false;
227
228         switch (ftepp->token) {
229                 case TOKEN_KEYWORD:
230                 case TOKEN_IDENT:
231                         if (!strcmp(ftepp_tokval(ftepp), "define")) {
232                                 return ftepp_define(ftepp);
233                         }
234                         else if (!strcmp(ftepp_tokval(ftepp), "ifdef")) {
235                                 if (!ftepp_ifdef(ftepp, &cond))
236                                         return false;
237                                 vec_push(ftepp->conditions, cond);
238                                 return true;
239                         }
240                         else if (!strcmp(ftepp_tokval(ftepp), "ifndef")) {
241                                 if (!ftepp_ifdef(ftepp, &cond))
242                                         return false;
243                                 cond.on = !cond.on;
244                                 vec_push(ftepp->conditions, cond);
245                                 return true;
246                         }
247                         else if (!strcmp(ftepp_tokval(ftepp), "elifdef")) {
248                                 if (!ftepp_else_allowed(ftepp))
249                                         return false;
250                                 if (!ftepp_ifdef(ftepp, &cond))
251                                         return false;
252                                 pc = &vec_last(ftepp->conditions);
253                                 pc->on     = !pc->was_on && cond.on;
254                                 pc->was_on = pc->was_on || pc->on;
255                                 return true;
256                         }
257                         else if (!strcmp(ftepp_tokval(ftepp), "elifndef")) {
258                                 if (!ftepp_else_allowed(ftepp))
259                                         return false;
260                                 if (!ftepp_ifdef(ftepp, &cond))
261                                         return false;
262                                 cond.on = !cond.on;
263                                 pc = &vec_last(ftepp->conditions);
264                                 pc->on     = !pc->was_on && cond.on;
265                                 pc->was_on = pc->was_on || pc->on;
266                                 return true;
267                         }
268                         else if (!strcmp(ftepp_tokval(ftepp), "elif")) {
269                                 if (!ftepp_else_allowed(ftepp))
270                                         return false;
271                                 if (!ftepp_if(ftepp, &cond))
272                                         return false;
273                                 pc = &vec_last(ftepp->conditions);
274                                 pc->on     = !pc->was_on && cond.on;
275                                 pc->was_on = pc->was_on  || pc->on;
276                                 return true;
277                         }
278                         else if (!strcmp(ftepp_tokval(ftepp), "if")) {
279                                 if (!ftepp_if(ftepp, &cond))
280                                         return false;
281                                 vec_push(ftepp->conditions, cond);
282                                 return true;
283                         }
284                         else if (!strcmp(ftepp_tokval(ftepp), "else")) {
285                                 if (!ftepp_else_allowed(ftepp))
286                                         return false;
287                                 pc = &vec_last(ftepp->conditions);
288                                 pc->on = !pc->was_on;
289                                 pc->had_else = true;
290                                 return true;
291                         }
292                         else if (!strcmp(ftepp_tokval(ftepp), "endif")) {
293                                 if (!vec_size(ftepp->conditions)) {
294                                         ftepp_error(ftepp, "#endif without #if");
295                                         return false;
296                                 }
297                                 vec_pop(ftepp->conditions);
298                                 break;
299                         }
300                         else {
301                                 ftepp_error(ftepp, "unrecognized preprocessor directive: `%s`", ftepp_tokval(ftepp));
302                                 return false;
303                         }
304                         break;
305                 default:
306                         ftepp_error(ftepp, "unexpected preprocessor token: `%s`", ftepp_tokval(ftepp));
307                         return false;
308                 case TOKEN_EOL:
309                         ftepp_errorat(ftepp, ctx, "empty preprocessor directive");
310                         return false;
311                 case TOKEN_EOF:
312                         ftepp_error(ftepp, "missing newline at end of file", ftepp_tokval(ftepp));
313                         return false;
314         }
315         return true;
316 }
317
318 static void ftepp_out(ftepp_t *ftepp, const char *str)
319 {
320         if (!vec_size(ftepp->conditions) ||
321                 vec_last(ftepp->conditions).on)
322         {
323                 printf("%s", str);
324         }
325 }
326
327 static bool ftepp_preprocess(ftepp_t *ftepp)
328 {
329         bool newline = true;
330
331         ftepp->lex->flags.preprocessing = true;
332
333         ftepp_next(ftepp);
334         do
335         {
336                 if (ftepp->token >= TOKEN_EOF)
337                         break;
338
339                 ftepp->newline = newline;
340                 newline = false;
341
342                 switch (ftepp->token) {
343                         case '#':
344                                 if (!ftepp->newline) {
345                                         ftepp_out(ftepp, ftepp_tokval(ftepp));
346                                         ftepp_next(ftepp);
347                                         break;
348                                 }
349                                 if (ftepp_next(ftepp) >= TOKEN_EOF) {
350                                         ftepp_error(ftepp, "error in preprocessor directive");
351                                         ftepp->token = TOKEN_ERROR;
352                                         break;
353                                 }
354                                 if (!ftepp_hash(ftepp))
355                                         ftepp->token = TOKEN_ERROR;
356                                 break;
357                         case TOKEN_EOL:
358                                 newline = true;
359                                 ftepp_out(ftepp, "\n");
360                                 ftepp_next(ftepp);
361                                 break;
362                         default:
363                                 ftepp_out(ftepp, ftepp_tokval(ftepp));
364                                 ftepp_next(ftepp);
365                                 break;
366                 }
367         } while (!ftepp->errors && ftepp->token < TOKEN_EOF);
368
369         ftepp_delete(ftepp);
370         return (ftepp->token == TOKEN_EOF);
371 }
372
373 bool ftepp_preprocess_file(const char *filename)
374 {
375         ftepp_t *ftepp = ftepp_init();
376     ftepp->lex = lex_open(filename);
377     if (!ftepp->lex) {
378         con_out("failed to open file \"%s\"\n", filename);
379         return false;
380     }
381     return ftepp_preprocess(ftepp);
382 }
383
384 bool ftepp_preprocess_string(const char *name, const char *str)
385 {
386         ftepp_t *ftepp = ftepp_init();
387     ftepp->lex = lex_open_string(str, strlen(str), name);
388     if (!ftepp->lex) {
389         con_out("failed to create lexer for string \"%s\"\n", name);
390         return false;
391     }
392     return ftepp_preprocess(ftepp);
393 }