mirror of
https://github.com/open-simh/simtools.git
synced 2026-01-13 15:27:18 +00:00
699 lines
18 KiB
C
699 lines
18 KiB
C
|
|
#define MACROS__C
|
|
|
|
|
|
/*
|
|
Dealing with MACROs
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
|
|
#include "macros.h" /* my own definitions */
|
|
|
|
#include "util.h"
|
|
#include "assemble_globals.h"
|
|
#include "assemble_aux.h"
|
|
#include "listing.h"
|
|
#include "parse.h"
|
|
#include "stream2.h"
|
|
#include "symbols.h"
|
|
|
|
|
|
/* Here's where I pretend I'm a C++ compiler. :-/ */
|
|
|
|
/* *** derive a MACRO_STREAM from a BUFFER_STREAM with a few other args */
|
|
|
|
|
|
/* macro_stream_delete is called when a macro expansion is
|
|
exhausted. The unique behavior is to unwind any stacked
|
|
conditionals. This allows a nested .MEXIT to work. */
|
|
|
|
void macro_stream_delete(
|
|
STREAM *str)
|
|
{
|
|
MACRO_STREAM *mstr = (MACRO_STREAM *) str;
|
|
|
|
pop_cond(mstr->cond);
|
|
buffer_stream_delete(str);
|
|
}
|
|
|
|
STREAM_VTBL macro_stream_vtbl = {
|
|
macro_stream_delete, buffer_stream_getline, buffer_stream_rewind
|
|
};
|
|
|
|
STREAM *new_macro_stream(
|
|
STREAM *refstr,
|
|
BUFFER *buf,
|
|
MACRO *mac,
|
|
int nargs)
|
|
{
|
|
MACRO_STREAM *mstr = memcheck(malloc(sizeof(MACRO_STREAM))); {
|
|
char *name = memcheck(malloc(strlen(refstr->name) + 32));
|
|
|
|
sprintf(name, "%s:%d->%s", refstr->name, refstr->line, mac->sym.label);
|
|
buffer_stream_construct(&mstr->bstr, buf, name);
|
|
free(name);
|
|
}
|
|
|
|
mstr->bstr.stream.vtbl = ¯o_stream_vtbl;
|
|
mstr->nargs = nargs; /* for .NARG directive: nonkeyword arguments in call*/
|
|
mstr->cond = last_cond;
|
|
return &mstr->bstr.stream;
|
|
}
|
|
|
|
/* read_body fetches the body of .MACRO, .REPT, .IRP, or .IRPC into a
|
|
BUFFER. */
|
|
|
|
void read_body(
|
|
STACK *stack,
|
|
BUFFER *gb,
|
|
char *name,
|
|
int called)
|
|
{
|
|
int nest;
|
|
|
|
/* Read the stream in until the end marker is hit */
|
|
|
|
/* Note: "called" says that this body is being pulled from a macro
|
|
library, and so under no circumstance should it be listed. */
|
|
|
|
nest = 1;
|
|
for (;;) {
|
|
SYMBOL *op;
|
|
char *nextline;
|
|
char *cp;
|
|
|
|
nextline = stack_getline(stack); /* Now read the line */
|
|
if (nextline == NULL) { /* End of file. */
|
|
report(stack->top, "Macro body of '%s' not closed\n", name);
|
|
break;
|
|
}
|
|
|
|
if (!(called & CALLED_NOLIST) &&
|
|
(list_level - 1 + list_md) > 0) {
|
|
list_flush();
|
|
list_source(stack->top, nextline);
|
|
}
|
|
|
|
op = get_op(nextline, &cp);
|
|
|
|
if (op == NULL) { /* Not a pseudo-op */
|
|
buffer_append_line(gb, nextline);
|
|
continue;
|
|
}
|
|
if (op->section->type == SECTION_PSEUDO) {
|
|
if (op->value == P_MACRO || op->value == P_REPT || op->value == P_IRP || op->value == P_IRPC) {
|
|
nest++;
|
|
}
|
|
|
|
if (op->value == P_ENDM || op->value == P_ENDR) {
|
|
nest--;
|
|
/* If there's a name on the .ENDM, then
|
|
* check if the name matches the one on .MACRO.
|
|
* See page 7-3.
|
|
* Since we don't keep a stack of nested
|
|
* .MACRO names, just check for the outer one.
|
|
*/
|
|
if (nest == 0 && name && op->value == P_ENDM) {
|
|
cp = skipwhite(cp);
|
|
if (!EOL(*cp)) {
|
|
char *label = get_symbol(cp, &cp, NULL);
|
|
|
|
if (label) {
|
|
if (strcmp(label, name) != 0) {
|
|
report(stack->top, ".ENDM '%s' does not match .MACRO '%s'\n", label, name);
|
|
}
|
|
free(label);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nest == 0)
|
|
return; /* All done. */
|
|
}
|
|
|
|
buffer_append_line(gb, nextline);
|
|
}
|
|
}
|
|
|
|
/* Diagnostic: dumpmacro dumps a macro definition to stdout.
|
|
I used this for debugging; it's not called at all right now, but
|
|
I hate to delete good code. */
|
|
|
|
void dumpmacro(
|
|
MACRO *mac,
|
|
FILE *fp)
|
|
{
|
|
ARG *arg;
|
|
|
|
fprintf(fp, ".MACRO %s ", mac->sym.label);
|
|
|
|
for (arg = mac->args; arg != NULL; arg = arg->next) {
|
|
fputs(arg->label, fp);
|
|
if (arg->value) {
|
|
fputc('=', fp);
|
|
fputs(arg->value, fp);
|
|
}
|
|
fputc(' ', fp);
|
|
}
|
|
fputc('\n', fp);
|
|
|
|
fputs(mac->text->buffer, fp);
|
|
|
|
fputs(".ENDM\n", fp);
|
|
}
|
|
|
|
/* defmacro - define a macro. */
|
|
/* Also used by .MCALL to pull macro definitions from macro libraries */
|
|
|
|
MACRO *defmacro(
|
|
char *cp,
|
|
STACK *stack,
|
|
int called)
|
|
{
|
|
MACRO *mac;
|
|
ARG *arg,
|
|
**argtail;
|
|
char *label;
|
|
|
|
cp = skipwhite(cp);
|
|
label = get_symbol(cp, &cp, NULL);
|
|
if (label == NULL) {
|
|
report(stack->top, "Invalid macro definition\n");
|
|
return NULL;
|
|
}
|
|
|
|
if (!(called & CALLED_NODEFINE)) {
|
|
/* Allow redefinition of a macro; new definition replaces the old. */
|
|
mac = (MACRO *) lookup_sym(label, ¯o_st);
|
|
if (mac) {
|
|
/* Remove from the symbol table... */
|
|
remove_sym(&mac->sym, ¯o_st);
|
|
free_macro(mac);
|
|
}
|
|
}
|
|
|
|
mac = new_macro(label);
|
|
|
|
if (!(called & CALLED_NODEFINE)) {
|
|
add_table(&mac->sym, ¯o_st);
|
|
}
|
|
|
|
argtail = &mac->args;
|
|
cp = skipdelim(cp);
|
|
|
|
while (!EOL(*cp)) {
|
|
arg = new_arg();
|
|
arg->locsym = *cp == '?';
|
|
if (arg->locsym) /* special argument flag? */
|
|
cp++;
|
|
arg->label = get_symbol(cp, &cp, NULL);
|
|
if (arg->label == NULL) {
|
|
/* It turns out that I have code which is badly formatted
|
|
but which MACRO.SAV assembles. Sigh. */
|
|
/* So, just quit defining arguments. */
|
|
break;
|
|
#if 0
|
|
report(str, "Illegal macro argument\n");
|
|
remove_sym(&mac->sym, ¯o_st);
|
|
free_macro(mac);
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
cp = skipwhite(cp);
|
|
if (*cp == '=') {
|
|
/* Default substitution given */
|
|
arg->value = getstring(cp + 1, &cp);
|
|
if (arg->value == NULL) {
|
|
report(stack->top, "Illegal macro argument\n");
|
|
remove_sym(&mac->sym, ¯o_st);
|
|
free_macro(mac);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* Append to list of arguments */
|
|
arg->next = NULL;
|
|
*argtail = arg;
|
|
argtail = &arg->next;
|
|
|
|
cp = skipdelim(cp);
|
|
}
|
|
|
|
/* Read the stream in until the end marker is hit */ {
|
|
BUFFER *gb;
|
|
int levelmod = 0;
|
|
|
|
gb = new_buffer();
|
|
|
|
if ((called & CALLED_NOLIST) && !list_md) {
|
|
list_level--;
|
|
levelmod = 1;
|
|
}
|
|
|
|
read_body(stack, gb, mac->sym.label, called);
|
|
|
|
list_level += levelmod;
|
|
|
|
if (mac->text != NULL) /* Discard old macro body */
|
|
buffer_free(mac->text);
|
|
|
|
mac->text = gb;
|
|
}
|
|
|
|
return mac;
|
|
}
|
|
|
|
|
|
|
|
/* Allocate a new ARG */
|
|
|
|
ARG *new_arg(
|
|
void)
|
|
{
|
|
ARG *arg = memcheck(malloc(sizeof(ARG)));
|
|
|
|
arg->locsym = 0;
|
|
arg->value = NULL;
|
|
arg->next = NULL;
|
|
arg->label = NULL;
|
|
return arg;
|
|
}
|
|
|
|
|
|
/* Free a list of args (as for a macro, or a macro expansion) */
|
|
|
|
static void free_args(
|
|
ARG *arg)
|
|
{
|
|
ARG *next;
|
|
|
|
while (arg) {
|
|
next = arg->next;
|
|
if (arg->label) {
|
|
free(arg->label);
|
|
arg->label = NULL;
|
|
}
|
|
if (arg->value) {
|
|
free(arg->value);
|
|
arg->value = NULL;
|
|
}
|
|
free(arg);
|
|
arg = next;
|
|
}
|
|
}
|
|
|
|
|
|
/* find_arg - looks for an arg with the given name in the given
|
|
argument list */
|
|
|
|
static ARG *find_arg(
|
|
ARG *arg,
|
|
char *name)
|
|
{
|
|
for (; arg != NULL; arg = arg->next)
|
|
if (strcmp(arg->label, name) == 0)
|
|
return arg;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* subst_args - given a BUFFER and a list of args, generate a new
|
|
BUFFER with argument replacement having taken place. */
|
|
|
|
BUFFER *subst_args(
|
|
BUFFER *text,
|
|
ARG *args)
|
|
{
|
|
char *in;
|
|
char *begin;
|
|
BUFFER *gb;
|
|
char *label;
|
|
ARG *arg;
|
|
|
|
gb = new_buffer();
|
|
|
|
/* Blindly look for argument symbols in the input. */
|
|
/* Don't worry about quotes or comments. */
|
|
|
|
for (begin = in = text->buffer; in < text->buffer + text->length;) {
|
|
char *next;
|
|
|
|
if (issym((unsigned char)*in)) {
|
|
label = get_symbol(in, &next, NULL);
|
|
if (label) {
|
|
if ((arg = find_arg(args, label))) {
|
|
/* An apostrophe may appear before or after the symbol. */
|
|
/* In either case, remove it from the expansion. */
|
|
|
|
if (in > begin && in[-1] == '\'')
|
|
in--; /* Don't copy it. */
|
|
if (*next == '\'')
|
|
next++;
|
|
|
|
/* Copy prior characters */
|
|
buffer_appendn(gb, begin, (int) (in - begin));
|
|
/* Copy replacement string */
|
|
buffer_append_line(gb, arg->value);
|
|
in = begin = next;
|
|
--in; /* prepare for subsequent increment */
|
|
}
|
|
free(label);
|
|
in = next;
|
|
} else
|
|
in++;
|
|
} else
|
|
in++;
|
|
}
|
|
|
|
/* Append the rest of the text */
|
|
buffer_appendn(gb, begin, (int) (in - begin));
|
|
|
|
return gb; /* Done. */
|
|
}
|
|
|
|
/* eval_str - the language allows an argument expression to be given
|
|
as "\expression" which means, evaluate the expression and
|
|
substitute the numeric value in the current radix. */
|
|
|
|
char *eval_str(
|
|
STREAM *refstr,
|
|
char *arg)
|
|
{
|
|
EX_TREE *value = parse_expr(arg, 0);
|
|
unsigned word = 0;
|
|
char temp[10];
|
|
|
|
if (value->type != EX_LIT) {
|
|
report(refstr, "Constant value required\n");
|
|
} else
|
|
word = value->data.lit;
|
|
|
|
free_tree(value);
|
|
|
|
/* printf can't do base 2. */
|
|
my_ultoa(word & 0177777, temp, radix);
|
|
free(arg);
|
|
arg = memcheck(strdup(temp));
|
|
return arg;
|
|
}
|
|
|
|
/* getstring_macarg - parse a string that possibly starts with a backslash.
|
|
* If so, performs expression evaluation.
|
|
*
|
|
* The current implementation over-accepts some input.
|
|
*
|
|
* MACRO V05.05:
|
|
|
|
.list me
|
|
.macro test x
|
|
.blkb x
|
|
.endm
|
|
|
|
size = 10
|
|
foo = 2
|
|
|
|
; likes:
|
|
|
|
test size
|
|
test \size
|
|
test \<size>
|
|
test \<size + foo>
|
|
test ^/size + foo/
|
|
|
|
; dislikes:
|
|
|
|
test <\size> ; arg is \size which could be ok in other cases
|
|
test size + foo ; gets split at the space
|
|
test /size + foo/ ; gets split at the space
|
|
test \/size + foo/
|
|
test \^/size + foo/ ; * accepted by this version
|
|
|
|
*/
|
|
|
|
char *getstring_macarg(
|
|
STREAM *refstr,
|
|
char *cp,
|
|
char **endp)
|
|
{
|
|
if (cp[0] == '\\') {
|
|
char *str = getstring(cp + 1, endp);
|
|
return eval_str(refstr, str); /* Perform expression evaluation */
|
|
} else {
|
|
return getstring(cp, endp);
|
|
}
|
|
}
|
|
|
|
/* expandmacro - return a STREAM containing the expansion of a macro */
|
|
|
|
STREAM *expandmacro(
|
|
STREAM *refstr,
|
|
MACRO *mac,
|
|
char *cp)
|
|
{
|
|
ARG *arg,
|
|
*args,
|
|
*macarg;
|
|
char *label;
|
|
STREAM *str;
|
|
BUFFER *buf;
|
|
int nargs;
|
|
int onemore;
|
|
|
|
args = NULL;
|
|
arg = NULL;
|
|
nargs = 0; /* for the .NARG directive */
|
|
onemore = 0;
|
|
|
|
/* Parse the arguments */
|
|
|
|
while (!EOL(*cp) || onemore) {
|
|
char *nextcp;
|
|
|
|
/* Check for named argument */
|
|
label = get_symbol(cp, &nextcp, NULL);
|
|
if (label && (nextcp = skipwhite(nextcp), *nextcp == '=') && (macarg = find_arg(mac->args, label))) {
|
|
/* Check if I've already got a value for it */
|
|
if (find_arg(args, label) != NULL) {
|
|
report(refstr, "Duplicate submission of keyword " "argument %s\n", label);
|
|
free(label);
|
|
free_args(args);
|
|
return NULL;
|
|
}
|
|
|
|
arg = new_arg();
|
|
arg->label = label;
|
|
nextcp = skipwhite(nextcp + 1);
|
|
arg->value = getstring_macarg(refstr, nextcp, &nextcp);
|
|
} else {
|
|
if (label)
|
|
free(label);
|
|
|
|
/* Find correct positional argument */
|
|
|
|
for (macarg = mac->args; macarg != NULL; macarg = macarg->next) {
|
|
if (find_arg(args, macarg->label) == NULL)
|
|
break; /* This is the next positional arg */
|
|
}
|
|
|
|
if (macarg == NULL)
|
|
break; /* Don't pick up any more arguments. */
|
|
|
|
nextcp = skipwhite (cp);
|
|
arg = new_arg();
|
|
arg->label = memcheck(strdup(macarg->label)); /* Copy the name */
|
|
if (*nextcp != ',') {
|
|
arg->value = getstring_macarg(refstr, cp, &nextcp);
|
|
}
|
|
else {
|
|
arg->value = NULL;
|
|
}
|
|
nargs++; /* Count nonkeyword arguments only. */
|
|
}
|
|
|
|
arg->next = args;
|
|
args = arg;
|
|
|
|
/* If there is a trailing comma, there is an empty last argument */
|
|
cp = skipdelim_comma(nextcp, &onemore);
|
|
}
|
|
|
|
/* Now go back and fill in defaults */ {
|
|
int locsym;
|
|
|
|
if (last_macro_lsb != lsb)
|
|
locsym = last_locsym = 32768;
|
|
else
|
|
locsym = last_locsym;
|
|
last_macro_lsb = lsb;
|
|
|
|
for (macarg = mac->args; macarg != NULL; macarg = macarg->next) {
|
|
arg = find_arg(args, macarg->label);
|
|
if (arg == NULL || arg->value == NULL) {
|
|
int wasnull = 0;
|
|
if (arg == NULL) {
|
|
wasnull = 1;
|
|
arg = new_arg();
|
|
arg->label = memcheck(strdup(macarg->label));
|
|
}
|
|
if (macarg->locsym) {
|
|
char temp[32];
|
|
|
|
/* Here's where we generate local labels */
|
|
sprintf(temp, "%d$", locsym++);
|
|
arg->value = memcheck(strdup(temp));
|
|
} else if (macarg->value) {
|
|
arg->value = memcheck(strdup(macarg->value));
|
|
} else
|
|
arg->value = memcheck(strdup(""));
|
|
|
|
if (wasnull) {
|
|
arg->next = args;
|
|
args = arg;
|
|
}
|
|
}
|
|
}
|
|
|
|
last_locsym = locsym;
|
|
}
|
|
|
|
buf = subst_args(mac->text, args);
|
|
|
|
str = new_macro_stream(refstr, buf, mac, nargs);
|
|
|
|
free_args(args);
|
|
buffer_free(buf);
|
|
|
|
return str;
|
|
}
|
|
|
|
|
|
/* dump_all_macros is a diagnostic function that's currently not
|
|
used. I used it while debugging, and I haven't removed it. */
|
|
|
|
void dump_all_macros(
|
|
void)
|
|
{
|
|
MACRO *mac;
|
|
SYMBOL_ITER iter;
|
|
|
|
for (mac = (MACRO *) first_sym(¯o_st, &iter); mac != NULL; mac = (MACRO *) next_sym(¯o_st, &iter)) {
|
|
dumpmacro(mac, lstfile);
|
|
|
|
fprintf(lstfile, "\n\n");
|
|
}
|
|
}
|
|
|
|
|
|
/* Allocate a new macro */
|
|
|
|
MACRO *new_macro(
|
|
char *label)
|
|
{
|
|
MACRO *mac = memcheck(malloc(sizeof(MACRO)));
|
|
|
|
mac->sym.flags = SYMBOLFLAG_DEFINITION;
|
|
mac->sym.label = label;
|
|
mac->sym.stmtno = stmtno;
|
|
mac->sym.next = NULL;
|
|
mac->sym.section = ¯o_section;
|
|
mac->sym.value = 0;
|
|
mac->args = NULL;
|
|
mac->text = NULL;
|
|
|
|
return mac;
|
|
}
|
|
|
|
/* free a macro, its args, its text, etc. */
|
|
void free_macro(
|
|
MACRO *mac)
|
|
{
|
|
if (mac->text) {
|
|
free(mac->text);
|
|
}
|
|
free_args(mac->args);
|
|
free_sym(&mac->sym);
|
|
}
|
|
|
|
int do_mcall (char *label, STACK *stack)
|
|
{
|
|
SYMBOL *op; /* The operation SYMBOL */
|
|
STREAM *macstr;
|
|
BUFFER *macbuf;
|
|
char *maccp;
|
|
int saveline;
|
|
MACRO *mac;
|
|
int i;
|
|
char macfile[FILENAME_MAX];
|
|
char hitfile[FILENAME_MAX];
|
|
|
|
/* Find the macro in the list of included
|
|
macro libraries */
|
|
macbuf = NULL;
|
|
for (i = 0; i < nr_mlbs; i++)
|
|
if ((macbuf = mlb_entry(mlbs[i], label)) != NULL)
|
|
break;
|
|
if (macbuf != NULL) {
|
|
macstr = new_buffer_stream(macbuf, label);
|
|
buffer_free(macbuf);
|
|
} else {
|
|
char *bufend = &macfile[sizeof(macfile)],
|
|
*end;
|
|
end = stpncpy(macfile, label, sizeof(macfile) - 5);
|
|
if (end >= bufend - 5) {
|
|
report(stack->top, ".MCALL: name too long: '%s'\n", label);
|
|
return 0;
|
|
}
|
|
stpncpy(end, ".MAC", bufend - end);
|
|
my_searchenv(macfile, "MCALL", hitfile, sizeof(hitfile));
|
|
if (hitfile[0])
|
|
macstr = new_file_stream(hitfile);
|
|
else
|
|
macstr = NULL;
|
|
}
|
|
|
|
if (macstr != NULL) {
|
|
for (;;) {
|
|
char *mlabel;
|
|
|
|
maccp = macstr->vtbl->getline(macstr);
|
|
if (maccp == NULL)
|
|
break;
|
|
mlabel = get_symbol(maccp, &maccp, NULL);
|
|
if (mlabel == NULL)
|
|
continue;
|
|
op = lookup_sym(mlabel, &system_st);
|
|
free(mlabel);
|
|
if (op == NULL)
|
|
continue;
|
|
if (op->value == P_MACRO)
|
|
break;
|
|
}
|
|
|
|
if (maccp != NULL) {
|
|
STACK macstack = {
|
|
macstr
|
|
};
|
|
int savelist = list_level;
|
|
|
|
saveline = stmtno;
|
|
list_level = -1;
|
|
mac = defmacro(maccp, &macstack, CALLED_NOLIST);
|
|
if (mac == NULL) {
|
|
report(stack->top, "Failed to define macro called %s\n", label);
|
|
}
|
|
|
|
stmtno = saveline;
|
|
list_level = savelist;
|
|
}
|
|
|
|
macstr->vtbl->delete(macstr);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|