b8097f3570fe38e3c0030ad320507c46b41adb16
[rmac] / macro.c
1 ////////////////////////////////////////////////////////////////////////////////////////////////////
2 // RMAC - Reboot's Macro Assembler for the Atari Jaguar Console System
3 // MACRO.C - Macro Definition and Invocation
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 "macro.h"
9 #include "token.h"
10 #include "error.h"
11 #include "expr.h"
12 #include "listing.h"
13 #include "symbol.h"
14 #include "procln.h"
15 #include "direct.h"
16 #include "debug.h"
17
18 LONG curuniq;                                               // Current macro's unique number
19 TOKEN **argp;                                               // Free spot in argptrs[]
20 int macnum;                                                 // Unique number for macro definition
21
22 static LONG macuniq;                                        // Unique-per-macro number
23 static SYM *curmac;                                         // Macro currently being defined
24 static char **curmln;                                       // Previous macro line (or NULL)
25 static VALUE argno;                                         // Formal argument count 
26
27 static LONG *firstrpt;                                      // First .rept line 
28 static LONG *nextrpt;                                       // Last .rept line 
29 static int rptlevel;                                        // .rept nesting level 
30
31 //
32 // --- Initialize Macro Processor ------------------------------------------------------------------
33 //
34
35 void init_macro(void) {
36    macuniq = 0;
37    macnum = 1;
38    argp = NULL;
39    ib_macro();
40 }
41
42 //
43 // -------------------------------------------------------------------------------------------------
44 // Exit from a Macro;
45 // o  pop any intervening include files and repeat blocks;
46 // o  restore argument stack;
47 // o  pop the macro.
48 // -------------------------------------------------------------------------------------------------
49 //
50
51 int exitmac(void) {
52    IMACRO *imacro;
53    TOKEN **p;
54
55    // Pop intervening include files and .rept blocks
56    while(cur_inobj != NULL && cur_inobj->in_type != SRC_IMACRO)
57       fpop();
58
59    if(cur_inobj == NULL)
60       fatal("too many ENDMs");
61
62    // Restore
63    // o  old arg context
64    // o  old unique number
65    // ...and then pop the macro.
66
67    imacro = cur_inobj->inobj.imacro;
68    curuniq = imacro->im_olduniq;
69
70    p = --argp;
71    argp = (TOKEN **)*argp;
72
73    fpop();
74    
75    mjump_align = 0;
76
77    return(0);
78 }
79
80 //
81 // --- Add a Formal Argument to a Macro Definition -------------------------------------------------
82 //
83
84 int defmac2(char *argname) {
85    SYM *arg;
86
87    if(lookup(argname, MACARG, (int)curmac->sattr) != NULL)
88       return(error("multiple formal argument definition"));
89    arg = newsym(argname, MACARG, (int)curmac->sattr);
90    arg->svalue = argno++;
91
92    return(OK);
93 }
94
95
96 //
97 // -------------------------------------------------------------------------------------------------
98 // Add a line to a macro definition; also print lines to listing file (if enabled).
99 // The last line of the macro (containing .endm) is not included in the macro.  A label on that line
100 // will be lost. `endflg' is misleading here.  It is -1 for all lines but the last one (.endm), 
101 // when it is 0.
102 // -------------------------------------------------------------------------------------------------
103 //
104
105 int defmac1(char *ln, int endflg) {
106    PTR p;
107    LONG len;
108
109    if(list_flag) {
110       listeol();                                            // Flush previous source line
111       lstout('.');                                          // Mark macro definition with period
112    }
113
114    if(endflg) {
115       len = strlen(ln) + 1 + sizeof(LONG);
116       p.cp = amem(len);
117       *p.lp = 0;
118       strcpy(p.cp + sizeof(LONG), ln);
119
120       // Link line of text onto end of list
121       if(curmln == NULL)
122          curmac->svalue = (VALUE)p.cp;
123       else
124          *curmln = p.cp;
125       curmln = (char **)p.cp;
126       return(1);                                            // Keep looking 
127    }
128    else 
129       return(0);                                            // Stop looking at the end
130 }
131
132 //
133 // -------------------------------------------------------------------------------------------------
134 // Define macro
135 //
136 // macro foo arg1,arg2,...
137 //    :
138 //     :
139 //    endm
140 //
141 //  Helper functions:
142 //  -----------------
143 //  `defmac1' adds lines of text to the macro definition
144 //  `defmac2' processes the formal arguments (and sticks them into the symbol table)
145 // -------------------------------------------------------------------------------------------------
146 //
147
148 int defmac(void) {
149    char *p;
150    SYM *mac;
151
152    // Setup entry in symbol table, make sure the macro isn't a duplicate entry, and that
153    // it doesn't override any processor mnemonic or assembler directive.
154    if(*tok++ != SYMBOL) return(error("missing symbol"));
155    p = (char *)*tok++;
156    if(lookup(p, MACRO, 0) != NULL)
157       return(error("multiple macro definition"));
158
159    curmac = mac = newsym(p, MACRO, 0);
160    mac->svalue = 0;
161    mac->sattr = (WORD)(macnum++);
162
163    // Parse and define formal arguments in symbol table
164    if(*tok != EOL) {
165       argno = 0;
166       symlist(defmac2);
167       at_eol();
168    }
169
170    // Suck in the macro definition; we're looking for an ENDM symbol on a line
171    // by itself to terminate the definition.
172    curmln = NULL;
173    lncatch(defmac1, "endm ");
174
175    return(0);
176 }
177
178 //
179 // --- Add lines to a .rept definition -------------------------------------------------------------
180 //
181
182 int defr1(char *ln, int kwno) {
183    LONG len;
184    LONG *p;
185
186    if(list_flag) {
187       listeol();                                            // Flush previous source line
188       lstout('#');                                          // Mark this a 'rept' block
189    }
190
191    switch(kwno) {
192       case 0:                                               // .endr 
193          if(--rptlevel == 0)
194             return(0);
195          goto addln;
196       case 1:                                               // .rept 
197          ++rptlevel;
198       default:
199
200          addln:
201
202          // Allocate length of line + 1('\0') + LONG
203          len = strlen(ln) + 1 + sizeof(LONG);
204          p = (LONG *)amem(len);
205          *p = 0;
206
207          strcpy((char*)(p + 1), ln);
208          
209          if(nextrpt == NULL) {
210             firstrpt = p;           // First line of rept statement
211          } else {
212             *nextrpt = (LONG)p;
213          }
214          nextrpt = p;
215
216          return(rptlevel);
217    }
218 }
219
220 //
221 // --- Define a .rept block, this gets hairy because they can be nested ----------------------------
222 //
223
224 int defrept(void) {
225    INOBJ *inobj;
226    IREPT *irept;
227    VALUE eval;
228
229    // Evaluate repeat expression
230    if(abs_expr(&eval) != OK)
231       return(ERROR);
232
233    // Suck in lines for .rept block
234    firstrpt = NULL;
235    nextrpt = NULL;
236    rptlevel = 1;
237    lncatch(defr1, "endr rept ");
238
239    // Alloc and init input object
240    if(firstrpt) {
241       inobj = a_inobj(SRC_IREPT);                           // Create a new REPT input object
242       irept = inobj->inobj.irept;
243       irept->ir_firstln = firstrpt;
244       irept->ir_nextln = NULL;
245       irept->ir_count = eval;
246    }
247
248    return(0);
249 }
250
251 //
252 // -------------------------------------------------------------------------------------------------
253 // Hand off lines of text to the function `lnfunc' until a line containing one of the directives in 
254 // `dirlist' is encountered. Return the number of the keyword encountered (0..n)
255 // 
256 // `dirlist' contains null-seperated terminated keywords.  A final null terminates the list.  
257 // Directives are compared to the keywords without regard to case.
258 // 
259 // If `lnfunc' is NULL, then lines are simply skipped.
260 // If `lnfunc' returns an error, processing is stopped.
261 // 
262 // `lnfunc' is called with an argument of -1 for every line but the last one, when it is called 
263 // with an argument of the keyword number that caused the match.
264 // -------------------------------------------------------------------------------------------------
265 //
266
267 int lncatch(int (*lnfunc)(), char *dirlist) {
268    char *p;
269    int k;
270
271    if(lnfunc != NULL)
272       ++lnsave;                                             // Tell tokenizer to keep lines 
273
274    for(;;) {
275       if(tokln() == TKEOF) {
276          errors("encountered end-of-file looking for '%s'", dirlist);
277          fatal("cannot continue");
278       }
279
280       // Test for end condition.  Two cases to handle:
281       //            <directive>
282       //    symbol: <directive>
283       p = NULL;
284       k = -1;
285
286       if(*tok == SYMBOL) {
287          if((tok[2] == ':' || tok[2] == DCOLON)) {
288             if(tok[3] == SYMBOL)                            // label: symbol
289                p = (char *)tok[4];
290          } else {
291             p = (char *)tok[1];                             // symbol 
292          }
293       }
294
295       if(p != NULL) {
296          if(*p == '.')                                      // ignore leading '.'s 
297             ++p;
298          k = kwmatch(p, dirlist);
299       }
300
301       // Hand-off line to function
302       // if it returns 0, and we found a keyword, stop looking.
303       // if it returns 1, hand off the line and keep looking.
304       if(lnfunc != NULL)
305          k = (*lnfunc)(lnbuf, k);
306
307       if(!k)
308          break;
309    }
310
311    if(lnfunc != NULL)
312       --lnsave;                                             // Tell tokenizer to stop keeping lines
313
314    return(0);
315 }
316
317 //
318 // -------------------------------------------------------------------------------------------------
319 // See if the string `kw' matches one of the keywords in `kwlist'.  If so, return the number of 
320 // the keyword matched.  Return -1 if there was no match. 
321 // Strings are compared without regard for case.
322 // -------------------------------------------------------------------------------------------------
323 //
324
325 int kwmatch(char *kw, char *kwlist) {
326    char *p;
327    char c1;
328    char c2;
329    int k;
330
331    for(k = 0; *kwlist; ++k) {
332       for(p = kw;;) {
333          c1 = *kwlist++;
334          c2 = *p++;
335
336          if(c2 >= 'A' && c2 <= 'Z')
337             c2 += 32;
338
339          if(c1 == ' ' && c2 == EOS)
340             return(k);
341
342          if(c1 != c2)
343             break;
344       }
345
346       // Skip to beginning of next keyword in `kwlist'
347       while(*kwlist && *kwlist != ' ')
348          ++kwlist;
349       if(*kwlist== ' ')
350          ++kwlist;
351    }
352
353    return(-1);
354 }
355
356 //
357 // -------------------------------------------------------------------------------------------------
358 // Invoke a macro
359 // o  parse, count and copy arguments
360 // o  push macro's string-stream
361 // -------------------------------------------------------------------------------------------------
362 //
363
364 int invokemac(SYM *mac, WORD siz) {
365    TOKEN *p = NULL;
366    IMACRO *imacro;
367    INOBJ *inobj;
368    int dry_run;
369    WORD nargs;
370    WORD arg_siz = 0;
371    TOKEN **argptr = NULL;
372    TOKEN *beg_tok;
373
374    if((!strcmp(mac->sname, "mjump") || !strcmp(mac->sname, "mpad")) && !in_main) {
375       error("macro cannot be used outside of .gpumain");
376       return(ERROR);
377    }
378
379    inobj = a_inobj(SRC_IMACRO);                             // Alloc and init IMACRO 
380    imacro = inobj->inobj.imacro;
381    imacro->im_siz = siz;
382    nargs = 0;
383    beg_tok = tok;
384
385    for(dry_run = 1;; --dry_run) {
386       for(tok = beg_tok; *tok != EOL;) {
387          if(dry_run) ++nargs;
388          else *argptr++ = p;
389
390          while(*tok != ',' && *tok != EOL) {
391             if(*tok == '\\' && tok[1] != EOL) ++tok;
392             switch((int)*tok) {
393                case CONST:
394                case SYMBOL:
395                case ACONST:
396                   if(dry_run) arg_siz += sizeof(TOKEN), ++tok;
397                   else *p++ = *tok++;
398                   // FALLTHROUGH
399                default:
400                   if(dry_run) arg_siz += sizeof(TOKEN), ++tok;
401                   else *p++ = *tok++;
402                   break;
403             }
404          }
405
406          if(dry_run) arg_siz += sizeof(TOKEN);
407          else *p++ = EOL;
408
409          if(*tok == ',') ++tok;
410       }
411
412       // Allocate space for argument ptrs and so on and then go back and construct the arg frame
413       if(dry_run) {
414          if(nargs != 0) p = (TOKEN *)malloc((LONG)(arg_siz + 1));
415          argptr = (TOKEN **)malloc((LONG)((nargs + 1) * sizeof(LONG)));
416          *argptr++ = (TOKEN *)argp;
417          argp = argptr;
418       } else 
419          break;
420    }
421
422
423    // Setup imacro:
424    // o  #arguments;
425    // o  -> macro symbol;
426    // o  -> macro definition string list;
427    // o  save 'curuniq', to be restored when the macro pops;
428    // o  bump `macuniq' counter and set 'curuniq' to it;
429    imacro->im_nargs = nargs;
430    imacro->im_macro = mac;
431    imacro->im_nextln = (LONG *)mac->svalue;
432    imacro->im_olduniq = curuniq;
433    curuniq = ++macuniq;
434
435    DEBUG {
436       printf("nargs=%d\n", nargs);
437       for(nargs = 0; nargs < imacro->im_nargs; ++nargs) {
438          printf("arg%d=", nargs);
439          dumptok(argp[imacro->im_nargs - nargs - 1]);
440       }
441    }
442    
443    return(OK);
444 }
445
446 //
447 // -------------------------------------------------------------------------------------------------
448 // Setup inbuilt macros
449 // -------------------------------------------------------------------------------------------------
450 //
451
452 void ib_macro(void) {
453    SYM *mac;
454
455    curmac = mac = newsym("mjump", MACRO, 0);
456    mac->svalue = 0;
457    mac->sattr = (WORD)(macnum++);
458    argno = 0;
459    defmac2("cc");
460    defmac2("addr");
461    defmac2("jreg");
462    curmln = NULL;
463    defmac1("      nop", -1);
464    defmac1("      movei #\\addr,\\jreg", -1);
465    defmac1("      jump  \\cc,(\\jreg)", -1);
466    defmac1("      nop", -1);
467    defmac1("      nop", -1);
468
469    curmac = mac = newsym("mjr", MACRO, 0);
470    mac->svalue = 0;
471    mac->sattr = (WORD)(macnum++);
472    argno = 0;
473    defmac2("cc");
474    defmac2("addr");
475    curmln = NULL;
476    defmac1("      jr    \\cc,\\addr", -1);
477    defmac1("      nop", -1);
478    defmac1("      nop", -1);
479
480    curmac = mac = newsym("mpad", MACRO, 0);
481    mac->svalue = 0;
482    mac->sattr = (WORD)(macnum++);
483    argno = 0;
484    defmac2("size");
485    curmln = NULL;
486    defmac1("      .rept (\\size/2)", -1);
487    defmac1("         nop", -1);
488    defmac1("      .endr", -1);
489 }