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