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