]> git.sur5r.net Git - cc65/blob - src/cc65/scanner.c
941fb75192a1947019ac07199255cc024ee45f59
[cc65] / src / cc65 / scanner.c
1 /*****************************************************************************/
2 /*                                                                           */
3 /*                                 scanner.c                                 */
4 /*                                                                           */
5 /*                      Source file line info structure                      */
6 /*                                                                           */
7 /*                                                                           */
8 /*                                                                           */
9 /* (C) 1998-2003 Ullrich von Bassewitz                                       */
10 /*               Römerstrasse 52                                             */
11 /*               D-70794 Filderstadt                                         */
12 /* EMail:        uz@cc65.org                                                 */
13 /*                                                                           */
14 /*                                                                           */
15 /* This software is provided 'as-is', without any expressed or implied       */
16 /* warranty.  In no event will the authors be held liable for any damages    */
17 /* arising from the use of this software.                                    */
18 /*                                                                           */
19 /* Permission is granted to anyone to use this software for any purpose,     */
20 /* including commercial applications, and to alter it and redistribute it    */
21 /* freely, subject to the following restrictions:                            */
22 /*                                                                           */
23 /* 1. The origin of this software must not be misrepresented; you must not   */
24 /*    claim that you wrote the original software. If you use this software   */
25 /*    in a product, an acknowledgment in the product documentation would be  */
26 /*    appreciated but is not required.                                       */
27 /* 2. Altered source versions must be plainly marked as such, and must not   */
28 /*    be misrepresented as being the original software.                      */
29 /* 3. This notice may not be removed or altered from any source              */
30 /*    distribution.                                                          */
31 /*                                                                           */
32 /*****************************************************************************/
33
34
35
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <errno.h>
40 #include <ctype.h>
41
42 /* common */
43 #include "chartype.h"
44 #include "tgttrans.h"
45
46 /* cc65 */
47 #include "datatype.h"
48 #include "error.h"
49 #include "function.h"
50 #include "global.h"
51 #include "hexval.h"
52 #include "ident.h"
53 #include "input.h"
54 #include "litpool.h"
55 #include "preproc.h"
56 #include "symtab.h"
57 #include "util.h"
58 #include "scanner.h"
59
60
61
62 /*****************************************************************************/
63 /*                                   data                                    */
64 /*****************************************************************************/
65
66
67
68 Token CurTok;           /* The current token */
69 Token NextTok;          /* The next token */
70
71
72
73 /* Token types */
74 #define TT_C    0               /* ANSI C token */
75 #define TT_EXT  1               /* cc65 extension */
76
77 /* Token table */
78 static const struct Keyword {
79     char*           Key;        /* Keyword name */
80     unsigned char   Tok;        /* The token */
81     unsigned char   Type;       /* Token type */
82 } Keywords [] = {
83     { "_Pragma",        TOK_PRAGMA,     TT_C    },
84     { "__A__",          TOK_A,          TT_C    },
85     { "__AX__",         TOK_AX,         TT_C    },
86     { "__EAX__",        TOK_EAX,        TT_C    },
87     { "__X__",          TOK_X,          TT_C    },
88     { "__Y__",          TOK_Y,          TT_C    },
89     { "__asm__",        TOK_ASM,        TT_C    },
90     { "__attribute__",  TOK_ATTRIBUTE,  TT_C    },
91     { "__far__",        TOK_FAR,        TT_C    },
92     { "__fastcall__",   TOK_FASTCALL,   TT_C    },
93     { "asm",            TOK_ASM,        TT_EXT  },
94     { "auto",           TOK_AUTO,       TT_C    },
95     { "break",          TOK_BREAK,      TT_C    },
96     { "case",           TOK_CASE,       TT_C    },
97     { "char",           TOK_CHAR,       TT_C    },
98     { "const",          TOK_CONST,      TT_C    },
99     { "continue",       TOK_CONTINUE,   TT_C    },
100     { "default",        TOK_DEFAULT,    TT_C    },
101     { "do",             TOK_DO,         TT_C    },
102     { "double",         TOK_DOUBLE,     TT_C    },
103     { "else",           TOK_ELSE,       TT_C    },
104     { "enum",           TOK_ENUM,       TT_C    },
105     { "extern",         TOK_EXTERN,     TT_C    },
106     { "far",            TOK_FAR,        TT_EXT  },
107     { "fastcall",       TOK_FASTCALL,   TT_EXT  },
108     { "float",          TOK_FLOAT,      TT_C    },
109     { "for",            TOK_FOR,        TT_C    },
110     { "goto",           TOK_GOTO,       TT_C    },
111     { "if",             TOK_IF,         TT_C    },
112     { "int",            TOK_INT,        TT_C    },
113     { "long",           TOK_LONG,       TT_C    },
114     { "register",       TOK_REGISTER,   TT_C    },
115     { "restrict",       TOK_RESTRICT,   TT_C    },
116     { "return",         TOK_RETURN,     TT_C    },
117     { "short",          TOK_SHORT,      TT_C    },
118     { "signed",         TOK_SIGNED,     TT_C    },
119     { "sizeof",         TOK_SIZEOF,     TT_C    },
120     { "static",         TOK_STATIC,     TT_C    },
121     { "struct",         TOK_STRUCT,     TT_C    },
122     { "switch",         TOK_SWITCH,     TT_C    },
123     { "typedef",        TOK_TYPEDEF,    TT_C    },
124     { "union",          TOK_UNION,      TT_C    },
125     { "unsigned",       TOK_UNSIGNED,   TT_C    },
126     { "void",           TOK_VOID,       TT_C    },
127     { "volatile",       TOK_VOLATILE,   TT_C    },
128     { "while",          TOK_WHILE,      TT_C    },
129 };
130 #define KEY_COUNT       (sizeof (Keywords) / sizeof (Keywords [0]))
131
132
133
134 /* Stuff for determining the type of an integer constant */
135 #define IT_INT          0x01
136 #define IT_UINT         0x02
137 #define IT_LONG         0x04
138 #define IT_ULONG        0x08
139
140
141
142 /*****************************************************************************/
143 /*                                   code                                    */
144 /*****************************************************************************/
145
146
147
148 static int CmpKey (const void* Key, const void* Elem)
149 /* Compare function for bsearch */
150 {
151     return strcmp ((const char*) Key, ((const struct Keyword*) Elem)->Key);
152 }
153
154
155
156 static int FindKey (const char* Key)
157 /* Find a keyword and return the token. Return IDENT if the token is not a
158  * keyword.
159  */
160 {
161     struct Keyword* K;
162     K = bsearch (Key, Keywords, KEY_COUNT, sizeof (Keywords [0]), CmpKey);
163     if (K && (K->Type != TT_EXT || ANSI == 0)) {
164         return K->Tok;
165     } else {
166         return TOK_IDENT;
167     }
168 }
169
170
171
172 static int SkipWhite (void)
173 /* Skip white space in the input stream, reading and preprocessing new lines
174  * if necessary. Return 0 if end of file is reached, return 1 otherwise.
175  */
176 {
177     while (1) {
178         while (CurC == 0) {
179             if (NextLine () == 0) {
180                 return 0;
181             }
182             Preprocess ();
183         }
184         if (IsSpace (CurC)) {
185             NextChar ();
186         } else {
187             return 1;
188         }
189     }
190 }
191
192
193
194 void SymName (char* s)
195 /* Get symbol from input stream */
196 {
197     unsigned k = 0;
198     do {
199         if (k != MAX_IDENTLEN) {
200             ++k;
201             *s++ = CurC;
202         }
203         NextChar ();
204     } while (IsIdent (CurC) || IsDigit (CurC));
205     *s = '\0';
206 }
207
208
209
210 int IsSym (char *s)
211 /* Get symbol from input stream or return 0 if not a symbol. */
212 {
213     if (IsIdent (CurC)) {
214         SymName (s);
215         return 1;
216     } else {
217         return 0;
218     }
219 }
220
221
222
223 static void UnknownChar (char C)
224 /* Error message for unknown character */
225 {
226     Error ("Invalid input character with code %02X", C & 0xFF);
227     NextChar ();                        /* Skip */
228 }
229
230
231
232 static void SetTok (int tok)
233 /* Set NextTok.Tok and bump line ptr */
234 {
235     NextTok.Tok = tok;
236     NextChar ();
237 }
238
239
240
241 static int ParseChar (void)
242 /* Parse a character. Converts \n into EOL, etc. */
243 {
244     int i;
245     unsigned val;
246     int C;
247
248     /* Check for escape chars */
249     if (CurC == '\\') {
250         NextChar ();
251         switch (CurC) {
252             case 'b':
253                 C = '\b';
254                 break;
255             case 'f':
256                 C = '\f';
257                 break;
258             case 'r':
259                 C = '\r';
260                 break;
261             case 'n':
262                 C = '\n';
263                 break;
264             case 't':
265                 C = '\t';
266                 break;
267             case '\"':
268                 C = '\"';
269                 break;
270             case '\'':
271                 C = '\'';
272                 break;
273             case '\\':
274                 C = '\\';
275                 break;
276             case 'x':
277             case 'X':
278                 /* Hex character constant */
279                 NextChar ();
280                 val = HexVal (CurC) << 4;
281                 NextChar ();
282                 C = val | HexVal (CurC);        /* Do not translate */
283                 break;
284             case '0':
285             case '1':
286             case '2':
287             case '3':
288                 /* Octal constant */
289                 i = 0;
290                 C = CurC - '0';
291                 while (NextC >= '0' && NextC <= '7' && i++ < 4) {
292                     NextChar ();
293                     C = (C << 3) | (CurC - '0');
294                 }
295                 break;
296             default:
297                 Error ("Illegal character constant");
298                 C = ' ';
299                 break;
300         }
301     } else {
302         C = CurC;
303     }
304
305     /* Skip the character read */
306     NextChar ();
307
308     /* Do correct sign extension */
309     return SignExtendChar (C);
310 }
311
312
313
314 static void CharConst (void)
315 /* Parse a character constant. */
316 {
317     int C;
318
319     /* Skip the quote */
320     NextChar ();
321
322     /* Get character */
323     C = ParseChar ();
324
325     /* Check for closing quote */
326     if (CurC != '\'') {
327         Error ("`\'' expected");
328     } else {
329         /* Skip the quote */
330         NextChar ();
331     }
332
333     /* Setup values and attributes */
334     NextTok.Tok  = TOK_CCONST;
335
336     /* Translate into target charset */
337     NextTok.IVal = SignExtendChar (TgtTranslateChar (C));
338
339     /* Character constants have type int */
340     NextTok.Type = type_int;
341 }
342
343
344
345 static void StringConst (void)
346 /* Parse a quoted string */
347 {
348     NextTok.IVal = GetLiteralPoolOffs ();
349     NextTok.Tok  = TOK_SCONST;
350
351     /* Be sure to concatenate strings */
352     while (CurC == '\"') {
353
354         /* Skip the quote char */
355         NextChar ();
356
357         while (CurC != '\"') {
358             if (CurC == '\0') {
359                 Error ("Unexpected newline");
360                 break;
361             }
362             AddLiteralChar (ParseChar ());
363         }
364
365         /* Skip closing quote char if there was one */
366         NextChar ();
367
368         /* Skip white space, read new input */
369         SkipWhite ();
370
371     }
372
373     /* Terminate the string */
374     AddLiteralChar ('\0');
375 }
376
377
378
379 void NextToken (void)
380 /* Get next token from input stream */
381 {
382     ident token;
383
384     /* We have to skip white space here before shifting tokens, since the
385      * tokens and the current line info is invalid at startup and will get
386      * initialized by reading the first time from the file. Remember if
387      * we were at end of input and handle that later.
388      */
389     int GotEOF = (SkipWhite() == 0);
390
391     /* Current token is the lookahead token */
392     if (CurTok.LI) {
393         ReleaseLineInfo (CurTok.LI);
394     }
395     CurTok = NextTok;
396
397     /* Remember the starting position of the next token */
398     NextTok.LI = UseLineInfo (GetCurLineInfo ());
399
400     /* Now handle end of input. */
401     if (GotEOF) {
402         /* End of file reached */
403         NextTok.Tok = TOK_CEOF;
404         return;
405     }
406
407     /* Determine the next token from the lookahead */
408     if (IsDigit (CurC)) {
409
410         /* A number */
411         int HaveSuffix;         /* True if we have a type suffix */
412         unsigned types;         /* Possible types */
413         unsigned Base;
414         unsigned DigitVal;
415         unsigned long k;        /* Value */
416
417         k     = 0;
418         Base  = 10;
419         types = IT_INT | IT_LONG | IT_ULONG;
420
421         if (CurC == '0') {
422             /* Octal or hex constants may also be of type unsigned int */
423             types = IT_INT | IT_UINT | IT_LONG | IT_ULONG;
424             /* gobble 0 and examin next char */
425             NextChar ();
426             if (toupper (CurC) == 'X') {
427                 Base = 16;
428                 NextTok.Type = type_uint;
429                 NextChar ();    /* gobble "x" */
430             } else {
431                 Base = 8;
432             }
433         }
434         while (IsXDigit (CurC) && (DigitVal = HexVal (CurC)) < Base) {
435             k = k * Base + DigitVal;
436             NextChar ();
437         }
438         /* Check for errorneous digits */
439         if (Base == 8 && IsDigit (CurC)) {
440             Error ("Numeric constant contains digits beyond the radix");
441             /* Do error recovery */
442             do {
443                 NextChar ();
444             } while (IsDigit (CurC));
445         } else if (Base != 16 && IsXDigit (CurC)) {
446             Error ("Nondigits in number and not hexadecimal");
447             do {
448                 NextChar ();
449             } while (IsXDigit (CurC));
450         }
451
452         /* Check for a suffix */
453         HaveSuffix = 1;
454         if (CurC == 'u' || CurC == 'U') {
455             /* Unsigned type */
456             NextChar ();
457             if (toupper (CurC) != 'L') {
458                 types = IT_UINT | IT_ULONG;
459             } else {
460                 NextChar ();
461                 types = IT_ULONG;
462             }
463         } else if (CurC == 'l' || CurC == 'L') {
464             /* Long type */
465             NextChar ();
466             if (toupper (CurC) != 'U') {
467                 types = IT_LONG | IT_ULONG;
468             } else {
469                 NextChar ();
470                 types = IT_ULONG;
471             }
472         } else {
473             HaveSuffix = 0;
474         }
475
476         /* Check the range to determine the type */
477         if (k > 0x7FFF) {
478             /* Out of range for int */
479             types &= ~IT_INT;
480             /* If the value is in the range 0x8000..0xFFFF, unsigned int is not
481              * allowed, and we don't have a type specifying suffix, emit a
482              * warning.
483              */
484             if (k <= 0xFFFF && (types & IT_UINT) == 0 && !HaveSuffix) {
485                 Warning ("Constant is long");
486             }
487         }
488         if (k > 0xFFFF) {
489             /* Out of range for unsigned int */
490             types &= ~IT_UINT;
491         }
492         if (k > 0x7FFFFFFF) {
493             /* Out of range for long int */
494             types &= ~IT_LONG;
495         }
496
497         /* Now set the type string to the smallest type in types */
498         if (types & IT_INT) {
499             NextTok.Type = type_int;
500         } else if (types & IT_UINT) {
501             NextTok.Type = type_uint;
502         } else if (types & IT_LONG) {
503             NextTok.Type = type_long;
504         } else {
505             NextTok.Type = type_ulong;
506         }
507
508         /* Set the value and the token */
509         NextTok.IVal = k;
510         NextTok.Tok  = TOK_ICONST;
511         return;
512     }
513
514     if (IsSym (token)) {
515
516         /* Check for a keyword */
517         if ((NextTok.Tok = FindKey (token)) != TOK_IDENT) {
518             /* Reserved word found */
519             return;
520         }
521         /* No reserved word, check for special symbols */
522         if (token [0] == '_') {
523             /* Special symbols */
524             if (strcmp (token, "__FILE__") == 0) {
525                 NextTok.IVal = AddLiteral (GetCurrentFile());
526                 NextTok.Tok  = TOK_SCONST;
527                 return;
528             } else if (strcmp (token, "__LINE__") == 0) {
529                 NextTok.Tok  = TOK_ICONST;
530                 NextTok.IVal = GetCurrentLine();
531                 NextTok.Type = type_int;
532                 return;
533             } else if (strcmp (token, "__func__") == 0) {
534                 /* __func__ is only defined in functions */
535                 if (CurrentFunc) {
536                     NextTok.IVal = AddLiteral (F_GetFuncName (CurrentFunc));
537                     NextTok.Tok  = TOK_SCONST;
538                     return;
539                 }
540             }
541         }
542
543         /* No reserved word but identifier */
544         strcpy (NextTok.Ident, token);
545         NextTok.Tok = TOK_IDENT;
546         return;
547     }
548
549     /* Monstrous switch statement ahead... */
550     switch (CurC) {
551
552         case '!':
553             NextChar ();
554             if (CurC == '=') {
555                 SetTok (TOK_NE);
556             } else {
557                 NextTok.Tok = TOK_BOOL_NOT;
558             }
559             break;
560
561         case '\"':
562             StringConst ();
563             break;
564
565         case '%':
566             NextChar ();
567             if (CurC == '=') {
568                 SetTok (TOK_MOD_ASSIGN);
569             } else {
570                 NextTok.Tok = TOK_MOD;
571             }
572             break;
573
574         case '&':
575             NextChar ();
576             switch (CurC) {
577                 case '&':
578                     SetTok (TOK_BOOL_AND);
579                     break;
580                 case '=':
581                     SetTok (TOK_AND_ASSIGN);
582                     break;
583                 default:
584                     NextTok.Tok = TOK_AND;
585             }
586             break;
587
588         case '\'':
589             CharConst ();
590             break;
591
592         case '(':
593             SetTok (TOK_LPAREN);
594             break;
595
596         case ')':
597             SetTok (TOK_RPAREN);
598             break;
599
600         case '*':
601             NextChar ();
602             if (CurC == '=') {
603                 SetTok (TOK_MUL_ASSIGN);
604             } else {
605                 NextTok.Tok = TOK_STAR;
606             }
607             break;
608
609         case '+':
610             NextChar ();
611             switch (CurC) {
612                 case '+':
613                     SetTok (TOK_INC);
614                     break;
615                 case '=':
616                     SetTok (TOK_PLUS_ASSIGN);
617                     break;
618                 default:
619                     NextTok.Tok = TOK_PLUS;
620             }
621             break;
622
623         case ',':
624             SetTok (TOK_COMMA);
625             break;
626
627         case '-':
628             NextChar ();
629             switch (CurC) {
630                 case '-':
631                     SetTok (TOK_DEC);
632                     break;
633                 case '=':
634                     SetTok (TOK_MINUS_ASSIGN);
635                     break;
636                 case '>':
637                     SetTok (TOK_PTR_REF);
638                     break;
639                 default:
640                     NextTok.Tok = TOK_MINUS;
641             }
642             break;
643
644         case '.':
645             NextChar ();
646             if (CurC == '.') {
647                 NextChar ();
648                 if (CurC == '.') {
649                     SetTok (TOK_ELLIPSIS);
650                 } else {
651                     UnknownChar (CurC);
652                 }
653             } else {
654                 NextTok.Tok = TOK_DOT;
655             }
656             break;
657
658         case '/':
659             NextChar ();
660             if (CurC == '=') {
661                 SetTok (TOK_DIV_ASSIGN);
662             } else {
663                 NextTok.Tok = TOK_DIV;
664             }
665             break;
666
667         case ':':
668             SetTok (TOK_COLON);
669             break;
670
671         case ';':
672             SetTok (TOK_SEMI);
673             break;
674
675         case '<':
676             NextChar ();
677             switch (CurC) {
678                 case '=':
679                     SetTok (TOK_LE);
680                     break;
681                 case '<':
682                     NextChar ();
683                     if (CurC == '=') {
684                         SetTok (TOK_SHL_ASSIGN);
685                     } else {
686                         NextTok.Tok = TOK_SHL;
687                     }
688                     break;
689                 default:
690                     NextTok.Tok = TOK_LT;
691             }
692             break;
693
694         case '=':
695             NextChar ();
696             if (CurC == '=') {
697                 SetTok (TOK_EQ);
698             } else {
699                 NextTok.Tok = TOK_ASSIGN;
700             }
701             break;
702
703         case '>':
704             NextChar ();
705             switch (CurC) {
706                 case '=':
707                     SetTok (TOK_GE);
708                     break;
709                 case '>':
710                     NextChar ();
711                     if (CurC == '=') {
712                         SetTok (TOK_SHR_ASSIGN);
713                     } else {
714                         NextTok.Tok = TOK_SHR;
715                     }
716                     break;
717                 default:
718                     NextTok.Tok = TOK_GT;
719             }
720             break;
721
722         case '?':
723             SetTok (TOK_QUEST);
724             break;
725
726         case '[':
727             SetTok (TOK_LBRACK);
728             break;
729
730         case ']':
731             SetTok (TOK_RBRACK);
732             break;
733
734         case '^':
735             NextChar ();
736             if (CurC == '=') {
737                 SetTok (TOK_XOR_ASSIGN);
738             } else {
739                 NextTok.Tok = TOK_XOR;
740             }
741             break;
742
743         case '{':
744             SetTok (TOK_LCURLY);
745             break;
746
747         case '|':
748             NextChar ();
749             switch (CurC) {
750                 case '|':
751                     SetTok (TOK_BOOL_OR);
752                     break;
753                 case '=':
754                     SetTok (TOK_OR_ASSIGN);
755                     break;
756                 default:
757                     NextTok.Tok = TOK_OR;
758             }
759             break;
760
761         case '}':
762             SetTok (TOK_RCURLY);
763             break;
764
765         case '~':
766             SetTok (TOK_COMP);
767             break;
768
769         default:
770             UnknownChar (CurC);
771
772     }
773
774 }
775
776
777
778 void SkipTokens (const token_t* TokenList, unsigned TokenCount)
779 /* Skip tokens until we reach TOK_CEOF or a token in the given token list.
780  * This routine is used for error recovery.
781  */
782 {
783     while (CurTok.Tok != TOK_CEOF) {
784
785         /* Check if the current token is in the token list */
786         unsigned I;
787         for (I = 0; I < TokenCount; ++I) {
788             if (CurTok.Tok == TokenList[I]) {
789                 /* Found a token in the list */
790                 return;
791             }
792         }
793
794         /* Not in the list: Skip it */
795         NextToken ();
796
797     }
798 }
799
800
801
802 int Consume (token_t Token, const char* ErrorMsg)
803 /* Eat token if it is the next in the input stream, otherwise print an error
804  * message. Returns true if the token was found and false otherwise.
805  */
806 {
807     if (CurTok.Tok == Token) {
808         NextToken ();
809         return 1;
810     } else {
811         Error (ErrorMsg);
812         return 0;
813     }
814 }
815
816
817
818 int ConsumeColon (void)
819 /* Check for a colon and skip it. */
820 {
821     return Consume (TOK_COLON, "`:' expected");
822 }
823
824
825
826 int ConsumeSemi (void)
827 /* Check for a semicolon and skip it. */
828 {
829     /* Try do be smart about typos... */
830     if (CurTok.Tok == TOK_SEMI) {
831         NextToken ();
832         return 1;
833     } else {
834         Error ("`;' expected");
835         if (CurTok.Tok == TOK_COLON || CurTok.Tok == TOK_COMMA) {
836             NextToken ();
837         }
838         return 0;
839     }
840 }
841
842
843
844 int ConsumeComma (void)
845 /* Check for a comma and skip it. */
846 {
847     /* Try do be smart about typos... */
848     if (CurTok.Tok == TOK_COMMA) {
849         NextToken ();
850         return 1;
851     } else {
852         Error ("`,' expected");
853         if (CurTok.Tok == TOK_SEMI) {
854             NextToken ();
855         }
856         return 0;
857     }
858 }
859
860
861
862 int ConsumeLParen (void)
863 /* Check for a left parenthesis and skip it */
864 {
865     return Consume (TOK_LPAREN, "`(' expected");
866 }
867
868
869
870 int ConsumeRParen (void)
871 /* Check for a right parenthesis and skip it */
872 {
873     return Consume (TOK_RPAREN, "`)' expected");
874 }
875
876
877
878 int ConsumeLBrack (void)
879 /* Check for a left bracket and skip it */
880 {
881     return Consume (TOK_LBRACK, "`[' expected");
882 }
883
884
885
886 int ConsumeRBrack (void)
887 /* Check for a right bracket and skip it */
888 {
889     return Consume (TOK_RBRACK, "`]' expected");
890 }
891
892
893
894 int ConsumeLCurly (void)
895 /* Check for a left curly brace and skip it */
896 {
897     return Consume (TOK_LCURLY, "`{' expected");
898 }
899
900
901
902 int ConsumeRCurly (void)
903 /* Check for a right curly brace and skip it */
904 {
905     return Consume (TOK_RCURLY, "`}' expected");
906 }
907
908
909