]> de.git.xonotic.org Git - xonotic/gmqcc.git/blob - lexer.c
Cleanup lexer old cruft
[xonotic/gmqcc.git] / lexer.c
1 /*
2  * Copyright (C) 2012, 2013
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 <string.h>
24 #include <stdlib.h>
25
26 #include "gmqcc.h"
27 #include "lexer.h"
28
29 /*
30  * List of Keywords
31  */
32
33 /* original */
34 static const char *keywords_qc[] = {
35     "for", "do", "while",
36     "if", "else",
37     "local",
38     "return",
39     "const"
40 };
41 /* For fte/gmgqcc */
42 static const char *keywords_fg[] = {
43     "switch", "case", "default",
44     "struct", "union",
45     "break", "continue",
46     "typedef",
47     "goto",
48
49     "__builtin_debug_printtype"
50 };
51
52 /*
53  * Lexer code
54  */
55 static char* *lex_filenames;
56
57 static void lexerror(lex_file *lex, const char *fmt, ...)
58 {
59     va_list ap;
60
61     va_start(ap, fmt);
62     if (lex)
63         con_vprintmsg(LVL_ERROR, lex->name, lex->sline, lex->column, "parse error", fmt, ap);
64     else
65         con_vprintmsg(LVL_ERROR, "", 0, 0, "parse error", fmt, ap);
66     va_end(ap);
67 }
68
69 static bool lexwarn(lex_file *lex, int warntype, const char *fmt, ...)
70 {
71     bool      r;
72     lex_ctx_t ctx;
73     va_list   ap;
74
75     ctx.file   = lex->name;
76     ctx.line   = lex->sline;
77     ctx.column = lex->column;
78
79     va_start(ap, fmt);
80     r = vcompile_warning(ctx, warntype, fmt, ap);
81     va_end(ap);
82     return r;
83 }
84
85 static void lex_token_new(lex_file *lex)
86 {
87     if (lex->tok.value)
88         vec_shrinkto(lex->tok.value, 0);
89
90     lex->tok.constval.t  = 0;
91     lex->tok.ctx.line    = lex->sline;
92     lex->tok.ctx.file    = lex->name;
93     lex->tok.ctx.column  = lex->column;
94 }
95
96 static void lex_ungetch(lex_file *lex, int ch);
97 static int lex_getch(lex_file *lex);
98
99 lex_file* lex_open(const char *file)
100 {
101     lex_file  *lex;
102     fs_file_t *in = fs_file_open(file, "rb");
103     uint32_t   read;
104
105     if (!in) {
106         lexerror(NULL, "open failed: '%s'\n", file);
107         return NULL;
108     }
109
110     lex = (lex_file*)mem_a(sizeof(*lex));
111     if (!lex) {
112         fs_file_close(in);
113         lexerror(NULL, "out of memory\n");
114         return NULL;
115     }
116
117     memset(lex, 0, sizeof(*lex));
118
119     lex->file    = in;
120     lex->name    = util_strdup(file);
121     lex->line    = 1; /* we start counting at 1 */
122     lex->column  = 0;
123     lex->peekpos = 0;
124     lex->eof     = false;
125
126     /* handle BOM */
127     if ((read = (lex_getch(lex) << 16) | (lex_getch(lex) << 8) | lex_getch(lex)) != 0xEFBBBF) {
128         lex_ungetch(lex, (read & 0x0000FF));
129         lex_ungetch(lex, (read & 0x00FF00) >> 8);
130         lex_ungetch(lex, (read & 0xFF0000) >> 16);
131     } else {
132         /*
133          * otherwise the lexer has advanced 3 bytes for the BOM, we need
134          * to set the column back to 0
135          */
136         lex->column = 0;
137     }
138
139     vec_push(lex_filenames, lex->name);
140     return lex;
141 }
142
143 lex_file* lex_open_string(const char *str, size_t len, const char *name)
144 {
145     lex_file *lex;
146
147     lex = (lex_file*)mem_a(sizeof(*lex));
148     if (!lex) {
149         lexerror(NULL, "out of memory\n");
150         return NULL;
151     }
152
153     memset(lex, 0, sizeof(*lex));
154
155     lex->file = NULL;
156     lex->open_string        = str;
157     lex->open_string_length = len;
158     lex->open_string_pos    = 0;
159
160     lex->name    = util_strdup(name ? name : "<string-source>");
161     lex->line    = 1; /* we start counting at 1 */
162     lex->peekpos = 0;
163     lex->eof     = false;
164     lex->column  = 0;
165
166     vec_push(lex_filenames, lex->name);
167
168     return lex;
169 }
170
171 void lex_cleanup(void)
172 {
173     size_t i;
174     for (i = 0; i < vec_size(lex_filenames); ++i)
175         mem_d(lex_filenames[i]);
176     vec_free(lex_filenames);
177 }
178
179 void lex_close(lex_file *lex)
180 {
181     size_t i;
182     for (i = 0; i < vec_size(lex->frames); ++i)
183         mem_d(lex->frames[i].name);
184     vec_free(lex->frames);
185
186     if (lex->modelname)
187         vec_free(lex->modelname);
188
189     if (lex->file)
190         fs_file_close(lex->file);
191
192     vec_free(lex->tok.value);
193
194     /* mem_d(lex->name); collected in lex_filenames */
195     mem_d(lex);
196 }
197
198
199
200 static int lex_fgetc(lex_file *lex)
201 {
202     if (lex->file) {
203         lex->column++;
204         return fs_file_getc(lex->file);
205     }
206     if (lex->open_string) {
207         if (lex->open_string_pos >= lex->open_string_length)
208             return FS_FILE_EOF;
209         lex->column++;
210         return lex->open_string[lex->open_string_pos++];
211     }
212     return FS_FILE_EOF;
213 }
214
215 /* Get or put-back data
216  * The following to functions do NOT understand what kind of data they
217  * are working on.
218  * The are merely wrapping get/put in order to count line numbers.
219  */
220 static int lex_try_trigraph(lex_file *lex, int old)
221 {
222     int c2, c3;
223     c2 = lex_fgetc(lex);
224     if (!lex->push_line && c2 == '\n') {
225         lex->line++;
226         lex->column = 0;
227     }
228
229     if (c2 != '?') {
230         lex_ungetch(lex, c2);
231         return old;
232     }
233
234     c3 = lex_fgetc(lex);
235     if (!lex->push_line && c3 == '\n') {
236         lex->line++;
237         lex->column = 0;
238     }
239
240     switch (c3) {
241         case '=': return '#';
242         case '/': return '\\';
243         case '\'': return '^';
244         case '(': return '[';
245         case ')': return ']';
246         case '!': return '|';
247         case '<': return '{';
248         case '>': return '}';
249         case '-': return '~';
250         default:
251             lex_ungetch(lex, c3);
252             lex_ungetch(lex, c2);
253             return old;
254     }
255 }
256
257 static int lex_try_digraph(lex_file *lex, int ch)
258 {
259     int c2;
260     c2 = lex_fgetc(lex);
261     /* we just used fgetc() so count lines
262      * need to offset a \n the ungetch would recognize
263      */
264     if (!lex->push_line && c2 == '\n')
265         lex->line++;
266     if      (ch == '<' && c2 == ':')
267         return '[';
268     else if (ch == ':' && c2 == '>')
269         return ']';
270     else if (ch == '<' && c2 == '%')
271         return '{';
272     else if (ch == '%' && c2 == '>')
273         return '}';
274     else if (ch == '%' && c2 == ':')
275         return '#';
276     lex_ungetch(lex, c2);
277     return ch;
278 }
279
280 static int lex_getch(lex_file *lex)
281 {
282     int ch;
283
284     if (lex->peekpos) {
285         lex->peekpos--;
286         if (!lex->push_line && lex->peek[lex->peekpos] == '\n') {
287             lex->line++;
288             lex->column = 0;
289         }
290         return lex->peek[lex->peekpos];
291     }
292
293     ch = lex_fgetc(lex);
294     if (!lex->push_line && ch == '\n') {
295         lex->line++;
296         lex->column = 0;
297     }
298     else if (ch == '?')
299         return lex_try_trigraph(lex, ch);
300     else if (!lex->flags.nodigraphs && (ch == '<' || ch == ':' || ch == '%'))
301         return lex_try_digraph(lex, ch);
302     return ch;
303 }
304
305 static void lex_ungetch(lex_file *lex, int ch)
306 {
307     lex->peek[lex->peekpos++] = ch;
308     lex->column--;
309     if (!lex->push_line && ch == '\n') {
310         lex->line--;
311         lex->column = 0;
312     }
313 }
314
315 /* classify characters
316  * some additions to the is*() functions of ctype.h
317  */
318
319 /* Idents are alphanumberic, but they start with alpha or _ */
320 static bool isident_start(int ch)
321 {
322     return util_isalpha(ch) || ch == '_';
323 }
324
325 static bool isident(int ch)
326 {
327     return isident_start(ch) || util_isdigit(ch);
328 }
329
330 /* isxdigit_only is used when we already know it's not a digit
331  * and want to see if it's a hex digit anyway.
332  */
333 static bool isxdigit_only(int ch)
334 {
335     return (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F');
336 }
337
338 /* Append a character to the token buffer */
339 static void lex_tokench(lex_file *lex, int ch)
340 {
341     vec_push(lex->tok.value, ch);
342 }
343
344 /* Append a trailing null-byte */
345 static void lex_endtoken(lex_file *lex)
346 {
347     vec_push(lex->tok.value, 0);
348     vec_shrinkby(lex->tok.value, 1);
349 }
350
351 static bool lex_try_pragma(lex_file *lex)
352 {
353     int ch;
354     char *pragma  = NULL;
355     char *command = NULL;
356     char *param   = NULL;
357     size_t line;
358
359     if (lex->flags.preprocessing)
360         return false;
361
362     line = lex->line;
363
364     ch = lex_getch(lex);
365     if (ch != '#') {
366         lex_ungetch(lex, ch);
367         return false;
368     }
369
370     for (ch = lex_getch(lex); vec_size(pragma) < 8 && ch >= 'a' && ch <= 'z'; ch = lex_getch(lex))
371         vec_push(pragma, ch);
372     vec_push(pragma, 0);
373
374     if (ch != ' ' || strcmp(pragma, "pragma")) {
375         lex_ungetch(lex, ch);
376         goto unroll;
377     }
378
379     for (ch = lex_getch(lex); vec_size(command) < 32 && ch >= 'a' && ch <= 'z'; ch = lex_getch(lex))
380         vec_push(command, ch);
381     vec_push(command, 0);
382
383     if (ch != '(') {
384         lex_ungetch(lex, ch);
385         goto unroll;
386     }
387
388     for (ch = lex_getch(lex); vec_size(param) < 1024 && ch != ')' && ch != '\n'; ch = lex_getch(lex))
389         vec_push(param, ch);
390     vec_push(param, 0);
391
392     if (ch != ')') {
393         lex_ungetch(lex, ch);
394         goto unroll;
395     }
396
397     if (!strcmp(command, "push")) {
398         if (!strcmp(param, "line")) {
399             lex->push_line++;
400             if (lex->push_line == 1)
401                 --line;
402         }
403         else
404             goto unroll;
405     }
406     else if (!strcmp(command, "pop")) {
407         if (!strcmp(param, "line")) {
408             if (lex->push_line)
409                 lex->push_line--;
410             if (lex->push_line == 0)
411                 --line;
412         }
413         else
414             goto unroll;
415     }
416     else if (!strcmp(command, "file")) {
417         lex->name = util_strdup(param);
418         vec_push(lex_filenames, lex->name);
419     }
420     else if (!strcmp(command, "line")) {
421         line = strtol(param, NULL, 0)-1;
422     }
423     else
424         goto unroll;
425
426     lex->line = line;
427     while (ch != '\n' && ch != FS_FILE_EOF)
428         ch = lex_getch(lex);
429     vec_free(command);
430     vec_free(param);
431     vec_free(pragma);
432     return true;
433
434 unroll:
435     if (command) {
436         vec_pop(command);
437         while (vec_size(command)) {
438             lex_ungetch(lex, (unsigned char)vec_last(command));
439             vec_pop(command);
440         }
441         vec_free(command);
442         lex_ungetch(lex, ' ');
443     }
444     if (param) {
445         vec_pop(param);
446         while (vec_size(param)) {
447             lex_ungetch(lex, (unsigned char)vec_last(param));
448             vec_pop(param);
449         }
450         vec_free(param);
451         lex_ungetch(lex, ' ');
452     }
453     if (pragma) {
454         vec_pop(pragma);
455         while (vec_size(pragma)) {
456             lex_ungetch(lex, (unsigned char)vec_last(pragma));
457             vec_pop(pragma);
458         }
459         vec_free(pragma);
460     }
461     lex_ungetch(lex, '#');
462
463     lex->line = line;
464     return false;
465 }
466
467 /* Skip whitespace and comments and return the first
468  * non-white character.
469  * As this makes use of the above getch() ungetch() functions,
470  * we don't need to care at all about line numbering anymore.
471  *
472  * In theory, this function should only be used at the beginning
473  * of lexing, or when we *know* the next character is part of the token.
474  * Otherwise, if the parser throws an error, the linenumber may not be
475  * the line of the error, but the line of the next token AFTER the error.
476  *
477  * This is currently only problematic when using c-like string-continuation,
478  * since comments and whitespaces are allowed between 2 such strings.
479  * Example:
480 printf(   "line one\n"
481 // A comment
482           "A continuation of the previous string"
483 // This line is skipped
484       , foo);
485
486  * In this case, if the parse decides it didn't actually want a string,
487  * and uses lex->line to print an error, it will show the ', foo);' line's
488  * linenumber.
489  *
490  * On the other hand, the parser is supposed to remember the line of the next
491  * token's beginning. In this case we would want skipwhite() to be called
492  * AFTER reading a token, so that the parser, before reading the NEXT token,
493  * doesn't store teh *comment's* linenumber, but the actual token's linenumber.
494  *
495  * THIS SOLUTION
496  *    here is to store the line of the first character after skipping
497  *    the initial whitespace in lex->sline, this happens in lex_do.
498  */
499 static int lex_skipwhite(lex_file *lex, bool hadwhite)
500 {
501     int ch = 0;
502     bool haswhite = hadwhite;
503
504     do
505     {
506         ch = lex_getch(lex);
507         while (ch != FS_FILE_EOF && util_isspace(ch)) {
508             if (ch == '\n') {
509                 if (lex_try_pragma(lex))
510                     continue;
511             }
512             if (lex->flags.preprocessing) {
513                 if (ch == '\n') {
514                     /* end-of-line */
515                     /* see if there was whitespace first */
516                     if (haswhite) { /* (vec_size(lex->tok.value)) { */
517                         lex_ungetch(lex, ch);
518                         lex_endtoken(lex);
519                         return TOKEN_WHITE;
520                     }
521                     /* otherwise return EOL */
522                     return TOKEN_EOL;
523                 }
524                 haswhite = true;
525                 lex_tokench(lex, ch);
526             }
527             ch = lex_getch(lex);
528         }
529
530         if (ch == '/') {
531             ch = lex_getch(lex);
532             if (ch == '/')
533             {
534                 /* one line comment */
535                 ch = lex_getch(lex);
536
537                 if (lex->flags.preprocessing) {
538                     haswhite = true;
539                     lex_tokench(lex, ' ');
540                     lex_tokench(lex, ' ');
541                 }
542
543                 while (ch != FS_FILE_EOF && ch != '\n') {
544                     if (lex->flags.preprocessing)
545                         lex_tokench(lex, ' '); /* ch); */
546                     ch = lex_getch(lex);
547                 }
548                 if (lex->flags.preprocessing) {
549                     lex_ungetch(lex, '\n');
550                     lex_endtoken(lex);
551                     return TOKEN_WHITE;
552                 }
553                 continue;
554             }
555             if (ch == '*')
556             {
557                 /* multiline comment */
558                 if (lex->flags.preprocessing) {
559                     haswhite = true;
560                     lex_tokench(lex, ' ');
561                     lex_tokench(lex, ' ');
562                 }
563
564                 while (ch != FS_FILE_EOF)
565                 {
566                     ch = lex_getch(lex);
567                     if (ch == '*') {
568                         ch = lex_getch(lex);
569                         if (ch == '/') {
570                             if (lex->flags.preprocessing) {
571                                 lex_tokench(lex, ' ');
572                                 lex_tokench(lex, ' ');
573                             }
574                             break;
575                         }
576                         lex_ungetch(lex, ch);
577                     }
578                     if (lex->flags.preprocessing) {
579                         if (ch == '\n')
580                             lex_tokench(lex, '\n');
581                         else
582                             lex_tokench(lex, ' '); /* ch); */
583                     }
584                 }
585                 ch = ' '; /* cause TRUE in the isspace check */
586                 continue;
587             }
588             /* Otherwise roll back to the slash and break out of the loop */
589             lex_ungetch(lex, ch);
590             ch = '/';
591             break;
592         }
593     } while (ch != FS_FILE_EOF && util_isspace(ch));
594
595     if (haswhite) {
596         lex_endtoken(lex);
597         lex_ungetch(lex, ch);
598         return TOKEN_WHITE;
599     }
600     return ch;
601 }
602
603 /* Get a token */
604 static bool GMQCC_WARN lex_finish_ident(lex_file *lex)
605 {
606     int ch;
607
608     ch = lex_getch(lex);
609     while (ch != FS_FILE_EOF && isident(ch))
610     {
611         lex_tokench(lex, ch);
612         ch = lex_getch(lex);
613     }
614
615     /* last ch was not an ident ch: */
616     lex_ungetch(lex, ch);
617
618     return true;
619 }
620
621 /* read one ident for the frame list */
622 static int lex_parse_frame(lex_file *lex)
623 {
624     int ch;
625
626     lex_token_new(lex);
627
628     ch = lex_getch(lex);
629     while (ch != FS_FILE_EOF && ch != '\n' && util_isspace(ch))
630         ch = lex_getch(lex);
631
632     if (ch == '\n')
633         return 1;
634
635     if (!isident_start(ch)) {
636         lexerror(lex, "invalid framename, must start with one of a-z or _, got %c", ch);
637         return -1;
638     }
639
640     lex_tokench(lex, ch);
641     if (!lex_finish_ident(lex))
642         return -1;
643     lex_endtoken(lex);
644     return 0;
645 }
646
647 /* read a list of $frames */
648 static bool lex_finish_frames(lex_file *lex)
649 {
650     do {
651         size_t i;
652         int    rc;
653         frame_macro m;
654
655         rc = lex_parse_frame(lex);
656         if (rc > 0) /* end of line */
657             return true;
658         if (rc < 0) /* error */
659             return false;
660
661         for (i = 0; i < vec_size(lex->frames); ++i) {
662             if (!strcmp(lex->tok.value, lex->frames[i].name)) {
663                 lex->frames[i].value = lex->framevalue++;
664                 if (lexwarn(lex, WARN_FRAME_MACROS, "duplicate frame macro defined: `%s`", lex->tok.value))
665                     return false;
666                 break;
667             }
668         }
669         if (i < vec_size(lex->frames))
670             continue;
671
672         m.value = lex->framevalue++;
673         m.name = util_strdup(lex->tok.value);
674         vec_shrinkto(lex->tok.value, 0);
675         vec_push(lex->frames, m);
676     } while (true);
677
678     return false;
679 }
680
681 static int GMQCC_WARN lex_finish_string(lex_file *lex, int quote)
682 {
683     utf8ch_t chr = 0;
684     int ch = 0;
685     int nextch;
686     bool hex;
687     bool oct;
688     char u8buf[8]; /* way more than enough */
689     int  u8len, uc;
690
691     while (ch != FS_FILE_EOF)
692     {
693         ch = lex_getch(lex);
694         if (ch == quote)
695             return TOKEN_STRINGCONST;
696
697         if (lex->flags.preprocessing && ch == '\\') {
698             lex_tokench(lex, ch);
699             ch = lex_getch(lex);
700             if (ch == FS_FILE_EOF) {
701                 lexerror(lex, "unexpected end of file");
702                 lex_ungetch(lex, FS_FILE_EOF); /* next token to be TOKEN_EOF */
703                 return (lex->tok.ttype = TOKEN_ERROR);
704             }
705             lex_tokench(lex, ch);
706         }
707         else if (ch == '\\') {
708             ch = lex_getch(lex);
709             if (ch == FS_FILE_EOF) {
710                 lexerror(lex, "unexpected end of file");
711                 lex_ungetch(lex, FS_FILE_EOF); /* next token to be TOKEN_EOF */
712                 return (lex->tok.ttype = TOKEN_ERROR);
713             }
714
715             switch (ch) {
716             case '\\': break;
717             case '\'': break;
718             case '"':  break;
719             case 'a':  ch = '\a'; break;
720             case 'b':  ch = '\b'; break;
721             case 'r':  ch = '\r'; break;
722             case 'n':  ch = '\n'; break;
723             case 't':  ch = '\t'; break;
724             case 'f':  ch = '\f'; break;
725             case 'v':  ch = '\v'; break;
726             case 'x':
727             case 'X':
728                 /* same procedure as in fteqcc */
729                 ch = 0;
730                 nextch = lex_getch(lex);
731                 if      (nextch >= '0' && nextch <= '9')
732                     ch += nextch - '0';
733                 else if (nextch >= 'a' && nextch <= 'f')
734                     ch += nextch - 'a' + 10;
735                 else if (nextch >= 'A' && nextch <= 'F')
736                     ch += nextch - 'A' + 10;
737                 else {
738                     lexerror(lex, "bad character code");
739                     lex_ungetch(lex, nextch);
740                     return (lex->tok.ttype = TOKEN_ERROR);
741                 }
742
743                 ch *= 0x10;
744                 nextch = lex_getch(lex);
745                 if      (nextch >= '0' && nextch <= '9')
746                     ch += nextch - '0';
747                 else if (nextch >= 'a' && nextch <= 'f')
748                     ch += nextch - 'a' + 10;
749                 else if (nextch >= 'A' && nextch <= 'F')
750                     ch += nextch - 'A' + 10;
751                 else {
752                     lexerror(lex, "bad character code");
753                     lex_ungetch(lex, nextch);
754                     return (lex->tok.ttype = TOKEN_ERROR);
755                 }
756                 break;
757
758             /* fteqcc support */
759             case '0': case '1': case '2': case '3':
760             case '4': case '5': case '6': case '7':
761             case '8': case '9':
762                 ch = 18 + ch - '0';
763                 break;
764             case '<':  ch = 29; break;
765             case '-':  ch = 30; break;
766             case '>':  ch = 31; break;
767             case '[':  ch = 16; break;
768             case ']':  ch = 17; break;
769             case '{':
770                 chr = 0;
771                 nextch = lex_getch(lex);
772                 hex = (nextch == 'x');
773                 oct = (nextch == '0');
774                 if (!hex && !oct)
775                     lex_ungetch(lex, nextch);
776                 for (nextch = lex_getch(lex); nextch != '}'; nextch = lex_getch(lex)) {
777                     if (!hex && !oct) {
778                         if (nextch >= '0' && nextch <= '9')
779                             chr = chr * 10 + nextch - '0';
780                         else {
781                             lexerror(lex, "bad character code");
782                             return (lex->tok.ttype = TOKEN_ERROR);
783                         }
784                     } else if (!oct) {
785                         if (nextch >= '0' && nextch <= '9')
786                             chr = chr * 0x10 + nextch - '0';
787                         else if (nextch >= 'a' && nextch <= 'f')
788                             chr = chr * 0x10 + nextch - 'a' + 10;
789                         else if (nextch >= 'A' && nextch <= 'F')
790                             chr = chr * 0x10 + nextch - 'A' + 10;
791                         else {
792                             lexerror(lex, "bad character code");
793                             return (lex->tok.ttype = TOKEN_ERROR);
794                         }
795                     } else {
796                         if (nextch >= '0' && nextch <= '9')
797                             chr = chr * 8 + chr - '0';
798                         else {
799                             lexerror(lex, "bad character code");
800                             return (lex->tok.ttype = TOKEN_ERROR);
801                         }
802                     }
803                     if (chr > 0x10FFFF || (!OPTS_FLAG(UTF8) && chr > 255))
804                     {
805                         lexerror(lex, "character code out of range");
806                         return (lex->tok.ttype = TOKEN_ERROR);
807                     }
808                 }
809                 if (OPTS_FLAG(UTF8) && chr >= 128) {
810                     u8len = utf8_from(u8buf, chr);
811                     if (!u8len)
812                         ch = 0;
813                     else {
814                         --u8len;
815                         lex->column += u8len;
816                         for (uc = 0; uc < u8len; ++uc)
817                             lex_tokench(lex, u8buf[uc]);
818                         /*
819                          * the last character will be inserted with the tokench() call
820                          * below the switch
821                          */
822                         ch = u8buf[uc];
823                     }
824                 }
825                 else
826                     ch = chr;
827                 break;
828             case '\n':  ch = '\n'; break;
829
830             default:
831                 lexwarn(lex, WARN_UNKNOWN_CONTROL_SEQUENCE, "unrecognized control sequence: \\%c", ch);
832                 /* so we just add the character plus backslash no matter what it actually is */
833                 lex_tokench(lex, '\\');
834             }
835             /* add the character finally */
836             lex_tokench(lex, ch);
837         }
838         else
839             lex_tokench(lex, ch);
840     }
841     lexerror(lex, "unexpected end of file within string constant");
842     lex_ungetch(lex, FS_FILE_EOF); /* next token to be TOKEN_EOF */
843     return (lex->tok.ttype = TOKEN_ERROR);
844 }
845
846 static int GMQCC_WARN lex_finish_digit(lex_file *lex, int lastch)
847 {
848     bool ishex = false;
849
850     int  ch = lastch;
851
852     /* parse a number... */
853     if (ch == '.')
854         lex->tok.ttype = TOKEN_FLOATCONST;
855     else
856         lex->tok.ttype = TOKEN_INTCONST;
857
858     lex_tokench(lex, ch);
859
860     ch = lex_getch(lex);
861     if (ch != '.' && !util_isdigit(ch))
862     {
863         if (lastch != '0' || ch != 'x')
864         {
865             /* end of the number or EOF */
866             lex_ungetch(lex, ch);
867             lex_endtoken(lex);
868
869             lex->tok.constval.i = lastch - '0';
870             return lex->tok.ttype;
871         }
872
873         ishex = true;
874     }
875
876     /* EOF would have been caught above */
877
878     if (ch != '.')
879     {
880         lex_tokench(lex, ch);
881         ch = lex_getch(lex);
882         while (util_isdigit(ch) || (ishex && isxdigit_only(ch)))
883         {
884             lex_tokench(lex, ch);
885             ch = lex_getch(lex);
886         }
887     }
888     /* NOT else, '.' can come from above as well */
889     if (lex->tok.ttype != TOKEN_FLOATCONST && ch == '.' && !ishex)
890     {
891         /* Allow floating comma in non-hex mode */
892         lex->tok.ttype = TOKEN_FLOATCONST;
893         lex_tokench(lex, ch);
894
895         /* continue digits-only */
896         ch = lex_getch(lex);
897         while (util_isdigit(ch))
898         {
899             lex_tokench(lex, ch);
900             ch = lex_getch(lex);
901         }
902     }
903     /* put back the last character */
904     /* but do not put back the trailing 'f' or a float */
905     if (lex->tok.ttype == TOKEN_FLOATCONST && ch == 'f')
906         ch = lex_getch(lex);
907
908     /* generally we don't want words to follow numbers: */
909     if (isident(ch)) {
910         lexerror(lex, "unexpected trailing characters after number");
911         return (lex->tok.ttype = TOKEN_ERROR);
912     }
913     lex_ungetch(lex, ch);
914
915     lex_endtoken(lex);
916     if (lex->tok.ttype == TOKEN_FLOATCONST)
917         lex->tok.constval.f = strtod(lex->tok.value, NULL);
918     else
919         lex->tok.constval.i = strtol(lex->tok.value, NULL, 0);
920     return lex->tok.ttype;
921 }
922
923 int lex_do(lex_file *lex)
924 {
925     int ch, nextch, thirdch;
926     bool hadwhite = false;
927
928     lex_token_new(lex);
929
930     while (true) {
931         ch = lex_skipwhite(lex, hadwhite);
932         hadwhite = true;
933         if (!lex->flags.mergelines || ch != '\\')
934             break;
935         ch = lex_getch(lex);
936         if (ch == '\r')
937             ch = lex_getch(lex);
938         if (ch != '\n') {
939             lex_ungetch(lex, ch);
940             ch = '\\';
941             break;
942         }
943         /* we reached a linemerge */
944         lex_tokench(lex, '\n');
945         continue;
946     }
947
948     if (lex->flags.preprocessing && (ch == TOKEN_WHITE || ch == TOKEN_EOL || ch == TOKEN_FATAL)) {
949         return (lex->tok.ttype = ch);
950     }
951
952     lex->sline = lex->line;
953     lex->tok.ctx.line = lex->sline;
954     lex->tok.ctx.file = lex->name;
955
956     if (lex->eof)
957         return (lex->tok.ttype = TOKEN_FATAL);
958
959     if (ch == FS_FILE_EOF) {
960         lex->eof = true;
961         return (lex->tok.ttype = TOKEN_EOF);
962     }
963
964     /* modelgen / spiritgen commands */
965     if (ch == '$' && !lex->flags.preprocessing) {
966         const char *v;
967         size_t frame;
968
969         ch = lex_getch(lex);
970         if (!isident_start(ch)) {
971             lexerror(lex, "hanging '$' modelgen/spritegen command line");
972             return lex_do(lex);
973         }
974         lex_tokench(lex, ch);
975         if (!lex_finish_ident(lex))
976             return (lex->tok.ttype = TOKEN_ERROR);
977         lex_endtoken(lex);
978         /* skip the known commands */
979         v = lex->tok.value;
980
981         if (!strcmp(v, "frame") || !strcmp(v, "framesave"))
982         {
983             /* frame/framesave command works like an enum
984              * similar to fteqcc we handle this in the lexer.
985              * The reason for this is that it is sensitive to newlines,
986              * which the parser is unaware of
987              */
988             if (!lex_finish_frames(lex))
989                  return (lex->tok.ttype = TOKEN_ERROR);
990             return lex_do(lex);
991         }
992
993         if (!strcmp(v, "framevalue"))
994         {
995             ch = lex_getch(lex);
996             while (ch != FS_FILE_EOF && util_isspace(ch) && ch != '\n')
997                 ch = lex_getch(lex);
998
999             if (!util_isdigit(ch)) {
1000                 lexerror(lex, "$framevalue requires an integer parameter");
1001                 return lex_do(lex);
1002             }
1003
1004             lex_token_new(lex);
1005             lex->tok.ttype = lex_finish_digit(lex, ch);
1006             lex_endtoken(lex);
1007             if (lex->tok.ttype != TOKEN_INTCONST) {
1008                 lexerror(lex, "$framevalue requires an integer parameter");
1009                 return lex_do(lex);
1010             }
1011             lex->framevalue = lex->tok.constval.i;
1012             return lex_do(lex);
1013         }
1014
1015         if (!strcmp(v, "framerestore"))
1016         {
1017             int rc;
1018
1019             lex_token_new(lex);
1020
1021             rc = lex_parse_frame(lex);
1022
1023             if (rc > 0) {
1024                 lexerror(lex, "$framerestore requires a framename parameter");
1025                 return lex_do(lex);
1026             }
1027             if (rc < 0)
1028                 return (lex->tok.ttype = TOKEN_FATAL);
1029
1030             v = lex->tok.value;
1031             for (frame = 0; frame < vec_size(lex->frames); ++frame) {
1032                 if (!strcmp(v, lex->frames[frame].name)) {
1033                     lex->framevalue = lex->frames[frame].value;
1034                     return lex_do(lex);
1035                 }
1036             }
1037             lexerror(lex, "unknown framename `%s`", v);
1038             return lex_do(lex);
1039         }
1040
1041         if (!strcmp(v, "modelname"))
1042         {
1043             int rc;
1044
1045             lex_token_new(lex);
1046
1047             rc = lex_parse_frame(lex);
1048
1049             if (rc > 0) {
1050                 lexerror(lex, "$modelname requires a parameter");
1051                 return lex_do(lex);
1052             }
1053             if (rc < 0)
1054                 return (lex->tok.ttype = TOKEN_FATAL);
1055
1056             if (lex->modelname) {
1057                 frame_macro m;
1058                 m.value = lex->framevalue;
1059                 m.name = lex->modelname;
1060                 lex->modelname = NULL;
1061                 vec_push(lex->frames, m);
1062             }
1063             lex->modelname = lex->tok.value;
1064             lex->tok.value = NULL;
1065             return lex_do(lex);
1066         }
1067
1068         if (!strcmp(v, "flush"))
1069         {
1070             size_t fi;
1071             for (fi = 0; fi < vec_size(lex->frames); ++fi)
1072                 mem_d(lex->frames[fi].name);
1073             vec_free(lex->frames);
1074             /* skip line (fteqcc does it too) */
1075             ch = lex_getch(lex);
1076             while (ch != FS_FILE_EOF && ch != '\n')
1077                 ch = lex_getch(lex);
1078             return lex_do(lex);
1079         }
1080
1081         if (!strcmp(v, "cd") ||
1082             !strcmp(v, "origin") ||
1083             !strcmp(v, "base") ||
1084             !strcmp(v, "flags") ||
1085             !strcmp(v, "scale") ||
1086             !strcmp(v, "skin"))
1087         {
1088             /* skip line */
1089             ch = lex_getch(lex);
1090             while (ch != FS_FILE_EOF && ch != '\n')
1091                 ch = lex_getch(lex);
1092             return lex_do(lex);
1093         }
1094
1095         for (frame = 0; frame < vec_size(lex->frames); ++frame) {
1096             if (!strcmp(v, lex->frames[frame].name)) {
1097                 lex->tok.constval.i = lex->frames[frame].value;
1098                 return (lex->tok.ttype = TOKEN_INTCONST);
1099             }
1100         }
1101
1102         lexerror(lex, "invalid frame macro");
1103         return lex_do(lex);
1104     }
1105
1106     /* single-character tokens */
1107     switch (ch)
1108     {
1109         case '[':
1110             nextch = lex_getch(lex);
1111             if (nextch == '[') {
1112                 lex_tokench(lex, ch);
1113                 lex_tokench(lex, nextch);
1114                 lex_endtoken(lex);
1115                 return (lex->tok.ttype = TOKEN_ATTRIBUTE_OPEN);
1116             }
1117             lex_ungetch(lex, nextch);
1118             /* FALL THROUGH */
1119         case '(':
1120         case ':':
1121         case '?':
1122             lex_tokench(lex, ch);
1123             lex_endtoken(lex);
1124             if (lex->flags.noops)
1125                 return (lex->tok.ttype = ch);
1126             else
1127                 return (lex->tok.ttype = TOKEN_OPERATOR);
1128
1129         case ']':
1130             if (lex->flags.noops) {
1131                 nextch = lex_getch(lex);
1132                 if (nextch == ']') {
1133                     lex_tokench(lex, ch);
1134                     lex_tokench(lex, nextch);
1135                     lex_endtoken(lex);
1136                     return (lex->tok.ttype = TOKEN_ATTRIBUTE_CLOSE);
1137                 }
1138                 lex_ungetch(lex, nextch);
1139             }
1140             /* FALL THROUGH */
1141         case ')':
1142         case ';':
1143         case '{':
1144         case '}':
1145
1146         case '#':
1147             lex_tokench(lex, ch);
1148             lex_endtoken(lex);
1149             return (lex->tok.ttype = ch);
1150         default:
1151             break;
1152     }
1153
1154     if (ch == '.') {
1155         nextch = lex_getch(lex);
1156         /* digits starting with a dot */
1157         if (util_isdigit(nextch)) {
1158             lex_ungetch(lex, nextch);
1159             lex->tok.ttype = lex_finish_digit(lex, ch);
1160             lex_endtoken(lex);
1161             return lex->tok.ttype;
1162         }
1163         lex_ungetch(lex, nextch);
1164     }
1165
1166     if (lex->flags.noops)
1167     {
1168         /* Detect characters early which are normally
1169          * operators OR PART of an operator.
1170          */
1171         switch (ch)
1172         {
1173             case '*':
1174             case '/':
1175             case '<':
1176             case '>':
1177             case '=':
1178             case '&':
1179             case '|':
1180             case '^':
1181             case '~':
1182             case ',':
1183             case '!':
1184                 lex_tokench(lex, ch);
1185                 lex_endtoken(lex);
1186                 return (lex->tok.ttype = ch);
1187             default:
1188                 break;
1189         }
1190     }
1191
1192     if (ch == '.')
1193     {
1194         lex_tokench(lex, ch);
1195         /* peak ahead once */
1196         nextch = lex_getch(lex);
1197         if (nextch != '.') {
1198             lex_ungetch(lex, nextch);
1199             lex_endtoken(lex);
1200             if (lex->flags.noops)
1201                 return (lex->tok.ttype = ch);
1202             else
1203                 return (lex->tok.ttype = TOKEN_OPERATOR);
1204         }
1205         /* peak ahead again */
1206         nextch = lex_getch(lex);
1207         if (nextch != '.') {
1208             lex_ungetch(lex, nextch);
1209             lex_ungetch(lex, '.');
1210             lex_endtoken(lex);
1211             if (lex->flags.noops)
1212                 return (lex->tok.ttype = ch);
1213             else
1214                 return (lex->tok.ttype = TOKEN_OPERATOR);
1215         }
1216         /* fill the token to be "..." */
1217         lex_tokench(lex, ch);
1218         lex_tokench(lex, ch);
1219         lex_endtoken(lex);
1220         return (lex->tok.ttype = TOKEN_DOTS);
1221     }
1222
1223     if (ch == ',' || ch == '.') {
1224         lex_tokench(lex, ch);
1225         lex_endtoken(lex);
1226         return (lex->tok.ttype = TOKEN_OPERATOR);
1227     }
1228
1229     if (ch == '+' || ch == '-' || /* ++, --, +=, -=  and -> as well! */
1230         ch == '>' || ch == '<' || /* <<, >>, <=, >=  and >< as well! */
1231         ch == '=' || ch == '!' || /* <=>, ==, !=                     */
1232         ch == '&' || ch == '|' || /* &&, ||, &=, |=                  */
1233         ch == '~' || ch == '^'    /* ~=, ~, ^                        */
1234     )  {
1235         lex_tokench(lex, ch);
1236
1237         nextch = lex_getch(lex);
1238         if ((nextch == '=' && ch != '<') ||
1239             (nextch == ch  && ch != '!') ||
1240             (nextch == '<' && ch == '>')) {
1241             lex_tokench(lex, nextch);
1242         } else if (ch == '<' && nextch == '=') {
1243             lex_tokench(lex, nextch);
1244             if ((thirdch = lex_getch(lex)) == '>')
1245                 lex_tokench(lex, thirdch);
1246             else
1247                 lex_ungetch(lex, thirdch);
1248
1249         } else if (ch == '-' && nextch == '>') {
1250             lex_tokench(lex, nextch);
1251         } else if (ch == '&' && nextch == '~') {
1252             thirdch = lex_getch(lex);
1253             if (thirdch != '=') {
1254                 lex_ungetch(lex, thirdch);
1255                 lex_ungetch(lex, nextch);
1256             }
1257             else {
1258                 lex_tokench(lex, nextch);
1259                 lex_tokench(lex, thirdch);
1260             }
1261         }
1262         else if (lex->flags.preprocessing &&
1263                  ch == '-' && util_isdigit(nextch))
1264         {
1265             lex->tok.ttype = lex_finish_digit(lex, nextch);
1266             if (lex->tok.ttype == TOKEN_INTCONST)
1267                 lex->tok.constval.i = -lex->tok.constval.i;
1268             else
1269                 lex->tok.constval.f = -lex->tok.constval.f;
1270             lex_endtoken(lex);
1271             return lex->tok.ttype;
1272         } else {
1273             lex_ungetch(lex, nextch);
1274         }
1275
1276         lex_endtoken(lex);
1277         return (lex->tok.ttype = TOKEN_OPERATOR);
1278     }
1279
1280     if (ch == '*' || ch == '/') /* *=, /= */
1281     {
1282         lex_tokench(lex, ch);
1283
1284         nextch = lex_getch(lex);
1285         if (nextch == '=' || nextch == '*') {
1286             lex_tokench(lex, nextch);
1287         } else
1288             lex_ungetch(lex, nextch);
1289
1290         lex_endtoken(lex);
1291         return (lex->tok.ttype = TOKEN_OPERATOR);
1292     }
1293
1294     if (ch == '%') {
1295         lex_tokench(lex, ch);
1296         lex_endtoken(lex);
1297         return (lex->tok.ttype = TOKEN_OPERATOR);
1298     }
1299
1300     if (isident_start(ch))
1301     {
1302         const char *v;
1303
1304         lex_tokench(lex, ch);
1305         if (!lex_finish_ident(lex)) {
1306             /* error? */
1307             return (lex->tok.ttype = TOKEN_ERROR);
1308         }
1309         lex_endtoken(lex);
1310         lex->tok.ttype = TOKEN_IDENT;
1311
1312         v = lex->tok.value;
1313         if (!strcmp(v, "void")) {
1314             lex->tok.ttype = TOKEN_TYPENAME;
1315             lex->tok.constval.t = TYPE_VOID;
1316         } else if (!strcmp(v, "int")) {
1317             lex->tok.ttype = TOKEN_TYPENAME;
1318             lex->tok.constval.t = TYPE_INTEGER;
1319         } else if (!strcmp(v, "float")) {
1320             lex->tok.ttype = TOKEN_TYPENAME;
1321             lex->tok.constval.t = TYPE_FLOAT;
1322         } else if (!strcmp(v, "string")) {
1323             lex->tok.ttype = TOKEN_TYPENAME;
1324             lex->tok.constval.t = TYPE_STRING;
1325         } else if (!strcmp(v, "entity")) {
1326             lex->tok.ttype = TOKEN_TYPENAME;
1327             lex->tok.constval.t = TYPE_ENTITY;
1328         } else if (!strcmp(v, "vector")) {
1329             lex->tok.ttype = TOKEN_TYPENAME;
1330             lex->tok.constval.t = TYPE_VECTOR;
1331         } else {
1332             size_t kw;
1333             for (kw = 0; kw < GMQCC_ARRAY_COUNT(keywords_qc); ++kw) {
1334                 if (!strcmp(v, keywords_qc[kw]))
1335                     return (lex->tok.ttype = TOKEN_KEYWORD);
1336             }
1337             if (OPTS_OPTION_U32(OPTION_STANDARD) != COMPILER_QCC) {
1338                 for (kw = 0; kw < GMQCC_ARRAY_COUNT(keywords_fg); ++kw) {
1339                     if (!strcmp(v, keywords_fg[kw]))
1340                         return (lex->tok.ttype = TOKEN_KEYWORD);
1341                 }
1342             }
1343         }
1344
1345         return lex->tok.ttype;
1346     }
1347
1348     if (ch == '"')
1349     {
1350         lex->flags.nodigraphs = true;
1351         if (lex->flags.preprocessing)
1352             lex_tokench(lex, ch);
1353         lex->tok.ttype = lex_finish_string(lex, '"');
1354         if (lex->flags.preprocessing)
1355             lex_tokench(lex, ch);
1356         while (!lex->flags.preprocessing && lex->tok.ttype == TOKEN_STRINGCONST)
1357         {
1358             /* Allow c style "string" "continuation" */
1359             ch = lex_skipwhite(lex, false);
1360             if (ch != '"') {
1361                 lex_ungetch(lex, ch);
1362                 break;
1363             }
1364
1365             lex->tok.ttype = lex_finish_string(lex, '"');
1366         }
1367         lex->flags.nodigraphs = false;
1368         lex_endtoken(lex);
1369         return lex->tok.ttype;
1370     }
1371
1372     if (ch == '\'')
1373     {
1374         /* we parse character constants like string,
1375          * but return TOKEN_CHARCONST, or a vector type if it fits...
1376          * Likewise actual unescaping has to be done by the parser.
1377          * The difference is we don't allow 'char' 'continuation'.
1378          */
1379         if (lex->flags.preprocessing)
1380             lex_tokench(lex, ch);
1381         lex->tok.ttype = lex_finish_string(lex, '\'');
1382         if (lex->flags.preprocessing)
1383             lex_tokench(lex, ch);
1384         lex_endtoken(lex);
1385
1386         lex->tok.ttype = TOKEN_CHARCONST;
1387
1388         /* It's a vector if we can successfully scan 3 floats */
1389         if (util_sscanf(lex->tok.value, " %f %f %f ",
1390                    &lex->tok.constval.v.x, &lex->tok.constval.v.y, &lex->tok.constval.v.z) == 3)
1391
1392         {
1393              lex->tok.ttype = TOKEN_VECTORCONST;
1394         }
1395         else
1396         {
1397             if (!lex->flags.preprocessing && strlen(lex->tok.value) > 1) {
1398                 utf8ch_t u8char;
1399                 /* check for a valid utf8 character */
1400                 if (!OPTS_FLAG(UTF8) || !utf8_to(&u8char, (const unsigned char *)lex->tok.value, 8)) {
1401                     if (lexwarn(lex, WARN_MULTIBYTE_CHARACTER,
1402                                 ( OPTS_FLAG(UTF8) ? "invalid multibyte character sequence `%s`"
1403                                                   : "multibyte character: `%s`" ),
1404                                 lex->tok.value))
1405                         return (lex->tok.ttype = TOKEN_ERROR);
1406                 }
1407                 else
1408                     lex->tok.constval.i = u8char;
1409             }
1410             else
1411                 lex->tok.constval.i = lex->tok.value[0];
1412         }
1413
1414         return lex->tok.ttype;
1415     }
1416
1417     if (util_isdigit(ch))
1418     {
1419         lex->tok.ttype = lex_finish_digit(lex, ch);
1420         lex_endtoken(lex);
1421         return lex->tok.ttype;
1422     }
1423
1424     if (lex->flags.preprocessing) {
1425         lex_tokench(lex, ch);
1426         lex_endtoken(lex);
1427         return (lex->tok.ttype = ch);
1428     }
1429
1430     lexerror(lex, "unknown token: `%c`", ch);
1431     return (lex->tok.ttype = TOKEN_ERROR);
1432 }