]> Shamusworld >> Repos - rmac/blob - macro.c
Fixed problem with nested MACROs.
[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
9 #include "macro.h"
10 #include "debug.h"
11 #include "direct.h"
12 #include "error.h"
13 #include "expr.h"
14 #include "listing.h"
15 #include "procln.h"
16 #include "symbol.h"
17 #include "token.h"
18
19
20 static void SetupDefaultMacros(void);
21
22 LONG curuniq;                                                           // Current macro's unique number
23 //TOKEN ** argp;                                                                // Free spot in argptrs[]
24 int macnum;                                                                     // Unique number for macro definition
25 TOKEN * argPtrs[128];                                           // 128 arguments ought to be enough for anyone
26 static int argp;
27
28 static LONG macuniq;                                            // Unique-per-macro number
29 static SYM * curmac;                                            // Macro currently being defined
30 //static char ** curmln;                                                // Previous macro line (or NULL)
31 static VALUE argno;                                                     // Formal argument count 
32
33 static LONG * firstrpt;                                         // First .rept line 
34 static LONG * nextrpt;                                          // Last .rept line 
35 static int rptlevel;                                            // .rept nesting level 
36
37
38 //
39 // Initialize Macro Processor
40 //
41 void InitMacro(void)
42 {
43         macuniq = 0;
44         macnum = 1;
45 //      argp = NULL;
46         argp = 0;
47         SetupDefaultMacros();
48 }
49
50
51 //
52 // Exit from a Macro;
53 // -- pop any intervening include files and repeat blocks;
54 // -- restore argument stack;
55 // -- pop the macro.
56 //
57 int ExitMacro(void)
58 {
59 #warning !!! Bad macro exiting !!!
60 /*
61 This is a problem. Currently, the argument logic just keeps the current
62 arguments and doesn't save anything if a new macro is called in the middle
63 of another (nested macros). Need to fix that somehow.
64 */
65         // Pop intervening include files and .rept blocks
66         while (cur_inobj != NULL && cur_inobj->in_type != SRC_IMACRO)
67                 fpop();
68
69         if (cur_inobj == NULL)
70                 fatal("too many ENDMs");
71
72         // Restore
73         // o  old arg context
74         // o  old unique number
75         // ...and then pop the macro.
76
77         IMACRO * imacro = cur_inobj->inobj.imacro;
78         curuniq = imacro->im_olduniq;
79
80 //      /*TOKEN ** p = */argp--;
81 //      argp = (TOKEN **)*argp;
82         DEBUG printf("ExitMacro: argp: %d -> ", argp);
83         argp -= imacro->im_nargs;
84         DEBUG printf("%d (nargs = %d)\n", argp, imacro->im_nargs);
85
86         fpop();
87         mjump_align = 0;
88         return 0;
89 }
90
91
92 //
93 // Add a Formal Argument to a Macro Definition
94 //
95 int defmac2(char * argname)
96 {
97         SYM * arg;
98
99         if (lookup(argname, MACARG, (int)curmac->sattr) != NULL)
100                 return error("multiple formal argument definition");
101
102         arg = NewSymbol(argname, MACARG, (int)curmac->sattr);
103         arg->svalue = argno++;
104
105         return OK;
106 }
107
108
109 //
110 // Add a line to a macro definition; also print lines to listing file (if
111 // enabled). The last line of the macro (containing .endm) is not included in
112 // the macro. A label on that line will be lost.
113 // notEndFlag is -1 for all lines but the last one (.endm), when it is 0.
114 //
115 int defmac1(char * ln, int notEndFlag)
116 {
117         PTR p;
118         LONG len;
119
120         if (list_flag)
121         {
122                 listeol();                                                      // Flush previous source line
123                 lstout('.');                                            // Mark macro definition with period
124         }
125
126         // This is just wrong, wrong, wrong. It makes up a weird kind of string with
127         // a pointer on front, and then uses a ** to manage them: This is a recipe
128         // for disaster.
129         // How to manage it then?
130         // Could use a linked list, like Landon uses everywhere else.
131 /*
132 How it works:
133 Allocate a space big enough for the string + NULL + pointer.
134 Set the pointer to NULL.
135 Copy the string to the space after the pointer.
136 If this is the 1st time through, set the SYM * "svalue" to the pointer.
137 If this is the 2nd time through, derefence the ** to point to the memory you just allocated.
138 Then, set the ** to the location of the memory you allocated for the next pass through.
139
140 This is a really low level way to do a linked list, and by bypassing all the safety
141 features of the language. Seems like we can do better here.
142 */
143         if (notEndFlag)
144         {
145 #if 0
146                 len = strlen(ln) + 1 + sizeof(LONG);
147                 p.cp = malloc(len);
148                 *p.lp = 0;
149                 strcpy(p.cp + sizeof(LONG), ln);
150
151                 // Link line of text onto end of list
152                 if (curmln == NULL)
153                         curmac->svalue = p.cp;
154                 else
155                         *curmln = p.cp;
156
157                 curmln = (char **)p.cp;
158                 return 1;                                                       // Keep looking 
159 #else
160                 if (curmac->lineList == NULL)
161                 {
162                         curmac->lineList = malloc(sizeof(struct LineList));
163                         curmac->lineList->next = NULL;
164                         curmac->lineList->line = strdup(ln);
165                         curmac->last = curmac->lineList;
166                 }
167                 else
168                 {
169                         curmac->last->next = malloc(sizeof(struct LineList));
170                         curmac->last->next->next = NULL;
171                         curmac->last->next->line = strdup(ln);
172                         curmac->last = curmac->last->next;
173                 }
174
175                 return 1;                                                       // Keep looking
176 #endif
177         }
178
179         return 0;                                                               // Stop looking at the end
180 }
181
182
183 //
184 // Define macro
185 //
186 // macro foo arg1,arg2,...
187 //    :
188 //     :
189 //    endm
190 //
191 //  Helper functions:
192 //  -----------------
193 //  `defmac1' adds lines of text to the macro definition
194 //  `defmac2' processes the formal arguments (and sticks them into the symbol table)
195 //
196 int DefineMacro(void)
197 {
198         // Setup entry in symbol table, make sure the macro isn't a duplicate
199         // entry, and that it doesn't override any processor mnemonic or assembler
200         // directive.
201         if (*tok++ != SYMBOL)
202                 return error("missing symbol");
203
204         char * name = string[*tok++];
205
206         if (lookup(name, MACRO, 0) != NULL)
207                 return error("duplicate macro definition");
208
209         curmac = NewSymbol(name, MACRO, 0);
210         curmac->svalue = 0;
211         curmac->sattr = (WORD)(macnum++);
212
213         // Parse and define formal arguments in symbol table
214         if (*tok != EOL)
215         {
216                 argno = 0;
217                 symlist(defmac2);
218                 at_eol();
219         }
220
221         // Suck in the macro definition; we're looking for an ENDM symbol on a line
222         // by itself to terminate the definition.
223 //      curmln = NULL;
224         curmac->lineList = NULL;
225         lncatch(defmac1, "endm ");
226
227         return 0;
228 }
229
230
231 //
232 // Add lines to a .rept definition
233 //
234 int defr1(char * ln, int kwno)
235 {
236         LONG len;
237         LONG * p;
238
239         if (list_flag)
240         {
241                 listeol();                                                              // Flush previous source line
242                 lstout('#');                                                    // Mark this a 'rept' block
243         }
244
245         switch (kwno)
246         {
247         case 0:                                                                         // .endr 
248                 if (--rptlevel == 0)
249                 return(0);
250                 goto addln;
251         case 1:                                                                         // .rept 
252                 rptlevel++;
253         default:
254 //MORE stupidity here...
255 #warning "!!! Casting (char *) as LONG !!!"
256         addln:
257                 // Allocate length of line + 1('\0') + LONG
258                 len = strlen(ln) + 1 + sizeof(LONG);
259 //              p = (LONG *)amem(len);
260                 p = (LONG *)malloc(len);
261                 *p = 0;
262
263                 strcpy((char *)(p + 1), ln);
264                 
265                 if (nextrpt == NULL)
266                 {
267                         firstrpt = p;           // First line of rept statement
268                 }
269                 else
270                 {
271                         *nextrpt = (LONG)p;
272                 }
273
274                 nextrpt = p;
275
276                 return rptlevel;
277         }
278 }
279
280
281 //
282 // Define a .rept block, this gets hairy because they can be nested
283 //
284 int defrept(void)
285 {
286         INOBJ * inobj;
287         IREPT * irept;
288         VALUE eval;
289
290         // Evaluate repeat expression
291         if (abs_expr(&eval) != OK)
292                 return ERROR;
293
294         // Suck in lines for .rept block
295         firstrpt = NULL;
296         nextrpt = NULL;
297         rptlevel = 1;
298         lncatch(defr1, "endr rept ");
299
300         // Alloc and init input object
301         if (firstrpt)
302         {
303                 inobj = a_inobj(SRC_IREPT);                             // Create a new REPT input object
304                 irept = inobj->inobj.irept;
305                 irept->ir_firstln = firstrpt;
306                 irept->ir_nextln = NULL;
307                 irept->ir_count = eval;
308         }
309
310         return 0;
311 }
312
313
314 //
315 // Hand off lines of text to the function `lnfunc' until a line containing one
316 // of the directives in `dirlist' is encountered. Return the number of the
317 // keyword encountered (0..n)
318 // 
319 // `dirlist' contains null-seperated terminated keywords.  A final null
320 // terminates the list. Directives are compared to the keywords without regard
321 // to case.
322 // 
323 // If `lnfunc' is NULL, then lines are simply skipped.
324 // If `lnfunc' returns an error, processing is stopped.
325 // 
326 // `lnfunc' is called with an argument of -1 for every line but the last one,
327 // when it is called with an argument of the keyword number that caused the
328 // match.
329 //
330 int lncatch(int (* lnfunc)(), char * dirlist)
331 {
332         char * p;
333         int k;
334
335         if (lnfunc != NULL)
336                 lnsave++;                                                               // Tell tokenizer to keep lines 
337
338         for(;;)
339         {
340                 if (tokln() == TKEOF)
341                 {
342                         errors("encountered end-of-file looking for '%s'", dirlist);
343                         fatal("cannot continue");
344                 }
345
346                 // Test for end condition.  Two cases to handle:
347                 //            <directive>
348                 //    symbol: <directive>
349                 p = NULL;
350                 k = -1;
351
352                 if (*tok == SYMBOL)
353                 {
354                         if ((tok[2] == ':' || tok[2] == DCOLON))
355                         {
356                                 if (tok[3] == SYMBOL)                   // label: symbol
357 #if 0
358                                         p = (char *)tok[4];
359 #else
360                                         p = string[tok[4]];
361 #endif
362                         }
363                         else
364                         {
365 #if 0
366                                 p = (char *)tok[1];                             // symbol 
367 #else
368                                 p = string[tok[1]];                             // Symbol
369 #endif
370                         }
371                 }
372
373                 if (p != NULL)
374                 {
375                         if (*p == '.')                                          // ignore leading '.'s 
376                                 p++;
377
378                         k = kwmatch(p, dirlist);
379                 }
380
381                 // Hand-off line to function
382                 // if it returns 0, and we found a keyword, stop looking.
383                 // if it returns 1, hand off the line and keep looking.
384                 if (lnfunc != NULL)
385                         k = (*lnfunc)(lnbuf, k);
386
387                 if (!k)
388                         break;
389         }
390
391         if (lnfunc != NULL)
392                 lnsave--;                                                               // Tell tokenizer to stop keeping lines
393
394         return 0;
395 }
396
397
398 //
399 // See if the string `kw' matches one of the keywords in `kwlist'.  If so,
400 // return the number of the keyword matched.  Return -1 if there was no match.
401 // Strings are compared without regard for case.
402 //
403 int kwmatch(char * kw, char * kwlist)
404 {
405         char * p;
406         char c1;
407         char c2;
408         int k;
409
410         for(k=0; *kwlist; ++k)
411         {
412                 for(p=kw;;)
413                 {
414                         c1 = *kwlist++;
415                         c2 = *p++;
416
417                         if (c2 >= 'A' && c2 <= 'Z')
418                                 c2 += 32;
419
420                         if (c1 == ' ' && c2 == EOS)
421                                 return k;
422
423                         if (c1 != c2)
424                                 break;
425                 }
426
427                 // Skip to beginning of next keyword in `kwlist'
428                 while (*kwlist && *kwlist != ' ')
429                         ++kwlist;
430
431                 if (*kwlist== ' ')
432                         kwlist++;
433         }
434
435         return -1;
436 }
437
438
439 //
440 // Invoke a macro
441 // o  parse, count and copy arguments
442 // o  push macro's string-stream
443 //
444 int InvokeMacro(SYM * mac, WORD siz)
445 {
446         TOKEN * p = NULL;
447         int dry_run;
448         WORD arg_siz = 0;
449 //      TOKEN ** argptr = NULL;
450 //Doesn't need to be global! (or does it???--it does)
451 //      argp = 0;
452         DEBUG printf("InvokeMacro: argp: %d -> ", argp);
453
454         if ((!strcmp(mac->sname, "mjump") || !strcmp(mac->sname, "mpad")) && !in_main)
455         {
456                 error("macro cannot be used outside of .gpumain");
457                 return ERROR;
458         }
459
460         INOBJ * inobj = a_inobj(SRC_IMACRO);            // Alloc and init IMACRO 
461         IMACRO * imacro = inobj->inobj.imacro;
462         imacro->im_siz = siz;
463         WORD nargs = 0;
464         TOKEN * beg_tok = tok;                                          // 'tok' comes from token.c
465         TOKEN * startOfArg;
466         TOKEN * dest;
467         int stringNum = 0;
468         int argumentNum = 0;
469         int i;
470
471         for(dry_run=1; ; dry_run--)
472         {
473                 for(tok=beg_tok; *tok!=EOL;)
474                 {
475                         if (dry_run)
476                                 nargs++;
477                         else
478                         {
479 #if 0                           
480                                 *argptr++ = p;
481 #else
482                                 argPtrs[argp++] = p;
483                                 startOfArg = p;
484 #endif
485                         }
486
487                         // Keep going while tok isn't pointing at a comma or EOL
488                         while (*tok != ',' && *tok != EOL)
489                         {
490                                 // Skip over backslash character, unless it's followed by an EOL
491                                 if (*tok == '\\' && tok[1] != EOL)
492                                         tok++;
493
494                                 switch (*tok)
495                                 {
496                                 case CONST:
497                                 case SYMBOL:
498 //Shamus: Possible bug. ACONST has 2 tokens after it, not just 1
499                                 case ACONST:
500                                         if (dry_run)
501                                         {
502                                                 arg_siz += sizeof(TOKEN);
503                                                 tok++;
504                                         }
505                                         else
506                                         {
507                                                 *p++ = *tok++;
508                                         }
509                                 // FALLTHROUGH (picks up the arg after a CONST, SYMBOL or ACONST)
510                                 default:
511                                         if (dry_run)
512                                         {
513                                                 arg_siz += sizeof(TOKEN);
514                                                 tok++;
515                                         }
516                                         else
517                                         {
518                                                 *p++ = *tok++;
519                                         }
520
521                                         break;
522                                 }
523                         }
524
525                         // We hit the comma or EOL, so count/stuff it
526                         if (dry_run)
527                                 arg_siz += sizeof(TOKEN);
528                         else
529                                 *p++ = EOL;
530
531                         // If we hit the comma instead of an EOL, skip over it
532                         if (*tok == ',')
533                                 tok++;
534
535                         // Do our QnD token grabbing (this will be redone once we get all
536                         // the data structures fixed as this is a really dirty hack)
537                         if (!dry_run)
538                         {
539                                 dest = imacro->argument[argumentNum].token;
540                                 stringNum = 0;
541
542                                 do
543                                 {
544                                         // Remap strings to point the IMACRO internal token storage
545                                         if (*startOfArg == SYMBOL || *startOfArg == STRING)
546                                         {
547                                                 *dest++ = *startOfArg++;
548                                                 imacro->argument[argumentNum].string[stringNum] = strdup(string[*startOfArg++]);
549                                                 *dest++ = stringNum++;
550                                         }
551                                         else
552                                                 *dest++ = *startOfArg++;
553                                 }
554                                 while (*startOfArg != EOL);
555
556                                 *dest = *startOfArg;            // Copy EOL...
557                                 argumentNum++;
558                         }
559                 }
560
561                 // Allocate space for argument ptrs and so on and then go back and
562                 // construct the arg frame
563                 if (dry_run)
564                 {
565                         if (nargs != 0)
566                                 p = (TOKEN *)malloc(arg_siz);
567 //                              p = (TOKEN *)malloc(arg_siz + sizeof(TOKEN));
568
569 /*
570 Shamus:
571 This construct is meant to deal with nested macros, so the simple minded way
572 we deal with them now won't work. :-/ Have to think about how to fix.
573 What we could do is simply move the argp with each call, and move it back by the
574 number of arguments in the macro that's ending. That would solve the problem nicely.
575 [Which we do now. But that uncovered another problem: the token strings are all
576 stale by the time a nested macro gets to the end. But they're supposed to be symbols,
577 which means if we put symbol references into the argument token streams, we can
578 alleviate this problem.]
579 */
580 #if 0
581                         argptr = (TOKEN **)malloc((nargs + 1) * sizeof(LONG));
582                         *argptr++ = (TOKEN *)argp;
583                         argp = argptr;
584 #else
585                         // We don't need to do anything here since we already advance argp
586                         // when parsing the arguments.
587 //                      argp += nargs;
588 #endif
589                 }
590                 else 
591                         break;
592         }
593
594         DEBUG printf("%d\n", argp);
595
596         // Setup imacro:
597         // o  # arguments;
598         // o  -> macro symbol;
599         // o  -> macro definition string list;
600         // o  save 'curuniq', to be restored when the macro pops;
601         // o  bump `macuniq' counter and set 'curuniq' to it;
602         imacro->im_nargs = nargs;
603         imacro->im_macro = mac;
604 //      imacro->im_nextln = (TOKEN *)mac->svalue;
605         imacro->im_nextln = mac->lineList;
606         imacro->im_olduniq = curuniq;
607         curuniq = macuniq++;
608         imacro->argBase = argp - nargs;                 // Shamus: keep track of argument base
609
610         DEBUG
611         {
612                 printf("nargs=%d\n", nargs);
613
614                 for(nargs=0; nargs<imacro->im_nargs; nargs++)
615                 {
616                         printf("arg%d=", nargs);
617 //                      dumptok(argp[imacro->im_nargs - nargs - 1]);
618 //                      dumptok(argPtrs[imacro->im_nargs - nargs - 1]);
619                         dumptok(argPtrs[(argp - imacro->im_nargs) + nargs]);
620                 }
621         }
622
623         return OK;
624 }
625
626
627 //
628 // Setup inbuilt macros (SubQMod)
629 //
630 static void SetupDefaultMacros(void)
631 {
632         curmac = NewSymbol("mjump", MACRO, 0);
633         curmac->svalue = 0;
634         curmac->sattr = (WORD)(macnum++);
635         argno = 0;
636         defmac2("cc");
637         defmac2("addr");
638         defmac2("jreg");
639 //      curmln = NULL;
640         curmac->lineList = NULL;
641         defmac1("  nop", -1);
642         defmac1("  movei #\\addr,\\jreg", -1);
643         defmac1("  jump  \\cc,(\\jreg)", -1);
644         defmac1("  nop", -1);
645         defmac1("  nop", -1);
646
647         curmac = NewSymbol("mjr", MACRO, 0);
648         curmac->svalue = 0;
649         curmac->sattr = (WORD)(macnum++);
650         argno = 0;
651         defmac2("cc");
652         defmac2("addr");
653 //      curmln = NULL;
654         curmac->lineList = NULL;
655         defmac1("  jr    \\cc,\\addr", -1);
656         defmac1("  nop", -1);
657         defmac1("  nop", -1);
658
659         curmac = NewSymbol("mpad", MACRO, 0);
660         curmac->svalue = 0;
661         curmac->sattr = (WORD)(macnum++);
662         argno = 0;
663         defmac2("size");
664 //      curmln = NULL;
665         curmac->lineList = NULL;
666         defmac1("  .rept (\\size/2)", -1);
667         defmac1("     nop", -1);
668         defmac1("  .endr", -1);
669 }