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