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