mirror of http://shamusworld.gotdns.org/git/rmac
515 lines
12 KiB
C
515 lines
12 KiB
C
//
|
|
// RMAC - Renamed Macro Assembler for all Atari computers
|
|
// MACRO.C - Macro Definition and Invocation
|
|
// Copyright (C) 199x Landon Dyer, 2011-2021 Reboot and Friends
|
|
// RMAC derived from MADMAC v1.07 Written by Landon Dyer, 1986
|
|
// Source utilised with the kind permission of Landon Dyer
|
|
//
|
|
|
|
#include "macro.h"
|
|
#include "debug.h"
|
|
#include "direct.h"
|
|
#include "error.h"
|
|
#include "expr.h"
|
|
#include "listing.h"
|
|
#include "procln.h"
|
|
#include "symbol.h"
|
|
#include "token.h"
|
|
|
|
|
|
LONG curuniq; // Current macro's unique number
|
|
int macnum; // Unique number for macro definition
|
|
|
|
static LONG macuniq; // Unique-per-macro number
|
|
static SYM * curmac; // Macro currently being defined
|
|
static uint32_t argno; // Formal argument count
|
|
LONG reptuniq; // Unique-per-rept number
|
|
|
|
static LLIST * firstrpt; // First .rept line
|
|
static LLIST * nextrpt; // Last .rept line
|
|
int rptlevel; // .rept nesting level
|
|
|
|
// Function prototypes
|
|
static int KWMatch(char *, char *);
|
|
static int LNCatch(int (*)(), char *);
|
|
|
|
|
|
//
|
|
// Initialize macro processor
|
|
//
|
|
void InitMacro(void)
|
|
{
|
|
macuniq = 0;
|
|
macnum = 1;
|
|
reptuniq = 0;
|
|
}
|
|
|
|
|
|
//
|
|
// Exit from a macro;
|
|
// -- pop any intervening include files and repeat blocks;
|
|
// -- restore argument stack;
|
|
// -- pop the macro.
|
|
//
|
|
int ExitMacro(void)
|
|
{
|
|
WARNING(!!! Bad macro exiting !!!)
|
|
/*
|
|
This is a problem. Currently, the argument logic just keeps the current
|
|
arguments and doesn't save anything if a new macro is called in the middle
|
|
of another (nested macros). Need to fix that somehow.
|
|
|
|
Is this still true, now that we have IMACROs with TOKENSTREAMs in them? Need to
|
|
check it out for sure...!
|
|
*/
|
|
// Pop intervening include files and .rept blocks
|
|
while (cur_inobj != NULL && cur_inobj->in_type != SRC_IMACRO)
|
|
fpop();
|
|
|
|
if (cur_inobj == NULL)
|
|
fatal("too many ENDMs");
|
|
|
|
// Restore
|
|
// o old arg context
|
|
// o old unique number
|
|
// ...and then pop the macro.
|
|
|
|
IMACRO * imacro = cur_inobj->inobj.imacro;
|
|
curuniq = imacro->im_olduniq;
|
|
|
|
DEBUG { printf("ExitMacro: nargs = %d\n", imacro->im_nargs); }
|
|
|
|
return fpop();
|
|
}
|
|
|
|
|
|
//
|
|
// Add a formal argument to a macro definition
|
|
//
|
|
int defmac2(char * argname)
|
|
{
|
|
if (lookup(argname, MACARG, (int)curmac->sattr) != NULL)
|
|
return error("multiple formal argument definition");
|
|
|
|
SYM * arg = NewSymbol(argname, MACARG, (int)curmac->sattr);
|
|
arg->svalue = argno++;
|
|
|
|
return OK;
|
|
}
|
|
|
|
|
|
//
|
|
// Add a line to a macro definition; also print lines to listing file (if
|
|
// enabled). The last line of the macro (containing .endm) is not included in
|
|
// the macro. A label on that line will be lost.
|
|
// notEndFlag is -1 for all lines but the last one (.endm), when it is 0.
|
|
//
|
|
int defmac1(char * ln, int notEndFlag)
|
|
{
|
|
if (list_flag)
|
|
{
|
|
listeol(); // Flush previous source line
|
|
lstout('.'); // Mark macro definition with period
|
|
}
|
|
|
|
if (notEndFlag)
|
|
{
|
|
if (curmac->lineList == NULL)
|
|
{
|
|
curmac->lineList = malloc(sizeof(LLIST));
|
|
curmac->lineList->next = NULL;
|
|
curmac->lineList->line = strdup(ln);
|
|
curmac->lineList->lineno = curlineno;
|
|
curmac->last = curmac->lineList;
|
|
}
|
|
else
|
|
{
|
|
curmac->last->next = malloc(sizeof(LLIST));
|
|
curmac->last->next->next = NULL;
|
|
curmac->last->next->line = strdup(ln);
|
|
curmac->lineList->lineno = curlineno;
|
|
curmac->last = curmac->last->next;
|
|
}
|
|
|
|
return 1; // Keep looking
|
|
}
|
|
|
|
return 0; // Stop looking; at the end
|
|
}
|
|
|
|
|
|
//
|
|
// Define macro
|
|
//
|
|
// macro foo arg1,arg2,...
|
|
// :
|
|
// :
|
|
// endm
|
|
//
|
|
// Helper functions:
|
|
// -----------------
|
|
// `defmac1' adds lines of text to the macro definition
|
|
// `defmac2' processes the formal arguments (and sticks them into the symbol
|
|
// table)
|
|
//
|
|
int DefineMacro(void)
|
|
{
|
|
// Setup entry in symbol table, make sure the macro isn't a duplicate
|
|
// entry, and that it doesn't override any processor mnemonic or assembler
|
|
// directive.
|
|
if (*tok++ != SYMBOL)
|
|
return error("missing symbol");
|
|
|
|
char * name = string[*tok++];
|
|
|
|
if (lookup(name, MACRO, 0) != NULL)
|
|
return error("duplicate macro definition");
|
|
|
|
curmac = NewSymbol(name, MACRO, 0);
|
|
curmac->svalue = 0;
|
|
curmac->sattr = (WORD)(macnum++);
|
|
|
|
// Parse and define formal arguments in symbol table
|
|
if (*tok != EOL)
|
|
{
|
|
argno = 0;
|
|
symlist(defmac2);
|
|
ErrorIfNotAtEOL();
|
|
}
|
|
|
|
// Suck in the macro definition; we're looking for an ENDM symbol on a line
|
|
// by itself to terminate the definition.
|
|
// curmln = NULL;
|
|
curmac->lineList = NULL;
|
|
LNCatch(defmac1, "endm ");
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
//
|
|
// Add lines to a .rept definition
|
|
//
|
|
int defr1(char * line, int kwno)
|
|
{
|
|
if (list_flag)
|
|
{
|
|
listeol(); // Flush previous source line
|
|
lstout('#'); // Mark this a 'rept' block
|
|
}
|
|
|
|
if (kwno == 0) // .endr
|
|
{
|
|
if (--rptlevel == 0)
|
|
return 0;
|
|
}
|
|
else if (kwno == 1) // .rept
|
|
rptlevel++;
|
|
|
|
//DEBUG { printf(" defr1: line=\"%s\", kwno=%d, rptlevel=%d\n", line, kwno, rptlevel); }
|
|
|
|
#if 0
|
|
//MORE stupidity here...
|
|
WARNING("!!! Casting (char *) as LONG !!!")
|
|
// Allocate length of line + 1('\0') + LONG
|
|
LONG * p = (LONG *)malloc(strlen(line) + 1 + sizeof(LONG));
|
|
*p = 0;
|
|
strcpy((char *)(p + 1), line);
|
|
|
|
if (nextrpt == NULL)
|
|
firstrpt = p; // First line of rept statement
|
|
else
|
|
*nextrpt = (LONG)p;
|
|
|
|
nextrpt = p;
|
|
#else
|
|
if (firstrpt == NULL)
|
|
{
|
|
firstrpt = malloc(sizeof(LLIST));
|
|
firstrpt->next = NULL;
|
|
firstrpt->line = strdup(line);
|
|
firstrpt->lineno = curlineno;
|
|
nextrpt = firstrpt;
|
|
}
|
|
else
|
|
{
|
|
nextrpt->next = malloc(sizeof(LLIST));
|
|
nextrpt->next->next = NULL;
|
|
nextrpt->next->line = strdup(line);
|
|
nextrpt->next->lineno = curlineno;
|
|
nextrpt = nextrpt->next;
|
|
}
|
|
#endif
|
|
|
|
return rptlevel;
|
|
}
|
|
|
|
|
|
//
|
|
// Handle a .rept block; this gets hairy because they can be nested
|
|
//
|
|
int HandleRept(void)
|
|
{
|
|
uint64_t eval;
|
|
|
|
// Evaluate repeat expression
|
|
if (abs_expr(&eval) != OK)
|
|
return ERROR;
|
|
|
|
// Suck in lines for .rept block
|
|
firstrpt = NULL;
|
|
nextrpt = NULL;
|
|
rptlevel = 1;
|
|
LNCatch(defr1, "endr rept ");
|
|
|
|
//DEBUG { printf("HandleRept: firstrpt=$%X\n", firstrpt); }
|
|
// Alloc and init input object
|
|
if (firstrpt)
|
|
{
|
|
INOBJ * inobj = a_inobj(SRC_IREPT); // Create a new REPT input object
|
|
IREPT * irept = inobj->inobj.irept;
|
|
irept->ir_firstln = firstrpt;
|
|
irept->ir_nextln = NULL;
|
|
irept->ir_count = (uint32_t)eval;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
//
|
|
// Hand off lines of text to the function 'lnfunc' until a line containing one
|
|
// of the directives in 'dirlist' is encountered.
|
|
//
|
|
// 'dirlist' contains space-separated terminated keywords. A final space
|
|
// terminates the list. Directives are case-insensitively compared to the
|
|
// keywords.
|
|
//
|
|
// If 'lnfunc' is NULL, then lines are simply skipped.
|
|
// If 'lnfunc' returns an error, processing is stopped.
|
|
//
|
|
// 'lnfunc' is called with an argument of -1 for every line but the last one,
|
|
// when it is called with an argument of the keyword number that caused the
|
|
// match.
|
|
//
|
|
static int LNCatch(int (* lnfunc)(), char * dirlist)
|
|
{
|
|
if (lnfunc != NULL)
|
|
lnsave++; // Tell tokenizer to keep lines
|
|
|
|
while (1)
|
|
{
|
|
if (TokenizeLine() == TKEOF)
|
|
{
|
|
error("encountered end-of-file looking for '%s'", dirlist);
|
|
fatal("cannot continue");
|
|
}
|
|
|
|
DEBUG { DumpTokenBuffer(); }
|
|
|
|
// Test for end condition. Two cases to handle:
|
|
// <directive>
|
|
// symbol: <directive>
|
|
char * p = NULL;
|
|
int k = -1;
|
|
|
|
if (*tok == SYMBOL)
|
|
{
|
|
// A string followed by a colon or double colon is a symbol and
|
|
// *not* a directive, see if we can find the directive after it
|
|
if ((tok[2] == ':' || tok[2] == DCOLON))
|
|
{
|
|
if (tok[3] == SYMBOL)
|
|
p = string[tok[4]];
|
|
}
|
|
else
|
|
{
|
|
// Otherwise, just grab the directive
|
|
p = string[tok[1]];
|
|
}
|
|
}
|
|
|
|
if (p != NULL)
|
|
{
|
|
if (*p == '.') // Ignore leading periods
|
|
p++;
|
|
|
|
k = KWMatch(p, dirlist);
|
|
}
|
|
|
|
// Hand-off line to function
|
|
// if it returns 0, and we found a keyword, stop looking.
|
|
// if it returns 1, hand off the line and keep looking.
|
|
if (lnfunc != NULL)
|
|
k = (*lnfunc)(lnbuf, k);
|
|
|
|
if (k == 0)
|
|
break;
|
|
}
|
|
|
|
if (lnfunc != NULL)
|
|
lnsave--; // Tell tokenizer to stop keeping lines
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
//
|
|
// See if the string `kw' matches one of the keywords in `kwlist'. If so,
|
|
// return the number of the keyword matched. Return -1 if there was no match.
|
|
// Strings are compared without regard for case.
|
|
//
|
|
static int KWMatch(char * kw, char * kwlist)
|
|
{
|
|
for(int k=0; *kwlist; k++)
|
|
{
|
|
for(char * p=kw;;)
|
|
{
|
|
char c1 = *kwlist++;
|
|
char c2 = *p++;
|
|
|
|
if (c2 >= 'A' && c2 <= 'Z')
|
|
c2 += 32;
|
|
|
|
if (c1 == ' ' && c2 == EOS)
|
|
return k;
|
|
|
|
if (c1 != c2)
|
|
break;
|
|
}
|
|
|
|
// Skip to beginning of next keyword in `kwlist'
|
|
while (*kwlist && (*kwlist != ' '))
|
|
++kwlist;
|
|
|
|
if (*kwlist== ' ')
|
|
kwlist++;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
//
|
|
// Invoke a macro by creating a new IMACRO object & chopping up the arguments
|
|
//
|
|
int InvokeMacro(SYM * mac, WORD siz)
|
|
{
|
|
DEBUG { printf("InvokeMacro: arguments="); DumpTokens(tok); }
|
|
|
|
INOBJ * inobj = a_inobj(SRC_IMACRO); // Alloc and init IMACRO
|
|
IMACRO * imacro = inobj->inobj.imacro;
|
|
uint16_t nargs = 0;
|
|
|
|
// Chop up the arguments, if any (tok comes from token.c, which at this
|
|
// point points at the macro argument token stream)
|
|
if (*tok != EOL)
|
|
{
|
|
// Parse out the arguments and set them up correctly
|
|
TOKEN * p = imacro->argument[nargs].token;
|
|
int stringNum = 0;
|
|
int numTokens = 0;
|
|
|
|
while (*tok != EOL)
|
|
{
|
|
if (*tok == ACONST)
|
|
{
|
|
// Sanity checking (it's numTokens + 1 because we need an EOL
|
|
// if we successfully parse this argument)
|
|
if ((numTokens + 3) >= TS_MAXTOKENS)
|
|
return error("Too many tokens in argument #%d in MACRO invocation", nargs + 1);
|
|
|
|
for(int i=0; i<3; i++)
|
|
*p++ = *tok++;
|
|
|
|
numTokens += 3;
|
|
}
|
|
else if (*tok == CONST) // Constants are 64-bits
|
|
{
|
|
// Sanity checking (it's numTokens + 1 because we need an EOL
|
|
// if we successfully parse this argument)
|
|
if ((numTokens + 3) >= TS_MAXTOKENS)
|
|
return error("Too many tokens in argument #%d in MACRO invocation", nargs + 1);
|
|
|
|
*p++ = *tok++; // Token
|
|
uint64_t *p64 = (uint64_t *)p;
|
|
uint64_t *tok64 = (uint64_t *)tok;
|
|
*p64++ = *tok64++;
|
|
tok = (TOKEN *)tok64;
|
|
p = (uint32_t *)p64;
|
|
numTokens += 3;
|
|
}
|
|
else if ((*tok == STRING) || (*tok == SYMBOL))
|
|
{
|
|
// Sanity checking (it's numTokens + 1 because we need an EOL
|
|
// if we successfully parse this argument)
|
|
if (stringNum >= TS_MAXSTRINGS)
|
|
return error("Too many strings in argument #%d in MACRO invocation", nargs + 1);
|
|
|
|
if ((numTokens + 2) >= TS_MAXTOKENS)
|
|
return error("Too many tokens in argument #%d in MACRO invocation", nargs + 1);
|
|
|
|
*p++ = *tok++;
|
|
imacro->argument[nargs].string[stringNum] = strdup(string[*tok++]);
|
|
*p++ = stringNum++;
|
|
numTokens += 2;
|
|
}
|
|
else if (*tok == ',')
|
|
{
|
|
// Sanity checking
|
|
if ((nargs + 1) >= TS_MAXARGS)
|
|
return error("Too many arguments in MACRO invocation");
|
|
|
|
// Comma delimiter was found, so set up for next argument
|
|
*p++ = EOL;
|
|
tok++;
|
|
stringNum = 0;
|
|
numTokens = 0;
|
|
nargs++;
|
|
p = imacro->argument[nargs].token;
|
|
}
|
|
else
|
|
{
|
|
// Sanity checking (it's numTokens + 1 because we need an EOL
|
|
// if we successfully parse this argument)
|
|
if ((numTokens + 1) >= TS_MAXTOKENS)
|
|
return error("Too many tokens in argument #%d in MACRO invocation", nargs + 1);
|
|
|
|
*p++ = *tok++;
|
|
numTokens++;
|
|
}
|
|
}
|
|
|
|
// Make sure to stuff the final EOL (otherwise, it will be skipped)
|
|
*p++ = EOL;
|
|
nargs++;
|
|
}
|
|
|
|
// Setup IMACRO:
|
|
// o # arguments;
|
|
// o -> macro symbol;
|
|
// o -> macro definition string list;
|
|
// o save 'curuniq', to be restored when the macro pops;
|
|
// o bump `macuniq' counter and set 'curuniq' to it;
|
|
imacro->im_nargs = nargs;
|
|
imacro->im_macro = mac;
|
|
imacro->im_siz = siz;
|
|
imacro->im_nextln = mac->lineList;
|
|
imacro->im_olduniq = curuniq;
|
|
curuniq = macuniq++;
|
|
|
|
DEBUG
|
|
{
|
|
printf("# args = %d\n", nargs);
|
|
|
|
for(uint16_t i=0; i<nargs; i++)
|
|
{
|
|
printf("arg%d=", i);
|
|
DumpTokens(imacro->argument[i].token);
|
|
}
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|