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

    MODUL
	$Id: alias.c $

    DESCRIPTION
	macros for XDME

    NOTES
	a part of that module is in command.c

    BUGS
	<none known>

    TODO
	-/-

    EXAMPLES
	-/-

    SEE ALSO
	command.c

    AUTHOR
	Bernd "0" Noll (b_noll@informatik.uni-kl.de)

    HISTORY
	$Log: alias.c $
	21-09-94 b_noll added DEFCMD/DEFHELP-support
	24-09-94 b_noll usage of std_-read/write-file
	01-10-94 b_noll introduced DEFMESSAGE
	16-11-94 b_noll added GetMVBase
	22-11-94 b_noll removed getmacroarg - args are normal vars now
	23-11-94 b_noll removed getmacrovar - done from vars.c now
	04-12-94 b_noll removed some dead protos


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

/*
**  (C)Copyright 1992 by Bernd Noll for null/zero-soft
**  All Rights Reserved
**
**  RCS Header: $Id: Alias.c 1.2 1994/09/20 11:05:32 b_noll Exp b_noll $
**
**  compiling with
**  "#define PATCH_NULL"
**  "#define PATCH_MACROS"
**  causes that module to be used
**
*!*********************************************************************
*!
*!  MACROS - OVERVIEW
**
**  this option allows translation of alias.c
**  and enables a change in do_command() to allow macro-execution
**  and (perhaps) 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)
*!
**********************************************************************/


/**************************************
		Includes
**************************************/
#define MACRO struct MacroNode /* OPAQUE 4 other modules */
#include "defs.h"
#include "AVL.h"

DEFDEFINITION( PATCH_ALIAS 1 )

/* ngra - the straight avl module is broken ... */
int iAVL_Append (APTR x, APTR y, APTR z);
int iAVL_Remove (APTR x, APTR y, APTR z);
int iAVL_Find	(APTR x, APTR y, APTR z);

#define ANY_Find(t,n)   iAVL_Find  (t,(APTR)NodeStringComparison, n)
#define ANY_Add(t,n)    iAVL_Append(t,(APTR)NodeNodeComparison,   n)
#define ANY_Remove(t,n) iAVL_Remove(t,(APTR)NodeNodeComparison,   n)
#define ANY_Init(t)     *(t) = NULL;

/**************************************
      Interne Defines & Struktures
**************************************/
/*
** U might use MACRO like an opaque type
*/

struct MacroNode {
	struct 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) */
	char	    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 */
	char	    NCalls; /* it seems to be necessary to forbid all deletions of an active macro */
};

struct MacroArg {
	struct MinNode	 ma_Node;	 /* hook for recursion-stack */
	struct MacroNode*ma_Macro;	 /* a reference to the called macro w/ all its data */
	VarRoot 	 ma_Locals;	 /* AVL-Tree or 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* 	   ma_Arguments[2];/* NOT USED ANY MORE array of ptr to string; there must always be at least two entries: a 1st one referencing the macroname and a last one == NULL */
};

/**************************************
	    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 struct TreeNode* macros = NULL;	/* ... OF MACRO */
static struct TreeNode* delenda= NULL;	/* ... OF MACRO */
static MLIST argstack;			/* ... OF struct MacroArg */
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;

    if ((m = getmacro(name))) {         /* find the node representing the macro ... */
	ANY_Remove (&macros, (APTR)m);  /* , remove it from its tree ... */
	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 */
	    ANY_Add (&delenda, (APTR)m);
	} /* if */
    } /* if */
} /* DelMacro */



/*
**  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
**
*/

static int SetMacro(char *name, char *body, int nargs) {
    MACRO *m;
    if (nargs >= NARGS) {
DEFMESSAGE( _MACRO_too_many_arguments, "too many arguments specified for macro" )
	error (_MACRO_too_many_arguments);
	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;
		ANY_Add (&macros, (APTR)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 */



/**********************************************
**
**  Access to Macros' arguments and local variables
**
**********************************************/

DEFVARTREE( 50, "MVAR_", VAR_MV, 5, 5, VF_COP|VF_UD_FUNC, GetMVBase, NULL, SetVarIntoTree, GetVarFromTree )

Prototype VBASE *GetMVBase (void);
VBASE *GetMVBase (void) {
    struct MacroArg* n;
    if (!(n = GetHead(&argstack)))
	return NULL;
    return &n->ma_Locals;
} /* GetMVBase */



#if 0

/*
**  SetMacroVar()
**	a help-function of do_setmacrovar()
*/

void SetMacroVar(char *name, char *value) {
    VBASE* v;

    if (!(v = GetMVBase())) /* this function can not work if not called from within a macrocall */
	SET_ABORTION( 1 );
    else
	SetVarIntoTree (v, name, value);
} /* SetMacroVar */

/*
**  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()
*/

UBYTE *getmacrovar(UBYTE *name)
{
    return GetVarFromTree (GetMVBase(), name);
} /* getmacrovar */


/* !!!this function is obsolete, since we handle macroargs like macrovars!!!!!!!
**  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
*!
*/
UBYTE *getmacroarg(UBYTE *name)
{
    struct MacroArg* n;
    int num;
    UBYTE *res;

    if (!(n = GetHead(&argstack))) /* this function can not work if not called from within a macrocall */
	return NULL;


    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);

    if (n->ma_Macro->NArgs>=num) {                                     /* if the last called function has an arg ARGx */
	if ((res = strdup (n->ma_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 */

/*
**  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



/**********************************************
**
**  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 top of argstack is cleared
**
**********************************************/


/*
**  callmacro()
**	Call a Macro as interface from do_command()
**	(first you must have got a "handle" via getmacro())
*/

Prototype int callmacro (MACRO*);
int callmacro (MACRO *m)
{
    struct MacroArg *n;
    //char** args;
    int ret	= 0;
    int i;
    int len	= sizeof(struct MacroArg);

    n	= (struct MacroArg*)malloc(len);                     /* allocate space for the stack-element... */

    if (n) {
	setmem(n,len,0);                        /* clear all data */
	AddHead((LIST*)&argstack,(NODE*)n);     /* and push the struct to argstack */

	InitVars (&n->ma_Locals);                  /* for use with encapsulated local variables we must initialize their list */

	av[0] = m->Node.ln_Name;		/* define argslot 0 - it might be asked during execution, and perhaps it was not set in do_command */
	for (i = m->NArgs; i >= 0; i--) {
	    char nam[6] = "arg0";
	    nam[3] += i;
	    SetVarIntoTree (&n->ma_Locals, nam, av[i]);/* then put all arguments into the struct */
	    //av[i] = NULL;			  /* and clear their global values */
	} /* for */

//AVL_ScanTree (&n->ma_Locals, PrintNode, 1);

	n->ma_Macro = m;
	m->NCalls++;				/* disable deletion of that macro during execution */

	MacroBreak = 0; 			/* clear the return - flag */

	ret = buffered_do_command(m->Body);     /* and call the macrobody */
	if (MacroBreak) {
	    ret = 1-ret;
	    SET_ABORTION( MacroBreak == 2 );
	    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 */
	    ANY_Remove(&delenda, (APTR)m);
	    FreeMacro(m);
	} /* if */

	DelAllVarsFromTree (&n->ma_Locals);     /* delete all local variables ... */

	Remove((NODE*)n);                       /* and pop the margs-struct from argstack */
    } else
	nomemory();

    if (n)                                      /* and free anything you allocated */
	free(n);

    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()
*/

Prototype MACRO *getmacro (char *name);
MACRO *getmacro(char *name) {
    MACRO *m;

    m = (MACRO *)ANY_Find (&macros, name);
    return m;
} /* getmacro */


/*
**  nummacroargs()
**	Get the Number of Arguments of a Macro
**	that function is used by do_command to get informations
**	how many argslots to fill
*/

Prototype int nummacroargs (MACRO *);
int nummacroargs (MACRO *m) {
    return m? (m->NArgs): 0;
} /* nummacroargs */



/**********************************************
**
**  Initial Access to Macros
**
**********************************************/


#if 0
/* support of default macros has been dropped, since there is imho no really */
/* need for it								     */

/*
**  resmacros
**	that structure shows all builtin macros
**	which are defined at program start
**	they are initialized via GetDefMacros
*/

typedef struct _mres
{
    char * name;
    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
*/

static int GetDefMacros (void)
{
    int i;

    for (i = 0; resmacros[i].name; i++) {
	if (!SetMacro(resmacros[i].name, resmacros[i].body, resmacros[i].numargs)) {
	    return (0);
	} /* if error */
    } /* for all builtins */

    return (1);
} /* GetDefMacros */

#else
#define GetDefMacros()
#endif


/*
**  ALIAS_init ()
**
**	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
*/

DEFAUTOINIT( ALIAS_Init )
{
    NewList ((LIST*)&argstack);

    ANY_Init (&delenda);
    ANY_Init (&macros);

    GetDefMacros ();
} /* init_macros */



/**********************************************
**
**  IO of Macros (dumping)
**
**********************************************/


/* That static variable is needed to communicate between savesinglemacro and savemacros */
static FILE * mfi;

static void savesinglemacro (MACRO *m)
{
    if (m && mfi) {
	fprintf (mfi, "\tMACRO %s\n", m->Node.ln_Name);
	if (m->NArgs) {
	    fprintf (mfi, "\t ARGS %d\n", m->NArgs);
	} /* if */
	fprintf (mfi, "\t BODY %s\n", m->Body);
    } /* if */
} /* savesinglemacro */

static int ALIAS_save (FILE *fo)
{
    fprintf(fo, "MACROS START\n");

    mfi = fo;
    AVL_ScanTree (&macros, (APTR)savesinglemacro, 1);

    fprintf(fo, "MACROS END\n");
    return (1);
} /* savemacros */



static int ALIAS_load (FILE *fi, int *lineno)
{
    char  *buf;
    char   nmacro [128];
    char   nargs  [128];
    char   body   [2*LINE_LENGTH];

    buf = getnextcomline(fi, lineno);
    if (!buf) {
	abort(0);
    } /* if */
    if (strncmp(buf, "MACROS START", 12) != 0) {
DEFMESSAGE( _MACRO_read_no_header, "No Macrostart header" )
	error (_MACRO_read_no_header);
	abort(0);
    } /* if */

    nmacro[0] = 0;
    nargs [0] = 0;
    body  [0] = 0;

    while (!IS_ABORTED()) {
	buf = getnextcomline(fi, lineno);
	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) {
DEFMESSAGE( _MACRO_read_numargs_wo_name, "%s:\nDeclared numargs not w/in a Macro" )
		error(_MACRO_read_numargs_wo_name, 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) {
DEFMESSAGE( _MACRO_read_body_wo_name, "<%s:\nDeclared Body w/out Macro" )
		error(_MACRO_read_body_wo_name, av[0]);
		abort(0);
	    } /* if */

	    if (*buf) {
		len = strlen(buf);
		strcpy (body, buf);
	    } /* if */

	    while ((!IS_ABORTED()) && (buf = getnextcomline(fi, lineno))) {
		char * inter = &body[len];
		if (strncmp (buf, "END", 3) == 0) {
		    goto setit;
		} /* if */
		len += strlen(buf)+1;
		if (len < 2*LINE_LENGTH) {
		    *inter = ' ';
		    strcpy(inter+1, buf);
		} else {
DEFMESSAGE( _MACRO_read_body_too_big, "%s:\nmacro-body too long" )
		    error (_MACRO_read_body_too_big, 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) {
DEFMESSAGE( _MACRO_read_body_wo_macro, "<%s:\nDeclared Body w/out Macro" )
		error(_MACRO_read_body_wo_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 {
DEFMESSAGE( _MACRO_read_unknown_id, "%s:\nunknown identifier '%s'" )
	    error(_MACRO_read_unknown_id, av[0], buf);
	} /* if types */
    } /* while not ready */

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





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

	    XDME COMMAND INTERFACE

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


/*
*! >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)
*!
*/

/*DEFHELP #cmd macro,var SMV name value - short for @{B}SETMACROVAR@{UB} */
/*DEFHELP #cmd macro,var SETMACROVAR name value - create/modify a macrolocal variable inside a macro */
DEFUSERCMD("SMV",         2, CF_VWM|CF_ICO|CF_COK, void, do_setmacrovar, (void),;)
DEFUSERCMD("SetMacroVar", 2, CF_VWM|CF_ICO|CF_COK, void, do_setmacrovar, (void),)
{
    VBASE *v;

    if (!(v = GetMVBase())) /* this function can not work if not called from within a macrocall */
	SET_ABORTION( 1 );
    else
	SetVarIntoTree (v, 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
*!
*/

/*DEFHELP #cmd macro,var UNSETMACROVAR name - deletion of a macro's local variable */
DEFUSERCMD("UnsetMacroVar", 1, CF_VWM|CF_ICO|CF_COK, void, do_unsetmacrovar, (void),)
{
    VBASE *v;

    if (!(v = GetMVBase())) /* this function can not work if not called from within a macrocall */
	SET_ABORTION( 1 );
    else
	DelVarFromTree (v, av[1]);
} /* do_unsetmacrovar */


/*
*! >RET
*!	Terminate the current macro
*!
*/

/*DEFHELP #cmd macro RET - terminate a macro (before reaching its end) */
DEFUSERCMD("Ret", 0, CF_VWM|CF_ICO|CF_COK, void, do_retmacro, (void),)
{
    MacroBreak = IS_ABORTED() ? 2 : 1;
    SET_ABORTION(1);
} /* do_retmacro */


/*
*! >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 XDME-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 returns the macro's
*!	name)
*!
*!  BUG:    if numargs is not a number then this command
*!	    will assume a value of "0"
*/

/*DEFHELP #cmd macro SETMACRO name nargs body - create/modify the commandmacro @{B}name@{UB} with @{B}nargs@{UB} arguments */
DEFUSERCMD("SetMacro", 3, CF_VWM|CF_ICO|CF_COK, void, do_setmacro, (void),)
{
    SetMacro (av[1], av[3], atoi(av[2]));
} /* do_setmacro */


/*
*!  >UNSETMACRO name
*!	remove a macro definition
*!
*/

/*DEFHELP #cmd macro UNSETMACRO name - delete the commandmacro @{B}name@{UB} */
DEFUSERCMD("UnsetMacro", 1, CF_VWM|CF_ICO|CF_COK, void, do_unsetmacro, (void),)
{
    DelMacro(av[1]);
} /* do_unsetmacro */


/*
*!  >MACROSAVE filename
*!	save the current macros into a file of a special format
*!
*/

/*DEFHELP #cmd macro MACROSAVE filename - save all commandmacros into a file with a special format */
DEFUSERCMD("MacroSave", 1, CF_VWM|CF_ICO, void, do_macrosave, (void),)
{
    std_writefile ((int (*) (FILE *, APTR))ALIAS_save, NULL);
} /* do_macrosave */


/*
*!  >MACROLOAD filename
*!	read a special macrofile
*!
*/

/*DEFHELP #cmd macro MACROLOAD name -  load commandmacros from a file */
DEFUSERCMD("MacroLoad", 1, CF_VWM|CF_ICO, void, do_macroload, (void),)
{
    int    lineno = 0;
    std_readfile (ALIAS_load, &lineno);
} /* do_macrosave */


/******************************************************************************
*****  END macros.c
******************************************************************************/

/* void PrintNode (struct Node *n) { printf ("PN: %08lx/`%s'\n", n, n->ln_Name); } */

