Version bump for last commit; now at v2.0.23.
[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-2020 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
23 static LONG macuniq;            // Unique-per-macro number
24 static SYM * curmac;            // Macro currently being defined
25 static uint32_t argno;                  // Formal argument count
26
27 static LLIST * firstrpt;        // First .rept line
28 static LLIST * nextrpt;         // Last .rept line
29 static int rptlevel;            // .rept nesting level
30
31 // Function prototypes
32 static int KWMatch(char *, char *);
33 static int LNCatch(int (*)(), char *);
34
35
36 //
37 // Initialize macro processor
38 //
39 void InitMacro(void)
40 {
41         macuniq = 0;
42         macnum = 1;
43 }
44
45
46 //
47 // Exit from a macro;
48 // -- pop any intervening include files and repeat blocks;
49 // -- restore argument stack;
50 // -- pop the macro.
51 //
52 int ExitMacro(void)
53 {
54 WARNING(!!! Bad macro exiting !!!)
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 Is this still true, now that we have IMACROs with TOKENSTREAMs in them? Need to
61 check it out for sure...!
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         DEBUG { printf("ExitMacro: nargs = %d\n", imacro->im_nargs); }
79
80         return fpop();
81 }
82
83
84 //
85 // Add a formal argument to a macro definition
86 //
87 int defmac2(char * argname)
88 {
89         if (lookup(argname, MACARG, (int)curmac->sattr) != NULL)
90                 return error("multiple formal argument definition");
91
92         SYM * arg = NewSymbol(argname, MACARG, (int)curmac->sattr);
93         arg->svalue = argno++;
94
95         return OK;
96 }
97
98
99 //
100 // Add a line to a macro definition; also print lines to listing file (if
101 // enabled). The last line of the macro (containing .endm) is not included in
102 // the macro. A label on that line will be lost.
103 // notEndFlag is -1 for all lines but the last one (.endm), when it is 0.
104 //
105 int defmac1(char * ln, int notEndFlag)
106 {
107         if (list_flag)
108         {
109                 listeol();              // Flush previous source line
110                 lstout('.');    // Mark macro definition with period
111         }
112
113         if (notEndFlag)
114         {
115                 if (curmac->lineList == NULL)
116                 {
117                         curmac->lineList = malloc(sizeof(LLIST));
118                         curmac->lineList->next = NULL;
119                         curmac->lineList->line = strdup(ln);
120                         curmac->lineList->lineno = curlineno;
121                         curmac->last = curmac->lineList;
122                 }
123                 else
124                 {
125                         curmac->last->next = malloc(sizeof(LLIST));
126                         curmac->last->next->next = NULL;
127                         curmac->last->next->line = strdup(ln);
128                         curmac->lineList->lineno = curlineno;
129                         curmac->last = curmac->last->next;
130                 }
131
132                 return 1;               // Keep looking
133         }
134
135         return 0;                       // Stop looking; at the end
136 }
137
138
139 //
140 // Define macro
141 //
142 // macro foo arg1,arg2,...
143 //    :
144 //     :
145 //    endm
146 //
147 //  Helper functions:
148 //  -----------------
149 //  `defmac1' adds lines of text to the macro definition
150 //  `defmac2' processes the formal arguments (and sticks them into the symbol
151 //   table)
152 //
153 int DefineMacro(void)
154 {
155         // Setup entry in symbol table, make sure the macro isn't a duplicate
156         // entry, and that it doesn't override any processor mnemonic or assembler
157         // directive.
158         if (*tok++ != SYMBOL)
159                 return error("missing symbol");
160
161         char * name = string[*tok++];
162
163         if (lookup(name, MACRO, 0) != NULL)
164                 return error("duplicate macro definition");
165
166         curmac = NewSymbol(name, MACRO, 0);
167         curmac->svalue = 0;
168         curmac->sattr = (WORD)(macnum++);
169
170         // Parse and define formal arguments in symbol table
171         if (*tok != EOL)
172         {
173                 argno = 0;
174                 symlist(defmac2);
175                 ErrorIfNotAtEOL();
176         }
177
178         // Suck in the macro definition; we're looking for an ENDM symbol on a line
179         // by itself to terminate the definition.
180 //      curmln = NULL;
181         curmac->lineList = NULL;
182         LNCatch(defmac1, "endm ");
183
184         return 0;
185 }
186
187
188 //
189 // Add lines to a .rept definition
190 //
191 int defr1(char * line, int kwno)
192 {
193         if (list_flag)
194         {
195                 listeol();                      // Flush previous source line
196                 lstout('#');            // Mark this a 'rept' block
197         }
198
199         if (kwno == 0)                  // .endr
200         {
201                 if (--rptlevel == 0)
202                         return 0;
203         }
204         else if (kwno == 1)             // .rept
205                 rptlevel++;
206
207 //DEBUG { printf("  defr1: line=\"%s\", kwno=%d, rptlevel=%d\n", line, kwno, rptlevel); }
208
209 #if 0
210 //MORE stupidity here...
211 WARNING("!!! Casting (char *) as LONG !!!")
212         // Allocate length of line + 1('\0') + LONG
213         LONG * p = (LONG *)malloc(strlen(line) + 1 + sizeof(LONG));
214         *p = 0;
215         strcpy((char *)(p + 1), line);
216
217         if (nextrpt == NULL)
218                 firstrpt = p;           // First line of rept statement
219         else
220                 *nextrpt = (LONG)p;
221
222         nextrpt = p;
223 #else
224         if (firstrpt == NULL)
225         {
226                 firstrpt = malloc(sizeof(LLIST));
227                 firstrpt->next = NULL;
228                 firstrpt->line = strdup(line);
229                 firstrpt->lineno = curlineno;
230                 nextrpt = firstrpt;
231         }
232         else
233         {
234                 nextrpt->next = malloc(sizeof(LLIST));
235                 nextrpt->next->next = NULL;
236                 nextrpt->next->line = strdup(line);
237                 nextrpt->next->lineno = curlineno;
238                 nextrpt = nextrpt->next;
239         }
240 #endif
241
242         return rptlevel;
243 }
244
245
246 //
247 // Handle a .rept block; this gets hairy because they can be nested
248 //
249 int HandleRept(void)
250 {
251         uint64_t eval;
252
253         // Evaluate repeat expression
254         if (abs_expr(&eval) != OK)
255                 return ERROR;
256
257         // Suck in lines for .rept block
258         firstrpt = NULL;
259         nextrpt = NULL;
260         rptlevel = 1;
261         LNCatch(defr1, "endr rept ");
262
263 //DEBUG { printf("HandleRept: firstrpt=$%X\n", firstrpt); }
264         // Alloc and init input object
265         if (firstrpt)
266         {
267                 INOBJ * inobj = a_inobj(SRC_IREPT);     // Create a new REPT input object
268                 IREPT * irept = inobj->inobj.irept;
269                 irept->ir_firstln = firstrpt;
270                 irept->ir_nextln = NULL;
271                 irept->ir_count = (uint32_t)eval;
272         }
273
274         return 0;
275 }
276
277
278 //
279 // Hand off lines of text to the function 'lnfunc' until a line containing one
280 // of the directives in 'dirlist' is encountered.
281 //
282 // 'dirlist' contains space-separated terminated keywords. A final space
283 // terminates the list. Directives are case-insensitively compared to the
284 // keywords.
285 //
286 // If 'lnfunc' is NULL, then lines are simply skipped.
287 // If 'lnfunc' returns an error, processing is stopped.
288 //
289 // 'lnfunc' is called with an argument of -1 for every line but the last one,
290 // when it is called with an argument of the keyword number that caused the
291 // match.
292 //
293 static int LNCatch(int (* lnfunc)(), char * dirlist)
294 {
295         if (lnfunc != NULL)
296                 lnsave++;                       // Tell tokenizer to keep lines
297
298         while (1)
299         {
300                 if (TokenizeLine() == TKEOF)
301                 {
302                         error("encountered end-of-file looking for '%s'", dirlist);
303                         fatal("cannot continue");
304                 }
305
306                 DEBUG { DumpTokenBuffer(); }
307
308                 // Test for end condition.  Two cases to handle:
309                 //            <directive>
310                 //    symbol: <directive>
311                 char * p = NULL;
312                 int k = -1;
313
314                 if (*tok == SYMBOL)
315                 {
316                         // A string followed by a colon or double colon is a symbol and
317                         // *not* a directive, see if we can find the directive after it
318                         if ((tok[2] == ':' || tok[2] == DCOLON))
319                         {
320                                 if (tok[3] == SYMBOL)
321                                         p = string[tok[4]];
322                         }
323                         else
324                         {
325                                 // Otherwise, just grab the directive
326                                 p = string[tok[1]];
327                         }
328                 }
329
330                 if (p != NULL)
331                 {
332                         if (*p == '.')          // Ignore leading periods
333                                 p++;
334
335                         k = KWMatch(p, dirlist);
336                 }
337
338                 // Hand-off line to function
339                 // if it returns 0, and we found a keyword, stop looking.
340                 // if it returns 1, hand off the line and keep looking.
341                 if (lnfunc != NULL)
342                         k = (*lnfunc)(lnbuf, k);
343
344                 if (k == 0)
345                         break;
346         }
347
348         if (lnfunc != NULL)
349                 lnsave--;                               // Tell tokenizer to stop keeping lines
350
351         return 0;
352 }
353
354
355 //
356 // See if the string `kw' matches one of the keywords in `kwlist'. If so,
357 // return the number of the keyword matched. Return -1 if there was no match.
358 // Strings are compared without regard for case.
359 //
360 static int KWMatch(char * kw, char * kwlist)
361 {
362         for(int k=0; *kwlist; k++)
363         {
364                 for(char * p=kw;;)
365                 {
366                         char c1 = *kwlist++;
367                         char c2 = *p++;
368
369                         if (c2 >= 'A' && c2 <= 'Z')
370                                 c2 += 32;
371
372                         if (c1 == ' ' && c2 == EOS)
373                                 return k;
374
375                         if (c1 != c2)
376                                 break;
377                 }
378
379                 // Skip to beginning of next keyword in `kwlist'
380                 while (*kwlist && (*kwlist != ' '))
381                         ++kwlist;
382
383                 if (*kwlist== ' ')
384                         kwlist++;
385         }
386
387         return -1;
388 }
389
390
391 //
392 // Invoke a macro by creating a new IMACRO object & chopping up the arguments
393 //
394 int InvokeMacro(SYM * mac, WORD siz)
395 {
396         DEBUG { printf("InvokeMacro: arguments="); DumpTokens(tok); }
397
398         INOBJ * inobj = a_inobj(SRC_IMACRO);    // Alloc and init IMACRO
399         IMACRO * imacro = inobj->inobj.imacro;
400         uint16_t nargs = 0;
401
402         // Chop up the arguments, if any (tok comes from token.c, which at this
403         // point points at the macro argument token stream)
404         if (*tok != EOL)
405         {
406                 // Parse out the arguments and set them up correctly
407                 TOKEN * p = imacro->argument[nargs].token;
408                 int stringNum = 0;
409
410                 while (*tok != EOL)
411                 {
412                         if (*tok == ACONST)
413                         {
414                                 for(int i=0; i<3; i++)
415                                         *p++ = *tok++;
416                         }
417                         else if (*tok == CONST)         // Constants are 64-bits
418                         {
419                                 *p++ = *tok++;                  // Token
420                                 uint64_t *p64 = (uint64_t *)p;
421                                 uint64_t *tok64 = (uint64_t *)tok;
422                                 *p64++ = *tok64++;
423                                 tok = (TOKEN *)tok64;
424                                 p = (uint32_t *)p64;
425                         }
426                         else if ((*tok == STRING) || (*tok == SYMBOL))
427                         {
428                                 *p++ = *tok++;
429                                 imacro->argument[nargs].string[stringNum] = strdup(string[*tok++]);
430                                 *p++ = stringNum++;
431                         }
432                         else if (*tok == ',')
433                         {
434                                 // Comma delimiter was found, so set up for next argument
435                                 *p++ = EOL;
436                                 tok++;
437                                 stringNum = 0;
438                                 nargs++;
439                                 p = imacro->argument[nargs].token;
440                         }
441                         else
442                         {
443                                 *p++ = *tok++;
444                         }
445                 }
446
447                 // Make sure to stuff the final EOL (otherwise, it will be skipped)
448                 *p++ = EOL;
449                 nargs++;
450         }
451
452         // Setup IMACRO:
453         //  o  # arguments;
454         //  o  -> macro symbol;
455         //  o  -> macro definition string list;
456         //  o  save 'curuniq', to be restored when the macro pops;
457         //  o  bump `macuniq' counter and set 'curuniq' to it;
458         imacro->im_nargs = nargs;
459         imacro->im_macro = mac;
460         imacro->im_siz = siz;
461         imacro->im_nextln = mac->lineList;
462         imacro->im_olduniq = curuniq;
463         curuniq = macuniq++;
464
465         DEBUG
466         {
467                 printf("# args = %d\n", nargs);
468
469                 for(uint16_t i=0; i<nargs; i++)
470                 {
471                         printf("arg%d=", i);
472                         DumpTokens(imacro->argument[i].token);
473                 }
474         }
475
476         return OK;
477 }
478