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