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