]> git.sur5r.net Git - cc65/blob - src/ca65/scanner.c
New module strstack
[cc65] / src / ca65 / scanner.c
1 /*****************************************************************************/
2 /*                                                                           */
3 /*                                 scanner.c                                 */
4 /*                                                                           */
5 /*                  The scanner for the ca65 macroassembler                  */
6 /*                                                                           */
7 /*                                                                           */
8 /*                                                                           */
9 /* (C) 1998-2003 Ullrich von Bassewitz                                       */
10 /*               Römerstraße 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 <ctype.h>
40 #include <errno.h>
41 #include <sys/types.h>          /* EMX needs this */
42 #include <sys/stat.h>
43
44 /* common */
45 #include "addrsize.h"
46 #include "chartype.h"
47 #include "check.h"
48 #include "fname.h"
49 #include "xmalloc.h"
50
51 /* ca65 */
52 #include "condasm.h"
53 #include "error.h"
54 #include "filetab.h"
55 #include "global.h"
56 #include "incpath.h"
57 #include "instr.h"
58 #include "istack.h"
59 #include "listing.h"
60 #include "macro.h"
61 #include "toklist.h"
62 #include "scanner.h"
63
64
65
66 /*****************************************************************************/
67 /*                                   Data                                    */
68 /*****************************************************************************/
69
70
71
72 enum Token Tok = TOK_NONE;              /* Current token */
73 int WS;                                 /* Flag: Whitespace before token */
74 long IVal;                              /* Integer token attribute */
75 char SVal [MAX_STR_LEN+1];              /* String token attribute */
76
77 FilePos CurPos = { 0, 0, 0 };           /* Name and position in current file */
78
79
80
81 /* Struct to handle include files. Note: The length of the input line may
82  * not exceed 255+1, since the column is stored in the file position struct
83  * as a character. Increasing this value means changing the FilePos struct,
84  * and the read and write routines in the assembler and linker.
85  */
86 typedef struct InputFile_ InputFile;
87 struct InputFile_ {
88     FILE*           F;                  /* Input file descriptor */
89     FilePos         Pos;                /* Position in file */
90     enum Token      Tok;                /* Last token */
91     int             C;                  /* Last character */
92     char            Line[256];          /* The current input line */
93     InputFile*      Next;               /* Linked list of input files */
94 };
95
96 /* Struct to handle textual input data */
97 typedef struct InputData_ InputData;
98 struct InputData_ {
99     char*           Data;               /* Pointer to the data */
100     const char*     Pos;                /* Pointer to current position */
101     int             Malloced;           /* Memory was malloced */
102     enum Token      Tok;                /* Last token */
103     int             C;                  /* Last character */
104     InputData*      Next;               /* Linked list of input data */
105 };
106
107 /* Current input variables */
108 static InputFile* IFile         = 0;    /* Current input file */
109 static InputData* IData         = 0;    /* Current input memory data */
110 static unsigned   ICount        = 0;    /* Count of input files */
111 static int        C             = 0;    /* Current input character */
112
113 /* Force end of assembly */
114 int               ForcedEnd = 0;
115
116 /* List of dot keywords with the corresponding tokens */
117 struct DotKeyword {
118     const char* Key;                    /* MUST be first field */
119     enum Token  Tok;
120 } DotKeywords [] = {
121     { ".A16",           TOK_A16         },
122     { ".A8",            TOK_A8          },
123     { ".ADDR",          TOK_ADDR        },
124     { ".ALIGN",         TOK_ALIGN       },
125     { ".AND",           TOK_BOOLAND     },
126     { ".ASCIIZ",        TOK_ASCIIZ      },
127     { ".ASSERT",        TOK_ASSERT      },
128     { ".AUTOIMPORT",    TOK_AUTOIMPORT  },
129     { ".BANKBYTE",      TOK_BANKBYTE    },
130     { ".BITAND",        TOK_AND         },
131     { ".BITNOT",        TOK_NOT         },
132     { ".BITOR",         TOK_OR          },
133     { ".BITXOR",        TOK_XOR         },
134     { ".BLANK",         TOK_BLANK       },
135     { ".BSS",           TOK_BSS         },
136     { ".BYT",           TOK_BYTE        },
137     { ".BYTE",          TOK_BYTE        },
138     { ".CASE",          TOK_CASE        },
139     { ".CHARMAP",       TOK_CHARMAP     },
140     { ".CODE",          TOK_CODE        },
141     { ".CONCAT",        TOK_CONCAT      },
142     { ".CONDES",        TOK_CONDES      },
143     { ".CONST",         TOK_CONST       },
144     { ".CONSTRUCTOR",   TOK_CONSTRUCTOR },
145     { ".CPU",           TOK_CPU         },
146     { ".DATA",          TOK_DATA        },
147     { ".DBG",           TOK_DBG         },
148     { ".DBYT",          TOK_DBYT        },
149     { ".DEBUGINFO",     TOK_DEBUGINFO   },
150     { ".DEF",           TOK_DEFINED     },
151     { ".DEFINE",        TOK_DEFINE      },
152     { ".DEFINED",       TOK_DEFINED     },
153     { ".DESTRUCTOR",    TOK_DESTRUCTOR  },
154     { ".DWORD",         TOK_DWORD       },
155     { ".ELSE",          TOK_ELSE        },
156     { ".ELSEIF",        TOK_ELSEIF      },
157     { ".END",           TOK_END         },
158     { ".ENDENUM",       TOK_ENDENUM     },
159     { ".ENDIF",         TOK_ENDIF       },
160     { ".ENDMAC",        TOK_ENDMACRO    },
161     { ".ENDMACRO",      TOK_ENDMACRO    },
162     { ".ENDPROC",       TOK_ENDPROC     },
163     { ".ENDREP",        TOK_ENDREP      },
164     { ".ENDREPEAT",     TOK_ENDREP      },
165     { ".ENDSCOPE",      TOK_ENDSCOPE    },
166     { ".ENDSTRUCT",     TOK_ENDSTRUCT   },
167     { ".ENDUNION",      TOK_ENDUNION    },
168     { ".ENUM",          TOK_ENUM        },
169     { ".ERROR",         TOK_ERROR       },
170     { ".EXITMAC",       TOK_EXITMACRO   },
171     { ".EXITMACRO",     TOK_EXITMACRO   },
172     { ".EXPORT",        TOK_EXPORT      },
173     { ".EXPORTZP",      TOK_EXPORTZP    },
174     { ".FARADDR",       TOK_FARADDR     },
175     { ".FEATURE",       TOK_FEATURE     },
176     { ".FILEOPT",       TOK_FILEOPT     },
177     { ".FOPT",          TOK_FILEOPT     },
178     { ".FORCEIMPORT",   TOK_FORCEIMPORT },
179     { ".FORCEWORD",     TOK_FORCEWORD   },
180     { ".GLOBAL",        TOK_GLOBAL      },
181     { ".GLOBALZP",      TOK_GLOBALZP    },
182     { ".HIBYTE",        TOK_HIBYTE      },
183     { ".HIWORD",        TOK_HIWORD      },
184     { ".I16",           TOK_I16         },
185     { ".I8",            TOK_I8          },
186     { ".IF",            TOK_IF          },
187     { ".IFBLANK",       TOK_IFBLANK     },
188     { ".IFCONST",       TOK_IFCONST     },
189     { ".IFDEF",         TOK_IFDEF       },
190     { ".IFNBLANK",      TOK_IFNBLANK    },
191     { ".IFNCONST",      TOK_IFNCONST    },
192     { ".IFNDEF",        TOK_IFNDEF      },
193     { ".IFNREF",        TOK_IFNREF      },
194     { ".IFP02",         TOK_IFP02       },
195     { ".IFP816",        TOK_IFP816      },
196     { ".IFPC02",        TOK_IFPC02      },
197     { ".IFPSC02",       TOK_IFPSC02     },
198     { ".IFREF",         TOK_IFREF       },
199     { ".IMPORT",        TOK_IMPORT      },
200     { ".IMPORTZP",      TOK_IMPORTZP    },
201     { ".INCBIN",        TOK_INCBIN      },
202     { ".INCLUDE",       TOK_INCLUDE     },
203     { ".LEFT",          TOK_LEFT        },
204     { ".LINECONT",      TOK_LINECONT    },
205     { ".LIST",          TOK_LIST        },
206     { ".LISTBYTES",     TOK_LISTBYTES   },
207     { ".LOBYTE",        TOK_LOBYTE      },
208     { ".LOCAL",         TOK_LOCAL       },
209     { ".LOCALCHAR",     TOK_LOCALCHAR   },
210     { ".LOWORD",        TOK_LOWORD      },
211     { ".MAC",           TOK_MACRO       },
212     { ".MACPACK",       TOK_MACPACK     },
213     { ".MACRO",         TOK_MACRO       },
214     { ".MATCH",         TOK_MATCH       },
215     { ".MID",           TOK_MID         },
216     { ".MOD",           TOK_MOD         },
217     { ".NOT",           TOK_BOOLNOT     },
218     { ".NULL",          TOK_NULL        },
219     { ".OR",            TOK_BOOLOR      },
220     { ".ORG",           TOK_ORG         },
221     { ".OUT",           TOK_OUT         },
222     { ".P02",           TOK_P02         },
223     { ".P816",          TOK_P816        },
224     { ".PAGELEN",       TOK_PAGELENGTH  },
225     { ".PAGELENGTH",    TOK_PAGELENGTH  },
226     { ".PARAMCOUNT",    TOK_PARAMCOUNT  },
227     { ".PC02",          TOK_PC02        },
228     { ".POPSEG",        TOK_POPSEG      },
229     { ".PROC",          TOK_PROC        },
230     { ".PSC02",         TOK_PSC02       },
231     { ".PUSHSEG",       TOK_PUSHSEG     },
232     { ".REF",           TOK_REFERENCED  },
233     { ".REFERENCED",    TOK_REFERENCED  },
234     { ".RELOC",         TOK_RELOC       },
235     { ".REPEAT",        TOK_REPEAT      },
236     { ".RES",           TOK_RES         },
237     { ".RIGHT",         TOK_RIGHT       },
238     { ".RODATA",        TOK_RODATA      },
239     { ".SCOPE",         TOK_SCOPE       },
240     { ".SEGMENT",       TOK_SEGMENT     },
241     { ".SETCPU",        TOK_SETCPU      },
242     { ".SHL",           TOK_SHL         },
243     { ".SHR",           TOK_SHR         },
244     { ".SIZEOF",        TOK_SIZEOF      },
245     { ".SMART",         TOK_SMART       },
246     { ".STRAT",         TOK_STRAT       },
247     { ".STRING",        TOK_STRING      },
248     { ".STRLEN",        TOK_STRLEN      },
249     { ".STRUCT",        TOK_STRUCT      },
250     { ".SUNPLUS",       TOK_SUNPLUS     },
251     { ".TAG",           TOK_TAG         },
252     { ".TCOUNT",        TOK_TCOUNT      },
253     { ".TIME",          TOK_TIME        },
254     { ".UNION",         TOK_UNION       },
255     { ".VERSION",       TOK_VERSION     },
256     { ".WARNING",       TOK_WARNING     },
257     { ".WORD",          TOK_WORD        },
258     { ".XMATCH",        TOK_XMATCH      },
259     { ".XOR",           TOK_BOOLXOR     },
260     { ".ZEROPAGE",      TOK_ZEROPAGE    },
261 };
262
263
264
265 /*****************************************************************************/
266 /*                                 Forwards                                  */
267 /*****************************************************************************/
268
269
270
271 static void NextChar (void);
272 /* Read the next character from the input file */
273
274
275
276 /*****************************************************************************/
277 /*                    Character classification functions                     */
278 /*****************************************************************************/
279
280
281
282 static int IsIdChar (int C)
283 /* Return true if the character is a valid character for an identifier */
284 {
285     return IsAlNum (C)                  ||
286            (C == '_')                   ||
287            (C == '@' && AtInIdents)     ||
288            (C == '$' && DollarInIdents);
289 }
290
291
292
293 static int IsIdStart (int C)
294 /* Return true if the character may start an identifier */
295 {
296     return IsAlpha (C) || C == '_';
297 }
298
299
300
301 /*****************************************************************************/
302 /*                                   Code                                    */
303 /*****************************************************************************/
304
305
306
307 void NewInputFile (const char* Name)
308 /* Open a new input file */
309 {
310     InputFile* I;
311     FILE* F;
312
313     /* First try to open the file */
314     F = fopen (Name, "r");
315     if (F == 0) {
316
317         char* PathName;
318
319         /* Error (fatal error if this is the main file) */
320         if (ICount == 0) {
321             Fatal ("Cannot open input file `%s': %s", Name, strerror (errno));
322         }
323
324         /* We are on include level. Search for the file in the include
325          * directories.
326          */
327         PathName = FindInclude (Name);
328         if (PathName == 0 || (F = fopen (PathName, "r")) == 0) {
329             /* Not found or cannot open, print an error and bail out */
330             Error ("Cannot open include file `%s': %s", Name, strerror (errno));
331         }
332
333         /* Free the allocated memory */
334         xfree (PathName);
335
336     }
337
338     /* check again if we do now have an open file */
339     if (F != 0) {
340
341         unsigned FileIdx;
342
343         /* Stat the file and remember the values */
344         struct stat Buf;
345         if (fstat (fileno (F), &Buf) != 0) {
346             Fatal ("Cannot stat input file `%s': %s", Name, strerror (errno));
347         }
348
349         /* Add the file to the input file table and remember the index */
350         FileIdx = AddFile (Name, Buf.st_size, Buf.st_mtime);
351
352         /* Create a new state variable and initialize it */
353         I           = xmalloc (sizeof (*I));
354         I->F        = F;
355         I->Pos.Line = 0;
356         I->Pos.Col  = 0;
357         I->Pos.Name = FileIdx;
358         I->Tok      = Tok;
359         I->C        = C;
360         I->Line[0]  = '\0';
361
362         /* Use the new file */
363         I->Next     = IFile;
364         IFile       = I;
365         ++ICount;
366
367         /* Prime the pump */
368         NextChar ();
369     }
370 }
371
372
373
374 void DoneInputFile (void)
375 /* Close the current input file */
376 {
377     InputFile* I;
378
379     /* Restore the old token */
380     Tok = IFile->Tok;
381     C   = IFile->C;
382
383     /* Save a pointer to the current struct, then set it back */
384     I     = IFile;
385     IFile = I->Next;
386
387     /* Cleanup the current stuff */
388     fclose (I->F);
389     xfree (I);
390     --ICount;
391 }
392
393
394
395 void NewInputData (char* Data, int Malloced)
396 /* Add a chunk of input data to the input stream */
397 {
398     InputData* I;
399
400     /* Create a new state variable and initialize it */
401     I           = xmalloc (sizeof (*I));
402     I->Data     = Data;
403     I->Pos      = Data;
404     I->Malloced = Malloced;
405     I->Tok      = Tok;
406     I->C        = C;
407
408     /* Use the new data */
409     I->Next     = IData;
410     IData       = I;
411
412     /* Prime the pump */
413     NextChar ();
414 }
415
416
417
418 static void DoneInputData (void)
419 /* End the current input data stream */
420 {
421     InputData* I;
422
423     /* Restore the old token */
424     Tok = IData->Tok;
425     C   = IData->C;
426
427     /* Save a pointer to the current struct, then set it back */
428     I     = IData;
429     IData = I->Next;
430
431     /* Cleanup the current stuff */
432     if (I->Malloced) {
433         xfree (I->Data);
434     }
435     xfree (I);
436 }
437
438
439
440 static unsigned DigitVal (unsigned char C)
441 /* Convert a digit into it's numerical representation */
442 {
443     if (IsDigit (C)) {
444         return C - '0';
445     } else {
446         return toupper (C) - 'A' + 10;
447     }
448 }
449
450
451
452 static void NextChar (void)
453 /* Read the next character from the input file */
454 {
455     /* If we have an input data structure, read from there */
456     if (IData) {
457
458         C = *IData->Pos++;
459         if (C == '\0') {
460             /* End of input data, will set to last file char */
461             DoneInputData ();
462         }
463
464     } else {
465
466         /* Check for end of line, read the next line if needed */
467         while (IFile->Line [IFile->Pos.Col] == '\0') {
468
469             /* End of current line reached, read next line */
470             if (fgets (IFile->Line, sizeof (IFile->Line), IFile->F) == 0) {
471                 /* End of file. Add an empty line to the listing. This is a
472                  * small hack needed to keep the PC output in sync.
473                  */
474                 NewListingLine ("", IFile->Pos.Name, ICount);
475                 C = EOF;
476                 return;
477             }
478
479             /* One more line */
480             IFile->Pos.Line++;
481             IFile->Pos.Col = 0;
482
483             /* Remember the new line for the listing */
484             NewListingLine (IFile->Line, IFile->Pos.Name, ICount);
485
486         }
487
488         /* Return the next character from the file */
489         C = IFile->Line [IFile->Pos.Col++];
490
491     }
492 }
493
494
495
496 void LocaseSVal (void)
497 /* Make SVal lower case */
498 {
499     unsigned I = 0;
500     while (SVal [I]) {
501         SVal [I] = tolower (SVal [I]);
502         ++I;
503     }
504 }
505
506
507
508 void UpcaseSVal (void)
509 /* Make SVal upper case */
510 {
511     unsigned I = 0;
512     while (SVal [I]) {
513         SVal [I] = toupper (SVal [I]);
514         ++I;
515     }
516 }
517
518
519
520 static int CmpDotKeyword (const void* K1, const void* K2)
521 /* Compare function for the dot keyword search */
522 {
523     return strcmp (((struct DotKeyword*)K1)->Key, ((struct DotKeyword*)K2)->Key);
524 }
525
526
527
528 static unsigned char FindDotKeyword (void)
529 /* Find the dot keyword in SVal. Return the corresponding token if found,
530  * return TOK_NONE if not found.
531  */
532 {
533     static const struct DotKeyword K = { SVal, 0 };
534     struct DotKeyword* R;
535
536     /* If we aren't in ignore case mode, we have to uppercase the keyword */
537     if (!IgnoreCase) {
538         UpcaseSVal ();
539     }
540
541     /* Search for the keyword */
542     R = bsearch (&K, DotKeywords, sizeof (DotKeywords) / sizeof (DotKeywords [0]),
543                  sizeof (DotKeywords [0]), CmpDotKeyword);
544     if (R != 0) {
545         return R->Tok;
546     } else {
547         return TOK_NONE;
548     }
549 }
550
551
552
553 static void ReadIdent (unsigned Index)
554 /* Read an identifier from the current input position into Ident. Filling SVal
555  * starts at Index with the current character in C. It is assumed that any
556  * characters already filled in are ok, and the character in C is checked.
557  */
558 {
559     /* Read the identifier */
560     do {
561         if (Index < MAX_STR_LEN) {
562             SVal [Index++] = C;
563         }
564         NextChar ();
565     } while (IsIdChar (C));
566     SVal [Index] = '\0';
567
568     /* If we should ignore case, convert the identifier to upper case */
569     if (IgnoreCase) {
570         UpcaseSVal ();
571     }
572 }
573
574
575
576 static unsigned ReadStringConst (int StringTerm)
577 /* Read a string constant into SVal. Check for maximum string length and all
578  * other stuff. The length of the string is returned.
579  */
580 {
581     unsigned I;
582
583     /* Skip the leading string terminator */
584     NextChar ();
585
586     /* Read the string */
587     I = 0;
588     while (1) {
589         if (C == StringTerm) {
590             break;
591         }
592         if (C == '\n' || C == EOF) {
593             Error ("Newline in string constant");
594             break;
595         }
596
597         /* Check for string length, print an error message once */
598         if (I == MAX_STR_LEN) {
599             Error ("Maximum string size exceeded");
600         } else if (I < MAX_STR_LEN) {
601             SVal [I] = C;
602         }
603         ++I;
604
605         /* Skip the character */
606         NextChar ();
607     }
608
609     /* Skip the trailing terminator */
610     NextChar ();
611
612     /* Terminate the string */
613     if (I >= MAX_STR_LEN) {
614         I = MAX_STR_LEN;
615     }
616     SVal [I] = '\0';
617
618     /* Return the length of the string */
619     return I;
620 }
621
622
623
624 void NextRawTok (void)
625 /* Read the next raw token from the input stream */
626 {
627     /* If we've a forced end of assembly, don't read further */
628     if (ForcedEnd) {
629         Tok = TOK_EOF;
630         return;
631     }
632
633 Restart:
634     /* Check if we have tokens from another input source */
635     if (InputFromStack ()) {
636         return;
637     }
638
639 Again:
640     /* Skip whitespace, remember if we had some */
641     if ((WS = IsBlank (C)) != 0) {
642         do {
643             NextChar ();
644         } while (IsBlank (C));
645     }
646
647     /* If we're reading from the file, update the location from where the
648      * next token will be read. If we're reading from input data, keep the
649      * current position.
650      */
651     if (IData == 0) {
652         CurPos = IFile->Pos;
653     }
654
655     /* Hex number or PC symbol? */
656     if (C == '$') {
657         NextChar ();
658
659         /* Hex digit must follow or DollarIsPC must be enabled */
660         if (!IsXDigit (C)) {
661             if (DollarIsPC) {
662                 Tok = TOK_PC;
663                 return;
664             } else {
665                 Error ("Hexadecimal digit expected");
666             }
667         }
668
669         /* Read the number */
670         IVal = 0;
671         while (IsXDigit (C)) {
672             if (IVal & 0xF0000000) {
673                 Error ("Overflow in hexadecimal number");
674                 IVal = 0;
675             }
676             IVal = (IVal << 4) + DigitVal (C);
677             NextChar ();
678         }
679
680         /* This is an integer constant */
681         Tok = TOK_INTCON;
682         return;
683     }
684
685     /* Binary number? */
686     if (C == '%') {
687         NextChar ();
688
689         /* 0 or 1 must follow */
690         if (!IsBDigit (C)) {
691             Error ("Binary digit expected");
692         }
693
694         /* Read the number */
695         IVal = 0;
696         while (IsBDigit (C)) {
697             if (IVal & 0x80000000) {
698                 Error ("Overflow in binary number");
699                 IVal = 0;
700             }
701             IVal = (IVal << 1) + DigitVal (C);
702             NextChar ();
703         }
704
705         /* This is an integer constant */
706         Tok = TOK_INTCON;
707         return;
708     }
709
710     /* Decimal number? */
711     if (IsDigit (C)) {
712
713         /* Read the number */
714         IVal = 0;
715         while (IsDigit (C)) {
716             if (IVal > (long) (0xFFFFFFFFUL / 10)) {
717                 Error ("Overflow in decimal number");
718                 IVal = 0;
719             }
720             IVal = (IVal * 10) + DigitVal (C);
721             NextChar ();
722         }
723
724         /* This is an integer constant */
725         Tok = TOK_INTCON;
726         return;
727     }
728
729     /* Control command? */
730     if (C == '.') {
731
732         /* Remember and skip the dot */
733         NextChar ();
734
735         /* Check if it's just a dot */
736         if (!IsIdStart (C)) {
737
738             /* Just a dot */
739             Tok = TOK_DOT;
740
741         } else {
742
743             /* Read the remainder of the identifier */
744             SVal[0] = '.';
745             ReadIdent (1);
746
747             /* Dot keyword, search for it */
748             Tok = FindDotKeyword ();
749             if (Tok == TOK_NONE) {
750
751                 /* Not found */
752                 if (!LeadingDotInIdents) {
753                     /* Invalid pseudo instruction */
754                     Error ("`%s' is not a recognized control command", SVal);
755                     goto Again;
756                 }
757
758                 /* An identifier with a dot. Check if it's a define style
759                  * macro.
760                  */
761                 if (IsDefine (SVal)) {
762                     /* This is a define style macro - expand it */
763                     MacExpandStart ();
764                     goto Restart;
765                 }
766
767                 /* Just an identifier with a dot */
768                 Tok = TOK_IDENT;
769             }
770
771         }
772         return;
773     }
774
775     /* Local symbol? */
776     if (C == LocalStart) {
777
778         /* Read the identifier */
779         ReadIdent (0);
780
781         /* Start character alone is not enough */
782         if (SVal [1] == '\0') {
783             Error ("Invalid cheap local symbol");
784             goto Again;
785         }
786
787         /* A local identifier */
788         Tok = TOK_LOCAL_IDENT;
789         return;
790     }
791
792
793     /* Identifier or keyword? */
794     if (IsIdStart (C)) {
795
796         /* Read the identifier */
797         ReadIdent (0);
798
799         /* Check for special names. Bail out if we have identified the type of
800          * the token. Go on if the token is an identifier.
801          */
802         if (SVal[1] == '\0') {
803             switch (toupper (SVal [0])) {
804
805                 case 'A':
806                     if (C == ':') {
807                         NextChar ();
808                         Tok = TOK_OVERRIDE_ABS;
809                     } else {
810                         Tok = TOK_A;
811                     }
812                     return;
813
814                 case 'F':
815                     if (C == ':') {
816                         NextChar ();
817                         Tok = TOK_OVERRIDE_FAR;
818                         return;
819                     }
820                     break;
821
822                 case 'S':
823                     Tok = TOK_S;
824                     return;
825
826                 case 'X':
827                     Tok = TOK_X;
828                     return;
829
830                 case 'Y':
831                     Tok = TOK_Y;
832                     return;
833
834                 case 'Z':
835                     if (C == ':') {
836                         NextChar ();
837                         Tok = TOK_OVERRIDE_ZP;
838                         return;
839                     }
840                     break;
841
842                 default:
843                     break;
844             }
845         }
846
847         /* Search for an opcode */
848         IVal = FindInstruction (SVal);
849         if (IVal >= 0) {
850             /* This is a mnemonic */
851             Tok = TOK_MNEMO;
852         } else if (IsDefine (SVal)) {
853             /* This is a define style macro - expand it */
854             MacExpandStart ();
855             goto Restart;
856         } else {
857             /* An identifier */
858             Tok = TOK_IDENT;
859         }
860         return;
861     }
862
863     /* Ok, let's do the switch */
864 CharAgain:
865     switch (C) {
866
867         case '+':
868             NextChar ();
869             Tok = TOK_PLUS;
870             return;
871
872         case '-':
873             NextChar ();
874             Tok = TOK_MINUS;
875             return;
876
877         case '/':
878             NextChar ();
879             Tok = TOK_DIV;
880             return;
881
882         case '*':
883             NextChar ();
884             Tok = TOK_MUL;
885             return;
886
887         case '^':
888             NextChar ();
889             Tok = TOK_XOR;
890             return;
891
892         case '&':
893             NextChar ();
894             if (C == '&') {
895                 NextChar ();
896                 Tok = TOK_BOOLAND;
897             } else {
898                 Tok = TOK_AND;
899             }
900             return;
901
902         case '|':
903             NextChar ();
904             if (C == '|') {
905                 NextChar ();
906                 Tok = TOK_BOOLOR;
907             } else {
908                 Tok = TOK_OR;
909             }
910             return;
911
912         case ':':
913             NextChar ();
914             switch (C) {
915
916                 case ':':
917                     NextChar ();
918                     Tok = TOK_NAMESPACE;
919                     break;
920
921                 case '-':
922                     IVal = 0;
923                     do {
924                         --IVal;
925                         NextChar ();
926                     } while (C == '-');
927                     Tok = TOK_ULABEL;
928                     break;
929
930                 case '+':
931                     IVal = 0;
932                     do {
933                         ++IVal;
934                         NextChar ();
935                     } while (C == '+');
936                     Tok = TOK_ULABEL;
937                     break;
938
939                 case '=':
940                     NextChar ();
941                     Tok = TOK_ASSIGN;
942                     break;
943
944                 default:
945                     Tok = TOK_COLON;
946                     break;
947             }
948             return;
949
950         case ',':
951             NextChar ();
952             Tok = TOK_COMMA;
953             return;
954
955         case ';':
956             NextChar ();
957             while (C != '\n' && C != EOF) {
958                 NextChar ();
959             }
960             goto CharAgain;
961
962         case '#':
963             NextChar ();
964             Tok = TOK_HASH;
965             return;
966
967         case '(':
968             NextChar ();
969             Tok = TOK_LPAREN;
970             return;
971
972         case ')':
973             NextChar ();
974             Tok = TOK_RPAREN;
975             return;
976
977         case '[':
978             NextChar ();
979             Tok = TOK_LBRACK;
980             return;
981
982         case ']':
983             NextChar ();
984             Tok = TOK_RBRACK;
985             return;
986
987         case '<':
988             NextChar ();
989             if (C == '=') {
990                 NextChar ();
991                 Tok = TOK_LE;
992             } else if (C == '<') {
993                 NextChar ();
994                 Tok = TOK_SHL;
995             } else if (C == '>') {
996                 NextChar ();
997                 Tok = TOK_NE;
998             } else {
999                 Tok = TOK_LT;
1000             }
1001             return;
1002
1003         case '=':
1004             NextChar ();
1005             Tok = TOK_EQ;
1006             return;
1007
1008         case '!':
1009             NextChar ();
1010             Tok = TOK_BOOLNOT;
1011             return;
1012
1013         case '>':
1014             NextChar ();
1015             if (C == '=') {
1016                 NextChar ();
1017                 Tok = TOK_GE;
1018             } else if (C == '>') {
1019                 NextChar ();
1020                 Tok = TOK_SHR;
1021             } else {
1022                 Tok = TOK_GT;
1023             }
1024             return;
1025
1026         case '~':
1027             NextChar ();
1028             Tok = TOK_NOT;
1029             return;
1030
1031         case '\'':
1032             /* Hack: If we allow ' as terminating character for strings, read
1033              * the following stuff as a string, and check for a one character
1034              * string later.
1035              */
1036             if (LooseStringTerm) {
1037                 if (ReadStringConst ('\'') == 1) {
1038                     IVal = SVal[0];
1039                     Tok = TOK_CHARCON;
1040                 } else {
1041                     Tok = TOK_STRCON;
1042                 }
1043             } else {
1044                 /* Always a character constant */
1045                 NextChar ();
1046                 if (C == '\n' || C == EOF) {
1047                     Error ("Illegal character constant");
1048                     goto CharAgain;
1049                 }
1050                 IVal = C;
1051                 Tok = TOK_CHARCON;
1052                 NextChar ();
1053                 if (C != '\'') {
1054                     Error ("Illegal character constant");
1055                 } else {
1056                     NextChar ();
1057                 }
1058             }
1059             return;
1060
1061         case '\"':
1062             ReadStringConst ('\"');
1063             Tok = TOK_STRCON;
1064             return;
1065
1066         case '\\':
1067             /* Line continuation? */
1068             if (LineCont) {
1069                 NextChar ();
1070                 if (C == '\n') {
1071                     /* Handle as white space */
1072                     NextChar ();
1073                     C = ' ';
1074                     goto Again;
1075                 }
1076             }
1077             break;
1078
1079         case '\n':
1080             NextChar ();
1081             Tok = TOK_SEP;
1082             return;
1083
1084         case EOF:
1085             /* Check if we have any open .IFs in this file */
1086             CheckOpenIfs ();
1087             /* Check if we have any open token lists in this file */
1088             CheckInputStack ();
1089
1090             /* If this was an include file, then close it and handle like a
1091              * separator. Do not close the main file, but return EOF.
1092              */
1093             if (ICount > 1) {
1094                 DoneInputFile ();
1095             } else {
1096                 Tok = TOK_EOF;
1097             }
1098             return;
1099
1100     }
1101
1102     /* If we go here, we could not identify the current character. Skip it
1103      * and try again.
1104      */
1105     Error ("Invalid input character: 0x%02X", C & 0xFF);
1106     NextChar ();
1107     goto Again;
1108 }
1109
1110
1111
1112 int TokHasSVal (enum Token Tok)
1113 /* Return true if the given token has an attached SVal */
1114 {
1115     return (Tok == TOK_IDENT || TOK_LOCAL_IDENT || Tok == TOK_STRCON);
1116 }
1117
1118
1119
1120 int TokHasIVal (enum Token Tok)
1121 /* Return true if the given token has an attached IVal */
1122 {
1123     return (Tok == TOK_INTCON || Tok == TOK_CHARCON || Tok == TOK_MNEMO);
1124 }
1125
1126
1127
1128 int GetSubKey (const char** Keys, unsigned Count)
1129 /* Search for a subkey in a table of keywords. The current token must be an
1130  * identifier and all keys must be in upper case. The identifier will be
1131  * uppercased in the process. The function returns the index of the keyword,
1132  * or -1 if the keyword was not found.
1133  */
1134 {
1135     unsigned I;
1136
1137     /* Must have an identifier */
1138     PRECONDITION (Tok == TOK_IDENT);
1139
1140     /* If we aren't in ignore case mode, we have to uppercase the identifier */
1141     if (!IgnoreCase) {
1142         UpcaseSVal ();
1143     }
1144
1145     /* Do a linear search (a binary search is not worth the effort) */
1146     for (I = 0; I < Count; ++I) {
1147         if (strcmp (SVal, Keys [I]) == 0) {
1148             /* Found it */
1149             return I;
1150         }
1151     }
1152
1153     /* Not found */
1154     return -1;
1155 }
1156
1157
1158
1159 unsigned char ParseAddrSize (void)
1160 /* Check if the next token is a keyword that denotes an address size specifier.
1161  * If so, return the corresponding address size constant, otherwise output an
1162  * error message and return ADDR_SIZE_DEFAULT.
1163  */
1164 {
1165     static const char* Keys[] = {
1166         "DIRECT", "ZEROPAGE", "ZP",
1167         "ABSOLUTE", "ABS", "NEAR",
1168         "FAR",
1169         "LONG", "DWORD",
1170     };
1171
1172     /* Check for an identifier */
1173     if (Tok != TOK_IDENT) {
1174         Error ("Address size specifier expected");
1175         return ADDR_SIZE_DEFAULT;
1176     }
1177
1178     /* Search for the attribute */
1179     switch (GetSubKey (Keys, sizeof (Keys) / sizeof (Keys [0]))) {
1180         case 0:
1181         case 1:
1182         case 2: return ADDR_SIZE_ZP;
1183         case 3:
1184         case 4:
1185         case 5: return ADDR_SIZE_ABS;
1186         case 6: return ADDR_SIZE_FAR;
1187         case 7:
1188         case 8: return ADDR_SIZE_LONG;
1189         default:
1190             Error ("Address size specifier expected");
1191             return ADDR_SIZE_DEFAULT;
1192     }
1193 }
1194
1195
1196
1197 void InitScanner (const char* InFile)
1198 /* Initialize the scanner, open the given input file */
1199 {
1200     /* Open the input file */
1201     NewInputFile (InFile);
1202 }
1203
1204
1205
1206 void DoneScanner (void)
1207 /* Release scanner resources */
1208 {
1209     DoneInputFile ();
1210 }
1211
1212
1213