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