]> git.xonotic.org Git - xonotic/gmqcc.git/blob - lexer.c
Add quotes to the known control sequences... darn. Add stringification via #
[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->peek[lex->peekpos] == '\n')
293             lex->line++;
294         return lex->peek[lex->peekpos];
295     }
296
297     ch = lex_fgetc(lex);
298     if (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 (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 /* Skip whitespace and comments and return the first
351  * non-white character.
352  * As this makes use of the above getch() ungetch() functions,
353  * we don't need to care at all about line numbering anymore.
354  *
355  * In theory, this function should only be used at the beginning
356  * of lexing, or when we *know* the next character is part of the token.
357  * Otherwise, if the parser throws an error, the linenumber may not be
358  * the line of the error, but the line of the next token AFTER the error.
359  *
360  * This is currently only problematic when using c-like string-continuation,
361  * since comments and whitespaces are allowed between 2 such strings.
362  * Example:
363 printf(   "line one\n"
364 // A comment
365           "A continuation of the previous string"
366 // This line is skipped
367       , foo);
368
369  * In this case, if the parse decides it didn't actually want a string,
370  * and uses lex->line to print an error, it will show the ', foo);' line's
371  * linenumber.
372  *
373  * On the other hand, the parser is supposed to remember the line of the next
374  * token's beginning. In this case we would want skipwhite() to be called
375  * AFTER reading a token, so that the parser, before reading the NEXT token,
376  * doesn't store teh *comment's* linenumber, but the actual token's linenumber.
377  *
378  * THIS SOLUTION
379  *    here is to store the line of the first character after skipping
380  *    the initial whitespace in lex->sline, this happens in lex_do.
381  */
382 static int lex_skipwhite(lex_file *lex)
383 {
384     int ch = 0;
385     bool haswhite = false;
386
387     do
388     {
389         ch = lex_getch(lex);
390         while (ch != EOF && isspace(ch)) {
391             if (lex->flags.preprocessing) {
392                 if (ch == '\n') {
393                     /* end-of-line */
394                     /* see if there was whitespace first */
395                     if (haswhite) { /* (vec_size(lex->tok.value)) { */
396                         lex_ungetch(lex, ch);
397                         lex_endtoken(lex);
398                         return TOKEN_WHITE;
399                     }
400                     /* otherwise return EOL */
401                     return TOKEN_EOL;
402                 }
403                 haswhite = true;
404                 lex_tokench(lex, ch);
405             }
406             ch = lex_getch(lex);
407         }
408
409         if (ch == '/') {
410             ch = lex_getch(lex);
411             if (ch == '/')
412             {
413                 /* one line comment */
414                 ch = lex_getch(lex);
415
416                 if (lex->flags.preprocessing) {
417                     haswhite = true;
418                     /*
419                     lex_tokench(lex, '/');
420                     lex_tokench(lex, '/');
421                     */
422                     lex_tokench(lex, ' ');
423                     lex_tokench(lex, ' ');
424                 }
425
426                 while (ch != EOF && ch != '\n') {
427                     if (lex->flags.preprocessing)
428                         lex_tokench(lex, ' '); /* ch); */
429                     ch = lex_getch(lex);
430                 }
431                 if (lex->flags.preprocessing) {
432                     lex_ungetch(lex, '\n');
433                     lex_endtoken(lex);
434                     return TOKEN_WHITE;
435                 }
436                 continue;
437             }
438             if (ch == '*')
439             {
440                 /* multiline comment */
441                 if (lex->flags.preprocessing) {
442                     haswhite = true;
443                     /*
444                     lex_tokench(lex, '/');
445                     lex_tokench(lex, '*');
446                     */
447                     lex_tokench(lex, ' ');
448                     lex_tokench(lex, ' ');
449                 }
450
451                 while (ch != EOF)
452                 {
453                     ch = lex_getch(lex);
454                     if (ch == '*') {
455                         ch = lex_getch(lex);
456                         if (ch == '/') {
457                             if (lex->flags.preprocessing) {
458                                 /*
459                                 lex_tokench(lex, '*');
460                                 lex_tokench(lex, '/');
461                                 */
462                                 lex_tokench(lex, ' ');
463                                 lex_tokench(lex, ' ');
464                             }
465                             break;
466                         }
467                     }
468                     if (lex->flags.preprocessing) {
469                         lex_tokench(lex, ' '); /* ch); */
470                     }
471                 }
472                 ch = ' '; /* cause TRUE in the isspace check */
473                 continue;
474             }
475             /* Otherwise roll back to the slash and break out of the loop */
476             lex_ungetch(lex, ch);
477             ch = '/';
478             break;
479         }
480     } while (ch != EOF && isspace(ch));
481
482     if (haswhite) {
483         lex_endtoken(lex);
484         lex_ungetch(lex, ch);
485         return TOKEN_WHITE;
486     }
487     return ch;
488 }
489
490 /* Get a token */
491 static bool GMQCC_WARN lex_finish_ident(lex_file *lex)
492 {
493     int ch;
494
495     ch = lex_getch(lex);
496     while (ch != EOF && isident(ch))
497     {
498         lex_tokench(lex, ch);
499         ch = lex_getch(lex);
500     }
501
502     /* last ch was not an ident ch: */
503     lex_ungetch(lex, ch);
504
505     return true;
506 }
507
508 /* read one ident for the frame list */
509 static int lex_parse_frame(lex_file *lex)
510 {
511     int ch;
512
513     lex_token_new(lex);
514
515     ch = lex_getch(lex);
516     while (ch != EOF && ch != '\n' && isspace(ch))
517         ch = lex_getch(lex);
518
519     if (ch == '\n')
520         return 1;
521
522     if (!isident_start(ch)) {
523         lexerror(lex, "invalid framename, must start with one of a-z or _, got %c", ch);
524         return -1;
525     }
526
527     lex_tokench(lex, ch);
528     if (!lex_finish_ident(lex))
529         return -1;
530     lex_endtoken(lex);
531     return 0;
532 }
533
534 /* read a list of $frames */
535 static bool lex_finish_frames(lex_file *lex)
536 {
537     do {
538         size_t i;
539         int    rc;
540         frame_macro m;
541
542         rc = lex_parse_frame(lex);
543         if (rc > 0) /* end of line */
544             return true;
545         if (rc < 0) /* error */
546             return false;
547
548         for (i = 0; i < vec_size(lex->frames); ++i) {
549             if (!strcmp(lex->tok.value, lex->frames[i].name)) {
550                 lex->frames[i].value = lex->framevalue++;
551                 if (lexwarn(lex, WARN_FRAME_MACROS, "duplicate frame macro defined: `%s`", lex->tok.value))
552                     return false;
553                 break;
554             }
555         }
556         if (i < vec_size(lex->frames))
557             continue;
558
559         m.value = lex->framevalue++;
560         m.name = util_strdup(lex->tok.value);
561         vec_shrinkto(lex->tok.value, 0);
562         vec_push(lex->frames, m);
563     } while (true);
564 }
565
566 static int GMQCC_WARN lex_finish_string(lex_file *lex, int quote)
567 {
568     int ch = 0;
569
570     while (ch != EOF)
571     {
572         ch = lex_getch(lex);
573         if (ch == quote)
574             return TOKEN_STRINGCONST;
575
576         if (lex->flags.preprocessing && ch == '\\') {
577             lex_tokench(lex, ch);
578             ch = lex_getch(lex);
579             if (ch == EOF) {
580                 lexerror(lex, "unexpected end of file");
581                 lex_ungetch(lex, EOF); /* next token to be TOKEN_EOF */
582                 return (lex->tok.ttype = TOKEN_ERROR);
583             }
584             lex_tokench(lex, ch);
585         }
586         else if (ch == '\\') {
587             ch = lex_getch(lex);
588             if (ch == EOF) {
589                 lexerror(lex, "unexpected end of file");
590                 lex_ungetch(lex, EOF); /* next token to be TOKEN_EOF */
591                 return (lex->tok.ttype = TOKEN_ERROR);
592             }
593
594             switch (ch) {
595             case '\\': break;
596             case '\'': break;
597             case '"':  break;
598             case 'a':  ch = '\a'; break;
599             case 'b':  ch = '\b'; break;
600             case 'r':  ch = '\r'; break;
601             case 'n':  ch = '\n'; break;
602             case 't':  ch = '\t'; break;
603             case 'f':  ch = '\f'; break;
604             case 'v':  ch = '\v'; break;
605             default:
606                 lexwarn(lex, WARN_UNKNOWN_CONTROL_SEQUENCE, "unrecognized control sequence: \\%c", ch);
607                 /* so we just add the character plus backslash no matter what it actually is */
608                 lex_tokench(lex, '\\');
609             }
610             /* add the character finally */
611             lex_tokench(lex, ch);
612         }
613         else
614             lex_tokench(lex, ch);
615     }
616     lexerror(lex, "unexpected end of file within string constant");
617     lex_ungetch(lex, EOF); /* next token to be TOKEN_EOF */
618     return (lex->tok.ttype = TOKEN_ERROR);
619 }
620
621 static int GMQCC_WARN lex_finish_digit(lex_file *lex, int lastch)
622 {
623     bool ishex = false;
624
625     int  ch = lastch;
626
627     /* parse a number... */
628     lex->tok.ttype = TOKEN_INTCONST;
629
630     lex_tokench(lex, ch);
631
632     ch = lex_getch(lex);
633     if (ch != '.' && !isdigit(ch))
634     {
635         if (lastch != '0' || ch != 'x')
636         {
637             /* end of the number or EOF */
638             lex_ungetch(lex, ch);
639             lex_endtoken(lex);
640
641             lex->tok.constval.i = lastch - '0';
642             return lex->tok.ttype;
643         }
644
645         ishex = true;
646     }
647
648     /* EOF would have been caught above */
649
650     if (ch != '.')
651     {
652         lex_tokench(lex, ch);
653         ch = lex_getch(lex);
654         while (isdigit(ch) || (ishex && isxdigit_only(ch)))
655         {
656             lex_tokench(lex, ch);
657             ch = lex_getch(lex);
658         }
659     }
660     /* NOT else, '.' can come from above as well */
661     if (ch == '.' && !ishex)
662     {
663         /* Allow floating comma in non-hex mode */
664         lex->tok.ttype = TOKEN_FLOATCONST;
665         lex_tokench(lex, ch);
666
667         /* continue digits-only */
668         ch = lex_getch(lex);
669         while (isdigit(ch))
670         {
671             lex_tokench(lex, ch);
672             ch = lex_getch(lex);
673         }
674     }
675     /* put back the last character */
676     /* but do not put back the trailing 'f' or a float */
677     if (lex->tok.ttype == TOKEN_FLOATCONST && ch == 'f')
678         ch = lex_getch(lex);
679
680     /* generally we don't want words to follow numbers: */
681     if (isident(ch)) {
682         lexerror(lex, "unexpected trailing characters after number");
683         return (lex->tok.ttype = TOKEN_ERROR);
684     }
685     lex_ungetch(lex, ch);
686
687     lex_endtoken(lex);
688     if (lex->tok.ttype == TOKEN_FLOATCONST)
689         lex->tok.constval.f = strtod(lex->tok.value, NULL);
690     else
691         lex->tok.constval.i = strtol(lex->tok.value, NULL, 0);
692     return lex->tok.ttype;
693 }
694
695 int lex_do(lex_file *lex)
696 {
697     int ch, nextch;
698
699     lex_token_new(lex);
700 #if 0
701     if (!lex->tok)
702         return TOKEN_FATAL;
703 #endif
704
705     while (true) {
706         ch = lex_skipwhite(lex);
707         if (!lex->flags.mergelines || ch != '\\')
708             break;
709         ch = lex_getch(lex);
710         if (ch != '\n') {
711             lex_ungetch(lex, ch);
712             ch = '\\';
713             break;
714         }
715         /* we reached a linemerge */
716         lex_tokench(lex, '\n');
717         continue;
718     }
719
720     lex->sline = lex->line;
721     lex->tok.ctx.line = lex->sline;
722     lex->tok.ctx.file = lex->name;
723
724     if (lex->flags.preprocessing && (ch == TOKEN_WHITE || ch == TOKEN_EOL || ch == TOKEN_FATAL)) {
725         return (lex->tok.ttype = ch);
726     }
727
728     if (lex->eof)
729         return (lex->tok.ttype = TOKEN_FATAL);
730
731     if (ch == EOF) {
732         lex->eof = true;
733         return (lex->tok.ttype = TOKEN_EOF);
734     }
735
736     /* modelgen / spiritgen commands */
737     if (ch == '$') {
738         const char *v;
739         size_t frame;
740
741         ch = lex_getch(lex);
742         if (!isident_start(ch)) {
743             lexerror(lex, "hanging '$' modelgen/spritegen command line");
744             return lex_do(lex);
745         }
746         lex_tokench(lex, ch);
747         if (!lex_finish_ident(lex))
748             return (lex->tok.ttype = TOKEN_ERROR);
749         lex_endtoken(lex);
750         /* skip the known commands */
751         v = lex->tok.value;
752
753         if (!strcmp(v, "frame") || !strcmp(v, "framesave"))
754         {
755             /* frame/framesave command works like an enum
756              * similar to fteqcc we handle this in the lexer.
757              * The reason for this is that it is sensitive to newlines,
758              * which the parser is unaware of
759              */
760             if (!lex_finish_frames(lex))
761                  return (lex->tok.ttype = TOKEN_ERROR);
762             return lex_do(lex);
763         }
764
765         if (!strcmp(v, "framevalue"))
766         {
767             ch = lex_getch(lex);
768             while (ch != EOF && isspace(ch) && ch != '\n')
769                 ch = lex_getch(lex);
770
771             if (!isdigit(ch)) {
772                 lexerror(lex, "$framevalue requires an integer parameter");
773                 return lex_do(lex);
774             }
775
776             lex_token_new(lex);
777             lex->tok.ttype = lex_finish_digit(lex, ch);
778             lex_endtoken(lex);
779             if (lex->tok.ttype != TOKEN_INTCONST) {
780                 lexerror(lex, "$framevalue requires an integer parameter");
781                 return lex_do(lex);
782             }
783             lex->framevalue = lex->tok.constval.i;
784             return lex_do(lex);
785         }
786
787         if (!strcmp(v, "framerestore"))
788         {
789             int rc;
790
791             lex_token_new(lex);
792
793             rc = lex_parse_frame(lex);
794
795             if (rc > 0) {
796                 lexerror(lex, "$framerestore requires a framename parameter");
797                 return lex_do(lex);
798             }
799             if (rc < 0)
800                 return (lex->tok.ttype = TOKEN_FATAL);
801
802             v = lex->tok.value;
803             for (frame = 0; frame < vec_size(lex->frames); ++frame) {
804                 if (!strcmp(v, lex->frames[frame].name)) {
805                     lex->framevalue = lex->frames[frame].value;
806                     return lex_do(lex);
807                 }
808             }
809             lexerror(lex, "unknown framename `%s`", v);
810             return lex_do(lex);
811         }
812
813         if (!strcmp(v, "modelname"))
814         {
815             int rc;
816
817             lex_token_new(lex);
818
819             rc = lex_parse_frame(lex);
820
821             if (rc > 0) {
822                 lexerror(lex, "$modelname requires a parameter");
823                 return lex_do(lex);
824             }
825             if (rc < 0)
826                 return (lex->tok.ttype = TOKEN_FATAL);
827
828             v = lex->tok.value;
829             if (lex->modelname) {
830                 frame_macro m;
831                 m.value = lex->framevalue;
832                 m.name = lex->modelname;
833                 lex->modelname = NULL;
834                 vec_push(lex->frames, m);
835             }
836             lex->modelname = lex->tok.value;
837             lex->tok.value = NULL;
838             return lex_do(lex);
839         }
840
841         if (!strcmp(v, "flush"))
842         {
843             size_t frame;
844             for (frame = 0; frame < vec_size(lex->frames); ++frame)
845                 mem_d(lex->frames[frame].name);
846             vec_free(lex->frames);
847             /* skip line (fteqcc does it too) */
848             ch = lex_getch(lex);
849             while (ch != EOF && ch != '\n')
850                 ch = lex_getch(lex);
851             return lex_do(lex);
852         }
853
854         if (!strcmp(v, "cd") ||
855             !strcmp(v, "origin") ||
856             !strcmp(v, "base") ||
857             !strcmp(v, "flags") ||
858             !strcmp(v, "scale") ||
859             !strcmp(v, "skin"))
860         {
861             /* skip line */
862             ch = lex_getch(lex);
863             while (ch != EOF && ch != '\n')
864                 ch = lex_getch(lex);
865             return lex_do(lex);
866         }
867
868         for (frame = 0; frame < vec_size(lex->frames); ++frame) {
869             if (!strcmp(v, lex->frames[frame].name)) {
870                 lex->tok.constval.i = lex->frames[frame].value;
871                 return (lex->tok.ttype = TOKEN_INTCONST);
872             }
873         }
874
875         lexerror(lex, "invalid frame macro");
876         return lex_do(lex);
877     }
878
879     /* single-character tokens */
880     switch (ch)
881     {
882         case '[':
883         case '(':
884             lex_tokench(lex, ch);
885             lex_endtoken(lex);
886             if (lex->flags.noops)
887                 return (lex->tok.ttype = ch);
888             else
889                 return (lex->tok.ttype = TOKEN_OPERATOR);
890         case ')':
891         case ';':
892         case ':':
893         case '{':
894         case '}':
895         case ']':
896
897         case '#':
898             lex_tokench(lex, ch);
899             lex_endtoken(lex);
900             return (lex->tok.ttype = ch);
901         default:
902             break;
903     }
904
905     if (lex->flags.noops)
906     {
907         /* Detect characters early which are normally
908          * operators OR PART of an operator.
909          */
910         switch (ch)
911         {
912             case '+':
913             case '-':
914             case '*':
915             case '/':
916             case '<':
917             case '>':
918             case '=':
919             case '&':
920             case '|':
921             case '^':
922             case '~':
923             case ',':
924             case '!':
925                 lex_tokench(lex, ch);
926                 lex_endtoken(lex);
927                 return (lex->tok.ttype = ch);
928             default:
929                 break;
930         }
931
932         if (ch == '.')
933         {
934             lex_tokench(lex, ch);
935             /* peak ahead once */
936             nextch = lex_getch(lex);
937             if (nextch != '.') {
938                 lex_ungetch(lex, nextch);
939                 lex_endtoken(lex);
940                 return (lex->tok.ttype = ch);
941             }
942             /* peak ahead again */
943             nextch = lex_getch(lex);
944             if (nextch != '.') {
945                 lex_ungetch(lex, nextch);
946                 lex_ungetch(lex, nextch);
947                 lex_endtoken(lex);
948                 return (lex->tok.ttype = ch);
949             }
950             /* fill the token to be "..." */
951             lex_tokench(lex, ch);
952             lex_tokench(lex, ch);
953             lex_endtoken(lex);
954             return (lex->tok.ttype = TOKEN_DOTS);
955         }
956     }
957
958     if (ch == ',' || ch == '.') {
959         lex_tokench(lex, ch);
960         lex_endtoken(lex);
961         return (lex->tok.ttype = TOKEN_OPERATOR);
962     }
963
964     if (ch == '+' || ch == '-' || /* ++, --, +=, -=  and -> as well! */
965         ch == '>' || ch == '<' || /* <<, >>, <=, >= */
966         ch == '=' || ch == '!' || /* ==, != */
967         ch == '&' || ch == '|')   /* &&, ||, &=, |= */
968     {
969         lex_tokench(lex, ch);
970
971         nextch = lex_getch(lex);
972         if (nextch == ch || nextch == '=') {
973             lex_tokench(lex, nextch);
974         } else if (ch == '-' && nextch == '>') {
975             lex_tokench(lex, nextch);
976         } else
977             lex_ungetch(lex, nextch);
978
979         lex_endtoken(lex);
980         return (lex->tok.ttype = TOKEN_OPERATOR);
981     }
982
983     /*
984     if (ch == '^' || ch == '~' || ch == '!')
985     {
986         lex_tokench(lex, ch);
987         lex_endtoken(lex);
988         return (lex->tok.ttype = TOKEN_OPERATOR);
989     }
990     */
991
992     if (ch == '*' || ch == '/') /* *=, /= */
993     {
994         lex_tokench(lex, ch);
995
996         nextch = lex_getch(lex);
997         if (nextch == '=') {
998             lex_tokench(lex, nextch);
999         } else
1000             lex_ungetch(lex, nextch);
1001
1002         lex_endtoken(lex);
1003         return (lex->tok.ttype = TOKEN_OPERATOR);
1004     }
1005
1006     if (isident_start(ch))
1007     {
1008         const char *v;
1009
1010         lex_tokench(lex, ch);
1011         if (!lex_finish_ident(lex)) {
1012             /* error? */
1013             return (lex->tok.ttype = TOKEN_ERROR);
1014         }
1015         lex_endtoken(lex);
1016         lex->tok.ttype = TOKEN_IDENT;
1017
1018         v = lex->tok.value;
1019         if (!strcmp(v, "void")) {
1020             lex->tok.ttype = TOKEN_TYPENAME;
1021             lex->tok.constval.t = TYPE_VOID;
1022         } else if (!strcmp(v, "int")) {
1023             lex->tok.ttype = TOKEN_TYPENAME;
1024             lex->tok.constval.t = TYPE_INTEGER;
1025         } else if (!strcmp(v, "float")) {
1026             lex->tok.ttype = TOKEN_TYPENAME;
1027             lex->tok.constval.t = TYPE_FLOAT;
1028         } else if (!strcmp(v, "string")) {
1029             lex->tok.ttype = TOKEN_TYPENAME;
1030             lex->tok.constval.t = TYPE_STRING;
1031         } else if (!strcmp(v, "entity")) {
1032             lex->tok.ttype = TOKEN_TYPENAME;
1033             lex->tok.constval.t = TYPE_ENTITY;
1034         } else if (!strcmp(v, "vector")) {
1035             lex->tok.ttype = TOKEN_TYPENAME;
1036             lex->tok.constval.t = TYPE_VECTOR;
1037         } else if (!strcmp(v, "for")  ||
1038                  !strcmp(v, "while")  ||
1039                  !strcmp(v, "do")     ||
1040                  !strcmp(v, "if")     ||
1041                  !strcmp(v, "else")   ||
1042                  !strcmp(v, "local")  ||
1043                  !strcmp(v, "return") ||
1044                  !strcmp(v, "not")    ||
1045                  !strcmp(v, "const"))
1046         {
1047             lex->tok.ttype = TOKEN_KEYWORD;
1048         }
1049         else if (opts_standard != COMPILER_QCC)
1050         {
1051             /* other standards reserve these keywords */
1052             if (!strcmp(v, "switch") ||
1053                 !strcmp(v, "struct") ||
1054                 !strcmp(v, "union")  ||
1055                 !strcmp(v, "break")  ||
1056                 !strcmp(v, "continue") ||
1057                 !strcmp(v, "var"))
1058             {
1059                 lex->tok.ttype = TOKEN_KEYWORD;
1060             }
1061         }
1062
1063         return lex->tok.ttype;
1064     }
1065
1066     if (ch == '"')
1067     {
1068         lex->flags.nodigraphs = true;
1069         if (lex->flags.preprocessing)
1070             lex_tokench(lex, ch);
1071         lex->tok.ttype = lex_finish_string(lex, '"');
1072         if (lex->flags.preprocessing)
1073             lex_tokench(lex, ch);
1074         while (!lex->flags.preprocessing && lex->tok.ttype == TOKEN_STRINGCONST)
1075         {
1076             /* Allow c style "string" "continuation" */
1077             ch = lex_skipwhite(lex);
1078             if (ch != '"') {
1079                 lex_ungetch(lex, ch);
1080                 break;
1081             }
1082
1083             lex->tok.ttype = lex_finish_string(lex, '"');
1084         }
1085         lex->flags.nodigraphs = false;
1086         lex_endtoken(lex);
1087         return lex->tok.ttype;
1088     }
1089
1090     if (ch == '\'')
1091     {
1092         /* we parse character constants like string,
1093          * but return TOKEN_CHARCONST, or a vector type if it fits...
1094          * Likewise actual unescaping has to be done by the parser.
1095          * The difference is we don't allow 'char' 'continuation'.
1096          */
1097         if (lex->flags.preprocessing)
1098             lex_tokench(lex, ch);
1099         lex->tok.ttype = lex_finish_string(lex, '\'');
1100         if (lex->flags.preprocessing)
1101             lex_tokench(lex, ch);
1102         lex_endtoken(lex);
1103
1104          /* It's a vector if we can successfully scan 3 floats */
1105 #ifdef WIN32
1106         if (sscanf_s(lex->tok.value, " %f %f %f ",
1107                    &lex->tok.constval.v.x, &lex->tok.constval.v.y, &lex->tok.constval.v.z) == 3)
1108 #else
1109         if (sscanf(lex->tok.value, " %f %f %f ",
1110                    &lex->tok.constval.v.x, &lex->tok.constval.v.y, &lex->tok.constval.v.z) == 3)
1111 #endif
1112
1113         {
1114              lex->tok.ttype = TOKEN_VECTORCONST;
1115         }
1116
1117         return lex->tok.ttype;
1118     }
1119
1120     if (isdigit(ch))
1121     {
1122         lex->tok.ttype = lex_finish_digit(lex, ch);
1123         lex_endtoken(lex);
1124         return lex->tok.ttype;
1125     }
1126
1127     lexerror(lex, "unknown token");
1128     return (lex->tok.ttype = TOKEN_ERROR);
1129 }