]> de.git.xonotic.org Git - xonotic/gmqcc.git/blob - lexer.c
lexer now turns '(' into an operator if noops=false
[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 }