Prototype int SetMacro (char* name, char* body, int nargs);
Prototype void do_retmacro (void);
Prototype int savemacros (FILE *); /* PATCH_NULL [25 Jan 1993] : proto'd for pack.c */
Prototype int loadmacros (FILE *, int *); /* PATCH_NULL [25 Jan 1993] : proto'd for pack.c */
Prototype int GetDefMacros (void); /* PATCH_NULL [25 Jan 1993] : proto'd for pack.c */
/**************************************
Interne Defines & Struktures
**************************************/
/*
** U might use MACRO like an opaque type
*/
typedef struct _macro
{
NODE Node; /* includes a ptr to the macro-name */
char* Body; /* statements to be executed at a macro-call (this all must fit in one line) */
short NArgs; /* 0..7 number of arguments to be given from do_command */
short NFlags; /* PERHAPS FUTURE: 0..n number of local-flags to be supported */
char freeme; /* NOT USED ANY MORE 0/1 flag if the macro is builtin or user-defined */
char killme; /* 0/1 flag if he maxro shall be deleted after termination of its last macrocall */
short NCalls; /* it seems to be necessary to forbid all deletions of an active macro */
} MACRO;
typedef struct _marg
{
MNODE Node; /* hook for recursion-stack */
MACRO* Macro; /* a reference to the called macro w/ all its data */
APTR Locals; /* AVL-Tree of Local Variables */
MLIST Locals; /* List of Local Variables */ /* we could also have a ptr to an array of ptrs instead of a list, but then it might be necessary to use one more parameter for SETMACRO */
char* Arguments[2]; /* array of ptr to string; there must always be at least two entries: a 1st one referencing the macroname and a last one == NULL */
} MARG;
#ifndef MACRO_NUM /* define the number of macro-lists to be used */
#define MACRO_NUM 8
#endif
/*
** usually I use a global Struct that contains all important
** variables - so the Macrolists too, that is to allow
** switching between several environments in future
** if that global structure is not used, we can support our
** own - local - variable, else we must access the lists via a
** macro.
** the argstack is not touched by that division, as command flow cannot
** be splitted
** !WARNING! it CAN be splitted when using asynchroneous Rexx-ports
** but when writiung that module, i did not intend to
** use async rexx-commands
**
** NOTE
** when using Packages, make sure to call init_pack()
** BEFORE init_macro()
*/
#ifdef PATCH_PACK
#define macros Package->PMacros /* FUTURE: Package - support */
#elifdef DBASE
#define macros DmeBase.macros
#else
static MLIST macros[MACRO_NUM]; /* ... OF MACRO */
#endif /* global macros, dbase or packages */
/**************************************
Internal Variables
**************************************/
/*
** *argstack contains all data which is created during one macro-call
** *delenda is a delete-buffer for all macros, will have been removed
** from their lists, but cannot deleted, as there is a prior call to
** not returned; they are deleted then aftermacro-termination or
** in exit_macros.
*/
static MLIST argstack; /* ... OF MARG */
static MLIST delenda; /* ... OF MACRO */
char MacroBreak = 0; /* internal possibility to terminate a macro */ /* PATCH_NULL [08 Mar 1993] : removed "static" */
/**********************************************
**
** Definition and deletion of Macros
**
**********************************************/
/*
** FreeMacro()
** DelMacro()
** 2 help-Functions of do_unsetmacro (and exit_macros)
**
** DelMacro() should delete only Macros, which are not in use
** for that reason it checks usecount, and if its not 0, the
** macro is put into another list and marked to be killed by
** callmacro() after execution.
*/
static void
FreeMacro(MACRO* m)
{
if (m->freeme)
{ /* if that macro is not a builtin one, */
free(m->Node.ln_Name); /* free it and its contents */
free(m->Body);
free(m);
} /* if */
} /* FreeMacro */
static void
DelMacro(char* name)
{
MACRO* m;
int key = name[0] & (MACRO_NUM-1);
if (m = getmacro(name))
{ /* find the node representing the macro ... */
Remove((NODE *)m); /* , remove it from its list ... */
if (!m->NCalls)
{ /* and if it is not in use */
FreeMacro(m); /* ... free its contents */
} else
{ /* ... else put it into a certain kill-list */
m->killme++; /* ... and mark it to be killed */
AddHead((LIST*)&delenda,(NODE*)m);
} /* if */
} /* if */
} /* DelMacro */
/*
*! >UNSETMACRO name
*! remove a macro definition
*!
*/
void
do_unsetmacro()
{
DelMacro(strlower(av[1]));
} /* do_unsetmacro */
/*
** SetMacro()
** a help-Function of do_setmacro (and maybe init_macros)
** (it is also called by packages.mergpack)
** which creates a new macro-node and fills copies of the parameters
**
*/
int
SetMacro(char* name, char* body, int nargs)
{
MACRO* m;
int key = name[0] & (MACRO_NUM-1);
if (nargs >= NARGS)
{
error ("too many arguments specified for macro");
return(0);
} /* if too many args */
DelMacro(name); /* for sure: remove all macros of the same name */
if (m = malloc(sizeof(MACRO)))
{ /* allocate a macro-structure... */
setmem(m, sizeof(MACRO),0);
if (m->Node.ln_Name = strdup(name))
{ /* ... the name ... */
if (m->Body = strdup(body))
{ /* ... and the body, */
m->NArgs= nargs; /* fill in all values, */
m->freeme = 1;
AddHead((LIST *)¯os[key], (NODE *)m); /* and append the structure to a certain macro-list */
return(1);
} /* if block copied */
free(m->Node.ln_Name); /* if anything went wrong */
} /* if name copied */ /* free all allocated frags */
free(m); /* and set an error */
} /* if struct allocated */
nomemory();
return(0);
} /* SetMacro */
/*
*! >SETMACRO macroname numargs body
*! create a macro definition
*! please note that a macro-body is read twice once on definition
*! and once on execution (variables should be excaped so)
*!
*! after such a definition it is possible to use macroname like
*! any DME-command; the following numargs expressions are used
*! as the macros variables, they can be accessed for read with
*! $arg0-$arg<numargs> (not for write)($arg0 returnes the macro's
*! name)
*!
*! BUG: if numargs is not a number then this command
*! will assume a value of "0"
*!
*! WARNING: the synopsis of that command MIGHT change in near future, as
*! I am thinking about use of macros' local flags or static vars
*! so there might come up another number of local-flags ->
*! SETMACRO name numargs NUMFLAGS|LIST-OF-NAMES body
*!
*/
void
do_setmacro()
{
SetMacro(strlower(av[1]), av[3], atoi(av[2]));
} /* do_setmacro */
/**********************************************
**
** Access to Macros' arguments and local variables
**
**********************************************/
/*
** getmacrovar()
** get a local variable of the current(== last) macrocall
** we are supposing that only one process has access to resources
** that function is meant to be interface for getvar()
*/
char*
getmacrovar(char* name)
{
MARG* n = NULL;
if (!((n = argstack.mlh_Head) && (n->Node.mln_Succ != NULL)))
{ /* this function can not work if not called from within a macrocall */
return(NULL);
} /* if */
return(GetVarFromTree (&n->Locals, name));
} /* getmacrovar */
/*
** SetMacroVar()
** a help-function of do_setmacrovar()
** and an interface to settypedvar()
*/
void
SetMacroVar(char* name, char* value)
{
MARG* n = NULL;
if (!((n = argstack.mlh_Head) && (n->Node.mln_Succ != NULL)))
{ /* this function can not work if not called from within a macrocall */
abort();
} /* if */
SetVarIntoTree (&n->Locals, name, value);
} /* SetMacroVar */
/*
*! >SETMACROVAR name value
*! >SMV name value
*! define a local variable for the current macro-call
*! all local vars will be deleted at the end of a macro-execution
*! (SMV is a shortcut for SETMACROVAR as we can't define a short
*! macro for that command)
*!
*/
void
do_setmacrovar()
{
SetMacroVar(av[1], av[2]);
} /* do_setmacrovar */
/*
*! >UNSETMACROVAR name
*! drop a local variable before the end of the current macro-call
*! all local vars will be deleted at the end of a macro-execution
*! so that command may be obsolete
*/
void
do_unsetmacrovar()
{
MARG* n = NULL;
if (!((n = argstack.mlh_Head) && (n->Node.mln_Succ != NULL)))
{ /* this function can not work if not called from within a macrocall */
abort();
} /* if */
DelVarFromTree (&n->Locals, av[1]);
} /* do_unsetmacrovar */
/*
** getmacroarg()
** get an parameter of the current(== last) macrocall
** we are supposing that only one process has access to resources
** so we only have to check the topmost element of argstack
*! NOTE: ARGUMENTS to a macro are READ-ONLY,
*! You can't alter their values from the body of the called macro
*!
*/
char*
getmacroarg(char* name)
{
MARG* n = NULL;
int num;
char* res;
if (!((n = argstack.mlh_Head) && (n->Node.mln_Succ != NULL)))
{ /* this function can not work if not called from within a macrocall */
return(NULL);
} /* if */
if ((strncmp(name,"arg",3) != 0) || (!is_number(name+3)))
{ /* and it responds only to names of the type "ARGx" with x in /N */
return(NULL);
} /* if */
num = atoi(name+3);
/* for (i = 0; n->Arguments[i] && i < num; i++); if (n->Arguments[i]) */ /* use that line if ther is no reference to Macro */
if (n->Macro->NArgs>=num)
{ /* if the last called function has an arg ARGx */
if (res = strdup (n->Arguments[num]))
{ /* and U can duplicate it, then return the copy */
return(res);
} else
{
nomemory();
abort(NULL);
} /* if (not) malloced res */
} else
{
abort(NULL);
} /* if (not) exists argX */
} /* getmacroarg */
/**********************************************
**
** Usage of Macros
**
** for calls from do_command the sequence must
** be like that:
**
** if (m=getmacro) then
** for i=1 to nummacroargs(m) do
** put nextarg to av[i]
** od;
** callmacro(m);
** fi;
**
** callmacro() pushes av[i] onto argstack and
** clears them, then locks the macro and
** calls do_command with a copy of the
** macro's block
** when the execution returns, av[i] are
** restored and topof argstack is cleared
**
**********************************************/
/*
** callmacro()
** Call a Macro as interface from do_command()
** (first you must have got a "handle" via getmacro())
*/
int
callmacro(MACRO* m)
{
MARG* n = NULL;
char** args = NULL;
char* com = NULL;
int ret = 0;
int i;
int len = m->NArgs*4+sizeof(MARG);
n = (MARG*)malloc(len); /* allocate space for the stack-element... */
com = strdup(m->Body); /* and duplicate the macro's body */
if (n && com)
{
setmem(n,len,0); /* clear all data */
AddHead((LIST*)&argstack,(NODE*)n); /* and push the struct to argstack */
n->Locals = NULL;
av[0] = m->Node.ln_Name; /* define argslot 0 - it might be asked during execution, and perhaps it was not set in do_command */
args = n->Arguments; /* then put all arguments into the struct */
for (i = m->NArgs; i >= 0; i--)
{ /* and clear their global values */
args[i] = av[i];
av[i] = NULL;
} /* for */
/* [25 Jan 1993] : line added */
n->Macro = m;
m->NCalls++; /* disable deletion of that macro during execution */
MacroBreak = 0; /* clear the return - flag */
ret = do_command(com); /* and call the macrobody (execute only the copy, as do_command might destroy the data) */
if (MacroBreak)
{
ret = 1-ret;
globalflags.Abortcommand = MacroBreak-1;
MacroBreak = 0;
} /* if */
for (i = m->NArgs; i >= 0; i--)
{ /* after the call restore the global values of all args, ... */
av[i] = args[i];
} /* for */
m->NCalls--; /* and enable deletion of that macro after execution */
if (m->NCalls == 0 && m->killme)
{ /* if we must delete the macro, do it */
Remove((NODE*)m);
FreeMacro(m);
} /* if */
DelAllVarsFromTree (&n->Locals); /* delete all local variables ... */
Remove((NODE*)n); /* and pop the margs-struct from argstack */
} else
{
nomemory();
} /* if */
if (n) /* and free anything you allocated */
free(n);
if (com)
free(com);
return(ret);
} /* callmacro */
/*
** getmacro()
**
** Find a Function of a given Name
** so macros can be tested for existance and user can get a ptr to a "Handle"
** that function is part of the interface to do_command()
*/
MACRO*
getmacro(char* name)
{
MACRO* m;
int key = name[0] & (MACRO_NUM-1);
m = (MACRO*)FindName((LIST*)(¯os[key]), name);
return(m);
} /* getmacro */
#ifdef NOT_DEF
/*
** That function might be used as an interface for getvar
** to access the contents of a macro
*/
char*
GetFuncBody(char* name)
{
MACRO* m = NULL;
char* str = NULL;
if (m = (MACRO*)getmacro(name))
{
str = strdup(m->Body);
} /* if */
return(str);
} /* GetFuncBody */
#endif /* NOT_DEF */
/*
** nummacroargs()
** Get the Number of Arguments of a Macro
** that function is used by do_command to get informations
** how many argslots to fill
*/
int
nummacroargs(MACRO* m)
{
return(m->NArgs);
} /* nummacroargs */
/*
*! >RET
*! Terminate the current macro
*!
*/
void do_retmacro (void)
{
MacroBreak = globalflags.Abortcommand ? 2 : 1;
globalflags.Abortcommand = 1;
} /* do_retmacro */
/**********************************************
**
** Initial Access to Macros
**
**
**********************************************/
/*
** resmacros
** that structure shows all builtin macros
** which are defined at program start
*/
typedef struct _mres
{
const char * name;
const char * body;
int numargs;
} MRES;
static CONST MRES resmacros[] =
{
/* name, block, args */
"alias", "setmacro $arg1 0 $arg2", 2,
"aslload", "arpload", 0,
"aslinsfile", "arpinsfile", 0,
"aslfont", "arpfont", 0,
"aslsave", "arpsave", 0,
/* "asl", "arp", 0, */
"unalias", "unsetmacro $arg1", 1,
"firstwindow", "select f", 0,
"lastwindow", "select l", 0,
"nextwindow", "select n", 0,
"prevwindow", "select p", 0,
"so", "if m saveold", 0,
NULL, NULL, 0
}; /* resmacros */
/*
** GetDefMacros
** initialize the builtin default-macros
*/
int
GetDefMacros ()
{
MRES* m;
for (m = resmacros; m->name; m++)
{
if (!SetMacro(m->name, m->body, m->numargs))
{
return (0); /* PATCH_NULL [25 Jan 1993] : line added */
} /* if error */ /* PATCH_NULL [25 Jan 1993] : line added */
} /* for all builtins */
return (1);
} /* GetDefMacros */
/*
** init_macros()
**
** Init All Macro-Stuff
** init all name-lists
** init the arg-stack
** enable the builtin macros (if macros are global)
**
** That function has to be called at programstart
** (that is done by the line below)
*/
int
init_macros()
{
int i;
for (i = 0;i < MACRO_NUM; i++)
{
NewList ((LIST*)¯os[i]);
} /* for */
NewList ((LIST*)&argstack);
NewList ((LIST*)&delenda);
#ifndef PATCH_PACK
GetDefMacros ();
#endif /* PATCH_PACK */
return (1);
} /* init_macros */
// int macros_are_usable = init_macros(); /* that method does not work and I don't want to use '__autoinit', so init_macros() must be called from main() */
#ifdef PATCH_PACK
/*
** DelMacros
** delete all current macros-definitions
** this function is called by exit_macros
** (and by packages.freepack
** to come around with packages:
** each Package has an own macro-list,
** so it should be possible to delete all
** macro of a certain list)
*/
void
DelMacros()
{
MACRO* m;
int i;
for (i = 0;i < MACRO_NUM; i++)
{
while (m = (MACRO*)RemHead(¯os[i]))
{
if (!m->NCalls)
{
FreeMacro(m);
} else
{
Addhead(&delenda, m);
m->killme = 1;
} /* if */
} /* while */
} /* for */
} /* DelMacros */
#endif /* PATCH_PACK */
#ifdef NOT_DEF
/*
** exit_macros()
** Closedown all macrostuff
**
** as we are currently using malloc() to get heap for our macros,
** we need not provide an exit function, that frees all memory
** which is used by our macros.
**
** NOTE: That function must not be called before the programs last macrocall
** has returned i.e. there must not be any pending macrocalls left.
*/
void
exit_macros()
{
MACRO* m;
int i;
/*
** --- clearing "argstack" is not necessary - as it is empty at programend,
** and not possible, as if it was not empty, that function had not been
** called, since there were pending macrocalls
** --- the same assumptions can be made for "delenda"
**
** --- it is necessary to clear all macros in all of the macro-lists
** that is done below:
*/
// if (argstack.mlh_Head && (argstack.mlh_Head->mln_Succ != NULL)))
// { return(); } /* if there are pending macrocalls left */
#ifndef PATCH_PACK
DelMacros();
#endif /* PATCH_PACK */
#ifdef NOT_DEF
while (m = RemHead(delenda))
{
FreeMacro(m)
} /* while */
#endif /* NOT_DEF */
} /* exit_macros */
#endif /* NOT_DEF */
int savemacros (FILE * fo)
{
MACRO * m;
int i;
fprintf(fo, "MACROS START\n");
for (i = 0; i < MACRO_NUM; i++)
{
for (m = (MACRO*)macros[i].mlh_Head; m != NULL && m->Node.ln_Succ != NULL; m = (MACRO*)m->Node.ln_Succ)
{
fprintf(fo, "\tMACRO %s\n", m->Node.ln_Name);
if (m->NArgs)
{
fprintf(fo, "\t ARGS %d\n", m->NArgs);
} /* if */
fprintf(fo, "\t BODY %s\n", m->Body);
} /* for nodes */
} /* for hash */
fprintf(fo, "MACROS END\n");
} /* savemacros */
int loadmacros (FILE * fi, int * lineno)
{
char * buf;
char nmacro [128];
char nargs [128];
char body [2*LINE_LENGTH];
int doloop = -1;
buf = getnextcomline(fi, lineno);
/* printf("read %s\n", buf); */
if (!buf)
{
abort(0);
} /* if */
if (strncmp(buf, "MACROS START", 12) != 0)
{
error ("No Macrostart header");
abort(0);
} /* if */
nmacro[0] = 0;
nargs [0] = 0;
body [0] = 0;
while (globalflags.Abortcommand == 0)
{
buf = getnextcomline(fi, lineno);
/* printf("read %s\n", buf); */
if (!buf)
{
abort(0);
} /* if */
if (strncmp(buf, "MACROS END", 10) == 0)
{
return(1);
} else if (strncmp(buf, "MACRO ", 6) == 0)
{
buf += 6;
strncpy(nmacro, buf, 128);
} else if (strncmp(buf, "ARGS ", 5) == 0)
{
buf += 5;
if (nargs[0] != 0)
{
error("%s:\nDeclared numargs not w/in a Macro", av[0]);
abort(0);
} /* if */
strncpy(nargs, buf, 128);
} else if (strncmp(buf, "BEGIN", 5) == 0)
{
int len = 0;
buf += 5;
while (*buf && *buf<33) buf++;
if (nmacro[0] == 0)
{
error("<%s:\nDeclared Body w/out Macro", av[0]);
abort(0);
} /* if */
if (*buf)
{
len = strlen(buf);
strcpy (body, buf);
} /* if */
while ((!globalflags.Abortcommand) && (buf = getnextcomline(fi, lineno)))