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