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