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/* Roemerstrasse 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 */
61unsigned InfoTok;
62char InfoSVal [CFG_MAX_IDENT_LEN+1];
63long InfoIVal;
64
65/* Error location */
66unsigned InfoErrorLine;
67unsigned InfoErrorCol;
68
69/* Input sources for the configuration */
70static const char* InfoFile = 0;
71
72/* Other input stuff */
73static int C = ' ';
74static unsigned InputLine = 1;
75static unsigned InputCol = 0;
76static FILE* InputFile = 0;
77static char* InputSrcName = 0;
78
79
80
81/*****************************************************************************/
82/* Error handling */
83/*****************************************************************************/
84
85
86
87void 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
103void 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
127static 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
147static 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
159static void SkipBlanks (int SingleLine)
160{
161 while (C != EOF && (!SingleLine || C != '\n') && IsSpace (C)) {
162 NextChar ();
163 }
164}
165
166
167
168static 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
181static 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 }
222Store:
223 if (*IPtr < Size - 1) {
224 Buf [(*IPtr)++] = Decoded;
225 }
226 Buf [*IPtr] = 0;
227 return 0;
228}
229
230
231
232static 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
310BadMarker:
311 InfoWarning ("Bad line marker");
312NotMarker:
313 while (C != EOF && C != '\n') {
314 NextChar ();
315 }
316 NextChar ();
317Last:
318 SB_Done (&SrcNameBuf);
319}
320
321
322
323void InfoNextTok (void)
324/* Read the next token from the input stream */
325{
326 unsigned I;
327 char DecodeBuf [2];
328
329Again:
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
492void 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
503void InfoConsumeLCurly (void)
504/* Consume a left curly brace */
505{
506 InfoConsume (INFOTOK_LCURLY, "'{' expected");
507}
508
509
510
511void InfoConsumeRCurly (void)
512/* Consume a right curly brace */
513{
514 InfoConsume (INFOTOK_RCURLY, "'}' expected");
515}
516
517
518
519void InfoConsumeSemi (void)
520/* Consume a semicolon */
521{
522 InfoConsume (INFOTOK_SEMI, "';' expected");
523}
524
525
526
527void InfoConsumeColon (void)
528/* Consume a colon */
529{
530 InfoConsume (INFOTOK_COLON, "':' expected");
531}
532
533
534
535void InfoOptionalComma (void)
536/* Consume a comma if there is one */
537{
538 if (InfoTok == INFOTOK_COMMA) {
539 InfoNextTok ();
540 }
541}
542
543
544
545void InfoOptionalAssign (void)
546/* Consume an equal sign if there is one */
547{
548 if (InfoTok == INFOTOK_EQ) {
549 InfoNextTok ();
550 }
551}
552
553
554
555void 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
565void 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
575void 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
585void 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
595void 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
605void 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
636void 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
662void 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
672int InfoAvail ()
673/* Return true if we have an info file given */
674{
675 return (InfoFile != 0);
676}
677
678
679
680void 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
700void 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}
709