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