ad462f38d2a108f418509542674e5b00777701f6
[rmac] / token.c
1 ////////////////////////////////////////////////////////////////////////////////////////////////////
2 // RMAC - Reboot's Macro Assembler for the Atari Jaguar Console System
3 // TOKEN.C - Token Handling
4 // Copyright (C) 199x Landon Dyer, 2011 Reboot and Friends
5 // RMAC derived from MADMAC v1.07 Written by Landon Dyer, 1986
6 // Source Utilised with the Kind Permission of Landon Dyer
7
8 #include "token.h"
9 #include "symbol.h"
10 #include "procln.h"
11 #include "macro.h"
12 #include "error.h"
13
14 #define DECL_KW                                             // Declare keyword arrays
15 #define DEF_KW                                              // Declare keyword values 
16 #include "kwtab.h"                                          // Incl generated keyword tables & defs
17
18 int lnsave;                                                 // 1; strcpy() text of current line
19 int curlineno;                                              // Current line number
20 int totlines;                                               // Total # of lines
21 int mjump_align = 0;                                        // mjump alignment flag
22 char lntag;                                                 // Line tag
23 char *curfname;                                             // Current filename
24 char tolowertab[128];                                       // Uppercase ==> lowercase 
25 char hextab[128];                                           // Table of hex values
26 char dotxtab[128];                                          // Table for ".b", ".s", etc.
27 char irbuf[LNSIZ];                                          // Text for .rept block line
28 char lnbuf[LNSIZ];                                          // Text of current line
29 WORD filecount;                                             // Unique file number counter
30 WORD cfileno;                                               // Current file number
31 TOKEN *tok;                                                 // Ptr to current token
32 TOKEN *etok;                                                // Ptr past last token in tokbuf[]
33 TOKEN tokeol[1] = {EOL};                                    // Bailout end-of-line token
34
35 // File record, used to maintain a list of every include file ever visited
36 #define FILEREC struct _filerec
37 FILEREC {
38    FILEREC *frec_next;
39    char *frec_name;
40 };
41
42 FILEREC *filerec;
43 FILEREC *last_fr;
44
45 INOBJ *cur_inobj;                                           // Ptr current input obj (IFILE/IMACRO)
46 static INOBJ *f_inobj;                                      // Ptr list of free INOBJs
47 static IFILE *f_ifile;                                      // Ptr list of free IFILEs
48 static IMACRO *f_imacro;                                    // Ptr list of free IMACROs
49
50 static TOKEN tokbuf[TOKBUFSIZE];                            // Token buffer (stack-like, all files)
51
52 char chrtab[] = {
53    ILLEG, ILLEG, ILLEG, ILLEG,                                    // NUL SOH STX ETX 
54    ILLEG, ILLEG, ILLEG, ILLEG,                                    // EOT ENQ ACK BEL 
55    ILLEG, WHITE, ILLEG, ILLEG,                                    // BS HT LF VT 
56    WHITE, ILLEG, ILLEG, ILLEG,                                    // FF CR SO SI 
57
58    ILLEG, ILLEG, ILLEG, ILLEG,                                    // DLE DC1 DC2 DC3 
59    ILLEG, ILLEG, ILLEG, ILLEG,                                    // DC4 NAK SYN ETB 
60    ILLEG, ILLEG, ILLEG, ILLEG,                                    // CAN EM SUB ESC 
61    ILLEG, ILLEG, ILLEG, ILLEG,                                    // FS GS RS US 
62
63    WHITE, MULTX, MULTX, SELF,                                     // SP ! " #
64    MULTX+CTSYM, MULTX, SELF, MULTX,                               // $ % & '
65    SELF, SELF, SELF, SELF,                                        // ( ) * +
66    SELF, SELF, STSYM, SELF,                                       // , - . /
67
68    DIGIT+HDIGIT+CTSYM, DIGIT+HDIGIT+CTSYM,                        // 0 1 
69    DIGIT+HDIGIT+CTSYM, DIGIT+HDIGIT+CTSYM,                        // 2 3 
70    DIGIT+HDIGIT+CTSYM, DIGIT+HDIGIT+CTSYM,                        // 4 5 
71    DIGIT+HDIGIT+CTSYM, DIGIT+HDIGIT+CTSYM,                        // 6 7 
72    DIGIT+HDIGIT+CTSYM, DIGIT+HDIGIT+CTSYM,                        // 8 9 
73    MULTX, MULTX,                                                  // : ; 
74    MULTX, MULTX, MULTX, STSYM+CTSYM,                              // < = > ? 
75
76    MULTX, STSYM+CTSYM+HDIGIT,                                     // @ A
77    (char)((BYTE)DOT)+STSYM+CTSYM+HDIGIT, STSYM+CTSYM+HDIGIT,              // B C
78    STSYM+CTSYM+HDIGIT, STSYM+CTSYM+HDIGIT,                        // D E
79    STSYM+CTSYM+HDIGIT, STSYM+CTSYM,                               // F G
80    STSYM+CTSYM, STSYM+CTSYM, STSYM+CTSYM, STSYM+CTSYM,            // H I J K
81    (char)((BYTE)DOT)+STSYM+CTSYM, STSYM+CTSYM, STSYM+CTSYM, STSYM+CTSYM,  // L M N O
82
83    STSYM+CTSYM, STSYM+CTSYM, STSYM+CTSYM, (char)((BYTE)DOT)+STSYM+CTSYM,  // P Q R S
84    STSYM+CTSYM, STSYM+CTSYM, STSYM+CTSYM, (char)((BYTE)DOT)+STSYM+CTSYM,  // T U V W
85    STSYM+CTSYM, STSYM+CTSYM, STSYM+CTSYM, SELF,                   // X Y Z [
86    SELF, SELF, MULTX, STSYM+CTSYM,                                // \ ] ^ _
87
88    ILLEG, STSYM+CTSYM+HDIGIT,                                     // ` a
89    (char)((BYTE)DOT)+STSYM+CTSYM+HDIGIT, STSYM+CTSYM+HDIGIT,              // b c
90    STSYM+CTSYM+HDIGIT, STSYM+CTSYM+HDIGIT,                        // d e
91    STSYM+CTSYM+HDIGIT, STSYM+CTSYM,                               // f g
92    STSYM+CTSYM, STSYM+CTSYM, STSYM+CTSYM, STSYM+CTSYM,            // h i j k
93    (char)((BYTE)DOT)+STSYM+CTSYM, STSYM+CTSYM, STSYM+CTSYM, STSYM+CTSYM,  // l m n o
94
95    STSYM+CTSYM, STSYM+CTSYM, STSYM+CTSYM, (char)((BYTE)DOT)+STSYM+CTSYM,  // p q r s 
96    STSYM+CTSYM, STSYM+CTSYM, STSYM+CTSYM, (char)((BYTE)DOT)+STSYM+CTSYM,  // t u v w 
97    STSYM+CTSYM, STSYM+CTSYM, STSYM+CTSYM, SELF,                   // x y z { 
98    SELF, SELF, SELF, ILLEG                                        // | } ~ DEL 
99 };
100
101 // Names of registers
102 static char *regname[] = {
103    "d0", "d1",  "d2",  "d3", "d4", "d5", "d6", "d7",
104    "a0", "a1",  "a2",  "a3", "a4", "a5", "a6", "a7",
105    "pc", "ssp", "usp", "sr", "ccr"
106 };
107
108 static char *riscregname[] = {
109     "r0",  "r1",  "r2",  "r3",  "r4", "r5",   "r6",  "r7", 
110     "r8",  "r9", "r10", "r11", "r12", "r13", "r14", "r15",
111    "r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23",
112    "r24", "r25", "r26", "r27", "r28", "r29", "r30", "r31"
113 };
114
115 //
116 // --- Make `fnum' the Current `curfname' ----------------------------------------------------------
117 //
118
119 void setfnum(WORD fnum) {
120    FILEREC *fr;
121
122    for(fr = filerec; fr != NULL && fnum--; fr = fr->frec_next)
123       ;
124
125    if(fr == NULL)
126       curfname = "(*top*)";
127    else
128       curfname = fr->frec_name;
129 }
130
131 //
132 // --- Allocate an IFILE or IMACRO -----------------------------------------------------------------
133 //
134
135 INOBJ *a_inobj(int typ) {
136    INOBJ *inobj;
137    IFILE *ifile;
138    IMACRO *imacro;
139
140    // Allocate and initialize INOBJ first
141    if(f_inobj == NULL)
142       inobj = (INOBJ *)amem((LONG)sizeof(INOBJ));
143    else {
144       inobj = f_inobj;
145       f_inobj = f_inobj->in_link;
146    }
147
148    switch(typ) {
149       case SRC_IFILE:                                       // Alloc and init an IFILE
150          if(f_ifile == NULL)
151             ifile = (IFILE *)amem((LONG)sizeof(IFILE));
152          else {
153             ifile = f_ifile;
154             f_ifile = f_ifile->if_link;
155          }
156          inobj->inobj.ifile = ifile;
157          break;
158       case SRC_IMACRO:                                      // Alloc and init an IMACRO 
159          if(f_imacro == NULL)
160             imacro = (IMACRO *)amem((LONG)sizeof(IMACRO));
161          else {
162             imacro = f_imacro;
163             f_imacro = f_imacro->im_link;
164          }
165          inobj->inobj.imacro = imacro;
166          break;
167       case SRC_IREPT:                                       // Alloc and init an IREPT
168          inobj->inobj.irept = (IREPT *)amem((LONG)sizeof(IREPT));
169          DEBUG printf("alloc IREPT\n");
170          break;
171    }
172
173    // Install INOBJ on top of input stack
174    inobj->in_ifent = ifent;                                 // Record .if context on entry
175    inobj->in_type = (WORD)typ;
176    inobj->in_otok = tok;
177    inobj->in_etok = etok;
178    inobj->in_link = cur_inobj;
179    cur_inobj = inobj;
180
181    return(inobj);
182 }
183
184 //
185 // -------------------------------------------------------------------------------------------------
186 // Perform macro substitution from 'orig' to 'dest'. Return OK or some error.
187 // A macro reference is in one of two forms:
188 // \name <non-name-character>
189 // \{name}
190 // A doubled backslash (\\) is compressed to a single backslash (\).
191 // Argument definitions have been pre-tokenized, so we have to turn them back into text.  This 
192 // means that numbers, in particular, become hex, regardless of their representation when the macro
193 // was invoked. This is a hack. 
194 // A label may appear at the beginning of the line:
195 // :<name><whitespace>
196 // (the colon must be in the first column).  These labels are stripped before macro expansion takes
197 // place.
198 // -------------------------------------------------------------------------------------------------
199 //
200
201 int mexpand(char *src, char *dest, int destsiz) {
202    char *s;
203    char *d = NULL;
204    char *dst;                                               // Next dest slot
205    char *edst;                                              // End+1 of dest buffer
206    int i;
207    int questmark;                                           // \? for testing argument existence
208    TOKEN *tk;
209    char mname[128];                                         // Assume max size of a formal arg name
210    int macnum;
211    SYM *arg;
212    IMACRO *imacro;
213    char numbuf[20];                                         // Buffer for text of CONSTs
214
215    imacro = cur_inobj->inobj.imacro;
216    macnum = (int)(imacro->im_macro->sattr);
217
218    --destsiz;
219    dst = dest;
220    edst = dest + destsiz;
221
222    // Check for (and skip over) any "label" on the line
223    s = src;
224    if(*s == ':') {
225       while(*s != EOS && !(chrtab[*s] & WHITE)) ++s;
226       if(*s != EOS) ++s;                                    // Skip first whitespace
227    }
228
229    // Expand the rest of the line
230    while(*s != EOS)
231       if(*s != '\\') {                                      // Copy single character
232          if(dst >= edst)
233             goto overflow;
234          *dst++ = *s++;
235       } else {                                              // Do macro expansion
236          questmark = 0;
237
238          // Do special cases
239          switch(*++s) {
240             case '\\':                                      // \\, \ (collapse to single backslash)
241                if(dst >= edst)
242                   goto overflow;
243                *dst++ = *s++;
244                continue;
245             case '?':                                       // \? <macro>  set `questmark' flag 
246                ++s;
247                questmark = 1;
248                break;
249             case '#':                                       // \#, number of arguments 
250                sprintf(numbuf, "%d", (int)imacro->im_nargs);
251                goto copystr;
252             case '!':                                       // \! size suffix supplied on invocation
253                switch((int)imacro->im_siz) {
254                   case SIZN: d = "";   break;
255                   case SIZB: d = ".b"; break;
256                   case SIZW: d = ".w"; break;
257                   case SIZL: d = ".l"; break;
258                }
259                goto copy_d;
260             case '~':                                       // ==> unique label string Mnnnn... 
261                sprintf(numbuf, "M%ld", curuniq);
262                
263                copystr:
264
265                d = numbuf;
266
267                copy_d:
268
269                ++s;
270                while(*d != EOS)
271                   if(dst >= edst) goto overflow;
272                   else *dst++ = *d++;
273                continue;
274             case EOS:
275                return(error("missing argument name"));
276          }
277
278          // \n ==> argument number 'n', 0..9
279          if(chrtab[*s] & DIGIT) {
280             i = *s++ - '1';
281             if(i < 0)
282                i = 9;
283             goto arg_num;
284          }
285
286          // Get argument name: \name, \{name}
287          d = mname;
288          if(*s != '{') {                                    // \foo
289             do 
290                *d++ = *s++;
291             while(chrtab[*s] & CTSYM);
292          } else {                                           // \\{foo} 
293             for(++s; *s != EOS && *s != '}';)
294                *d++ = *s++;
295             if(*s != '}') return(error("missing '}'"));
296             else ++s;
297          }
298          *d = EOS;
299
300          // Lookup the argument and copy its (string) value into the destination string
301                         DEBUG printf("mname='%s'\n", mname);
302          if((arg = lookup(mname, MACARG, macnum)) == NULL)
303             return(errors("undefined argument: '%s'", mname));
304          else {
305             // Convert a string of tokens (terminated with EOL) back into text. If an argument 
306             // is out of range (not specified in the macro invocation) then it is ignored.
307             i = (int)arg->svalue;
308
309             arg_num:
310
311             DEBUG printf("~argnumber=%d\n", i);
312
313             tk = NULL;
314             if(i < imacro->im_nargs)
315                tk = argp[i];
316
317             // \?arg yields:
318             //    0  if the argument is empty or non-existant,
319             //    1  if the argument is not empty
320             if(questmark) {
321                if(tk == NULL || *tk == EOL)
322                   questmark = 0;
323                if(dst >= edst)
324                   goto overflow;
325                *dst++ = (char)(questmark + '0');
326                continue;
327             }
328
329             if(tk != NULL)                                  // arg# is in range, so expand it
330                while(*tk != EOL) {
331                   // Reverse-translation from a token number to a string.  This is a hack.
332                   // It might be better table-driven.
333                   d = NULL;
334                   if((*tk >= KW_D0) && !rdsp && !rgpu) {
335                      d = regname[(int)*tk++ - KW_D0];
336                      goto strcopy;
337                   } else if((*tk >= KW_R0) && (*tk <= KW_R31)) {
338                      d = riscregname[(int)*tk++ - KW_R0];
339                      goto strcopy;
340                   } else 
341                      switch((int)*tk++) {
342                         case SYMBOL:
343                            d = (char *)*tk++;
344                            break;
345                         case STRING:
346                            d = (char *)*tk++;
347
348                            if(dst >= edst)
349                               goto overflow;
350                            *dst++ = '"';
351
352                            while(*d != EOS)
353                               if(dst >= edst)
354                                  goto overflow;
355                               else *dst++ = *d++;
356
357                            if(dst >= edst)
358                               goto overflow;
359                            *dst++ = '"';
360                            continue;
361                            break;
362                         case CONST:
363                            sprintf(numbuf, "$%lx", (LONG)*tk++);
364                            d = numbuf;
365                            break;
366                         case DEQUALS:
367                            d = "==";
368                            break;
369                         case SET:
370                            d = "set";
371                            break;
372                         case COLON:
373                            d = ":";
374                            break;
375                         case DCOLON:
376                            d = "::";
377                            break;
378                         case GE:
379                            d = ">=";
380                            break;
381                         case LE:
382                            d = "<=";
383                            break;
384                         case NE:
385                            d = "<>";
386                            break;
387                         case SHR:
388                            d = ">>";
389                            break;
390                         case SHL:
391                            d = "<<";
392                            break;
393                         case DOTB:
394                            d = ".b";
395                            break;
396                         case DOTW:
397                            d = ".w";
398                            break;
399                         case DOTL:
400                            d = ".l";
401                            break;
402                         case CR_DATE:
403                            d = "^^date";
404                            break;
405                         case CR_TIME:
406                            d = "^^time";
407                            break;
408                         case CR_DEFINED:
409                            d = "^^defined ";
410                            break;
411                         case CR_REFERENCED:
412                            d = "^^referenced ";
413                            break;
414                         case CR_STREQ:
415                            d = "^^streq ";
416                            break;
417                         case CR_MACDEF:
418                            d = "^^macdef ";
419                            break;
420                         default:
421                            if(dst >= edst)
422                               goto overflow;
423                            *dst++ = (char)*(tk-1);
424                            break;
425                      }
426
427                   // If 'd' != NULL, copy string to destination
428                   if(d != NULL) {
429
430                      strcopy:
431
432                      DEBUG printf("d='%s'\n", d);
433                      while(*d != EOS)
434                         if(dst >= edst)
435                            goto overflow;
436                         else *dst++ = *d++;
437                   }
438                }
439          }
440       }
441
442         
443    *dst = EOS;
444    return(OK);
445
446    overflow:
447
448    *dst = EOS;
449    return(fatal("line too long as a result of macro expansion"));
450 }
451
452 //
453 // --- Get Next Line of Text from a Macro ----------------------------------------------------------
454 //
455
456 char *getmln(void) {
457    IMACRO *imacro;
458    LONG *strp;
459    unsigned source_addr;
460
461    imacro = cur_inobj->inobj.imacro;
462    strp = imacro->im_nextln;
463
464    if(strp == NULL)                                         // End-of-macro
465       return(NULL);
466
467    imacro->im_nextln = (LONG *)*strp;
468    mexpand((char *)(strp + 1), imacro->im_lnbuf, LNSIZ);
469
470    if(!strcmp(imacro->im_macro->sname, "mjump") && !mjump_align) {
471       // if we need to adjust the alignment of the jump source address to meet the rules of
472       // gpu main execution we need to skip the first nop of the macro. This is simpler than
473       // trying to insert nop's mid macro.
474       source_addr = (orgactive) ? orgaddr : sloc;
475       source_addr += 8;
476       if(source_addr % 4) {
477          strp = imacro->im_nextln;
478          if(strp == NULL) return(NULL);
479          imacro->im_nextln = (LONG *)*strp;
480          mexpand((char *)(strp + 1), imacro->im_lnbuf, LNSIZ);
481       }
482       mjump_align = 1;
483    }
484
485    return(imacro->im_lnbuf);
486 }
487
488 //
489 // --- Get Next Line of Text from a Repeat Block ---------------------------------------------------
490 //
491  
492 char *getrln(void) {
493    IREPT *irept;
494    LONG *strp;
495
496    irept = cur_inobj->inobj.irept;
497    strp = irept->ir_nextln;         // initial null
498
499    // Do repeat at end of .rept block's string list
500    if(strp == NULL) {
501       DEBUG printf("back-to-top-of-repeat-block count=%d\n", irept->ir_count);
502       irept->ir_nextln = irept->ir_firstln;  // copy first line
503       if(irept->ir_count-- == 0) {
504          DEBUG printf("end-repeat-block\n");
505          return(NULL);
506       }
507       strp = irept->ir_nextln;               //strp
508    }
509
510    strcpy(irbuf, (char*)(irept->ir_nextln + 1));
511
512    DEBUG printf("repeat line='%s'\n", irbuf);
513    irept->ir_nextln = (LONG *)*strp;
514
515    return(irbuf);
516 }
517
518 //
519 // --- Include a Source File used at the Root, and for ".include" Files ----------------------------
520 //
521
522 int include(int handle, char *fname) {
523    IFILE *ifile;
524    INOBJ *inobj;
525    FILEREC *fr;
526
527    if(verb_flag) printf("[Including: %s]\n", fname);        // Verbose mode
528
529    // Alloc and initialize include-descriptors
530    inobj = a_inobj(SRC_IFILE);
531    ifile = inobj->inobj.ifile;
532
533    ifile->ifhandle = handle;                                // Setup file handle
534    ifile->ifind = ifile->ifcnt = 0;                         // Setup buffer indices
535    ifile->ifoldlineno = curlineno;                          // Save old line number
536    ifile->ifoldfname = curfname;                            // Save old filename
537    ifile->ifno = cfileno;                                   // Save old file number
538    cfileno = ++filecount;                                   // Compute new file number
539    curfname = nstring(fname);                                 // Set current filename (alloc storage)
540    curlineno = 0;                                           // Start on line zero
541
542    // Add another file to the file-record
543    fr = (FILEREC *)amem((LONG)sizeof(FILEREC));
544    fr->frec_next = NULL;
545    fr->frec_name = curfname;
546    if(last_fr == NULL)
547       filerec = fr;                                         // Add first filerec 
548    else
549       last_fr->frec_next = fr;                              // Append to list of filerecs 
550    last_fr = fr;
551
552    return(OK);
553 }
554
555 //
556 // --- Initialize Tokenizer ------------------------------------------------------------------------
557 //
558
559 void init_token(void) {
560    int i;                                                   // Iterator
561    char *htab = "0123456789abcdefABCDEF";                   // Hex character table
562
563    lnsave = 0;                                              // Don't save lines
564    curfname = "";                                           // No file, empty filename
565    filecount = (WORD)-1;
566    cfileno = (WORD)-1;                                      // cfileno gets bumped to 0
567    curlineno = 0;
568    totlines = 0;
569    etok = tokbuf;
570    f_inobj = NULL;
571    f_ifile = NULL;
572    f_imacro = NULL;
573    cur_inobj = NULL;
574    filerec = NULL;
575    last_fr = NULL;
576    lntag = SPACE;
577
578    // Initialize hex, "dot" and tolower tables
579    for(i = 0; i < 128; ++i) {
580       hextab[i] = -1;
581       dotxtab[i] = 0;
582       tolowertab[i] = (char)i;
583    }
584    for(i = 0; htab[i] != EOS; ++i)
585       hextab[htab[i]] = (char)((i < 16) ? i : i - 6);
586    for(i = 'A'; i <= 'Z'; ++i)
587       tolowertab[i] |= 0x20;
588
589    // These characters are legal immediately after a period
590    dotxtab['b'] = DOTB;                                     // .b .B .s .S 
591    dotxtab['B'] = DOTB;
592    dotxtab['s'] = DOTB;
593    dotxtab['S'] = DOTB;
594    dotxtab['w'] = DOTW;                                     // .w .W 
595    dotxtab['W'] = DOTW;
596    dotxtab['l'] = DOTL;                                     // .l .L 
597    dotxtab['L'] = DOTL;
598    dotxtab['I'] = DOTI;                                     // .l .L 
599    dotxtab['I'] = DOTI;
600 }
601
602 //
603 // --- Pop the Current Input Level -----------------------------------------------------------------
604 //
605 int fpop(void) {
606    INOBJ *inobj;
607    IFILE *ifile;
608    IMACRO *imacro;
609    LONG *p, *p1;
610
611    inobj = cur_inobj;
612    if(inobj != NULL) {
613       // Pop IFENT levels until we reach the conditional assembly context we were at when the 
614       // input object was entered.
615       while(ifent != inobj->in_ifent)
616          d_endif();
617
618       tok = inobj->in_otok;                                 // Restore tok and otok
619       etok = inobj->in_etok;
620
621       switch(inobj->in_type) {
622          case SRC_IFILE:                                    // Pop and release an IFILE
623             if(verb_flag)
624                printf("[Leaving: %s]\n", curfname);
625             ifile = inobj->inobj.ifile;
626             ifile->if_link = f_ifile;
627             f_ifile = ifile;
628             close(ifile->ifhandle);                         // Close source file
629             curfname = ifile->ifoldfname;                   // Set current filename
630             curlineno = ifile->ifoldlineno;                 // Set current line# 
631             DEBUG printf("cfileno=%d ifile->ifno=%d\n", (int)cfileno, (int)ifile->ifno);
632             cfileno = ifile->ifno;                          // Restore current file number
633             break;
634          case SRC_IMACRO:                                   // Pop and release an IMACRO
635             imacro = inobj->inobj.imacro;
636             imacro->im_link = f_imacro;
637             f_imacro = imacro;
638             break;
639          case SRC_IREPT:                                    // Pop and release an IREPT
640             DEBUG printf("dealloc IREPT\n");
641             p = inobj->inobj.irept->ir_firstln;
642             while(p != NULL) {
643                p1 = (LONG *)*p;
644                p = p1;
645             }
646             break;
647       }
648
649       cur_inobj = inobj->in_link;
650       inobj->in_link = f_inobj;
651       f_inobj = inobj;
652    }
653
654    return(0);
655 }
656
657 //
658 // --- Get line from file into buf, return NULL on EOF or ptr to the start of a null-term line -----
659 //
660
661 char *getln(void) {
662    IFILE *fl;
663    int i, j;
664    char *p, *d;
665    int readamt;
666
667    readamt = -1;                                            // 0 if last read() yeilded 0 bytes
668    fl = cur_inobj->inobj.ifile;
669
670    for(;;) {
671       // Scan for next end-of-line; handle stupid text formats by treating \r\n the same as \n.
672       // (lone '\r' at end of buffer means we have to check for '\n').
673       i = 0;
674       j = fl->ifcnt;
675       d = &fl->ifbuf[fl->ifind];
676
677       for(p = d; i < j; ++i, ++p) {
678          if(*p == '\r' || *p == '\n') {
679             ++i;
680             if(*p == '\r') {
681                if(i >= j) {
682                   break;                                    // Look for '\n' to eat 
683                } else if(p[1] == '\n') {
684                   ++i;
685                }
686             }
687
688             *p = '\0';
689
690             fl->ifind += i;
691             fl->ifcnt -= i;
692             return(d);
693          }
694       }
695
696       // Handle hanging lines by ignoring them (Input file is exhausted, no \r or \n on last line)
697       if(!readamt && fl->ifcnt) {
698          fl->ifcnt = 0;
699          *p = '\0';
700          return(NULL);
701       }
702
703       // Truncate and return absurdly long lines.
704       if(fl->ifcnt >= QUANTUM) {
705          fl->ifbuf[fl->ifind + fl->ifcnt - 1] = '\0';
706          fl->ifcnt = 0;
707          return(&fl->ifbuf[fl->ifind]);
708       }
709
710       // Relocate what's left of a line to the beginning of the buffer, and read some more of the 
711       // file in; return NULL if the buffer's empty and on EOF.
712       if(fl->ifind != 0) {
713          p = &fl->ifbuf[fl->ifind];
714          d = &fl->ifbuf[fl->ifcnt & 1];
715          for(i = 0; i < fl->ifcnt; ++i)
716             *d++ = *p++;
717          fl->ifind = fl->ifcnt & 1;
718       }
719
720       if((readamt = read(fl->ifhandle, &fl->ifbuf[fl->ifind + fl->ifcnt], QUANTUM)) < 0)
721          return(NULL);
722
723       if((fl->ifcnt += readamt) == 0)
724          return(NULL);
725    }
726 }
727
728 //
729 // --- Tokenize a Line -----------------------------------------------------------------------------
730 //
731
732 int tokln(void) {
733    char *ln = NULL;                                         // Ptr to current position in line
734    char *p;                                                 // Random character ptr
735    TOKEN *tk;                                               // Token-deposit ptr
736    int state = 0;                                           // State for keyword detector
737    int j = 0;                                               // Var for keyword detector
738    char c;                                                  // Random char
739    VALUE v;                                                 // Random value
740    char *nullspot = NULL;                                   // Spot to clobber for SYMBOL terminatn
741    int stuffnull;                                           // 1:terminate SYMBOL '\0' at *nullspot
742    char c1;
743
744    retry:
745
746    if(cur_inobj == NULL)                                    // Return EOF if input stack is empty
747       return(TKEOF);
748
749    // Get another line of input from the current input source: a file, a macro, or a repeat-block
750    switch(cur_inobj->in_type) {
751       // Include-file:
752       // o  handle EOF;
753       // o  bump source line number;
754       // o  tag the listing-line with a space;
755       // o  kludge lines generated by Alcyon C.
756       case SRC_IFILE:
757          if((ln = getln()) == NULL) {
758             fpop();                                         // Pop input level
759             goto retry;                                     // Try for more lines 
760          }
761          ++curlineno;                                       // Bump line number
762          lntag = SPACE;
763          if(as68_flag) {
764             // AS68 compatibility, throw away all lines starting with back-quotes, tildes, or '*'
765             // On other lines, turn the first '*' into a semi-colon.
766             if(*ln == '`' || *ln == '~' || *ln == '*') *ln = ';';
767             else for(p = ln; *p != EOS; ++p) {
768                if(*p == '*') {
769                   *p = ';';
770                   break;
771                }
772             }
773          }
774          break;
775
776       // Macro-block:
777       // o  Handle end-of-macro;
778       // o  tag the listing-line with an at (@) sign.
779       case SRC_IMACRO:
780          if((ln = getmln()) == NULL) {
781             exitmac();                                      // Exit macro (pop args, do fpop(), etc)
782             goto retry;                                     // Try for more lines...
783          }
784          lntag = '@';
785          break;
786
787       // Repeat-block:
788       // o  Handle end-of-repeat-block;
789       // o  tag the listing-line with a pound (#) sign.
790       case SRC_IREPT:
791          if((ln = getrln()) == NULL) {
792             fpop();
793             goto retry;
794          }
795          lntag = '#';
796          break;
797    }
798
799    // Save text of the line.  We only do this during listings and within macro-type blocks, 
800    // since it is expensive to unconditionally copy every line.
801    if(lnsave) strcpy(lnbuf, ln);
802
803    // General house-keeping
804         tok = tokeol;                                            // Set "tok" to EOL in case of error
805         tk = etok;                                               // Reset token ptr
806         stuffnull = 0;                                           // Don't stuff nulls
807         ++totlines;                                              // Bump total #lines assembled
808
809    // See if the entire line is a comment.  This is a win if the programmer puts in lots of comments
810    if(*ln == '*' || *ln == ';' || ((*ln == '/') && (*(ln+1) == '/'))) goto goteol;
811
812    // Main tokenization loop;
813    // o  skip whitespace;
814    // o  handle end-of-line;
815    // o  handle symbols;
816    // o  handle single-character tokens (operators, etc.);
817    // o  handle multiple-character tokens (constants, strings, etc.).
818    for(; *ln != EOS;) {
819       // Skip whitespace, handle EOL
820       while((int)chrtab[*ln] & WHITE)
821          ++ln;
822
823       // Handle EOL, comment with ';'
824       if(*ln == EOS || *ln == ';'|| ((*ln == '/') && (*(ln+1) == '/'))) 
825          break;
826
827       // Handle start of symbol. Symbols are null-terminated in place. The termination is
828       // always one symbol behind, since there may be no place for a null in the case that 
829       // an operator immediately follows the name.
830       c = chrtab[*ln];
831       if(c & STSYM) {
832          if(stuffnull)                                      // Terminate old symbol 
833             *nullspot = EOS;
834          v = 0;                                             // Assume no DOT attrib follows symbol
835          stuffnull = 1;
836          p = nullspot = ln++;                               // Nullspot -> start of this symbol
837
838          // Find end of symbol (and compute its length)
839          for(j = 1; (int)chrtab[*ln] & CTSYM; ++j)
840             ++ln;
841
842          // Handle "DOT" special forms (like ".b") that follow a normal symbol or keyword:
843          if(*ln == '.') {
844             *ln++ = EOS;                                    // Terminate symbol
845             stuffnull = 0;                                  // And never try it again 
846
847             // Character following the `.' must have a DOT attribute, and the chararacter after 
848             // THAT one must not have a start-symbol attribute (to prevent symbols that look
849             // like, for example, "zingo.barf", which might be a good idea anyway....)
850             if((((int)chrtab[*ln] & DOT) == 0) || ((int)dotxtab[*ln] <= 0))
851                return(error("[bwsl] must follow `.' in symbol"));
852             v = (VALUE)dotxtab[*ln++];
853             if((int)chrtab[*ln] & CTSYM)
854                return(error("misuse of `.', not allowed in symbols"));
855          }
856
857          // If the symbol is small, check to see if it's really the name of a register.
858          if(j <= KWSIZE) {
859             for(state = 0; state >= 0;) {
860                j = (int)tolowertab[*p++];
861                j += kwbase[state];
862                if(kwcheck[j] != state) {
863                   j = -1;
864                   break;
865                }
866
867                if(*p == EOS || p == ln) {
868                   j = kwaccept[j];
869                   break;
870                }
871
872                state = kwtab[j];
873             }
874          } else {
875             j = -1;
876          }
877
878          //make j = -1 if time, date etc with no preceeding ^^
879          //defined, referenced, streq, macdef, date and time
880          switch((TOKEN)j) {
881             case 112:   // defined
882             case 113:   // referenced
883             case 118:   // streq
884             case 119:   // macdef
885             case 120:   // time
886             case 121:   // date
887                j = -1;
888                break;
889          }
890
891          if(j < 0 || state < 0) {
892             *tk++ = SYMBOL;
893             *tk++ = (TOKEN)nullspot;
894          } else {
895             *tk++ = (TOKEN)j;
896             stuffnull = 0;
897          }
898
899          if(v)                                              // Record attribute token (if any)
900             *tk++ = (TOKEN)v;
901
902          if(stuffnull)                                      // Arrange for string termination 
903             nullspot = ln;
904          continue;
905       }
906
907       // Handle identity tokens
908       if(c & SELF) {
909          *tk++ = *ln++;
910          continue;
911       }
912
913       // Handle multiple-character tokens
914       if(c & MULTX) {
915          switch(*ln++) {
916             case '!':                                       // ! or != 
917                if(*ln == '=') {
918                   *tk++ = NE;
919                   ++ln;
920                } else *tk++ = '!';
921                continue;
922             case '\'':                                      // 'string' 
923             case '\"':                                      // "string" 
924                c1 = ln[-1];
925                *tk++ = STRING;
926                *tk++ = (TOKEN)ln;
927
928                for(p = ln; *ln != EOS && *ln != c1;) {
929                   c = *ln++;
930                   if(c == '\\')
931                      switch(*ln++) {
932                         case EOS:
933                            return(error("unterminated string"));
934                         case 'e':
935                            c = '\033';
936                            break;
937                         case 'n':
938                            c = '\n';
939                            break;
940                         case 'b':
941                            c = '\b';
942                            break;
943                         case 't':
944                            c = '\t';
945                            break;
946                         case 'r':
947                            c = '\r';
948                            break;
949                         case 'f':
950                            c = '\f';
951                            break;
952                         case '\"':
953                            c = '\"';
954                            break;
955                         case '\'':
956                            c = '\'';
957                            break;
958                         case '\\':
959                            c = '\\';
960                            break;
961                         default:
962                            warn("bad backslash code in string");
963                            --ln;
964                            break;
965                      }
966                   *p++ = c;
967                }
968
969                if(*ln++ != c1)
970                   return(error("unterminated string"));
971                *p++ = EOS;
972                continue;
973             case '$':                                       // $, hex constant
974                if((int)chrtab[*ln] & HDIGIT) {
975                   v = 0;
976                   while((int)hextab[*ln] >= 0)
977                      v = (v << 4) + (int)hextab[*ln++];
978                   if(*ln == '.') {
979                      if((*(ln+1) == 'b') || (*(ln+1) == 'B')) { v &= 0x000000FF; ln += 2; }
980                      if((*(ln+1) == 'w') || (*(ln+1) == 'W')) { v &= 0x0000FFFF; ln += 2; }
981                      if((*(ln+1) == 'l') || (*(ln+1) == 'L')) { ln += 2; }
982                   }
983                   *tk++ = CONST;
984                   *tk++ = v;
985                } else *tk++ = '$';
986                continue;
987             case '<':                                       // < or << or <> or <= 
988                switch(*ln) {
989                   case '<':
990                      *tk++ = SHL;
991                      ++ln;
992                      continue;
993                   case '>':
994                      *tk++ = NE;
995                      ++ln;
996                      continue;
997                   case '=':
998                      *tk++ = LE;
999                      ++ln;
1000                      continue;
1001                   default:
1002                      *tk++ = '<';
1003                      continue;
1004                }
1005             case ':':                                       // : or ::
1006                if(*ln == ':') {
1007                   *tk++ = DCOLON;
1008                   ++ln;
1009                } else *tk++ = ':';
1010                continue;
1011             case '=':                                       // = or == 
1012                if(*ln == '=') {
1013                   *tk++ = DEQUALS;
1014                   ++ln;
1015                } else *tk++ = '=';
1016                continue;
1017             case '>':                                       // > or >> or >= 
1018                switch(*ln) {
1019                   case '>':
1020                      *tk++ = SHR;
1021                      ++ln;
1022                      continue;
1023                   case '=':
1024                      *tk++ = GE;
1025                      ++ln;
1026                      continue;
1027                   default:
1028                      *tk++ = '>';
1029                      continue;
1030                }
1031             case '%':                                       // % or binary constant 
1032                if(*ln < '0' || *ln > '1') {
1033                   *tk++ = '%';
1034                   continue;
1035                }
1036                v = 0;
1037                while(*ln >= '0' && *ln <= '1')
1038                   v = (v << 1) + *ln++ - '0';
1039                if(*ln == '.') {
1040                   if((*(ln+1) == 'b') || (*(ln+1) == 'B')) { v &= 0x000000FF; ln += 2; }
1041                   if((*(ln+1) == 'w') || (*(ln+1) == 'W')) { v &= 0x0000FFFF; ln += 2; }
1042                   if((*(ln+1) == 'l') || (*(ln+1) == 'L')) { ln += 2; }
1043                }
1044                *tk++ = CONST;
1045                *tk++ = v;
1046                continue;
1047             case '@':                                       // @ or octal constant 
1048                if(*ln < '0' || *ln > '7') {
1049                   *tk++ = '@';
1050                   continue;
1051                }
1052                v = 0;
1053                while(*ln >= '0' && *ln <= '7')
1054                   v = (v << 3) + *ln++ - '0';
1055                if(*ln == '.') {
1056                   if((*(ln+1) == 'b') || (*(ln+1) == 'B')) { v &= 0x000000FF; ln += 2; }
1057                   if((*(ln+1) == 'w') || (*(ln+1) == 'W')) { v &= 0x0000FFFF; ln += 2; }
1058                   if((*(ln+1) == 'l') || (*(ln+1) == 'L')) { ln += 2; }
1059                }
1060                *tk++ = CONST;
1061                *tk++ = v;
1062                continue;
1063             case '^':                                       // ^ or ^^ <operator-name>
1064                if(*ln != '^') {
1065                   *tk++ = '^';
1066                   continue;
1067                }
1068
1069                if(((int)chrtab[*++ln] & STSYM) == 0) {
1070                   error("invalid symbol following ^^");
1071                   continue;
1072                }
1073
1074                p = ln++;
1075                while((int)chrtab[*ln] & CTSYM)
1076                   ++ln;
1077
1078                for(state = 0; state >= 0;) {
1079                   // Get char, convert to lowercase 
1080                   j = *p++;
1081                   if(j >= 'A' && j <= 'Z')
1082                      j += 0x20;
1083
1084                   j += kwbase[state];
1085                   if(kwcheck[j] != state) {
1086                      j = -1;
1087                      break;
1088                   }
1089
1090                   if(*p == EOS || p == ln) {
1091                      j = kwaccept[j];
1092                      break;
1093                   }
1094                   state = kwtab[j];
1095                }
1096
1097                if(j < 0 || state < 0) {
1098                   error("unknown symbol following ^^");
1099                   continue;
1100                }
1101
1102                *tk++ = (TOKEN)j;
1103                continue;
1104             default:
1105                interror(2);                                 // Bad MULTX entry in chrtab
1106                continue;
1107          }
1108       }
1109
1110                 
1111       // Handle decimal constant
1112       if(c & DIGIT) {
1113          v = 0;
1114          while((int)chrtab[*ln] & DIGIT)
1115             v = (v * 10) + *ln++ - '0';
1116          if(*ln == '.') {
1117             if((*(ln+1) == 'b') || (*(ln+1) == 'B')) { v &= 0x000000FF; ln += 2; }
1118             if((*(ln+1) == 'w') || (*(ln+1) == 'W')) { v &= 0x0000FFFF; ln += 2; }
1119             if((*(ln+1) == 'l') || (*(ln+1) == 'L')) { ln += 2; }
1120          }
1121          *tk++ = CONST;
1122          *tk++ = v;
1123          continue;
1124       }
1125
1126       // Handle illegal character
1127       return(error("illegal character"));
1128    }
1129
1130    // Terminate line of tokens and return "success."
1131
1132    goteol:
1133
1134    tok = etok;                                              // Set tok to beginning of line
1135    if(stuffnull)                                            // Terminate last SYMBOL
1136       *nullspot = EOS;
1137    *tk++ = EOL;
1138
1139    return(OK);
1140 }
1141
1142 //
1143 // -------------------------------------------------------------------------------------------------
1144 // .GOTO <label>        goto directive
1145 // 
1146 // The label is searched for starting from the first line of the current, enclosing macro 
1147 // definition.  If no enclosing macro exists, an error is generated.
1148 // 
1149 // A label is of the form:
1150 // 
1151 // :<name><whitespace>
1152 // 
1153 // The colon must appear in column 1.  The label is stripped prior to macro expansion, and is NOT
1154 // subject to macro expansion.  The whitespace may also be EOL.
1155 // -------------------------------------------------------------------------------------------------
1156 //
1157
1158 //int d_goto(WORD siz) {
1159 int d_goto(void) {
1160    char *sym;                                               // Label to search for 
1161    LONG *defln;                                             // Macro definition strings 
1162    char *s1;                                                // Temps for string comparison 
1163    char *s2;
1164    IMACRO *imacro;                                          // Macro invocation block
1165
1166    // Setup for the search
1167    if(*tok != SYMBOL) return(error("missing label"));
1168    sym = (char *)tok[1];
1169    tok += 2;
1170
1171    if(cur_inobj->in_type != SRC_IMACRO) return(error("goto not in macro"));
1172    imacro = cur_inobj->inobj.imacro;
1173    defln = (LONG *)imacro->im_macro->svalue;
1174
1175    // Find the label, starting with the first line.
1176    for(; defln != NULL; defln = (LONG *)*defln)
1177       if(*(char *)(defln + 1) == ':') {
1178          // Compare names (sleazo string compare)
1179          s1 = sym;
1180          s2 = (char *)(defln + 1) + 1;
1181          while(*s1 == *s2)
1182             if(*s1 == EOS) break;
1183             else {
1184                ++s1;
1185                ++s2;
1186             }
1187
1188          // Found the label, set new macro next-line and return.
1189          if((*s2 == EOS) || ((int)chrtab[*s2] & WHITE)) {
1190             imacro->im_nextln = defln;
1191             return(0);
1192          }
1193       }
1194
1195    return(error("goto label not found"));
1196 }