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