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