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