]> git.xonotic.org Git - xonotic/gmqcc.git/blob - lexer.cpp
tests: xor peephole optimization regression test
[xonotic/gmqcc.git] / lexer.cpp
1 #include <string.h>
2 #include <stdlib.h>
3
4 #include "gmqcc.h"
5 #include "lexer.h"
6
7 /*
8  * List of Keywords
9  */
10
11 /* original */
12 static const char *keywords_qc[] = {
13     "for", "do", "while",
14     "if", "else",
15     "local",
16     "return",
17     "const"
18 };
19 /* For fte/gmgqcc */
20 static const char *keywords_fg[] = {
21     "switch", "case", "default",
22     "struct", "union",
23     "break", "continue",
24     "typedef",
25     "goto",
26
27     "__builtin_debug_printtype"
28 };
29
30 /*
31  * Lexer code
32  */
33 static char* *lex_filenames;
34
35 static void lexerror(lex_file *lex, const char *fmt, ...)
36 {
37     va_list ap;
38
39     va_start(ap, fmt);
40     if (lex)
41         con_vprintmsg(LVL_ERROR, lex->name, lex->sline, lex->column, "parse error", fmt, ap);
42     else
43         con_vprintmsg(LVL_ERROR, "", 0, 0, "parse error", fmt, ap);
44     va_end(ap);
45 }
46
47 static bool lexwarn(lex_file *lex, int warntype, const char *fmt, ...)
48 {
49     bool      r;
50     lex_ctx_t ctx;
51     va_list   ap;
52
53     ctx.file   = lex->name;
54     ctx.line   = lex->sline;
55     ctx.column = lex->column;
56
57     va_start(ap, fmt);
58     r = vcompile_warning(ctx, warntype, fmt, ap);
59     va_end(ap);
60     return r;
61 }
62
63 static void lex_token_new(lex_file *lex)
64 {
65     if (lex->tok.value)
66         vec_shrinkto(lex->tok.value, 0);
67
68     lex->tok.constval.t  = TYPE_VOID;
69     lex->tok.ctx.line    = lex->sline;
70     lex->tok.ctx.file    = lex->name;
71     lex->tok.ctx.column  = lex->column;
72 }
73
74 static void lex_ungetch(lex_file *lex, int ch);
75 static int lex_getch(lex_file *lex);
76
77 lex_file* lex_open(const char *file)
78 {
79     lex_file  *lex;
80     FILE *in = fopen(file, "rb");
81     uint32_t   read;
82
83     if (!in) {
84         lexerror(nullptr, "open failed: '%s'\n", file);
85         return nullptr;
86     }
87
88     lex = (lex_file*)mem_a(sizeof(*lex));
89     if (!lex) {
90         fclose(in);
91         lexerror(nullptr, "out of memory\n");
92         return nullptr;
93     }
94
95     memset(lex, 0, sizeof(*lex));
96
97     lex->file    = in;
98     lex->name    = util_strdup(file);
99     lex->line    = 1; /* we start counting at 1 */
100     lex->column  = 0;
101     lex->peekpos = 0;
102     lex->eof     = false;
103
104     /* handle BOM */
105     if ((read = (lex_getch(lex) << 16) | (lex_getch(lex) << 8) | lex_getch(lex)) != 0xEFBBBF) {
106         lex_ungetch(lex, (read & 0x0000FF));
107         lex_ungetch(lex, (read & 0x00FF00) >> 8);
108         lex_ungetch(lex, (read & 0xFF0000) >> 16);
109     } else {
110         /*
111          * otherwise the lexer has advanced 3 bytes for the BOM, we need
112          * to set the column back to 0
113          */
114         lex->column = 0;
115     }
116
117     vec_push(lex_filenames, lex->name);
118     return lex;
119 }
120
121 lex_file* lex_open_string(const char *str, size_t len, const char *name)
122 {
123     lex_file *lex;
124
125     lex = (lex_file*)mem_a(sizeof(*lex));
126     if (!lex) {
127         lexerror(nullptr, "out of memory\n");
128         return nullptr;
129     }
130
131     memset(lex, 0, sizeof(*lex));
132
133     lex->file = nullptr;
134     lex->open_string        = str;
135     lex->open_string_length = len;
136     lex->open_string_pos    = 0;
137
138     lex->name    = util_strdup(name ? name : "<string-source>");
139     lex->line    = 1; /* we start counting at 1 */
140     lex->peekpos = 0;
141     lex->eof     = false;
142     lex->column  = 0;
143
144     vec_push(lex_filenames, lex->name);
145
146     return lex;
147 }
148
149 void lex_cleanup(void)
150 {
151     size_t i;
152     for (i = 0; i < vec_size(lex_filenames); ++i)
153         mem_d(lex_filenames[i]);
154     vec_free(lex_filenames);
155 }
156
157 void lex_close(lex_file *lex)
158 {
159     size_t i;
160     for (i = 0; i < vec_size(lex->frames); ++i)
161         mem_d(lex->frames[i].name);
162     vec_free(lex->frames);
163
164     if (lex->modelname)
165         vec_free(lex->modelname);
166
167     if (lex->file)
168         fclose(lex->file);
169
170     vec_free(lex->tok.value);
171
172     /* mem_d(lex->name); collected in lex_filenames */
173     mem_d(lex);
174 }
175
176
177
178 static int lex_fgetc(lex_file *lex)
179 {
180     if (lex->file) {
181         lex->column++;
182         return fgetc(lex->file);
183     }
184     if (lex->open_string) {
185         if (lex->open_string_pos >= lex->open_string_length)
186             return EOF;
187         lex->column++;
188         return lex->open_string[lex->open_string_pos++];
189     }
190     return EOF;
191 }
192
193 /* Get or put-back data
194  * The following to functions do NOT understand what kind of data they
195  * are working on.
196  * The are merely wrapping get/put in order to count line numbers.
197  */
198 static int lex_try_trigraph(lex_file *lex, int old)
199 {
200     int c2, c3;
201     c2 = lex_fgetc(lex);
202     if (!lex->push_line && c2 == '\n') {
203         lex->line++;
204         lex->column = 0;
205     }
206
207     if (c2 != '?') {
208         lex_ungetch(lex, c2);
209         return old;
210     }
211
212     c3 = lex_fgetc(lex);
213     if (!lex->push_line && c3 == '\n') {
214         lex->line++;
215         lex->column = 0;
216     }
217
218     switch (c3) {
219         case '=': return '#';
220         case '/': return '\\';
221         case '\'': return '^';
222         case '(': return '[';
223         case ')': return ']';
224         case '!': return '|';
225         case '<': return '{';
226         case '>': return '}';
227         case '-': return '~';
228         default:
229             lex_ungetch(lex, c3);
230             lex_ungetch(lex, c2);
231             return old;
232     }
233 }
234
235 static int lex_try_digraph(lex_file *lex, int ch)
236 {
237     int c2;
238     c2 = lex_fgetc(lex);
239     /* we just used fgetc() so count lines
240      * need to offset a \n the ungetch would recognize
241      */
242     if (!lex->push_line && c2 == '\n')
243         lex->line++;
244     if      (ch == '<' && c2 == ':')
245         return '[';
246     else if (ch == ':' && c2 == '>')
247         return ']';
248     else if (ch == '<' && c2 == '%')
249         return '{';
250     else if (ch == '%' && c2 == '>')
251         return '}';
252     else if (ch == '%' && c2 == ':')
253         return '#';
254     lex_ungetch(lex, c2);
255     return ch;
256 }
257
258 static int lex_getch(lex_file *lex)
259 {
260     int ch;
261
262     if (lex->peekpos) {
263         lex->peekpos--;
264         if (!lex->push_line && lex->peek[lex->peekpos] == '\n') {
265             lex->line++;
266             lex->column = 0;
267         }
268         return lex->peek[lex->peekpos];
269     }
270
271     ch = lex_fgetc(lex);
272     if (!lex->push_line && ch == '\n') {
273         lex->line++;
274         lex->column = 0;
275     }
276     else if (ch == '?')
277         return lex_try_trigraph(lex, ch);
278     else if (!lex->flags.nodigraphs && (ch == '<' || ch == ':' || ch == '%'))
279         return lex_try_digraph(lex, ch);
280     return ch;
281 }
282
283 static void lex_ungetch(lex_file *lex, int ch)
284 {
285     lex->peek[lex->peekpos++] = ch;
286     lex->column--;
287     if (!lex->push_line && ch == '\n') {
288         lex->line--;
289         lex->column = 0;
290     }
291 }
292
293 /* classify characters
294  * some additions to the is*() functions of ctype.h
295  */
296
297 /* Idents are alphanumberic, but they start with alpha or _ */
298 static bool isident_start(int ch)
299 {
300     return util_isalpha(ch) || ch == '_';
301 }
302
303 static bool isident(int ch)
304 {
305     return isident_start(ch) || util_isdigit(ch);
306 }
307
308 /* isxdigit_only is used when we already know it's not a digit
309  * and want to see if it's a hex digit anyway.
310  */
311 static bool isxdigit_only(int ch)
312 {
313     return (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F');
314 }
315
316 /* Append a character to the token buffer */
317 static void lex_tokench(lex_file *lex, int ch)
318 {
319     vec_push(lex->tok.value, ch);
320 }
321
322 /* Append a trailing null-byte */
323 static void lex_endtoken(lex_file *lex)
324 {
325     vec_push(lex->tok.value, 0);
326     vec_shrinkby(lex->tok.value, 1);
327 }
328
329 static bool lex_try_pragma(lex_file *lex)
330 {
331     int ch;
332     char *pragma  = nullptr;
333     char *command = nullptr;
334     char *param   = nullptr;
335     size_t line;
336
337     if (lex->flags.preprocessing)
338         return false;
339
340     line = lex->line;
341
342     ch = lex_getch(lex);
343     if (ch != '#') {
344         lex_ungetch(lex, ch);
345         return false;
346     }
347
348     for (ch = lex_getch(lex); vec_size(pragma) < 8 && ch >= 'a' && ch <= 'z'; ch = lex_getch(lex))
349         vec_push(pragma, ch);
350     vec_push(pragma, 0);
351
352     if (ch != ' ' || strcmp(pragma, "pragma")) {
353         lex_ungetch(lex, ch);
354         goto unroll;
355     }
356
357     for (ch = lex_getch(lex); vec_size(command) < 32 && ch >= 'a' && ch <= 'z'; ch = lex_getch(lex))
358         vec_push(command, ch);
359     vec_push(command, 0);
360
361     if (ch != '(') {
362         lex_ungetch(lex, ch);
363         goto unroll;
364     }
365
366     for (ch = lex_getch(lex); vec_size(param) < 1024 && ch != ')' && ch != '\n'; ch = lex_getch(lex))
367         vec_push(param, ch);
368     vec_push(param, 0);
369
370     if (ch != ')') {
371         lex_ungetch(lex, ch);
372         goto unroll;
373     }
374
375     if (!strcmp(command, "push")) {
376         if (!strcmp(param, "line")) {
377             lex->push_line++;
378             if (lex->push_line == 1)
379                 --line;
380         }
381         else
382             goto unroll;
383     }
384     else if (!strcmp(command, "pop")) {
385         if (!strcmp(param, "line")) {
386             if (lex->push_line)
387                 lex->push_line--;
388             if (lex->push_line == 0)
389                 --line;
390         }
391         else
392             goto unroll;
393     }
394     else if (!strcmp(command, "file")) {
395         lex->name = util_strdup(param);
396         vec_push(lex_filenames, lex->name);
397     }
398     else if (!strcmp(command, "line")) {
399         line = strtol(param, nullptr, 0)-1;
400     }
401     else
402         goto unroll;
403
404     lex->line = line;
405     while (ch != '\n' && ch != EOF)
406         ch = lex_getch(lex);
407     vec_free(command);
408     vec_free(param);
409     vec_free(pragma);
410     return true;
411
412 unroll:
413     if (command) {
414         vec_pop(command);
415         while (vec_size(command)) {
416             lex_ungetch(lex, (unsigned char)vec_last(command));
417             vec_pop(command);
418         }
419         vec_free(command);
420         lex_ungetch(lex, ' ');
421     }
422     if (param) {
423         vec_pop(param);
424         while (vec_size(param)) {
425             lex_ungetch(lex, (unsigned char)vec_last(param));
426             vec_pop(param);
427         }
428         vec_free(param);
429         lex_ungetch(lex, ' ');
430     }
431     if (pragma) {
432         vec_pop(pragma);
433         while (vec_size(pragma)) {
434             lex_ungetch(lex, (unsigned char)vec_last(pragma));
435             vec_pop(pragma);
436         }
437         vec_free(pragma);
438     }
439     lex_ungetch(lex, '#');
440
441     lex->line = line;
442     return false;
443 }
444
445 /* Skip whitespace and comments and return the first
446  * non-white character.
447  * As this makes use of the above getch() ungetch() functions,
448  * we don't need to care at all about line numbering anymore.
449  *
450  * In theory, this function should only be used at the beginning
451  * of lexing, or when we *know* the next character is part of the token.
452  * Otherwise, if the parser throws an error, the linenumber may not be
453  * the line of the error, but the line of the next token AFTER the error.
454  *
455  * This is currently only problematic when using c-like string-continuation,
456  * since comments and whitespaces are allowed between 2 such strings.
457  * Example:
458 printf(   "line one\n"
459 // A comment
460           "A continuation of the previous string"
461 // This line is skipped
462       , foo);
463
464  * In this case, if the parse decides it didn't actually want a string,
465  * and uses lex->line to print an error, it will show the ', foo);' line's
466  * linenumber.
467  *
468  * On the other hand, the parser is supposed to remember the line of the next
469  * token's beginning. In this case we would want skipwhite() to be called
470  * AFTER reading a token, so that the parser, before reading the NEXT token,
471  * doesn't store teh *comment's* linenumber, but the actual token's linenumber.
472  *
473  * THIS SOLUTION
474  *    here is to store the line of the first character after skipping
475  *    the initial whitespace in lex->sline, this happens in lex_do.
476  */
477 static int lex_skipwhite(lex_file *lex, bool hadwhite)
478 {
479     int ch = 0;
480     bool haswhite = hadwhite;
481
482     do
483     {
484         ch = lex_getch(lex);
485         while (ch != EOF && util_isspace(ch)) {
486             if (ch == '\n') {
487                 if (lex_try_pragma(lex))
488                     continue;
489             }
490             if (lex->flags.preprocessing) {
491                 if (ch == '\n') {
492                     /* end-of-line */
493                     /* see if there was whitespace first */
494                     if (haswhite) { /* (vec_size(lex->tok.value)) { */
495                         lex_ungetch(lex, ch);
496                         lex_endtoken(lex);
497                         return TOKEN_WHITE;
498                     }
499                     /* otherwise return EOL */
500                     return TOKEN_EOL;
501                 }
502                 haswhite = true;
503                 lex_tokench(lex, ch);
504             }
505             ch = lex_getch(lex);
506         }
507
508         if (ch == '/') {
509             ch = lex_getch(lex);
510             if (ch == '/')
511             {
512                 /* one line comment */
513                 ch = lex_getch(lex);
514
515                 if (lex->flags.preprocessing) {
516                     haswhite = true;
517                     lex_tokench(lex, ' ');
518                     lex_tokench(lex, ' ');
519                 }
520
521                 while (ch != EOF && ch != '\n') {
522                     if (lex->flags.preprocessing)
523                         lex_tokench(lex, ' '); /* ch); */
524                     ch = lex_getch(lex);
525                 }
526                 if (lex->flags.preprocessing) {
527                     lex_ungetch(lex, '\n');
528                     lex_endtoken(lex);
529                     return TOKEN_WHITE;
530                 }
531                 continue;
532             }
533             if (ch == '*')
534             {
535                 /* multiline comment */
536                 if (lex->flags.preprocessing) {
537                     haswhite = true;
538                     lex_tokench(lex, ' ');
539                     lex_tokench(lex, ' ');
540                 }
541
542                 while (ch != EOF)
543                 {
544                     ch = lex_getch(lex);
545                     if (ch == '*') {
546                         ch = lex_getch(lex);
547                         if (ch == '/') {
548                             if (lex->flags.preprocessing) {
549                                 lex_tokench(lex, ' ');
550                                 lex_tokench(lex, ' ');
551                             }
552                             break;
553                         }
554                         lex_ungetch(lex, ch);
555                     }
556                     if (lex->flags.preprocessing) {
557                         if (ch == '\n')
558                             lex_tokench(lex, '\n');
559                         else
560                             lex_tokench(lex, ' ');
561                     }
562                 }
563                 ch = ' '; /* cause TRUE in the isspace check */
564                 continue;
565             }
566             /* Otherwise roll back to the slash and break out of the loop */
567             lex_ungetch(lex, ch);
568             ch = '/';
569             break;
570         }
571     } while (ch != EOF && util_isspace(ch));
572
573     if (haswhite) {
574         lex_endtoken(lex);
575         lex_ungetch(lex, ch);
576         return TOKEN_WHITE;
577     }
578     return ch;
579 }
580
581 /* Get a token */
582 static bool GMQCC_WARN lex_finish_ident(lex_file *lex)
583 {
584     int ch;
585
586     ch = lex_getch(lex);
587     while (ch != EOF && isident(ch))
588     {
589         lex_tokench(lex, ch);
590         ch = lex_getch(lex);
591     }
592
593     /* last ch was not an ident ch: */
594     lex_ungetch(lex, ch);
595
596     return true;
597 }
598
599 /* read one ident for the frame list */
600 static int lex_parse_frame(lex_file *lex)
601 {
602     int ch;
603
604     lex_token_new(lex);
605
606     ch = lex_getch(lex);
607     while (ch != EOF && ch != '\n' && util_isspace(ch))
608         ch = lex_getch(lex);
609
610     if (ch == '\n')
611         return 1;
612
613     if (!isident_start(ch)) {
614         lexerror(lex, "invalid framename, must start with one of a-z or _, got %c", ch);
615         return -1;
616     }
617
618     lex_tokench(lex, ch);
619     if (!lex_finish_ident(lex))
620         return -1;
621     lex_endtoken(lex);
622     return 0;
623 }
624
625 /* read a list of $frames */
626 static bool lex_finish_frames(lex_file *lex)
627 {
628     do {
629         size_t i;
630         int    rc;
631         frame_macro m;
632
633         rc = lex_parse_frame(lex);
634         if (rc > 0) /* end of line */
635             return true;
636         if (rc < 0) /* error */
637             return false;
638
639         for (i = 0; i < vec_size(lex->frames); ++i) {
640             if (!strcmp(lex->tok.value, lex->frames[i].name)) {
641                 lex->frames[i].value = lex->framevalue++;
642                 if (lexwarn(lex, WARN_FRAME_MACROS, "duplicate frame macro defined: `%s`", lex->tok.value))
643                     return false;
644                 break;
645             }
646         }
647         if (i < vec_size(lex->frames))
648             continue;
649
650         m.value = lex->framevalue++;
651         m.name = util_strdup(lex->tok.value);
652         vec_shrinkto(lex->tok.value, 0);
653         vec_push(lex->frames, m);
654     } while (true);
655
656     return false;
657 }
658
659 static int GMQCC_WARN lex_finish_string(lex_file *lex, int quote)
660 {
661     utf8ch_t chr = 0;
662     int ch = 0, texttype = 0;
663     int nextch;
664     bool hex;
665     bool oct;
666     char u8buf[8]; /* way more than enough */
667     int  u8len, uc;
668
669     while (ch != EOF)
670     {
671         ch = lex_getch(lex);
672         if (ch == quote)
673             return TOKEN_STRINGCONST;
674
675         if (lex->flags.preprocessing && ch == '\\') {
676             lex_tokench(lex, ch);
677             ch = lex_getch(lex);
678             if (ch == EOF) {
679                 lexerror(lex, "unexpected end of file");
680                 lex_ungetch(lex, EOF); /* next token to be TOKEN_EOF */
681                 return (lex->tok.ttype = TOKEN_ERROR);
682             }
683             lex_tokench(lex, ch);
684         }
685         else if (ch == '\\') {
686             ch = lex_getch(lex);
687             if (ch == EOF) {
688                 lexerror(lex, "unexpected end of file");
689                 lex_ungetch(lex, EOF); /* next token to be TOKEN_EOF */
690                 return (lex->tok.ttype = TOKEN_ERROR);
691             }
692
693             switch (ch) {
694             case '\\': break;
695             case '\'': break;
696             case '"':  break;
697             case 'a': ch = '\a'; break;
698             case 'r': ch = '\r'; break;
699             case 'n': ch = '\n'; break;
700             case 't': ch = '\t'; break;
701             case 'f': ch = '\f'; break;
702             case 'v': ch = '\v'; break;
703             case 'x':
704             case 'X':
705                 /* same procedure as in fteqcc */
706                 ch = 0;
707                 nextch = lex_getch(lex);
708                 if      (nextch >= '0' && nextch <= '9')
709                     ch += nextch - '0';
710                 else if (nextch >= 'a' && nextch <= 'f')
711                     ch += nextch - 'a' + 10;
712                 else if (nextch >= 'A' && nextch <= 'F')
713                     ch += nextch - 'A' + 10;
714                 else {
715                     lexerror(lex, "bad character code");
716                     lex_ungetch(lex, nextch);
717                     return (lex->tok.ttype = TOKEN_ERROR);
718                 }
719
720                 ch *= 0x10;
721                 nextch = lex_getch(lex);
722                 if      (nextch >= '0' && nextch <= '9')
723                     ch += nextch - '0';
724                 else if (nextch >= 'a' && nextch <= 'f')
725                     ch += nextch - 'a' + 10;
726                 else if (nextch >= 'A' && nextch <= 'F')
727                     ch += nextch - 'A' + 10;
728                 else {
729                     lexerror(lex, "bad character code");
730                     lex_ungetch(lex, nextch);
731                     return (lex->tok.ttype = TOKEN_ERROR);
732                 }
733                 break;
734
735             /* fteqcc support */
736             case '0': case '1': case '2': case '3':
737             case '4': case '5': case '6': case '7':
738             case '8': case '9':
739                 ch = 18 + ch - '0';
740                 break;
741             case '<':  ch = 29; break;
742             case '-':  ch = 30; break;
743             case '>':  ch = 31; break;
744             case '[':  ch = 16; break;
745             case ']':  ch = 17; break;
746             case '{':
747                 chr = 0;
748                 nextch = lex_getch(lex);
749                 hex = (nextch == 'x');
750                 oct = (nextch == '0');
751                 if (!hex && !oct)
752                     lex_ungetch(lex, nextch);
753                 for (nextch = lex_getch(lex); nextch != '}'; nextch = lex_getch(lex)) {
754                     if (!hex && !oct) {
755                         if (nextch >= '0' && nextch <= '9')
756                             chr = chr * 10 + nextch - '0';
757                         else {
758                             lexerror(lex, "bad character code");
759                             return (lex->tok.ttype = TOKEN_ERROR);
760                         }
761                     } else if (!oct) {
762                         if (nextch >= '0' && nextch <= '9')
763                             chr = chr * 0x10 + nextch - '0';
764                         else if (nextch >= 'a' && nextch <= 'f')
765                             chr = chr * 0x10 + nextch - 'a' + 10;
766                         else if (nextch >= 'A' && nextch <= 'F')
767                             chr = chr * 0x10 + nextch - 'A' + 10;
768                         else {
769                             lexerror(lex, "bad character code");
770                             return (lex->tok.ttype = TOKEN_ERROR);
771                         }
772                     } else {
773                         if (nextch >= '0' && nextch <= '9')
774                             chr = chr * 8 + chr - '0';
775                         else {
776                             lexerror(lex, "bad character code");
777                             return (lex->tok.ttype = TOKEN_ERROR);
778                         }
779                     }
780                     if (chr > 0x10FFFF || (!OPTS_FLAG(UTF8) && chr > 255))
781                     {
782                         lexerror(lex, "character code out of range");
783                         return (lex->tok.ttype = TOKEN_ERROR);
784                     }
785                 }
786                 if (OPTS_FLAG(UTF8) && chr >= 128) {
787                     u8len = utf8_from(u8buf, chr);
788                     if (!u8len)
789                         ch = 0;
790                     else {
791                         --u8len;
792                         lex->column += u8len;
793                         for (uc = 0; uc < u8len; ++uc)
794                             lex_tokench(lex, u8buf[uc]);
795                         /*
796                          * the last character will be inserted with the tokench() call
797                          * below the switch
798                          */
799                         ch = u8buf[uc];
800                     }
801                 }
802                 else
803                     ch = chr;
804                 break;
805
806             /* high bit text */
807             case 'b': case 's':
808                 texttype ^= 128;
809                 continue;
810
811             case '\n':
812                 ch = '\n';
813                 break;
814
815             default:
816                 lexwarn(lex, WARN_UNKNOWN_CONTROL_SEQUENCE, "unrecognized control sequence: \\%c", ch);
817                 /* so we just add the character plus backslash no matter what it actually is */
818                 lex_tokench(lex, '\\');
819             }
820             /* add the character finally */
821             lex_tokench(lex, ch | texttype);
822         }
823         else
824             lex_tokench(lex, ch);
825     }
826     lexerror(lex, "unexpected end of file within string constant");
827     lex_ungetch(lex, EOF); /* next token to be TOKEN_EOF */
828     return (lex->tok.ttype = TOKEN_ERROR);
829 }
830
831 static int GMQCC_WARN lex_finish_digit(lex_file *lex, int lastch)
832 {
833     bool ishex = false;
834     bool isoct = false;
835
836     int  ch = lastch;
837
838     /* parse a number... */
839     if (ch == '.')
840         lex->tok.ttype = TOKEN_FLOATCONST;
841     else
842         lex->tok.ttype = TOKEN_INTCONST;
843
844     lex_tokench(lex, ch);
845
846     ch = lex_getch(lex);
847
848     if (lastch == '0' && util_isdigit(ch)) {
849       if (ch < '0' || ch > '7') {
850         lexerror(lex, "invalid octal constant");
851         return (lex->tok.ttype = TOKEN_ERROR);
852       }
853       isoct = true;
854     }
855
856     if (!isoct && ch != '.' && !util_isdigit(ch))
857     {
858         if (lastch != '0' || ch != 'x')
859         {
860             /* end of the number or EOF */
861             lex_ungetch(lex, ch);
862             lex_endtoken(lex);
863
864             lex->tok.constval.i = lastch - '0';
865             return lex->tok.ttype;
866         }
867
868         ishex = true;
869     }
870
871     /* EOF would have been caught above */
872
873     if (ch != '.')
874     {
875         lex_tokench(lex, ch);
876         ch = lex_getch(lex);
877         while (util_isdigit(ch) || (ishex && isxdigit_only(ch)))
878         {
879             lex_tokench(lex, ch);
880             ch = lex_getch(lex);
881         }
882     }
883     /* NOT else, '.' can come from above as well */
884     if (lex->tok.ttype != TOKEN_FLOATCONST && ch == '.' && !ishex)
885     {
886         /* Allow floating comma in non-hex mode */
887         lex->tok.ttype = TOKEN_FLOATCONST;
888         lex_tokench(lex, ch);
889
890         /* continue digits-only */
891         ch = lex_getch(lex);
892         while (util_isdigit(ch))
893         {
894             lex_tokench(lex, ch);
895             ch = lex_getch(lex);
896         }
897     }
898     /* put back the last character */
899     /* but do not put back the trailing 'f' or a float */
900     if (lex->tok.ttype == TOKEN_FLOATCONST && ch == 'f')
901         ch = lex_getch(lex);
902
903     /* generally we don't want words to follow numbers: */
904     if (isident(ch)) {
905         lexerror(lex, "unexpected trailing characters after number");
906         return (lex->tok.ttype = TOKEN_ERROR);
907     }
908     lex_ungetch(lex, ch);
909
910     lex_endtoken(lex);
911     if (lex->tok.ttype == TOKEN_FLOATCONST) {
912         lex->tok.constval.f = strtod(lex->tok.value, nullptr);
913     } else {
914       /* determine base for strtol */
915       int base = 10;
916       if (ishex) base = 16;
917       if (isoct) base = 8;
918       lex->tok.constval.i = strtol(lex->tok.value, nullptr, base);
919     }
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 == 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 != 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 = nullptr;
1061                 vec_push(lex->frames, m);
1062             }
1063             lex->modelname = lex->tok.value;
1064             lex->tok.value = nullptr;
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 != 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 != 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         nextch = lex_getch(lex);
1237
1238         if ((nextch == '=' && ch != '<') || (nextch == '<' && ch == '>'))
1239             lex_tokench(lex, nextch);
1240         else if (nextch == ch && ch != '!') {
1241             lex_tokench(lex, nextch);
1242             if ((thirdch = lex_getch(lex)) == '=')
1243                 lex_tokench(lex, thirdch);
1244             else
1245                 lex_ungetch(lex, thirdch);
1246         } else if (ch == '<' && nextch == '=') {
1247             lex_tokench(lex, nextch);
1248             if ((thirdch = lex_getch(lex)) == '>')
1249                 lex_tokench(lex, thirdch);
1250             else
1251                 lex_ungetch(lex, thirdch);
1252
1253         } else if (ch == '-' && nextch == '>') {
1254             lex_tokench(lex, nextch);
1255         } else if (ch == '&' && nextch == '~') {
1256             thirdch = lex_getch(lex);
1257             if (thirdch != '=') {
1258                 lex_ungetch(lex, thirdch);
1259                 lex_ungetch(lex, nextch);
1260             }
1261             else {
1262                 lex_tokench(lex, nextch);
1263                 lex_tokench(lex, thirdch);
1264             }
1265         }
1266         else if (lex->flags.preprocessing &&
1267                  ch == '-' && util_isdigit(nextch))
1268         {
1269             lex->tok.ttype = lex_finish_digit(lex, nextch);
1270             if (lex->tok.ttype == TOKEN_INTCONST)
1271                 lex->tok.constval.i = -lex->tok.constval.i;
1272             else
1273                 lex->tok.constval.f = -lex->tok.constval.f;
1274             lex_endtoken(lex);
1275             return lex->tok.ttype;
1276         } else {
1277             lex_ungetch(lex, nextch);
1278         }
1279
1280         lex_endtoken(lex);
1281         return (lex->tok.ttype = TOKEN_OPERATOR);
1282     }
1283
1284     if (ch == '*' || ch == '/') /* *=, /= */
1285     {
1286         lex_tokench(lex, ch);
1287
1288         nextch = lex_getch(lex);
1289         if (nextch == '=' || nextch == '*') {
1290             lex_tokench(lex, nextch);
1291         } else
1292             lex_ungetch(lex, nextch);
1293
1294         lex_endtoken(lex);
1295         return (lex->tok.ttype = TOKEN_OPERATOR);
1296     }
1297
1298     if (ch == '%') {
1299         lex_tokench(lex, ch);
1300         lex_endtoken(lex);
1301         return (lex->tok.ttype = TOKEN_OPERATOR);
1302     }
1303
1304     if (isident_start(ch))
1305     {
1306         const char *v;
1307
1308         lex_tokench(lex, ch);
1309         if (!lex_finish_ident(lex)) {
1310             /* error? */
1311             return (lex->tok.ttype = TOKEN_ERROR);
1312         }
1313         lex_endtoken(lex);
1314         lex->tok.ttype = TOKEN_IDENT;
1315
1316         v = lex->tok.value;
1317         if (!strcmp(v, "void")) {
1318             lex->tok.ttype = TOKEN_TYPENAME;
1319             lex->tok.constval.t = TYPE_VOID;
1320         } else if (!strcmp(v, "int")) {
1321             lex->tok.ttype = TOKEN_TYPENAME;
1322             lex->tok.constval.t = TYPE_INTEGER;
1323         } else if (!strcmp(v, "float")) {
1324             lex->tok.ttype = TOKEN_TYPENAME;
1325             lex->tok.constval.t = TYPE_FLOAT;
1326         } else if (!strcmp(v, "string")) {
1327             lex->tok.ttype = TOKEN_TYPENAME;
1328             lex->tok.constval.t = TYPE_STRING;
1329         } else if (!strcmp(v, "entity")) {
1330             lex->tok.ttype = TOKEN_TYPENAME;
1331             lex->tok.constval.t = TYPE_ENTITY;
1332         } else if (!strcmp(v, "vector")) {
1333             lex->tok.ttype = TOKEN_TYPENAME;
1334             lex->tok.constval.t = TYPE_VECTOR;
1335         } else if (!strcmp(v, "_length")) {
1336             lex->tok.ttype = TOKEN_OPERATOR;
1337         } else {
1338             size_t kw;
1339             for (kw = 0; kw < GMQCC_ARRAY_COUNT(keywords_qc); ++kw) {
1340                 if (!strcmp(v, keywords_qc[kw]))
1341                     return (lex->tok.ttype = TOKEN_KEYWORD);
1342             }
1343             if (OPTS_OPTION_U32(OPTION_STANDARD) != COMPILER_QCC) {
1344                 for (kw = 0; kw < GMQCC_ARRAY_COUNT(keywords_fg); ++kw) {
1345                     if (!strcmp(v, keywords_fg[kw]))
1346                         return (lex->tok.ttype = TOKEN_KEYWORD);
1347                 }
1348             }
1349         }
1350
1351         return lex->tok.ttype;
1352     }
1353
1354     if (ch == '"')
1355     {
1356         lex->flags.nodigraphs = true;
1357         if (lex->flags.preprocessing)
1358             lex_tokench(lex, ch);
1359         lex->tok.ttype = lex_finish_string(lex, '"');
1360         if (lex->flags.preprocessing)
1361             lex_tokench(lex, ch);
1362         while (!lex->flags.preprocessing && lex->tok.ttype == TOKEN_STRINGCONST)
1363         {
1364             /* Allow c style "string" "continuation" */
1365             ch = lex_skipwhite(lex, false);
1366             if (ch != '"') {
1367                 lex_ungetch(lex, ch);
1368                 break;
1369             }
1370
1371             lex->tok.ttype = lex_finish_string(lex, '"');
1372         }
1373         lex->flags.nodigraphs = false;
1374         lex_endtoken(lex);
1375         return lex->tok.ttype;
1376     }
1377
1378     if (ch == '\'')
1379     {
1380         /* we parse character constants like string,
1381          * but return TOKEN_CHARCONST, or a vector type if it fits...
1382          * Likewise actual unescaping has to be done by the parser.
1383          * The difference is we don't allow 'char' 'continuation'.
1384          */
1385         if (lex->flags.preprocessing)
1386             lex_tokench(lex, ch);
1387         lex->tok.ttype = lex_finish_string(lex, '\'');
1388         if (lex->flags.preprocessing)
1389             lex_tokench(lex, ch);
1390         lex_endtoken(lex);
1391
1392         lex->tok.ttype = TOKEN_CHARCONST;
1393
1394         /* It's a vector if we can successfully scan 3 floats */
1395         if (util_sscanf(lex->tok.value, " %f %f %f ",
1396                    &lex->tok.constval.v.x, &lex->tok.constval.v.y, &lex->tok.constval.v.z) == 3)
1397
1398         {
1399              lex->tok.ttype = TOKEN_VECTORCONST;
1400         }
1401         else
1402         {
1403             if (!lex->flags.preprocessing && strlen(lex->tok.value) > 1) {
1404                 utf8ch_t u8char;
1405                 /* check for a valid utf8 character */
1406                 if (!OPTS_FLAG(UTF8) || !utf8_to(&u8char, (const unsigned char *)lex->tok.value, 8)) {
1407                     if (lexwarn(lex, WARN_MULTIBYTE_CHARACTER,
1408                                 ( OPTS_FLAG(UTF8) ? "invalid multibyte character sequence `%s`"
1409                                                   : "multibyte character: `%s`" ),
1410                                 lex->tok.value))
1411                         return (lex->tok.ttype = TOKEN_ERROR);
1412                 }
1413                 else
1414                     lex->tok.constval.i = u8char;
1415             }
1416             else
1417                 lex->tok.constval.i = lex->tok.value[0];
1418         }
1419
1420         return lex->tok.ttype;
1421     }
1422
1423     if (util_isdigit(ch))
1424     {
1425         lex->tok.ttype = lex_finish_digit(lex, ch);
1426         lex_endtoken(lex);
1427         return lex->tok.ttype;
1428     }
1429
1430     if (lex->flags.preprocessing) {
1431         lex_tokench(lex, ch);
1432         lex_endtoken(lex);
1433         return (lex->tok.ttype = ch);
1434     }
1435
1436     lexerror(lex, "unknown token: `%c`", ch);
1437     return (lex->tok.ttype = TOKEN_ERROR);
1438 }