]> git.xonotic.org Git - xonotic/gmqcc.git/blob - lexer.c
Fix indentation of lexer.c - was still using some tabs there
[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             if (!lex_tokench(lex, ch) ||
706                 !lex_endtoken(lex))
707             {
708                 return (lex->tok->ttype = TOKEN_FATAL);
709             }
710             if (lex->flags.noops)
711                 return (lex->tok->ttype = ch);
712             else
713                 return (lex->tok->ttype = TOKEN_OPERATOR);
714         case ')':
715         case ';':
716         case '{':
717         case '}':
718         case '[':
719         case ']':
720
721         case '#':
722             if (!lex_tokench(lex, ch) ||
723                 !lex_endtoken(lex))
724             {
725                 return (lex->tok->ttype = TOKEN_FATAL);
726             }
727             return (lex->tok->ttype = ch);
728         default:
729             break;
730     }
731
732     if (lex->flags.noops)
733     {
734         /* Detect characters early which are normally
735          * operators OR PART of an operator.
736          */
737         switch (ch)
738         {
739             case '+':
740             case '-':
741             case '*':
742             case '/':
743             case '<':
744             case '>':
745             case '=':
746             case '&':
747             case '|':
748             case '^':
749             case '~':
750             case ',':
751             case '.':
752             case '!':
753                 if (!lex_tokench(lex, ch) ||
754                     !lex_endtoken(lex))
755                 {
756                     return (lex->tok->ttype = TOKEN_FATAL);
757                 }
758                 return (lex->tok->ttype = ch);
759             default:
760                 break;
761         }
762     }
763
764     if (ch == ',' || ch == '.') {
765         if (!lex_tokench(lex, ch) ||
766             !lex_endtoken(lex))
767         {
768             return (lex->tok->ttype = TOKEN_FATAL);
769         }
770         return (lex->tok->ttype = TOKEN_OPERATOR);
771     }
772
773     if (ch == '+' || ch == '-' || /* ++, --, +=, -=  and -> as well! */
774         ch == '>' || ch == '<' || /* <<, >>, <=, >= */
775         ch == '=' || ch == '!' || /* ==, != */
776         ch == '&' || ch == '|')   /* &&, ||, &=, |= */
777     {
778         if (!lex_tokench(lex, ch))
779             return (lex->tok->ttype = TOKEN_FATAL);
780
781         nextch = lex_getch(lex);
782         if (nextch == ch || nextch == '=') {
783             if (!lex_tokench(lex, nextch))
784                 return (lex->tok->ttype = TOKEN_FATAL);
785         } else if (ch == '-' && nextch == '>') {
786             if (!lex_tokench(lex, nextch))
787                 return (lex->tok->ttype = TOKEN_FATAL);
788         } else
789             lex_ungetch(lex, nextch);
790
791         if (!lex_endtoken(lex))
792             return (lex->tok->ttype = TOKEN_FATAL);
793         return (lex->tok->ttype = TOKEN_OPERATOR);
794     }
795
796     /*
797     if (ch == '^' || ch == '~' || ch == '!')
798     {
799         if (!lex_tokench(lex, ch) ||
800             !lex_endtoken(lex))
801         {
802             return (lex->tok->ttype = TOKEN_FATAL);
803         }
804         return (lex->tok->ttype = TOKEN_OPERATOR);
805     }
806     */
807
808     if (ch == '*' || ch == '/') /* *=, /= */
809     {
810         if (!lex_tokench(lex, ch))
811             return (lex->tok->ttype = TOKEN_FATAL);
812
813         nextch = lex_getch(lex);
814         if (nextch == '=') {
815             if (!lex_tokench(lex, nextch))
816                 return (lex->tok->ttype = TOKEN_FATAL);
817         } else
818             lex_ungetch(lex, nextch);
819
820         if (!lex_endtoken(lex))
821             return (lex->tok->ttype = TOKEN_FATAL);
822         return (lex->tok->ttype = TOKEN_OPERATOR);
823     }
824
825     if (isident_start(ch))
826     {
827         const char *v;
828
829         if (!lex_tokench(lex, ch))
830             return (lex->tok->ttype = TOKEN_FATAL);
831         if (!lex_finish_ident(lex)) {
832             /* error? */
833             return (lex->tok->ttype = TOKEN_ERROR);
834         }
835         if (!lex_endtoken(lex))
836             return (lex->tok->ttype = TOKEN_FATAL);
837         lex->tok->ttype = TOKEN_IDENT;
838
839         v = lex->tok->value;
840         if (!strcmp(v, "void")) {
841             lex->tok->ttype = TOKEN_TYPENAME;
842             lex->tok->constval.t = TYPE_VOID;
843         } else if (!strcmp(v, "int")) {
844             lex->tok->ttype = TOKEN_TYPENAME;
845             lex->tok->constval.t = TYPE_INTEGER;
846         } else if (!strcmp(v, "float")) {
847             lex->tok->ttype = TOKEN_TYPENAME;
848             lex->tok->constval.t = TYPE_FLOAT;
849         } else if (!strcmp(v, "string")) {
850             lex->tok->ttype = TOKEN_TYPENAME;
851             lex->tok->constval.t = TYPE_STRING;
852         } else if (!strcmp(v, "entity")) {
853             lex->tok->ttype = TOKEN_TYPENAME;
854             lex->tok->constval.t = TYPE_ENTITY;
855         } else if (!strcmp(v, "vector")) {
856             lex->tok->ttype = TOKEN_TYPENAME;
857             lex->tok->constval.t = TYPE_VECTOR;
858         } else if (!strcmp(v, "for")  ||
859                  !strcmp(v, "while")  ||
860                  !strcmp(v, "do")     ||
861                  !strcmp(v, "if")     ||
862                  !strcmp(v, "else")   ||
863                  !strcmp(v, "local")  ||
864                  !strcmp(v, "return") ||
865                  !strcmp(v, "const"))
866             lex->tok->ttype = TOKEN_KEYWORD;
867
868         return lex->tok->ttype;
869     }
870
871     if (ch == '"')
872     {
873         lex->tok->ttype = lex_finish_string(lex, '"');
874         while (lex->tok->ttype == TOKEN_STRINGCONST)
875         {
876             /* Allow c style "string" "continuation" */
877             ch = lex_skipwhite(lex);
878             if (ch != '"') {
879                 lex_ungetch(lex, ch);
880                 break;
881             }
882
883             lex->tok->ttype = lex_finish_string(lex, '"');
884         }
885         if (!lex_endtoken(lex))
886             return (lex->tok->ttype = TOKEN_FATAL);
887         return lex->tok->ttype;
888     }
889
890     if (ch == '\'')
891     {
892         /* we parse character constants like string,
893          * but return TOKEN_CHARCONST, or a vector type if it fits...
894          * Likewise actual unescaping has to be done by the parser.
895          * The difference is we don't allow 'char' 'continuation'.
896          */
897          lex->tok->ttype = lex_finish_string(lex, '\'');
898          if (!lex_endtoken(lex))
899               return (lex->tok->ttype = TOKEN_FATAL);
900
901          /* It's a vector if we can successfully scan 3 floats */
902 #ifdef WIN32
903          if (sscanf_s(lex->tok->value, " %f %f %f ",
904                     &lex->tok->constval.v.x, &lex->tok->constval.v.y, &lex->tok->constval.v.z) == 3)
905 #else
906          if (sscanf(lex->tok->value, " %f %f %f ",
907                     &lex->tok->constval.v.x, &lex->tok->constval.v.y, &lex->tok->constval.v.z) == 3)
908 #endif
909          {
910               lex->tok->ttype = TOKEN_VECTORCONST;
911          }
912
913          return lex->tok->ttype;
914     }
915
916     if (isdigit(ch))
917     {
918         lex->tok->ttype = lex_finish_digit(lex, ch);
919         if (!lex_endtoken(lex))
920             return (lex->tok->ttype = TOKEN_FATAL);
921         return lex->tok->ttype;
922     }
923
924     lexerror(lex, "unknown token");
925     return (lex->tok->ttype = TOKEN_ERROR);
926 }