]> git.sur5r.net Git - cc65/blob - src/ld65/scanner.c
Allow escape sequences prefixed by '%' in strings. '%%' denotes a single
[cc65] / src / ld65 / scanner.c
1 /*****************************************************************************/
2 /*                                                                           */
3 /*                                 scanner.c                                 */
4 /*                                                                           */
5 /*              Configuration file scanner for the ld65 linker               */
6 /*                                                                           */
7 /*                                                                           */
8 /*                                                                           */
9 /* (C) 1998-2009, 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 #include <ctype.h>
41
42 /* common */
43 #include "chartype.h"
44 #include "strbuf.h"
45 #include "xsprintf.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 cfgtok_t        CfgTok;
62 StrBuf          CfgSVal = STATIC_STRBUF_INITIALIZER;
63 unsigned long   CfgIVal;
64
65 /* Error location */
66 unsigned                CfgErrorLine;
67 unsigned                CfgErrorCol;
68
69 /* Input sources for the configuration */
70 static const char*      CfgName         = 0;
71 static const char*      CfgBuf          = 0;
72
73 /* Other input stuff */
74 static int              C               = ' ';
75 static unsigned         InputLine       = 1;
76 static unsigned         InputCol        = 0;
77 static FILE*            InputFile       = 0;
78
79
80
81 /*****************************************************************************/
82 /*                              Error handling                               */
83 /*****************************************************************************/
84
85
86
87 void CfgWarning (const char* Format, ...)
88 /* Print a warning message adding file name and line number of the config file */
89 {
90     StrBuf Buf = STATIC_STRBUF_INITIALIZER;
91     va_list ap;
92
93     va_start (ap, Format);
94     SB_VPrintf (&Buf, Format, ap);
95     va_end (ap);
96
97     Warning ("%s(%u): %s", CfgGetName(), CfgErrorLine, SB_GetConstBuf (&Buf));
98     SB_Done (&Buf);
99 }
100
101
102
103 void CfgError (const char* Format, ...)
104 /* Print an error message adding file name and line number of the config file */
105 {
106     StrBuf Buf = STATIC_STRBUF_INITIALIZER;
107     va_list ap;
108
109     va_start (ap, Format);
110     SB_VPrintf (&Buf, Format, ap);
111     va_end (ap);
112
113     Error ("%s(%u): %s", CfgGetName(), CfgErrorLine, SB_GetConstBuf (&Buf));
114     SB_Done (&Buf);
115 }
116
117
118
119 /*****************************************************************************/
120 /*                                   Code                                    */
121 /*****************************************************************************/
122
123
124
125 static void NextChar (void)
126 /* Read the next character from the input file */
127 {
128     if (CfgBuf) {
129         /* Read from buffer */
130         C = (unsigned char)(*CfgBuf);
131         if (C == 0) {
132             C = EOF;
133         } else {
134             ++CfgBuf;
135         }
136     } else {
137         /* Read from the file */
138         C = getc (InputFile);
139     }
140
141     /* Count columns */
142     if (C != EOF) {
143         ++InputCol;
144     }
145
146     /* Count lines */
147     if (C == '\n') {
148         ++InputLine;
149         InputCol = 0;
150     }
151 }
152
153
154
155 static unsigned DigitVal (int C)
156 /* Return the value for a numeric digit */
157 {
158     if (isdigit (C)) {
159         return C - '0';
160     } else {
161         return toupper (C) - 'A' + 10;
162     }
163 }
164
165
166
167 static void StrVal (void)
168 /* Parse a string value and expand escape sequences */
169 {
170     /* Skip the starting double quotes */
171     NextChar ();
172
173     /* Read input chars */
174     SB_Clear (&CfgSVal);
175     while (C != '\"') {
176         switch (C) {
177
178             case EOF:
179             case '\n':
180                 CfgError ("Unterminated string");
181                 break;
182
183             case '%':
184                 NextChar ();
185                 switch (C) {
186
187                     case EOF:
188                     case '\n':
189                     case '\"':
190                         CfgError ("Unterminated '%%' escape sequence");
191                         break;
192
193                     case '%':
194                         SB_AppendChar (&CfgSVal, '%');
195                         NextChar ();
196                         break;
197
198                     case 'O':
199                         /* Replace by output file */
200                         if (OutputName) {
201                             SB_AppendStr (&CfgSVal, OutputName);
202                         }
203                         NextChar ();
204                         break;
205
206                     default:
207                         CfgWarning ("Unkown escape sequence `%%%c'", C);
208                         SB_AppendChar (&CfgSVal, '%');
209                         SB_AppendChar (&CfgSVal, C);
210                         NextChar ();
211                         break;
212                 }
213                 break;
214
215             default:
216                 SB_AppendChar (&CfgSVal, C);
217                 NextChar ();
218         }
219     }
220
221     /* Skip the terminating double quotes */
222     NextChar ();
223
224     /* Terminate the string */
225     SB_Terminate (&CfgSVal);
226
227     /* We've read a string value */
228     CfgTok = CFGTOK_STRCON;
229 }
230
231
232
233 void CfgNextTok (void)
234 /* Read the next token from the input stream */
235 {
236 Again:
237     /* Skip whitespace */
238     while (isspace (C)) {
239         NextChar ();
240     }
241
242     /* Remember the current position */
243     CfgErrorLine = InputLine;
244     CfgErrorCol  = InputCol;
245
246     /* Identifier? */
247     if (C == '_' || IsAlpha (C)) {
248
249         /* Read the identifier */
250         SB_Clear (&CfgSVal);
251         while (C == '_' || IsAlNum (C)) {
252             SB_AppendChar (&CfgSVal, C);
253             NextChar ();
254         }
255         SB_Terminate (&CfgSVal);
256         CfgTok = CFGTOK_IDENT;
257         return;
258     }
259
260     /* Hex number? */
261     if (C == '$') {
262         NextChar ();
263         if (!isxdigit (C)) {
264             CfgError ("Hex digit expected");
265         }
266         CfgIVal = 0;
267         while (isxdigit (C)) {
268             CfgIVal = CfgIVal * 16 + DigitVal (C);
269             NextChar ();
270         }
271         CfgTok = CFGTOK_INTCON;
272         return;
273     }
274
275     /* Decimal number? */
276     if (isdigit (C)) {
277         CfgIVal = 0;
278         while (isdigit (C)) {
279             CfgIVal = CfgIVal * 10 + DigitVal (C);
280             NextChar ();
281         }
282         CfgTok = CFGTOK_INTCON;
283         return;
284     }
285
286     /* Other characters */
287     switch (C) {
288
289         case '-':
290             NextChar ();
291             CfgTok = CFGTOK_MINUS;
292             break;
293
294         case '+':
295             NextChar ();
296             CfgTok = CFGTOK_PLUS;
297             break;
298
299         case '*':
300             NextChar ();
301             CfgTok = CFGTOK_MUL;
302             break;
303
304         case '/':
305             NextChar ();
306             CfgTok = CFGTOK_DIV;
307             break;
308
309         case '(':
310             NextChar ();
311             CfgTok = CFGTOK_LPAR;
312             break;
313
314         case ')':
315             NextChar ();
316             CfgTok = CFGTOK_RPAR;
317             break;
318
319         case '{':
320             NextChar ();
321             CfgTok = CFGTOK_LCURLY;
322             break;
323
324         case '}':
325             NextChar ();
326             CfgTok = CFGTOK_RCURLY;
327             break;
328
329         case ';':
330             NextChar ();
331             CfgTok = CFGTOK_SEMI;
332             break;
333
334         case '.':
335             NextChar ();
336             CfgTok = CFGTOK_DOT;
337             break;
338
339         case ',':
340             NextChar ();
341             CfgTok = CFGTOK_COMMA;
342             break;
343
344         case '=':
345             NextChar ();
346             CfgTok = CFGTOK_EQ;
347             break;
348
349         case ':':
350             NextChar ();
351             CfgTok = CFGTOK_COLON;
352             break;
353
354         case '\"':
355             StrVal ();
356             break;
357
358         case '#':
359             /* Comment */
360             while (C != '\n' && C != EOF) {
361                 NextChar ();
362             }
363             if (C != EOF) {
364                 goto Again;
365             }
366             CfgTok = CFGTOK_EOF;
367             break;
368
369         case '%':
370             NextChar ();
371             switch (C) {
372
373                 case 'O':
374                     NextChar ();
375                     if (OutputName) {
376                         SB_CopyStr (&CfgSVal, OutputName);
377                     } else {
378                         SB_Clear (&CfgSVal);
379                     }
380                     SB_Terminate (&CfgSVal);
381                     CfgTok = CFGTOK_STRCON;
382                     break;
383
384                 case 'S':
385                     NextChar ();
386                     CfgIVal = StartAddr;
387                     CfgTok = CFGTOK_INTCON;
388                     break;
389
390                 default:
391                     CfgError ("Invalid format specification");
392             }
393             break;
394
395         case EOF:
396             CfgTok = CFGTOK_EOF;
397             break;
398
399         default:
400             CfgError ("Invalid character `%c'", C);
401
402     }
403 }
404
405
406
407 void CfgConsume (cfgtok_t T, const char* Msg)
408 /* Skip a token, print an error message if not found */
409 {
410     if (CfgTok != T) {
411         CfgError ("%s", Msg);
412     }
413     CfgNextTok ();
414 }
415
416
417
418 void CfgConsumeSemi (void)
419 /* Consume a semicolon */
420 {
421     CfgConsume (CFGTOK_SEMI, "`;' expected");
422 }
423
424
425
426 void CfgConsumeColon (void)
427 /* Consume a colon */
428 {
429     CfgConsume (CFGTOK_COLON, "`:' expected");
430 }
431
432
433
434 void CfgOptionalComma (void)
435 /* Consume a comma if there is one */
436 {
437     if (CfgTok == CFGTOK_COMMA) {
438         CfgNextTok ();
439     }
440 }
441
442
443
444 void CfgOptionalAssign (void)
445 /* Consume an equal sign if there is one */
446 {
447     if (CfgTok == CFGTOK_EQ) {
448         CfgNextTok ();
449     }
450 }
451
452
453
454 void CfgAssureInt (void)
455 /* Make sure the next token is an integer */
456 {
457     if (CfgTok != CFGTOK_INTCON) {
458         CfgError ("Integer constant expected");
459     }
460 }
461
462
463
464 void CfgAssureStr (void)
465 /* Make sure the next token is a string constant */
466 {
467     if (CfgTok != CFGTOK_STRCON) {
468         CfgError ("String constant expected");
469     }
470 }
471
472
473
474 void CfgAssureIdent (void)
475 /* Make sure the next token is an identifier */
476 {
477     if (CfgTok != CFGTOK_IDENT) {
478         CfgError ("Identifier expected");
479     }
480 }
481
482
483
484 void CfgRangeCheck (unsigned long Lo, unsigned long Hi)
485 /* Check the range of CfgIVal */
486 {
487     if (CfgIVal < Lo || CfgIVal > Hi) {
488         CfgError ("Range error");
489     }
490 }
491
492
493
494 void CfgSpecialToken (const IdentTok* Table, unsigned Size, const char* Name)
495 /* Map an identifier to one of the special tokens in the table */
496 {
497     unsigned I;
498
499     /* We need an identifier */
500     if (CfgTok == CFGTOK_IDENT) {
501
502         /* Make it upper case */
503         SB_ToUpper (&CfgSVal);
504
505         /* Linear search */
506         for (I = 0; I < Size; ++I) {
507             if (SB_CompareStr (&CfgSVal, Table[I].Ident) == 0) {
508                 CfgTok = Table[I].Tok;
509                 return;
510             }
511         }
512
513     }
514
515     /* Not found or no identifier */
516     CfgError ("%s expected", Name);
517 }
518
519
520
521 void CfgBoolToken (void)
522 /* Map an identifier or integer to a boolean token */
523 {
524     static const IdentTok Booleans [] = {
525         {   "YES",      CFGTOK_TRUE     },
526         {   "NO",       CFGTOK_FALSE    },
527         {   "TRUE",     CFGTOK_TRUE     },
528         {   "FALSE",    CFGTOK_FALSE    },
529     };
530
531     /* If we have an identifier, map it to a boolean token */
532     if (CfgTok == CFGTOK_IDENT) {
533         CfgSpecialToken (Booleans, ENTRY_COUNT (Booleans), "Boolean");
534     } else {
535         /* We expected an integer here */
536         if (CfgTok != CFGTOK_INTCON) {
537             CfgError ("Boolean value expected");
538         }
539         CfgTok = (CfgIVal == 0)? CFGTOK_FALSE : CFGTOK_TRUE;
540     }
541 }
542
543
544
545 void CfgSetName (const char* Name)
546 /* Set a name for a config file */
547 {
548     CfgName = Name;
549 }
550
551
552
553 const char* CfgGetName (void)
554 /* Get the name of the config file */
555 {
556     if (CfgName) {
557         return CfgName;
558     } else if (CfgBuf) {
559         return "[builtin config]";
560     } else {
561         return "";
562     }
563 }
564
565
566
567 void CfgSetBuf (const char* Buf)
568 /* Set a memory buffer for the config */
569 {
570     CfgBuf = Buf;
571 }
572
573
574
575 int CfgAvail (void)
576 /* Return true if we have a configuration available */
577 {
578     return CfgName != 0 || CfgBuf != 0;
579 }
580
581
582
583 void CfgOpenInput (void)
584 /* Open the input file if we have one */
585 {
586     /* If we have a config name given, open the file, otherwise we will read
587      * from a buffer.
588      */
589     if (!CfgBuf) {
590
591         /* Open the file */
592         InputFile = fopen (CfgName, "r");
593         if (InputFile == 0) {
594             Error ("Cannot open `%s': %s", CfgName, strerror (errno));
595         }
596
597     }
598
599     /* Initialize variables */
600     C         = ' ';
601     InputLine = 1;
602     InputCol  = 0;
603
604     /* Start the ball rolling ... */
605     CfgNextTok ();
606 }
607
608
609
610 void CfgCloseInput (void)
611 /* Close the input file if we have one */
612 {
613     /* Close the input file if we had one */
614     if (InputFile) {
615         (void) fclose (InputFile);
616         InputFile = 0;
617     }
618 }
619
620
621
622