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