/******************************************************************************

    MODUL
	alias.c

    DESCRIPTION
	macros for DME/XDME

    NOTES
	a part of that path is in command.c

    BUGS
	<none known>

    TODO
	-/-

    EXAMPLES
	-/-

    SEE ALSO
	command.c

    INDEX

    HISTORY
	<see RCS-File>


******************************************************************************/

/*
**  (C)Copyright 1992 by Bernd Noll for null/zero-soft
**  All Rights Reserved
**
**  RCS Header: $Id: alias.c,v 1.65 92/11/09 12:47:29 b_noll Exp $
**
**  compiling with
**  "#define PATCH_NULL"
**  "#define PATCH_MACROS"
**  causes that module to be used
**
*!*********************************************************************
*!
*!  PATCH_MACROS - OVERVIEW
**
**  this option allows translation of alias.c
**  and enables a change in do_command() to allow macro-execution
**  and one in main() to initialize the macro-lists
*!
*!  the module supports several commands:
*!  SETMACRO		definition of a new macro
*!  UNSETMACRO		deletion of a macro
*!
*!  SETMACROVAR 	definition of a macro's local variable
*!  UNSETMACROVAR	deletion of suh a variable
*!		    (at the end of a macrocall all its local variables
*!		     are deleted automatically)
*!
***********************************************************************
**
**  Implementation:
**
**  At the moment macros are stored in MACRO_NUM Lists, which
**  are selected with the 1st char of the macro's name
**  to speed up search-operations
**
**  All names are switched to lowercase, so there should be
**  no case sensitivity
**
**  please note, that there is acess to "av[]" and to several functions of
**  the module "varsbases" in this module, so porting to another program
**  than DME might need some changes.
**
*/


/**************************************
		Includes
**************************************/
#include "defs.h"

/**************************************
	   Prototypes & Exports
**************************************/
Prototype int	init_macros	(void);
Prototype void	do_setmacro	(void);
Prototype void	do_unsetmacro	(void);
Prototype void	SetMacroVar	(char* name, char* value);
Prototype void	do_setmacrovar	(void);
Prototype void	do_unsetmacrovar(void);
Prototype void* getmacro	(char* name);
Prototype int	callmacro	(void*);
Prototype int	nummacroargs	(void*);
Prototype char* getmacroarg	(char* name);
Prototype char* getmacrovar	(char* name);
Prototype void	DelMacros	(void);
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 *)&macros[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*)(&macros[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*)&macros[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(&macros[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)))
	    {
		char * inter = &body[len];
/* printf ("reading %s\n", buf); */
		if (strncmp (buf, "END", 3) == 0)
		{
/* printf ("setting %s\n", body); */
		    goto setit;
		} /* if */
		len += strlen(buf)+1;
		if (len < 2*LINE_LENGTH)
		{
		    *inter = ' ';
		    strcpy(inter+1, buf);
		} else
		{
		    error ("%s:\nmacro-body too long", av[0]);
		} /* if */
	    } /* while */
	    goto retfail;

	} else if (strncmp(buf, "BODY", 4) == 0 && (buf[4] < 0x21))
	{
	    buf += 4;
	    if (*buf) buf++;
	    if (nmacro[0] == 0)
	    {
		error("<%s:\nDeclared Body w/out Macro", av[0]);
		abort(0);
	    } /* if */
	    strncpy(body, buf, 2*LINE_LENGTH);
setit:
	    av[1] = nmacro;
	    av[2] = nargs[0] ? nargs : "0";
	    av[3] = body;
	    do_setmacro();
	    nmacro[0] = 0;
	    nargs [0] = 0;
	} else
	{
	    error ("%s:\nunknown identifier '%s'", av[0], buf);
	} /* if types */
    } /* while not ready */

retfail:
    return(0);
} /* loadmacros */





/* test: */
Prototype void do_macrosave (void);
void do_macrosave (void)
{
    FILE * fo = NULL;

    if (fo = fopen(av[1], "w"))
    {
	savemacros(fo);
	fclose(fo);
    } else
    {
	error ("%s:\nCan't open file %s for output", av[0], av[1]);
	abort();
    } /* if */
} /* do_macrosave */


Prototype void do_macroload (void);
void do_macroload (void)
{
    FILE * fi	  = NULL;
    int    lineno = 0;

    if (fi = fopen(av[1], "r"))
    {

	loadmacros(fi, &lineno);

	fclose(fi);
    } else
    {
	error ("%s:\nCan't open file %s for input", av[0], av[1]);
	globalflags.Abortcommand = 1;
    } /* if */
} /* do_macrosave */


/******************************************************************************
*****  ENDE macros.c
******************************************************************************/

