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