]> de.git.xonotic.org Git - xonotic/gmqcc.git/blob - lexer.c
Oh my god microsoft you suck
[xonotic/gmqcc.git] / lexer.c
1 /*
2  * Copyright (C) 2012, 2013
3  *     Wolfgang Bumiller
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a copy of
6  * this software and associated documentation files (the "Software"), to deal in
7  * the Software without restriction, including without limitation the rights to
8  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9  * of the Software, and to permit persons to whom the Software is furnished to do
10  * so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in all
13  * copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21  * SOFTWARE.
22  */
23 #include <string.h>
24 #include <stdlib.h>
25
26 #include "gmqcc.h"
27 #include "lexer.h"
28 /*
29  * List of Keywords
30  */
31
32 /* original */
33 static const char *keywords_qc[] = {
34     "for", "do", "while",
35     "if", "else",
36     "local",
37     "return",
38     "const"
39 };
40 static size_t num_keywords_qc = sizeof(keywords_qc) / sizeof(keywords_qc[0]);
41
42 /* For fte/gmgqcc */
43 static const char *keywords_fg[] = {
44     "switch", "case", "default",
45     "struct", "union",
46     "break", "continue",
47     "typedef",
48     "goto",
49
50     "__builtin_debug_printtype"
51 };
52 static size_t num_keywords_fg = sizeof(keywords_fg) / sizeof(keywords_fg[0]);
53
54 /*
55  * Lexer code
56  */
57
58 static char* *lex_filenames;
59
60 static void lexerror(lex_file *lex, const char *fmt, ...)
61 {
62     va_list ap;
63
64     va_start(ap, fmt);
65     if (lex)
66         con_vprintmsg(LVL_ERROR, lex->name, lex->sline, lex->column, "parse error", fmt, ap);
67     else
68         con_vprintmsg(LVL_ERROR, "", 0, 0, "parse error", fmt, ap);
69     va_end(ap);
70 }
71
72 static bool lexwarn(lex_file *lex, int warntype, const char *fmt, ...)
73 {
74     bool    r;
75     lex_ctx ctx;
76     va_list ap;
77
78     ctx.file = lex->name;
79     ctx.line = lex->sline;
80
81     va_start(ap, fmt);
82     r = vcompile_warning(ctx, warntype, fmt, ap);
83     va_end(ap);
84     return r;
85 }
86
87
88 #if 0
89 token* token_new()
90 {
91     token *tok = (token*)mem_a(sizeof(token));
92     if (!tok)
93         return NULL;
94     memset(tok, 0, sizeof(*tok));
95     return tok;
96 }
97
98 void token_delete(token *self)
99 {
100     if (self->next && self->next->prev == self)
101         self->next->prev = self->prev;
102     if (self->prev && self->prev->next == self)
103         self->prev->next = self->next;
104     MEM_VECTOR_CLEAR(self, value);
105     mem_d(self);
106 }
107
108 token* token_copy(const token *cp)
109 {
110     token* self = token_new();
111     if (!self)
112         return NULL;
113     /* copy the value */
114     self->value_alloc = cp->value_count + 1;
115     self->value_count = cp->value_count;
116     self->value = (char*)mem_a(self->value_alloc);
117     if (!self->value) {
118         mem_d(self);
119         return NULL;
120     }
121     memcpy(self->value, cp->value, cp->value_count);
122     self->value[self->value_alloc-1] = 0;
123
124     /* rest */
125     self->ctx = cp->ctx;
126     self->ttype = cp->ttype;
127     memcpy(&self->constval, &cp->constval, sizeof(self->constval));
128     return self;
129 }
130
131 void token_delete_all(token *t)
132 {
133     token *n;
134
135     do {
136         n = t->next;
137         token_delete(t);
138         t = n;
139     } while(t);
140 }
141
142 token* token_copy_all(const token *cp)
143 {
144     token *cur;
145     token *out;
146
147     out = cur = token_copy(cp);
148     if (!out)
149         return NULL;
150
151     while (cp->next) {
152         cp = cp->next;
153         cur->next = token_copy(cp);
154         if (!cur->next) {
155             token_delete_all(out);
156             return NULL;
157         }
158         cur->next->prev = cur;
159         cur = cur->next;
160     }
161
162     return out;
163 }
164 #else
165 static void lex_token_new(lex_file *lex)
166 {
167 #if 0
168     if (lex->tok)
169         token_delete(lex->tok);
170     lex->tok = token_new();
171 #else
172     if (lex->tok.value)
173         vec_shrinkto(lex->tok.value, 0);
174
175     lex->tok.constval.t  = 0;
176     lex->tok.ctx.line    = lex->sline;
177     lex->tok.ctx.file    = lex->name;
178     lex->tok.ctx.column  = lex->column;
179 #endif
180 }
181 #endif
182
183 lex_file* lex_open(const char *file)
184 {
185     lex_file *lex;
186     FILE *in = fs_file_open(file, "rb");
187
188     if (!in) {
189         lexerror(NULL, "open failed: '%s'\n", file);
190         return NULL;
191     }
192
193     lex = (lex_file*)mem_a(sizeof(*lex));
194     if (!lex) {
195         fs_file_close(in);
196         lexerror(NULL, "out of memory\n");
197         return NULL;
198     }
199
200     memset(lex, 0, sizeof(*lex));
201
202     lex->file    = in;
203     lex->name    = util_strdup(file);
204     lex->line    = 1; /* we start counting at 1 */
205     lex->column  = 0;
206     lex->peekpos = 0;
207     lex->eof     = false;
208
209     vec_push(lex_filenames, lex->name);
210     return lex;
211 }
212
213 lex_file* lex_open_string(const char *str, size_t len, const char *name)
214 {
215     lex_file *lex;
216
217     lex = (lex_file*)mem_a(sizeof(*lex));
218     if (!lex) {
219         lexerror(NULL, "out of memory\n");
220         return NULL;
221     }
222
223     memset(lex, 0, sizeof(*lex));
224
225     lex->file = NULL;
226     lex->open_string        = str;
227     lex->open_string_length = len;
228     lex->open_string_pos    = 0;
229
230     lex->name    = util_strdup(name ? name : "<string-source>");
231     lex->line    = 1; /* we start counting at 1 */
232     lex->peekpos = 0;
233     lex->eof     = false;
234     lex->column  = 0;
235
236     vec_push(lex_filenames, lex->name);
237
238     return lex;
239 }
240
241 void lex_cleanup(void)
242 {
243     size_t i;
244     for (i = 0; i < vec_size(lex_filenames); ++i)
245         mem_d(lex_filenames[i]);
246     vec_free(lex_filenames);
247 }
248
249 void lex_close(lex_file *lex)
250 {
251     size_t i;
252     for (i = 0; i < vec_size(lex->frames); ++i)
253         mem_d(lex->frames[i].name);
254     vec_free(lex->frames);
255
256     if (lex->modelname)
257         vec_free(lex->modelname);
258
259     if (lex->file)
260         fs_file_close(lex->file);
261 #if 0
262     if (lex->tok)
263         token_delete(lex->tok);
264 #else
265     vec_free(lex->tok.value);
266 #endif
267     /* mem_d(lex->name); collected in lex_filenames */
268     mem_d(lex);
269 }
270
271 static int lex_fgetc(lex_file *lex)
272 {
273     if (lex->file) {
274         lex->column++;
275         return fs_file_getc(lex->file);
276     }
277     if (lex->open_string) {
278         if (lex->open_string_pos >= lex->open_string_length)
279             return EOF;
280         lex->column++;
281         return lex->open_string[lex->open_string_pos++];
282     }
283     return EOF;
284 }
285
286 /* Get or put-back data
287  * The following to functions do NOT understand what kind of data they
288  * are working on.
289  * The are merely wrapping get/put in order to count line numbers.
290  */
291 static void lex_ungetch(lex_file *lex, int ch);
292 static int lex_try_trigraph(lex_file *lex, int old)
293 {
294     int c2, c3;
295     c2 = lex_fgetc(lex);
296     if (!lex->push_line && c2 == '\n') {
297         lex->line++;
298         lex->column = 0;
299     }
300
301     if (c2 != '?') {
302         lex_ungetch(lex, c2);
303         return old;
304     }
305
306     c3 = lex_fgetc(lex);
307     if (!lex->push_line && c3 == '\n') {
308         lex->line++;
309         lex->column = 0;
310     }
311
312     switch (c3) {
313         case '=': return '#';
314         case '/': return '\\';
315         case '\'': return '^';
316         case '(': return '[';
317         case ')': return ']';
318         case '!': return '|';
319         case '<': return '{';
320         case '>': return '}';
321         case '-': return '~';
322         default:
323             lex_ungetch(lex, c3);
324             lex_ungetch(lex, c2);
325             return old;
326     }
327 }
328
329 static int lex_try_digraph(lex_file *lex, int ch)
330 {
331     int c2;
332     c2 = lex_fgetc(lex);
333     /* we just used fgetc() so count lines
334      * need to offset a \n the ungetch would recognize
335      */
336     if (!lex->push_line && c2 == '\n')
337         lex->line++;
338     if      (ch == '<' && c2 == ':')
339         return '[';
340     else if (ch == ':' && c2 == '>')
341         return ']';
342     else if (ch == '<' && c2 == '%')
343         return '{';
344     else if (ch == '%' && c2 == '>')
345         return '}';
346     else if (ch == '%' && c2 == ':')
347         return '#';
348     lex_ungetch(lex, c2);
349     return ch;
350 }
351
352 static int lex_getch(lex_file *lex)
353 {
354     int ch;
355
356     if (lex->peekpos) {
357         lex->peekpos--;
358         if (!lex->push_line && lex->peek[lex->peekpos] == '\n')
359             lex->line++;
360         return lex->peek[lex->peekpos];
361     }
362
363     ch = lex_fgetc(lex);
364     if (!lex->push_line && ch == '\n')
365         lex->line++;
366     else if (ch == '?')
367         return lex_try_trigraph(lex, ch);
368     else if (!lex->flags.nodigraphs && (ch == '<' || ch == ':' || ch == '%'))
369         return lex_try_digraph(lex, ch);
370     return ch;
371 }
372
373 static void lex_ungetch(lex_file *lex, int ch)
374 {
375     lex->peek[lex->peekpos++] = ch;
376     lex->column--;
377     if (!lex->push_line && ch == '\n') {
378         lex->line--;
379         lex->column = 0;
380     }
381 }
382
383 /* classify characters
384  * some additions to the is*() functions of ctype.h
385  */
386
387 /* Idents are alphanumberic, but they start with alpha or _ */
388 static bool isident_start(int ch)
389 {
390     return util_isalpha(ch) || ch == '_';
391 }
392
393 static bool isident(int ch)
394 {
395     return isident_start(ch) || util_isdigit(ch);
396 }
397
398 /* isxdigit_only is used when we already know it's not a digit
399  * and want to see if it's a hex digit anyway.
400  */
401 static bool isxdigit_only(int ch)
402 {
403     return (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F');
404 }
405
406 /* Append a character to the token buffer */
407 static void lex_tokench(lex_file *lex, int ch)
408 {
409     vec_push(lex->tok.value, ch);
410 }
411
412 /* Append a trailing null-byte */
413 static void lex_endtoken(lex_file *lex)
414 {
415     vec_push(lex->tok.value, 0);
416     vec_shrinkby(lex->tok.value, 1);
417 }
418
419 static bool lex_try_pragma(lex_file *lex)
420 {
421     int ch;
422     char *pragma  = NULL;
423     char *command = NULL;
424     char *param   = NULL;
425     size_t line;
426
427     if (lex->flags.preprocessing)
428         return false;
429
430     line = lex->line;
431
432     ch = lex_getch(lex);
433     if (ch != '#') {
434         lex_ungetch(lex, ch);
435         return false;
436     }
437
438     for (ch = lex_getch(lex); vec_size(pragma) < 8 && ch >= 'a' && ch <= 'z'; ch = lex_getch(lex))
439         vec_push(pragma, ch);
440     vec_push(pragma, 0);
441
442     if (ch != ' ' || strcmp(pragma, "pragma")) {
443         lex_ungetch(lex, ch);
444         goto unroll;
445     }
446
447     for (ch = lex_getch(lex); vec_size(command) < 32 && ch >= 'a' && ch <= 'z'; ch = lex_getch(lex))
448         vec_push(command, ch);
449     vec_push(command, 0);
450
451     if (ch != '(') {
452         lex_ungetch(lex, ch);
453         goto unroll;
454     }
455
456     for (ch = lex_getch(lex); vec_size(param) < 1024 && ch != ')' && ch != '\n'; ch = lex_getch(lex))
457         vec_push(param, ch);
458     vec_push(param, 0);
459
460     if (ch != ')') {
461         lex_ungetch(lex, ch);
462         goto unroll;
463     }
464
465     if (!strcmp(command, "push")) {
466         if (!strcmp(param, "line")) {
467             lex->push_line++;
468             if (lex->push_line == 1)
469                 --line;
470         }
471         else
472             goto unroll;
473     }
474     else if (!strcmp(command, "pop")) {
475         if (!strcmp(param, "line")) {
476             if (lex->push_line)
477                 lex->push_line--;
478             if (lex->push_line == 0)
479                 --line;
480         }
481         else
482             goto unroll;
483     }
484     else if (!strcmp(command, "file")) {
485         lex->name = util_strdup(param);
486         vec_push(lex_filenames, lex->name);
487     }
488     else if (!strcmp(command, "line")) {
489         line = strtol(param, NULL, 0)-1;
490     }
491     else
492         goto unroll;
493
494     lex->line = line;
495     while (ch != '\n' && ch != EOF)
496         ch = lex_getch(lex);
497     vec_free(command);
498     vec_free(param);
499     vec_free(pragma);
500     return true;
501
502 unroll:
503     if (command) {
504         vec_pop(command);
505         while (vec_size(command)) {
506             lex_ungetch(lex, (unsigned char)vec_last(command));
507             vec_pop(command);
508         }
509         vec_free(command);
510         lex_ungetch(lex, ' ');
511     }
512     if (param) {
513         vec_pop(param);
514         while (vec_size(param)) {
515             lex_ungetch(lex, (unsigned char)vec_last(param));
516             vec_pop(param);
517         }
518         vec_free(param);
519         lex_ungetch(lex, ' ');
520     }
521     if (pragma) {
522         vec_pop(pragma);
523         while (vec_size(pragma)) {
524             lex_ungetch(lex, (unsigned char)vec_last(pragma));
525             vec_pop(pragma);
526         }
527         vec_free(pragma);
528     }
529     lex_ungetch(lex, '#');
530
531     lex->line = line;
532     return false;
533 }
534
535 /* Skip whitespace and comments and return the first
536  * non-white character.
537  * As this makes use of the above getch() ungetch() functions,
538  * we don't need to care at all about line numbering anymore.
539  *
540  * In theory, this function should only be used at the beginning
541  * of lexing, or when we *know* the next character is part of the token.
542  * Otherwise, if the parser throws an error, the linenumber may not be
543  * the line of the error, but the line of the next token AFTER the error.
544  *
545  * This is currently only problematic when using c-like string-continuation,
546  * since comments and whitespaces are allowed between 2 such strings.
547  * Example:
548 printf(   "line one\n"
549 // A comment
550           "A continuation of the previous string"
551 // This line is skipped
552       , foo);
553
554  * In this case, if the parse decides it didn't actually want a string,
555  * and uses lex->line to print an error, it will show the ', foo);' line's
556  * linenumber.
557  *
558  * On the other hand, the parser is supposed to remember the line of the next
559  * token's beginning. In this case we would want skipwhite() to be called
560  * AFTER reading a token, so that the parser, before reading the NEXT token,
561  * doesn't store teh *comment's* linenumber, but the actual token's linenumber.
562  *
563  * THIS SOLUTION
564  *    here is to store the line of the first character after skipping
565  *    the initial whitespace in lex->sline, this happens in lex_do.
566  */
567 static int lex_skipwhite(lex_file *lex, bool hadwhite)
568 {
569     int ch = 0;
570     bool haswhite = hadwhite;
571
572     do
573     {
574         ch = lex_getch(lex);
575         while (ch != EOF && util_isspace(ch)) {
576             if (ch == '\n') {
577                 if (lex_try_pragma(lex))
578                     continue;
579             }
580             if (lex->flags.preprocessing) {
581                 if (ch == '\n') {
582                     /* end-of-line */
583                     /* see if there was whitespace first */
584                     if (haswhite) { /* (vec_size(lex->tok.value)) { */
585                         lex_ungetch(lex, ch);
586                         lex_endtoken(lex);
587                         return TOKEN_WHITE;
588                     }
589                     /* otherwise return EOL */
590                     return TOKEN_EOL;
591                 }
592                 haswhite = true;
593                 lex_tokench(lex, ch);
594             }
595             ch = lex_getch(lex);
596         }
597
598         if (ch == '/') {
599             ch = lex_getch(lex);
600             if (ch == '/')
601             {
602                 /* one line comment */
603                 ch = lex_getch(lex);
604
605                 if (lex->flags.preprocessing) {
606                     haswhite = true;
607                     /*
608                     lex_tokench(lex, '/');
609                     lex_tokench(lex, '/');
610                     */
611                     lex_tokench(lex, ' ');
612                     lex_tokench(lex, ' ');
613                 }
614
615                 while (ch != EOF && ch != '\n') {
616                     if (lex->flags.preprocessing)
617                         lex_tokench(lex, ' '); /* ch); */
618                     ch = lex_getch(lex);
619                 }
620                 if (lex->flags.preprocessing) {
621                     lex_ungetch(lex, '\n');
622                     lex_endtoken(lex);
623                     return TOKEN_WHITE;
624                 }
625                 continue;
626             }
627             if (ch == '*')
628             {
629                 /* multiline comment */
630                 if (lex->flags.preprocessing) {
631                     haswhite = true;
632                     /*
633                     lex_tokench(lex, '/');
634                     lex_tokench(lex, '*');
635                     */
636                     lex_tokench(lex, ' ');
637                     lex_tokench(lex, ' ');
638                 }
639
640                 while (ch != EOF)
641                 {
642                     ch = lex_getch(lex);
643                     if (ch == '*') {
644                         ch = lex_getch(lex);
645                         if (ch == '/') {
646                             if (lex->flags.preprocessing) {
647                                 /*
648                                 lex_tokench(lex, '*');
649                                 lex_tokench(lex, '/');
650                                 */
651                                 lex_tokench(lex, ' ');
652                                 lex_tokench(lex, ' ');
653                             }
654                             break;
655                         }
656                         lex_ungetch(lex, ch);
657                     }
658                     if (lex->flags.preprocessing) {
659                         if (ch == '\n')
660                             lex_tokench(lex, '\n');
661                         else
662                             lex_tokench(lex, ' '); /* ch); */
663                     }
664                 }
665                 ch = ' '; /* cause TRUE in the isspace check */
666                 continue;
667             }
668             /* Otherwise roll back to the slash and break out of the loop */
669             lex_ungetch(lex, ch);
670             ch = '/';
671             break;
672         }
673     } while (ch != EOF && util_isspace(ch));
674
675     if (haswhite) {
676         lex_endtoken(lex);
677         lex_ungetch(lex, ch);
678         return TOKEN_WHITE;
679     }
680     return ch;
681 }
682
683 /* Get a token */
684 static bool GMQCC_WARN lex_finish_ident(lex_file *lex)
685 {
686     int ch;
687
688     ch = lex_getch(lex);
689     while (ch != EOF && isident(ch))
690     {
691         lex_tokench(lex, ch);
692         ch = lex_getch(lex);
693     }
694
695     /* last ch was not an ident ch: */
696     lex_ungetch(lex, ch);
697
698     return true;
699 }
700
701 /* read one ident for the frame list */
702 static int lex_parse_frame(lex_file *lex)
703 {
704     int ch;
705
706     lex_token_new(lex);
707
708     ch = lex_getch(lex);
709     while (ch != EOF && ch != '\n' && util_isspace(ch))
710         ch = lex_getch(lex);
711
712     if (ch == '\n')
713         return 1;
714
715     if (!isident_start(ch)) {
716         lexerror(lex, "invalid framename, must start with one of a-z or _, got %c", ch);
717         return -1;
718     }
719
720     lex_tokench(lex, ch);
721     if (!lex_finish_ident(lex))
722         return -1;
723     lex_endtoken(lex);
724     return 0;
725 }
726
727 /* read a list of $frames */
728 static bool lex_finish_frames(lex_file *lex)
729 {
730     do {
731         size_t i;
732         int    rc;
733         frame_macro m;
734
735         rc = lex_parse_frame(lex);
736         if (rc > 0) /* end of line */
737             return true;
738         if (rc < 0) /* error */
739             return false;
740
741         for (i = 0; i < vec_size(lex->frames); ++i) {
742             if (!strcmp(lex->tok.value, lex->frames[i].name)) {
743                 lex->frames[i].value = lex->framevalue++;
744                 if (lexwarn(lex, WARN_FRAME_MACROS, "duplicate frame macro defined: `%s`", lex->tok.value))
745                     return false;
746                 break;
747             }
748         }
749         if (i < vec_size(lex->frames))
750             continue;
751
752         m.value = lex->framevalue++;
753         m.name = util_strdup(lex->tok.value);
754         vec_shrinkto(lex->tok.value, 0);
755         vec_push(lex->frames, m);
756     } while (true);
757
758     return false;
759 }
760
761 static int GMQCC_WARN lex_finish_string(lex_file *lex, int quote)
762 {
763     uchar_t chr;
764     int ch = 0;
765     int nextch;
766     bool hex;
767     char u8buf[8]; /* way more than enough */
768     int  u8len, uc;
769
770     while (ch != EOF)
771     {
772         ch = lex_getch(lex);
773         if (ch == quote)
774             return TOKEN_STRINGCONST;
775
776         if (lex->flags.preprocessing && ch == '\\') {
777             lex_tokench(lex, ch);
778             ch = lex_getch(lex);
779             if (ch == EOF) {
780                 lexerror(lex, "unexpected end of file");
781                 lex_ungetch(lex, EOF); /* next token to be TOKEN_EOF */
782                 return (lex->tok.ttype = TOKEN_ERROR);
783             }
784             lex_tokench(lex, ch);
785         }
786         else if (ch == '\\') {
787             ch = lex_getch(lex);
788             if (ch == EOF) {
789                 lexerror(lex, "unexpected end of file");
790                 lex_ungetch(lex, EOF); /* next token to be TOKEN_EOF */
791                 return (lex->tok.ttype = TOKEN_ERROR);
792             }
793
794             switch (ch) {
795             case '\\': break;
796             case '\'': break;
797             case '"':  break;
798             case 'a':  ch = '\a'; break;
799             case 'b':  ch = '\b'; break;
800             case 'r':  ch = '\r'; break;
801             case 'n':  ch = '\n'; break;
802             case 't':  ch = '\t'; break;
803             case 'f':  ch = '\f'; break;
804             case 'v':  ch = '\v'; break;
805             case 'x':
806             case 'X':
807                 /* same procedure as in fteqcc */
808                 ch = 0;
809                 nextch = lex_getch(lex);
810                 if      (nextch >= '0' && nextch <= '9')
811                     ch += nextch - '0';
812                 else if (nextch >= 'a' && nextch <= 'f')
813                     ch += nextch - 'a' + 10;
814                 else if (nextch >= 'A' && nextch <= 'F')
815                     ch += nextch - 'A' + 10;
816                 else {
817                     lexerror(lex, "bad character code");
818                     lex_ungetch(lex, nextch);
819                     return (lex->tok.ttype = TOKEN_ERROR);
820                 }
821
822                 ch *= 0x10;
823                 nextch = lex_getch(lex);
824                 if      (nextch >= '0' && nextch <= '9')
825                     ch += nextch - '0';
826                 else if (nextch >= 'a' && nextch <= 'f')
827                     ch += nextch - 'a' + 10;
828                 else if (nextch >= 'A' && nextch <= 'F')
829                     ch += nextch - 'A' + 10;
830                 else {
831                     lexerror(lex, "bad character code");
832                     lex_ungetch(lex, nextch);
833                     return (lex->tok.ttype = TOKEN_ERROR);
834                 }
835                 break;
836
837             /* fteqcc support */
838             case '0': case '1': case '2': case '3':
839             case '4': case '5': case '6': case '7':
840             case '8': case '9':
841                 ch = 18 + ch - '0';
842                 break;
843             case '<':  ch = 29; break;
844             case '-':  ch = 30; break;
845             case '>':  ch = 31; break;
846             case '[':  ch = 16; break;
847             case ']':  ch = 17; break;
848             case '{':
849                 chr = 0;
850                 nextch = lex_getch(lex);
851                 hex = (nextch == 'x');
852                 if (!hex)
853                     lex_ungetch(lex, nextch);
854                 for (nextch = lex_getch(lex); nextch != '}'; nextch = lex_getch(lex)) {
855                     if (!hex) {
856                         if (nextch >= '0' && nextch <= '9')
857                             chr = chr * 10 + nextch - '0';
858                         else {
859                             lexerror(lex, "bad character code");
860                             return (lex->tok.ttype = TOKEN_ERROR);
861                         }
862                     } else {
863                         if (nextch >= '0' && nextch <= '9')
864                             chr = chr * 0x10 + nextch - '0';
865                         else if (nextch >= 'a' && nextch <= 'f')
866                             chr = chr * 0x10 + nextch - 'a' + 10;
867                         else if (nextch >= 'A' && nextch <= 'F')
868                             chr = chr * 0x10 + nextch - 'A' + 10;
869                         else {
870                             lexerror(lex, "bad character code");
871                             return (lex->tok.ttype = TOKEN_ERROR);
872                         }
873                     }
874                     if (chr > 0x10FFFF || (!OPTS_FLAG(UTF8) && chr > 255))
875                     {
876                         lexerror(lex, "character code out of range");
877                         return (lex->tok.ttype = TOKEN_ERROR);
878                     }
879                 }
880                 if (OPTS_FLAG(UTF8) && chr >= 128) {
881                     u8len = u8_fromchar(chr, u8buf, sizeof(u8buf));
882                     if (!u8len)
883                         ch = 0;
884                     else {
885                         --u8len;
886                         lex->column += u8len;
887                         for (uc = 0; uc < u8len; ++uc)
888                             lex_tokench(lex, u8buf[uc]);
889                         /* the last character will be inserted with the tokench() call
890                          * below the switch
891                          */
892                         ch = u8buf[uc];
893                     }
894                 }
895                 else
896                     ch = chr;
897                 break;
898             case '\n':  ch = '\n'; break;
899
900             default:
901                 lexwarn(lex, WARN_UNKNOWN_CONTROL_SEQUENCE, "unrecognized control sequence: \\%c", ch);
902                 /* so we just add the character plus backslash no matter what it actually is */
903                 lex_tokench(lex, '\\');
904             }
905             /* add the character finally */
906             lex_tokench(lex, ch);
907         }
908         else
909             lex_tokench(lex, ch);
910     }
911     lexerror(lex, "unexpected end of file within string constant");
912     lex_ungetch(lex, EOF); /* next token to be TOKEN_EOF */
913     return (lex->tok.ttype = TOKEN_ERROR);
914 }
915
916 static int GMQCC_WARN lex_finish_digit(lex_file *lex, int lastch)
917 {
918     bool ishex = false;
919
920     int  ch = lastch;
921
922     /* parse a number... */
923     if (ch == '.')
924         lex->tok.ttype = TOKEN_FLOATCONST;
925     else
926         lex->tok.ttype = TOKEN_INTCONST;
927
928     lex_tokench(lex, ch);
929
930     ch = lex_getch(lex);
931     if (ch != '.' && !util_isdigit(ch))
932     {
933         if (lastch != '0' || ch != 'x')
934         {
935             /* end of the number or EOF */
936             lex_ungetch(lex, ch);
937             lex_endtoken(lex);
938
939             lex->tok.constval.i = lastch - '0';
940             return lex->tok.ttype;
941         }
942
943         ishex = true;
944     }
945
946     /* EOF would have been caught above */
947
948     if (ch != '.')
949     {
950         lex_tokench(lex, ch);
951         ch = lex_getch(lex);
952         while (util_isdigit(ch) || (ishex && isxdigit_only(ch)))
953         {
954             lex_tokench(lex, ch);
955             ch = lex_getch(lex);
956         }
957     }
958     /* NOT else, '.' can come from above as well */
959     if (lex->tok.ttype != TOKEN_FLOATCONST && ch == '.' && !ishex)
960     {
961         /* Allow floating comma in non-hex mode */
962         lex->tok.ttype = TOKEN_FLOATCONST;
963         lex_tokench(lex, ch);
964
965         /* continue digits-only */
966         ch = lex_getch(lex);
967         while (util_isdigit(ch))
968         {
969             lex_tokench(lex, ch);
970             ch = lex_getch(lex);
971         }
972     }
973     /* put back the last character */
974     /* but do not put back the trailing 'f' or a float */
975     if (lex->tok.ttype == TOKEN_FLOATCONST && ch == 'f')
976         ch = lex_getch(lex);
977
978     /* generally we don't want words to follow numbers: */
979     if (isident(ch)) {
980         lexerror(lex, "unexpected trailing characters after number");
981         return (lex->tok.ttype = TOKEN_ERROR);
982     }
983     lex_ungetch(lex, ch);
984
985     lex_endtoken(lex);
986     if (lex->tok.ttype == TOKEN_FLOATCONST)
987         lex->tok.constval.f = strtod(lex->tok.value, NULL);
988     else
989         lex->tok.constval.i = strtol(lex->tok.value, NULL, 0);
990     return lex->tok.ttype;
991 }
992
993 int lex_do(lex_file *lex)
994 {
995     int ch, nextch, thirdch;
996     bool hadwhite = false;
997
998     lex_token_new(lex);
999 #if 0
1000     if (!lex->tok)
1001         return TOKEN_FATAL;
1002 #endif
1003
1004     while (true) {
1005         ch = lex_skipwhite(lex, hadwhite);
1006         hadwhite = true;
1007         if (!lex->flags.mergelines || ch != '\\')
1008             break;
1009         ch = lex_getch(lex);
1010         if (ch == '\r')
1011             ch = lex_getch(lex);
1012         if (ch != '\n') {
1013             lex_ungetch(lex, ch);
1014             ch = '\\';
1015             break;
1016         }
1017         /* we reached a linemerge */
1018         lex_tokench(lex, '\n');
1019         continue;
1020     }
1021
1022     if (lex->flags.preprocessing && (ch == TOKEN_WHITE || ch == TOKEN_EOL || ch == TOKEN_FATAL)) {
1023         return (lex->tok.ttype = ch);
1024     }
1025
1026     lex->sline = lex->line;
1027     lex->tok.ctx.line = lex->sline;
1028     lex->tok.ctx.file = lex->name;
1029
1030     if (lex->eof)
1031         return (lex->tok.ttype = TOKEN_FATAL);
1032
1033     if (ch == EOF) {
1034         lex->eof = true;
1035         return (lex->tok.ttype = TOKEN_EOF);
1036     }
1037
1038     /* modelgen / spiritgen commands */
1039     if (ch == '$' && !lex->flags.preprocessing) {
1040         const char *v;
1041         size_t frame;
1042
1043         ch = lex_getch(lex);
1044         if (!isident_start(ch)) {
1045             lexerror(lex, "hanging '$' modelgen/spritegen command line");
1046             return lex_do(lex);
1047         }
1048         lex_tokench(lex, ch);
1049         if (!lex_finish_ident(lex))
1050             return (lex->tok.ttype = TOKEN_ERROR);
1051         lex_endtoken(lex);
1052         /* skip the known commands */
1053         v = lex->tok.value;
1054
1055         if (!strcmp(v, "frame") || !strcmp(v, "framesave"))
1056         {
1057             /* frame/framesave command works like an enum
1058              * similar to fteqcc we handle this in the lexer.
1059              * The reason for this is that it is sensitive to newlines,
1060              * which the parser is unaware of
1061              */
1062             if (!lex_finish_frames(lex))
1063                  return (lex->tok.ttype = TOKEN_ERROR);
1064             return lex_do(lex);
1065         }
1066
1067         if (!strcmp(v, "framevalue"))
1068         {
1069             ch = lex_getch(lex);
1070             while (ch != EOF && util_isspace(ch) && ch != '\n')
1071                 ch = lex_getch(lex);
1072
1073             if (!util_isdigit(ch)) {
1074                 lexerror(lex, "$framevalue requires an integer parameter");
1075                 return lex_do(lex);
1076             }
1077
1078             lex_token_new(lex);
1079             lex->tok.ttype = lex_finish_digit(lex, ch);
1080             lex_endtoken(lex);
1081             if (lex->tok.ttype != TOKEN_INTCONST) {
1082                 lexerror(lex, "$framevalue requires an integer parameter");
1083                 return lex_do(lex);
1084             }
1085             lex->framevalue = lex->tok.constval.i;
1086             return lex_do(lex);
1087         }
1088
1089         if (!strcmp(v, "framerestore"))
1090         {
1091             int rc;
1092
1093             lex_token_new(lex);
1094
1095             rc = lex_parse_frame(lex);
1096
1097             if (rc > 0) {
1098                 lexerror(lex, "$framerestore requires a framename parameter");
1099                 return lex_do(lex);
1100             }
1101             if (rc < 0)
1102                 return (lex->tok.ttype = TOKEN_FATAL);
1103
1104             v = lex->tok.value;
1105             for (frame = 0; frame < vec_size(lex->frames); ++frame) {
1106                 if (!strcmp(v, lex->frames[frame].name)) {
1107                     lex->framevalue = lex->frames[frame].value;
1108                     return lex_do(lex);
1109                 }
1110             }
1111             lexerror(lex, "unknown framename `%s`", v);
1112             return lex_do(lex);
1113         }
1114
1115         if (!strcmp(v, "modelname"))
1116         {
1117             int rc;
1118
1119             lex_token_new(lex);
1120
1121             rc = lex_parse_frame(lex);
1122
1123             if (rc > 0) {
1124                 lexerror(lex, "$modelname requires a parameter");
1125                 return lex_do(lex);
1126             }
1127             if (rc < 0)
1128                 return (lex->tok.ttype = TOKEN_FATAL);
1129
1130             if (lex->modelname) {
1131                 frame_macro m;
1132                 m.value = lex->framevalue;
1133                 m.name = lex->modelname;
1134                 lex->modelname = NULL;
1135                 vec_push(lex->frames, m);
1136             }
1137             lex->modelname = lex->tok.value;
1138             lex->tok.value = NULL;
1139             return lex_do(lex);
1140         }
1141
1142         if (!strcmp(v, "flush"))
1143         {
1144             size_t fi;
1145             for (fi = 0; fi < vec_size(lex->frames); ++fi)
1146                 mem_d(lex->frames[fi].name);
1147             vec_free(lex->frames);
1148             /* skip line (fteqcc does it too) */
1149             ch = lex_getch(lex);
1150             while (ch != EOF && ch != '\n')
1151                 ch = lex_getch(lex);
1152             return lex_do(lex);
1153         }
1154
1155         if (!strcmp(v, "cd") ||
1156             !strcmp(v, "origin") ||
1157             !strcmp(v, "base") ||
1158             !strcmp(v, "flags") ||
1159             !strcmp(v, "scale") ||
1160             !strcmp(v, "skin"))
1161         {
1162             /* skip line */
1163             ch = lex_getch(lex);
1164             while (ch != EOF && ch != '\n')
1165                 ch = lex_getch(lex);
1166             return lex_do(lex);
1167         }
1168
1169         for (frame = 0; frame < vec_size(lex->frames); ++frame) {
1170             if (!strcmp(v, lex->frames[frame].name)) {
1171                 lex->tok.constval.i = lex->frames[frame].value;
1172                 return (lex->tok.ttype = TOKEN_INTCONST);
1173             }
1174         }
1175
1176         lexerror(lex, "invalid frame macro");
1177         return lex_do(lex);
1178     }
1179
1180     /* single-character tokens */
1181     switch (ch)
1182     {
1183         case '[':
1184             nextch = lex_getch(lex);
1185             if (nextch == '[') {
1186                 lex_tokench(lex, ch);
1187                 lex_tokench(lex, nextch);
1188                 lex_endtoken(lex);
1189                 return (lex->tok.ttype = TOKEN_ATTRIBUTE_OPEN);
1190             }
1191             lex_ungetch(lex, nextch);
1192             /* FALL THROUGH */
1193         case '(':
1194         case ':':
1195         case '?':
1196             lex_tokench(lex, ch);
1197             lex_endtoken(lex);
1198             if (lex->flags.noops)
1199                 return (lex->tok.ttype = ch);
1200             else
1201                 return (lex->tok.ttype = TOKEN_OPERATOR);
1202
1203         case ']':
1204             if (lex->flags.noops) {
1205                 nextch = lex_getch(lex);
1206                 if (nextch == ']') {
1207                     lex_tokench(lex, ch);
1208                     lex_tokench(lex, nextch);
1209                     lex_endtoken(lex);
1210                     return (lex->tok.ttype = TOKEN_ATTRIBUTE_CLOSE);
1211                 }
1212                 lex_ungetch(lex, nextch);
1213             }
1214             /* FALL THROUGH */
1215         case ')':
1216         case ';':
1217         case '{':
1218         case '}':
1219
1220         case '#':
1221             lex_tokench(lex, ch);
1222             lex_endtoken(lex);
1223             return (lex->tok.ttype = ch);
1224         default:
1225             break;
1226     }
1227
1228     if (ch == '.') {
1229         nextch = lex_getch(lex);
1230         /* digits starting with a dot */
1231         if (util_isdigit(nextch)) {
1232             lex_ungetch(lex, nextch);
1233             lex->tok.ttype = lex_finish_digit(lex, ch);
1234             lex_endtoken(lex);
1235             return lex->tok.ttype;
1236         }
1237         lex_ungetch(lex, nextch);
1238     }
1239
1240     if (lex->flags.noops)
1241     {
1242         /* Detect characters early which are normally
1243          * operators OR PART of an operator.
1244          */
1245         switch (ch)
1246         {
1247             /*
1248             case '+':
1249             case '-':
1250             */
1251             case '*':
1252             case '/':
1253             case '<':
1254             case '>':
1255             case '=':
1256             case '&':
1257             case '|':
1258             case '^':
1259             case '~':
1260             case ',':
1261             case '!':
1262                 lex_tokench(lex, ch);
1263                 lex_endtoken(lex);
1264                 return (lex->tok.ttype = ch);
1265             default:
1266                 break;
1267         }
1268     }
1269
1270     if (ch == '.')
1271     {
1272         lex_tokench(lex, ch);
1273         /* peak ahead once */
1274         nextch = lex_getch(lex);
1275         if (nextch != '.') {
1276             lex_ungetch(lex, nextch);
1277             lex_endtoken(lex);
1278             if (lex->flags.noops)
1279                 return (lex->tok.ttype = ch);
1280             else
1281                 return (lex->tok.ttype = TOKEN_OPERATOR);
1282         }
1283         /* peak ahead again */
1284         nextch = lex_getch(lex);
1285         if (nextch != '.') {
1286             lex_ungetch(lex, nextch);
1287             lex_ungetch(lex, '.');
1288             lex_endtoken(lex);
1289             if (lex->flags.noops)
1290                 return (lex->tok.ttype = ch);
1291             else
1292                 return (lex->tok.ttype = TOKEN_OPERATOR);
1293         }
1294         /* fill the token to be "..." */
1295         lex_tokench(lex, ch);
1296         lex_tokench(lex, ch);
1297         lex_endtoken(lex);
1298         return (lex->tok.ttype = TOKEN_DOTS);
1299     }
1300
1301     if (ch == ',' || ch == '.') {
1302         lex_tokench(lex, ch);
1303         lex_endtoken(lex);
1304         return (lex->tok.ttype = TOKEN_OPERATOR);
1305     }
1306
1307     if (ch == '+' || ch == '-' || /* ++, --, +=, -=  and -> as well! */
1308         ch == '>' || ch == '<' || /* <<, >>, <=, >=                  */
1309         ch == '=' || ch == '!' || /* <=>, ==, !=                     */
1310         ch == '&' || ch == '|' || /* &&, ||, &=, |=                  */
1311         ch == '~' || ch == '^'    /* ~=, ~, ^                        */
1312     )  {
1313         lex_tokench(lex, ch);
1314
1315         nextch = lex_getch(lex);
1316         if ((nextch == '=' && ch != '<') || (nextch == ch && ch != '!')) {
1317             lex_tokench(lex, nextch);
1318         } else if (ch == '<' && nextch == '=') {
1319             lex_tokench(lex, nextch);
1320             if ((thirdch = lex_getch(lex)) == '>')
1321                 lex_tokench(lex, thirdch);
1322             else
1323                 lex_ungetch(lex, thirdch);
1324
1325         } else if (ch == '-' && nextch == '>') {
1326             lex_tokench(lex, nextch);
1327         } else if (ch == '&' && nextch == '~') {
1328             thirdch = lex_getch(lex);
1329             if (thirdch != '=') {
1330                 lex_ungetch(lex, thirdch);
1331                 lex_ungetch(lex, nextch);
1332             }
1333             else {
1334                 lex_tokench(lex, nextch);
1335                 lex_tokench(lex, thirdch);
1336             }
1337         }
1338         else if (lex->flags.preprocessing &&
1339                  ch == '-' && util_isdigit(nextch))
1340         {
1341             lex->tok.ttype = lex_finish_digit(lex, nextch);
1342             if (lex->tok.ttype == TOKEN_INTCONST)
1343                 lex->tok.constval.i = -lex->tok.constval.i;
1344             else
1345                 lex->tok.constval.f = -lex->tok.constval.f;
1346             lex_endtoken(lex);
1347             return lex->tok.ttype;
1348         } else {
1349             lex_ungetch(lex, nextch);
1350         }
1351
1352         lex_endtoken(lex);
1353         return (lex->tok.ttype = TOKEN_OPERATOR);
1354     }
1355
1356     /*
1357     if (ch == '^' || ch == '~' || ch == '!')
1358     {
1359         lex_tokench(lex, ch);
1360         lex_endtoken(lex);
1361         return (lex->tok.ttype = TOKEN_OPERATOR);
1362     }
1363     */
1364
1365     if (ch == '*' || ch == '/') /* *=, /= */
1366     {
1367         lex_tokench(lex, ch);
1368
1369         nextch = lex_getch(lex);
1370         if (nextch == '=' || nextch == '*') {
1371             lex_tokench(lex, nextch);
1372         } else
1373             lex_ungetch(lex, nextch);
1374
1375         lex_endtoken(lex);
1376         return (lex->tok.ttype = TOKEN_OPERATOR);
1377     }
1378
1379     if (ch == '%') {
1380         lex_tokench(lex, ch);
1381         lex_endtoken(lex);
1382         return (lex->tok.ttype = TOKEN_OPERATOR);
1383     }
1384
1385     if (isident_start(ch))
1386     {
1387         const char *v;
1388
1389         lex_tokench(lex, ch);
1390         if (!lex_finish_ident(lex)) {
1391             /* error? */
1392             return (lex->tok.ttype = TOKEN_ERROR);
1393         }
1394         lex_endtoken(lex);
1395         lex->tok.ttype = TOKEN_IDENT;
1396
1397         v = lex->tok.value;
1398         if (!strcmp(v, "void")) {
1399             lex->tok.ttype = TOKEN_TYPENAME;
1400             lex->tok.constval.t = TYPE_VOID;
1401         } else if (!strcmp(v, "int")) {
1402             lex->tok.ttype = TOKEN_TYPENAME;
1403             lex->tok.constval.t = TYPE_INTEGER;
1404         } else if (!strcmp(v, "float")) {
1405             lex->tok.ttype = TOKEN_TYPENAME;
1406             lex->tok.constval.t = TYPE_FLOAT;
1407         } else if (!strcmp(v, "string")) {
1408             lex->tok.ttype = TOKEN_TYPENAME;
1409             lex->tok.constval.t = TYPE_STRING;
1410         } else if (!strcmp(v, "entity")) {
1411             lex->tok.ttype = TOKEN_TYPENAME;
1412             lex->tok.constval.t = TYPE_ENTITY;
1413         } else if (!strcmp(v, "vector")) {
1414             lex->tok.ttype = TOKEN_TYPENAME;
1415             lex->tok.constval.t = TYPE_VECTOR;
1416         } else {
1417             size_t kw;
1418             for (kw = 0; kw < num_keywords_qc; ++kw) {
1419                 if (!strcmp(v, keywords_qc[kw]))
1420                     return (lex->tok.ttype = TOKEN_KEYWORD);
1421             }
1422             if (OPTS_OPTION_U32(OPTION_STANDARD) != COMPILER_QCC) {
1423                 for (kw = 0; kw < num_keywords_fg; ++kw) {
1424                     if (!strcmp(v, keywords_fg[kw]))
1425                         return (lex->tok.ttype = TOKEN_KEYWORD);
1426                 }
1427             }
1428         }
1429
1430         return lex->tok.ttype;
1431     }
1432
1433     if (ch == '"')
1434     {
1435         lex->flags.nodigraphs = true;
1436         if (lex->flags.preprocessing)
1437             lex_tokench(lex, ch);
1438         lex->tok.ttype = lex_finish_string(lex, '"');
1439         if (lex->flags.preprocessing)
1440             lex_tokench(lex, ch);
1441         while (!lex->flags.preprocessing && lex->tok.ttype == TOKEN_STRINGCONST)
1442         {
1443             /* Allow c style "string" "continuation" */
1444             ch = lex_skipwhite(lex, false);
1445             if (ch != '"') {
1446                 lex_ungetch(lex, ch);
1447                 break;
1448             }
1449
1450             lex->tok.ttype = lex_finish_string(lex, '"');
1451         }
1452         lex->flags.nodigraphs = false;
1453         lex_endtoken(lex);
1454         return lex->tok.ttype;
1455     }
1456
1457     if (ch == '\'')
1458     {
1459         /* we parse character constants like string,
1460          * but return TOKEN_CHARCONST, or a vector type if it fits...
1461          * Likewise actual unescaping has to be done by the parser.
1462          * The difference is we don't allow 'char' 'continuation'.
1463          */
1464         if (lex->flags.preprocessing)
1465             lex_tokench(lex, ch);
1466         lex->tok.ttype = lex_finish_string(lex, '\'');
1467         if (lex->flags.preprocessing)
1468             lex_tokench(lex, ch);
1469         lex_endtoken(lex);
1470
1471         lex->tok.ttype = TOKEN_CHARCONST;
1472          /* It's a vector if we can successfully scan 3 floats */
1473 #ifdef _MSC_VER
1474         if (sscanf_s(lex->tok.value, " %f %f %f ",
1475                    &lex->tok.constval.v.x, &lex->tok.constval.v.y, &lex->tok.constval.v.z) == 3)
1476 #else
1477         if (sscanf(lex->tok.value, " %f %f %f ",
1478                    &lex->tok.constval.v.x, &lex->tok.constval.v.y, &lex->tok.constval.v.z) == 3)
1479 #endif
1480
1481         {
1482              lex->tok.ttype = TOKEN_VECTORCONST;
1483         }
1484         else
1485         {
1486             if (!lex->flags.preprocessing && strlen(lex->tok.value) > 1) {
1487                 uchar_t u8char;
1488                 /* check for a valid utf8 character */
1489                 if (!OPTS_FLAG(UTF8) || !u8_analyze(lex->tok.value, NULL, NULL, &u8char, 8)) {
1490                     if (lexwarn(lex, WARN_MULTIBYTE_CHARACTER,
1491                                 ( OPTS_FLAG(UTF8) ? "invalid multibyte character sequence `%s`"
1492                                                   : "multibyte character: `%s`" ),
1493                                 lex->tok.value))
1494                         return (lex->tok.ttype = TOKEN_ERROR);
1495                 }
1496                 else
1497                     lex->tok.constval.i = u8char;
1498             }
1499             else
1500                 lex->tok.constval.i = lex->tok.value[0];
1501         }
1502
1503         return lex->tok.ttype;
1504     }
1505
1506     if (util_isdigit(ch))
1507     {
1508         lex->tok.ttype = lex_finish_digit(lex, ch);
1509         lex_endtoken(lex);
1510         return lex->tok.ttype;
1511     }
1512
1513     if (lex->flags.preprocessing) {
1514         lex_tokench(lex, ch);
1515         lex_endtoken(lex);
1516         return (lex->tok.ttype = ch);
1517     }
1518
1519     lexerror(lex, "unknown token: `%s`", lex->tok.value);
1520     return (lex->tok.ttype = TOKEN_ERROR);
1521 }