]> git.sur5r.net Git - cc65/blob - src/da65/scanner.c
da65: properly scan empty strings
[cc65] / src / da65 / scanner.c
1 /*****************************************************************************/
2 /*                                                                           */
3 /*                                 scanner.c                                 */
4 /*                                                                           */
5 /*           Configuration file scanner for the da65 disassembler            */
6 /*                                                                           */
7 /*                                                                           */
8 /*                                                                           */
9 /* (C) 2000-2005 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 <stdarg.h>
37 #include <stdio.h>
38 #include <string.h>
39 #include <errno.h>
40
41 /* common */
42 #include "chartype.h"
43 #include "xsprintf.h"
44 #include "xmalloc.h"
45 #include "strbuf.h"
46
47 /* ld65 */
48 #include "global.h"
49 #include "error.h"
50 #include "scanner.h"
51
52
53
54 /*****************************************************************************/
55 /*                                   Data                                    */
56 /*****************************************************************************/
57
58
59
60 /* Current token and attributes */
61 unsigned        InfoTok;
62 char            InfoSVal [CFG_MAX_IDENT_LEN+1];
63 long            InfoIVal;
64
65 /* Error location */
66 unsigned                InfoErrorLine;
67 unsigned                InfoErrorCol;
68
69 /* Input sources for the configuration */
70 static const char*      InfoFile        = 0;
71
72 /* Other input stuff */
73 static int              C               = ' ';
74 static unsigned         InputLine       = 1;
75 static unsigned         InputCol        = 0;
76 static FILE*            InputFile       = 0;
77 static char*            InputSrcName    = 0;
78
79
80
81 /*****************************************************************************/
82 /*                              Error handling                               */
83 /*****************************************************************************/
84
85
86
87 void InfoWarning (const char* Format, ...)
88 /* Print a warning message adding file name and line number of the config file */
89 {
90     char Buf [512];
91     va_list ap;
92
93     va_start (ap, Format);
94     xvsprintf (Buf, sizeof (Buf), Format, ap);
95     va_end (ap);
96
97     fprintf (stderr, "%s(%u): Warning: %s\n",
98             InputSrcName, InfoErrorLine, Buf);
99 }
100
101
102
103 void InfoError (const char* Format, ...)
104 /* Print an error message adding file name and line number of the config file */
105 {
106     char Buf [512];
107     va_list ap;
108
109     va_start (ap, Format);
110     xvsprintf (Buf, sizeof (Buf), Format, ap);
111     va_end (ap);
112
113     fprintf (stderr, "%s(%u): Error: %s\n",
114             InputSrcName, InfoErrorLine, Buf);
115     exit (EXIT_FAILURE);
116 }
117
118
119
120
121 /*****************************************************************************/
122 /*                                   Code                                    */
123 /*****************************************************************************/
124
125
126
127 static void NextChar (void)
128 /* Read the next character from the input file */
129 {
130     /* Read from the file */
131     C = getc (InputFile);
132
133     /* Count columns */
134     if (C != EOF) {
135         ++InputCol;
136     }
137
138     /* Count lines */
139     if (C == '\n') {
140         ++InputLine;
141         InputCol = 0;
142     }
143 }
144
145
146
147 static unsigned DigitVal (int C)
148 /* Return the value for a numeric digit */
149 {
150     if (IsDigit (C)) {
151         return C - '0';
152     } else {
153         return toupper (C) - 'A' + 10;
154     }
155 }
156
157
158
159 static void SkipBlanks (int SingleLine)
160 {
161     while (C != EOF && (!SingleLine || C != '\n') && IsSpace (C)) {
162         NextChar ();
163     }
164 }
165
166
167
168 static long GetDecimalToken (void)
169 {
170     long Value = 0;
171
172     while (C != EOF && IsDigit (C)) {
173         Value = Value * 10 + DigitVal (C);
174         NextChar ();
175     }
176     return Value;
177 }
178
179
180
181 static int GetEncodedChar (char* Buf, unsigned* IPtr, unsigned Size)
182 {
183     char Decoded = 0;
184     int Count;
185
186     if (C == EOF) {
187         return -1;
188     } else if (C != '\\') {
189         Decoded = C;
190         NextChar ();
191         goto Store;
192     }
193     NextChar (); /* consume '\\' */
194     if (C == EOF) {
195         return -1;
196     } else if (IsODigit (C)) {
197         Count = 3;
198         do {
199             Decoded = Decoded * 8 + DigitVal (C);
200             NextChar ();
201             --Count;
202         } while (Count > 0 && C != EOF && IsODigit (C));
203     } else if (C == 'x') {
204         NextChar (); /* consume 'x' */
205         Count = 2;
206         while (Count > 0 && C != EOF && IsXDigit (C)) {
207             Decoded = Decoded * 16 + DigitVal (C);
208             NextChar ();
209             --Count;
210         }
211     } else {
212         switch (C) {
213             case '"': case '\'': case '\\':
214                         Decoded = C;        break;
215             case 't':   Decoded = '\t';     break;
216             case 'r':   Decoded = '\r';     break;
217             case 'n':   Decoded = '\n';     break;
218             default:    return -1;
219         }
220         NextChar ();
221     }
222 Store:
223     if (*IPtr < Size - 1) {
224         Buf [(*IPtr)++] = Decoded;
225     }
226     Buf [*IPtr] = 0;
227     return 0;
228 }
229
230
231
232 static void LineMarkerOrComment ()
233 /* Handle a line beginning with '#'. Possible interpretations are:
234 ** - #line <lineno> ["<filename>"]          (C preprocessor input)
235 ** - # <lineno> "<filename>" [<flag>]...    (gcc preprocessor output)
236 ** - #<comment>
237 */
238 {
239     unsigned long LineNo = 0;
240     int LineDirective = 0;
241     StrBuf SrcNameBuf = AUTO_STRBUF_INITIALIZER;
242
243     /* Skip the first "# " */
244     NextChar ();
245     SkipBlanks (1);
246
247     /* Check "line" */
248     if (C == 'l') {
249         char MaybeLine [6];
250         unsigned I;
251         for (I = 0; I < sizeof MaybeLine - 1 && C != EOF && IsAlNum (C); ++I) {
252             MaybeLine [I] = C;
253             NextChar ();
254         }
255         MaybeLine [I] = 0;
256         if (strcmp (MaybeLine, "line") != 0) {
257             goto NotMarker;
258         }
259         LineDirective = 1;
260         SkipBlanks (1);
261     }
262
263     /* Get line number */
264     if (C == EOF || !IsDigit (C)) {
265         goto NotMarker;
266     }
267     LineNo = GetDecimalToken ();
268     SkipBlanks (1);
269
270     /* Get the source file name */
271     if (C != '\"') {
272         /* The source file name is missing */
273         if (LineDirective && C == '\n') {
274             /* got #line <lineno> */
275             NextChar ();
276             InputLine = LineNo;
277             goto Last;
278         } else {
279             goto NotMarker;
280         }
281     }
282     NextChar ();
283     while (C != EOF && C != '\n' && C != '\"') {
284         char DecodeBuf [2];
285         unsigned I = 0;
286         if (GetEncodedChar (DecodeBuf, &I, sizeof DecodeBuf) < 0) {
287             goto BadMarker;
288         }
289         SB_AppendBuf (&SrcNameBuf, DecodeBuf, I);
290     }
291     if (C != '\"') {
292         goto BadMarker;
293     }
294     NextChar ();
295
296     /* Ignore until the end of line */
297     while (C != EOF && C != '\n') {
298         NextChar ();
299     }
300
301     /* Accepted a line marker */
302     SB_Terminate (&SrcNameBuf);
303     xfree (InputSrcName);
304     InputSrcName = SB_GetBuf (&SrcNameBuf);
305     SB_Init (&SrcNameBuf);
306     InputLine = (unsigned)LineNo;
307     NextChar ();
308     goto Last;
309
310 BadMarker:
311     InfoWarning ("Bad line marker");
312 NotMarker:
313     while (C != EOF && C != '\n') {
314         NextChar ();
315     }
316     NextChar ();
317 Last:
318     SB_Done (&SrcNameBuf);
319 }
320
321
322
323 void InfoNextTok (void)
324 /* Read the next token from the input stream */
325 {
326     unsigned I;
327     char DecodeBuf [2];
328
329 Again:
330     /* Skip whitespace */
331     SkipBlanks (0);
332
333     /* Remember the current position */
334     InfoErrorLine = InputLine;
335     InfoErrorCol  = InputCol;
336
337     /* Identifier? */
338     if (C == '_' || IsAlpha (C)) {
339
340         /* Read the identifier */
341         I = 0;
342         while (C == '_' || IsAlNum (C)) {
343             if (I < CFG_MAX_IDENT_LEN) {
344                 InfoSVal [I++] = C;
345             }
346             NextChar ();
347         }
348         InfoSVal [I] = '\0';
349         InfoTok = INFOTOK_IDENT;
350         return;
351     }
352
353     /* Hex number? */
354     if (C == '$') {
355         NextChar ();
356         if (!IsXDigit (C)) {
357             InfoError ("Hex digit expected");
358         }
359         InfoIVal = 0;
360         while (IsXDigit (C)) {
361             InfoIVal = InfoIVal * 16 + DigitVal (C);
362             NextChar ();
363         }
364         InfoTok = INFOTOK_INTCON;
365         return;
366     }
367
368     /* Decimal number? */
369     if (IsDigit (C)) {
370         InfoIVal = GetDecimalToken ();
371         InfoTok = INFOTOK_INTCON;
372         return;
373     }
374
375     /* Other characters */
376     switch (C) {
377
378         case '{':
379             NextChar ();
380             InfoTok = INFOTOK_LCURLY;
381             break;
382
383         case '}':
384             NextChar ();
385             InfoTok = INFOTOK_RCURLY;
386             break;
387
388         case ';':
389             NextChar ();
390             InfoTok = INFOTOK_SEMI;
391             break;
392
393         case '.':
394             NextChar ();
395             InfoTok = INFOTOK_DOT;
396             break;
397
398         case ',':
399             NextChar ();
400             InfoTok = INFOTOK_COMMA;
401             break;
402
403         case '=':
404             NextChar ();
405             InfoTok = INFOTOK_EQ;
406             break;
407
408         case ':':
409             NextChar ();
410             InfoTok = INFOTOK_COLON;
411             break;
412
413         case '\"':
414             NextChar ();
415             I = 0;
416             InfoSVal[0] = '\0';
417             while (C != EOF && C != '\"') {
418                 if (GetEncodedChar (InfoSVal, &I, sizeof InfoSVal) < 0) {
419                     if (C == EOF) {
420                         InfoError ("Unterminated string");
421                     } else  {
422                         InfoError ("Invalid escape char: %c", C);
423                     }
424                 }
425             }
426             if (C != '\"') {
427                 InfoError ("Unterminated string");
428             }
429             NextChar ();
430             InfoTok = INFOTOK_STRCON;
431             break;
432
433         case '\'':
434             NextChar ();
435             if (C == EOF || IsControl (C) || C == '\'') {
436                 InfoError ("Invalid character constant");
437             }
438             if (GetEncodedChar (DecodeBuf, &I, sizeof DecodeBuf) < 0 || I != 1) {
439                 InfoError ("Invalid character constant");
440             }
441             InfoIVal = DecodeBuf [0];
442             if (C != '\'') {
443                 InfoError ("Unterminated character constant");
444             }
445             NextChar ();
446             InfoTok = INFOTOK_CHARCON;
447             break;
448
449         case '#':
450             /* # lineno "sourcefile" or # comment */
451             if (SyncLines && InputCol == 1) {
452                 LineMarkerOrComment ();
453             } else {
454                 do {
455                     NextChar ();
456                 } while (C != EOF && C != '\n');
457                 NextChar ();
458             }
459             if (C != EOF) {
460                 goto Again;
461             }
462             InfoTok = INFOTOK_EOF;
463             break;
464
465         case '/':
466             /* C++ style comment */
467             NextChar ();
468             if (C != '/') {
469                 InfoError ("Invalid token `/'");
470             }
471             do {
472                 NextChar ();
473             } while (C != '\n' && C != EOF);
474             if (C != EOF) {
475                 goto Again;
476             }
477             InfoTok = INFOTOK_EOF;
478             break;
479
480         case EOF:
481             InfoTok = INFOTOK_EOF;
482             break;
483
484         default:
485             InfoError ("Invalid character `%c'", C);
486
487     }
488 }
489
490
491
492 void InfoConsume (unsigned T, const char* Msg)
493 /* Skip a token, print an error message if not found */
494 {
495     if (InfoTok != T) {
496         InfoError (Msg);
497     }
498     InfoNextTok ();
499 }
500
501
502
503 void InfoConsumeLCurly (void)
504 /* Consume a left curly brace */
505 {
506     InfoConsume (INFOTOK_LCURLY, "`{' expected");
507 }
508
509
510
511 void InfoConsumeRCurly (void)
512 /* Consume a right curly brace */
513 {
514     InfoConsume (INFOTOK_RCURLY, "`}' expected");
515 }
516
517
518
519 void InfoConsumeSemi (void)
520 /* Consume a semicolon */
521 {
522     InfoConsume (INFOTOK_SEMI, "`;' expected");
523 }
524
525
526
527 void InfoConsumeColon (void)
528 /* Consume a colon */
529 {
530     InfoConsume (INFOTOK_COLON, "`:' expected");
531 }
532
533
534
535 void InfoOptionalComma (void)
536 /* Consume a comma if there is one */
537 {
538     if (InfoTok == INFOTOK_COMMA) {
539         InfoNextTok ();
540     }
541 }
542
543
544
545 void InfoOptionalAssign (void)
546 /* Consume an equal sign if there is one */
547 {
548     if (InfoTok == INFOTOK_EQ) {
549         InfoNextTok ();
550     }
551 }
552
553
554
555 void InfoAssureInt (void)
556 /* Make sure the next token is an integer */
557 {
558     if (InfoTok != INFOTOK_INTCON) {
559         InfoError ("Integer constant expected");
560     }
561 }
562
563
564
565 void InfoAssureStr (void)
566 /* Make sure the next token is a string constant */
567 {
568     if (InfoTok != INFOTOK_STRCON) {
569         InfoError ("String constant expected");
570     }
571 }
572
573
574
575 void InfoAssureChar (void)
576 /* Make sure the next token is a char constant */
577 {
578     if (InfoTok != INFOTOK_STRCON) {
579         InfoError ("Character constant expected");
580     }
581 }
582
583
584
585 void InfoAssureIdent (void)
586 /* Make sure the next token is an identifier */
587 {
588     if (InfoTok != INFOTOK_IDENT) {
589         InfoError ("Identifier expected");
590     }
591 }
592
593
594
595 void InfoRangeCheck (long Lo, long Hi)
596 /* Check the range of InfoIVal */
597 {
598     if (InfoIVal < Lo || InfoIVal > Hi) {
599         InfoError ("Range error");
600     }
601 }
602
603
604
605 void InfoSpecialToken (const IdentTok* Table, unsigned Size, const char* Name)
606 /* Map an identifier to one of the special tokens in the table */
607 {
608     unsigned I;
609
610     /* We need an identifier */
611     if (InfoTok == INFOTOK_IDENT) {
612
613         /* Make it upper case */
614         I = 0;
615         while (InfoSVal [I]) {
616             InfoSVal [I] = toupper (InfoSVal [I]);
617             ++I;
618         }
619
620         /* Linear search */
621         for (I = 0; I < Size; ++I) {
622             if (strcmp (InfoSVal, Table [I].Ident) == 0) {
623                 InfoTok = Table [I].Tok;
624                 return;
625             }
626         }
627
628     }
629
630     /* Not found or no identifier */
631     InfoError ("%s expected", Name);
632 }
633
634
635
636 void InfoBoolToken (void)
637 /* Map an identifier or integer to a boolean token */
638 {
639     static const IdentTok Booleans [] = {
640         {   "YES",      INFOTOK_TRUE     },
641         {   "NO",       INFOTOK_FALSE    },
642         {   "TRUE",     INFOTOK_TRUE     },
643         {   "FALSE",    INFOTOK_FALSE    },
644         {   "ON",       INFOTOK_TRUE     },
645         {   "OFF",      INFOTOK_FALSE    },
646     };
647
648     /* If we have an identifier, map it to a boolean token */
649     if (InfoTok == INFOTOK_IDENT) {
650         InfoSpecialToken (Booleans, ENTRY_COUNT (Booleans), "Boolean");
651     } else {
652         /* We expected an integer here */
653         if (InfoTok != INFOTOK_INTCON) {
654             InfoError ("Boolean value expected");
655         }
656         InfoTok = (InfoIVal == 0)? INFOTOK_FALSE : INFOTOK_TRUE;
657     }
658 }
659
660
661
662 void InfoSetName (const char* Name)
663 /* Set a name for a config file */
664 {
665     InfoFile = Name;
666     xfree(InputSrcName);
667     InputSrcName = xstrdup(Name);
668 }
669
670
671
672 int InfoAvail ()
673 /* Return true if we have an info file given */
674 {
675     return (InfoFile != 0);
676 }
677
678
679
680 void InfoOpenInput (void)
681 /* Open the input file */
682 {
683     /* Open the file */
684     InputFile = fopen (InfoFile, "r");
685     if (InputFile == 0) {
686         Error ("Cannot open `%s': %s", InfoFile, strerror (errno));
687     }
688
689     /* Initialize variables */
690     C         = ' ';
691     InputLine = 1;
692     InputCol  = 0;
693
694     /* Start the ball rolling ... */
695     InfoNextTok ();
696 }
697
698
699
700 void InfoCloseInput (void)
701 /* Close the input file if we have one */
702 {
703     /* Close the input file if we had one */
704     if (InputFile) {
705         (void) fclose (InputFile);
706         InputFile = 0;
707     }
708 }