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