/***********************************************************************/
/* Open Visualization Data Explorer                                    */
/* (C) Copyright IBM Corp. 1989,1999                                   */
/* ALL RIGHTS RESERVED                                                 */
/* This code licensed under the                                        */
/*    "IBM PUBLIC LICENSE - Open Visualization Data Explorer"          */
/***********************************************************************/

#include <dxconfig.h>


#include <dx/dx.h>
#include "pmodflags.h"
#include "utils.h"
#include "distp.h"
#include "parse.h"

#include <stdio.h>
#include <ctype.h>
#include <stdarg.h>
#include <stdarg.h>
#include <string.h>
#include <fcntl.h>


/* funcs for dynamic loading.  these belong in a header file somewhere */
extern Error DXLoadAndRunObjFile (char *fname, char *envvar);
extern Error DXUnloadObjFile (char *fname, char *envvar);
extern PFI   DXLoadObjFile (char *fname, char *envvar);

/* belongs in libdx somewhere.  see loader.c for source and usage.  */
extern Error _dxf_fileSearch(char *inname, char **outname, char *extension,
			     char *environment);

/* for starting a task on one or all processors.  private to the exec */
Error _dxf_ExRunOnAll (PFE func, Pointer arg, int size);
Error _dxf_ExRunOn (int JID, PFE func, Pointer arg, int size);




/*
 * token and private flag constants:
 */
#define T_NONE		0x00000000
#define T_MODULE	0x00000001
#define T_CATEGORY	0x00000002
#define T_DESCRIPTION	0x00000004
#define T_INPUT		0x00000008
#define T_REPEAT	0x00000010
#define T_OUTPUT	0x00000020	
#define T_OPTIONS	0x00000040	
#define T_FLAGS		0x00000080
#define T_OUTBOARD      0x00000100
#define T_LOADABLE      0x00000200
#define T_ERROR         0x00008000

#define PF_RELOAD	0x00000000   /* redefinition of an existing module */
#define PF_LOADABLE	0x00000001   /* run-time dynamicly loaded module */
#define PF_OUTBOARD     0x00000002   /* outboard module definition */
	

#define F_NONE          0

#define REPCOUNT        20   /* should match constant in mdf2c file */

/* this now works for both outboards and loadable modules.  
 */

struct modargs {
    char *		thisarg;      /* name of this input/output */
    char * 		deflt;        /* default value (not used yet) */
    struct modargs *	nextarg;      /* linked list pointer */
};

struct moddef {
    char *		m_name;       /* module name */
    PFI			m_func;       /* entry point - function address */ 
    int			m_flags;      /* module flags */
    int			m_nin;        /* number of inputs */
    struct modargs *	m_innames;    /* list of inputs - modargs structs */
    int			m_nout;       /* number of outputs */
    struct modargs *	m_outnames;   /* list of outputs - modargs structs */
    char *		m_exec;       /* for outboards, filename to run */
    char *		m_host;       /* for outboards, host to run on */
    int			m_pflags;     /* private flags for parsemdf use */
    char *		m_loadfile;   /* filename for loadable modules */
};

extern Error DXOutboard (Object *in, Object *out);
extern EXDictionary     _dxd_exGraphCache;
extern int _dxd_exGoneMP;
static Error do_mdf_load(struct moddef *mp);



/* this has to be in global memory for MP systems
 * since adding run-time modules has to happen on 
 * all processors 
 */
static struct moddef *newmod()
{
    struct moddef *mp;

    mp = DXAllocateZero (sizeof(struct moddef));
    if (!mp)
	return NULL;

    return mp;
}


static Error delmod(struct moddef *mp)
{
    struct modargs *map, *next;
    int i;

    if (!mp)
	return OK;

    if (mp->m_name)
	DXFree ((Pointer)mp->m_name);
    if (mp->m_exec)
	DXFree ((Pointer)mp->m_exec);
    if (mp->m_host)
	DXFree ((Pointer)mp->m_host);
    if (mp->m_loadfile)
	DXFree ((Pointer)mp->m_loadfile);

    /* follow the chains here and delete the char strings */
    for (i=0, map = mp->m_innames; i<mp->m_nin; i++, map=next) {
	if (!map)
	    break;

	next = map->nextarg;
	DXFree ((Pointer)map->thisarg);
	DXFree ((Pointer)map->deflt);
	DXFree ((Pointer)map);
    }
    for (i=0, map = mp->m_outnames; i<mp->m_nout; i++, map=next) {
	if (!map)
	    break;

	next = map->nextarg;
	DXFree ((Pointer)map->thisarg);
	DXFree ((Pointer)map->deflt);
	DXFree ((Pointer)map);
    }

    DXFree ((Pointer)mp);
    return OK;
}

#define INPUTARG 1
#define OUTPUTARG 2

static Error addarg(struct moddef *mp, int type, char *name, char *def)
{
    struct modargs *map, *newmap;
    int i;

    if (!mp)
	return OK;

    newmap = (struct modargs *)DXAllocateZero (sizeof(struct modargs));
    if (!newmap)
	return ERROR;

    newmap->thisarg = name;
    newmap->deflt = def;

    if (type == INPUTARG) 
    {
	mp->m_nin++;
	if (!mp->m_innames) 
	{
	    mp->m_innames = newmap;
	    return OK;
	} 
	map = mp->m_innames;
    } 
    else 
    {
	mp->m_nout++;
	if (!mp->m_outnames) 
	{
	    mp->m_outnames = newmap;
	    return OK;
	}
	map = mp->m_outnames;
    }
	
    while (map->nextarg)
	map = map->nextarg;

    map->nextarg = newmap;
    return OK;
}


static Error argdup(struct moddef *mp, int type, int repcount)
{
    int i;
    int startrep;
    struct modargs *map;
    char *name, *value;

    if (type == INPUTARG) {
	startrep = mp->m_nin - repcount;
	map = mp->m_innames;
    } else {
	startrep = mp->m_nout - repcount;
	map = mp->m_outnames;
    } 
    if (startrep < 0) {
	DXSetError(ERROR_INVALID_DATA, "invalid repeat count");
	return ERROR;
    }
    
    for (i=0; i<startrep; i++, map=map->nextarg)
	;
    
    for (i=0; i<repcount*REPCOUNT; i++) {
	name = NULL;
	if (map->thisarg) {
	    name = DXAllocateZero (strlen(map->thisarg) + 4);
	    if (!name)
		return ERROR;
	    sprintf(name, "%s%d", map->thisarg, i);
	}
	
	value = NULL;
	if (map->deflt) {
	    value = DXAllocateZero (strlen(map->deflt) + 1);
	    if (!value)
		return ERROR;
	}

	if (!addarg(mp, type, name, value))
	    return ERROR;
    }

    return OK;
}



/*
 *  DXAddModuleV (name, func, flags, nin, inlist, nout, outlist, exec, host)
 *
 * struct moddef {
 *    char *		m_name;
 *    PFI		m_func;  
 *    int		m_flags;
 *    int		m_nin;
 *    struct modargs *	m_innames;
 *    int		m_nout;
 *    struct modargs *	m_outnames;
 *    char *		m_exec;
 *    char *		m_host;
 *    int		m_pflags;
 *    char *		m_loadfile;
 * }
 */

static Error
callmdf_remote(char *fname)
{
    if (! DXLoadAndRunObjFile(fname, "MODULES"))
	return ERROR;
    else
	return OK;
}

static Error callmdf(struct moddef *mp, int doremote)
{
    int rc;
    
    
    /* check the parms against the already defined module */
    if (mp->m_pflags == PF_RELOAD) {
	/* add code here */
	if (!(mp->m_pflags & PF_LOADABLE) && !(mp->m_pflags & PF_OUTBOARD)) {
	    /* set error code here?  we are going to ignore this defn */
	    DXMessage("ignoring redefinition of module %s", mp->m_name);
	    return OK;
	}
	/* fall through - it's ok to reload an outboard or runtime loadable */
    }
    
    if (mp->m_pflags & PF_LOADABLE) {
	/* on an MP machine, we have to load the code into the
         * running exec for each process.
	 */
	
        if (DXProcessors(0) > 1 && _dxd_exGoneMP) {
	    
#ifdef DXD_NO_MP_RUNTIME
	    if (_dxd_exGoneMP) {
		/* can't do runtime loadable after forking on sgi or solaris */
#define LONGHELP "Runtime-loadable modules cannot be added after startup when running with more than 1 processor.  Either specify -mdf on the startup command line, or run -processors 1"
		DXSetError(ERROR_INVALID_DATA, LONGHELP);
		return ERROR;
	    }
#endif
	    
	    if (_dxf_ExRunOnAll (callmdf_remote, mp->m_loadfile,
				 strlen(mp->m_loadfile)+1) == ERROR) {
		DXAddMessage("module %s", mp->m_name); 
		return ERROR;
	    }
	} else {
	    /* do the load for a single process machine, or MP machine
	     * before the fork.
	     */
	    if (!DXLoadAndRunObjFile(mp->m_loadfile, "DXMODULES")) {
		DXAddMessage("module %s", mp->m_name);
		return ERROR;
	    }
	}
	return OK;
    }
    
    if (mp->m_flags & MODULE_OUTBOARD)
	mp->m_func = DXOutboard;
    
    if (mp->m_func == NULL) {
	DXSetError(ERROR_INVALID_DATA, 
	      "module %s must have an OUTBOARD or LOADABLE entry to be added at run time",
	       mp->m_name);
	return ERROR;
    }
    
    /* mark this module as being loaded at run time instead of being
     * known at compile time.  i don't think any one cares, but just
     * in case it turns out to be useful...
     */
    mp->m_flags |= RUNTIMELOAD;
    
    
    /* the work happens here.
     */
    if (DXProcessors(0) == 1 || _dxd_exGoneMP == 0)
	rc = do_mdf_load(mp);
    else
	rc = _dxf_ExRunOn (1, do_mdf_load, (Pointer)mp, 0);

    
    /* if running distributed, tell the others about the new module.
     */ 
    if (doremote)
	_dxf_ExDistributeMsg(DM_LOADMDF, (Pointer)mp, 0, TOSLAVES);
    
    return rc;
}


static Error do_mdf_load(struct moddef *mp)
{
    int i;
    char **inlist = NULL, **outlist = NULL;
    struct modargs *map;
    Error rc;

    if (mp->m_nin) {
	inlist = (char **)DXAllocateZero(mp->m_nin * sizeof (char *));
	if (!inlist)
	    return ERROR;
    }
    if (mp->m_nout) {
	outlist = (char **)DXAllocateZero(mp->m_nout * sizeof (char *));
	if (!outlist) {
	    DXFree((Pointer)inlist);
	    return ERROR;
	}
    }

    for (i=0, map=mp->m_innames; i<mp->m_nin; i++, map=map->nextarg)
	inlist[i] = map->thisarg;

    for (i=0, map=mp->m_outnames; i<mp->m_nout; i++, map=map->nextarg)
	outlist[i] = map->thisarg;


    /* this loads the executive jump table.
     */
    rc = DXAddModuleV(mp->m_name, mp->m_func, mp->m_flags,
		      mp->m_nin, inlist,
		      mp->m_nout, outlist,
		      mp->m_exec, mp->m_host);


    /* we may have some graph with these functions built in already.
     * this forces us to rebuild all graphs with the new mdf defintion.
     */
    if ((node *)_dxf_ExMacroSearch (mp->m_name) != NULL)
        _dxf_ExDictionaryPurge (_dxd_exGraphCache);


    DXFree((Pointer)inlist);
    DXFree((Pointer)outlist);
    
    return rc;
}


/* communicating with remote execs for distributed.
 */

void _dxf_ExSendMdfPkg(Pointer *data, int tofd)
{
    int i, len;
    struct moddef *mp;
    struct modargs *map;

    mp = (struct moddef *)data;

    len = (mp->m_name) ? strlen(mp->m_name) : 0;
    _dxf_ExWriteSock(tofd, &len, sizeof(int));
    if (len > 0)
	_dxf_ExWriteSock(tofd, mp->m_name, len);
    _dxf_ExWriteSock(tofd, &mp->m_flags, sizeof(int));

    _dxf_ExWriteSock(tofd, &mp->m_nin, sizeof(int));
    map = mp->m_innames;
    for (i=0, map=mp->m_innames; i<mp->m_nin; i++, map=map->nextarg) {
	len = (map->thisarg) ? strlen(map->thisarg) : 0;
	_dxf_ExWriteSock(tofd, &len, sizeof(int));
	if (len > 0)
	    _dxf_ExWriteSock(tofd, map->thisarg, len);
	/* i'm NOT writing the default value here because they are not
	 * supported yet.  this will have to be added sometime.
	 */
    }

    _dxf_ExWriteSock(tofd, &mp->m_nout, sizeof(int));
    map = mp->m_outnames;
    for (i=0, map=mp->m_outnames; i<mp->m_nout; i++, map=map->nextarg) {
	len = (map->thisarg) ? strlen(map->thisarg) : 0;
	_dxf_ExWriteSock(tofd, &len, sizeof(int));
	if (len > 0)
	    _dxf_ExWriteSock(tofd, map->thisarg, len);
	/* i'm NOT writing the default value here because they are not
	 * supported yet.  this will have to be added sometime.
	 */
    }

    len = (mp->m_exec) ? strlen(mp->m_exec) : 0;
    _dxf_ExWriteSock(tofd, &len, sizeof(int));
    if (len > 0)
	_dxf_ExWriteSock(tofd, mp->m_exec, len);
    len = (mp->m_host) ? strlen(mp->m_host) : 0;
    _dxf_ExWriteSock(tofd, &len, sizeof(int));
    if (len > 0)
	_dxf_ExWriteSock(tofd, mp->m_host, len);

    _dxf_ExWriteSock(tofd, &mp->m_pflags, sizeof(int));
    len = (mp->m_loadfile) ? strlen(mp->m_loadfile) : 0;
    _dxf_ExWriteSock(tofd, &len, sizeof(int));
    if (len > 0)
	_dxf_ExWriteSock(tofd, mp->m_loadfile, len);

    return;
}


Error _dxf_ExRecvMdfPkg(int fromfd, int swap)
{
    int i, len, num;
    struct moddef *mp;
    struct modargs *map;
    char *argname;


    if ((mp = newmod()) == NULL)
	return ERROR;

    _dxf_ExReceiveBuffer(fromfd, &len, 1, TYPE_INT, swap);
    if (len > 0) {
	mp->m_name = DXAllocateZero(len+1);
	_dxf_ExReceiveBuffer(fromfd, mp->m_name, len, TYPE_UBYTE, swap);
    }
    _dxf_ExReceiveBuffer(fromfd, &mp->m_flags, 1, TYPE_INT, swap);

    _dxf_ExReceiveBuffer(fromfd, &num, 1, TYPE_INT, swap);
    for (i=0; i<num; i++) {
	_dxf_ExReceiveBuffer(fromfd, &len, 1, TYPE_INT, swap);
	if (len > 0) {
	    argname = DXAllocateZero(len+1);
	    _dxf_ExReceiveBuffer(fromfd, argname, len, TYPE_UBYTE, swap);
	    addarg(mp, INPUTARG, argname, NULL);
	}
    }

    _dxf_ExReceiveBuffer(fromfd, &num, 1, TYPE_INT, swap);
    for (i=0; i<num; i++) {
	_dxf_ExReceiveBuffer(fromfd, &len, 1, TYPE_INT, swap);
	if (len > 0) {
	    argname = DXAllocateZero(len+1);
	    _dxf_ExReceiveBuffer(fromfd, argname, len, TYPE_UBYTE, swap);
	    addarg(mp, OUTPUTARG, argname, NULL);
	}
    }

    _dxf_ExReceiveBuffer(fromfd, &len, 1, TYPE_INT, swap);
    if (len > 0) {
	mp->m_exec = DXAllocateZero(len+1);
	_dxf_ExReceiveBuffer(fromfd, mp->m_exec, len, TYPE_UBYTE, swap);
    }

    _dxf_ExReceiveBuffer(fromfd, &len, 1, TYPE_INT, swap);
    if (len > 0) {
	mp->m_host = DXAllocateZero(len+1);
	_dxf_ExReceiveBuffer(fromfd, mp->m_host, len, TYPE_UBYTE, swap);
    }

    _dxf_ExReceiveBuffer(fromfd, &mp->m_pflags, 1, TYPE_INT, swap);
    _dxf_ExReceiveBuffer(fromfd, &len, 1, TYPE_INT, swap);
    if (len > 0) {
	mp->m_loadfile = DXAllocateZero(len+1);
	_dxf_ExReceiveBuffer(fromfd, mp->m_loadfile, len, TYPE_UBYTE, swap);
    }


    if (!callmdf(mp, 0))
	goto error;

    delmod(mp);
    return OK;

  error:
    if (mp)
	delmod(mp);
    return ERROR;
}




/*
 * utility type routines.
 */

/* return pointer to start of next line.  if flag is set, assume you are
 *  in the middle of a line and always search forward.  if flag is zero,
 *  assume you could already be at the start of the line and only space
 *  forward if the line is empty or a comment.
 */
static char *nextline(char *str, int forward, int *lineno)
{
    char *cp = str;

    if (!cp)
	return NULL;

  again:
    (*lineno)++;
    
    if (!forward) {
	if (*cp != '#' && *cp != '\n' && *cp != '\r')
	    return cp;
	
	forward = 1;
    }

    while (*cp != '\0' && *cp != '\n' && *cp != '\r')
	cp++;
    
    if (*cp == '\0')
	return NULL;
	
    if (*cp == '\n' || *cp == '\r')
	{
	    while (*cp == '\n' || *cp == '\r')
	        cp ++;
	
	    forward = 0;
	    goto again;
    }

    if (*cp == '#')
	goto again;

    return cp;
}


/* return a pointer to the next char which matches.  this is different
 *  than strindex or strstr because it won't space past a newline.
 *  return NULL if there isn't a match before the next newline or 
 *  end of file (actually, end of string).
 */
static char *find_next(char *str, char find)
{
    char *cp = str;

    if (cp == NULL)
	return NULL;

    while (*cp != '\0' && *cp != '\n' && *cp != find)
	cp++;

    return (*cp == find) ? cp : NULL;
}

static char *end_of_line(char *str)
{
    return find_next(str, '\n');
}

/*
 * return start of next whitespace separated token.  return NULL if
 * end-of-line or end-of-string encountered first.  if semi is set,
 * allow semicolons to be considered as whitespace.
 */
static char *find_next_token(char *str, int semi)
{
    char *cp = str;

    if (cp == NULL)
	return NULL;

    /* find whitespace (or semi)
     */
    if (semi)
	while (*cp != '\0' && *cp != '\n' &&  
	       *cp != ' '  && *cp != '\t' &&
	       *cp != '\r' && *cp != ';')
	    cp++;
    else
	while (*cp != '\0' && *cp != '\n' &&  
	       *cp != ' ' && *cp != '\t' &&
	       *cp != '\r')
	    cp++;

	if (*cp == '\r')
		cp++;
    
    if (*cp == '\0' || *cp == '\n')
	return NULL;
    
    /* now find non-whitespace 
     */
    if (semi && *cp == ';')
	cp++;
    
    while (*cp != '\0' && *cp != '\n' && 
		*cp != '\r' && (*cp == ' ' || *cp == '\t'))
	cp++;

    if (*cp == '\r')
		cp++;
    
    if (*cp == '\0' || *cp == '\n')
	return NULL;

    return cp;
}

/* return the end of the current string of chars, delimited by either
 * end-of-line, end-of-string, whitespace or semicolon.
 */
static char *find_token_end(char *str)
{
    char *cp = str;

    if (cp == NULL)
	return NULL;

    while (*cp != '\0' && *cp != '\n' && 
	   *cp != ' '  && *cp != '\t' && 
	   *cp != '\r' && *cp != ';')
	cp++;

    return cp;
}

/* names have to start with an alpha, and then
 * contain only alphas or digits
 */
static int IsGoodIdentifier(char *name)
{
    char *cp = name;

    if (!isalpha(*cp++))
	return FALSE;
    
    while (*cp) {
	if (!isalnum(*cp))
	    return FALSE;
	cp++;
    }
    
    return TRUE;
}

/* allocate space for and make a copy of the next string up to the
 * next ';', ' ' or end of line
 */
static char *AllocToken(char *str)
{
    char *newbuf;
    char *strend;
    
    strend = find_token_end(str);

    newbuf = DXAllocate(strend - str + 1);
    if (!newbuf)
	return NULL;

    strncpy(newbuf, str, strend-str);
    newbuf[strend-str] = '\0';

    return newbuf;
}

/* allocate space for and make a copy of the next string up to the
 * next '"'
 */
static char *AllocQuote(char *str)
{
    char *newbuf;
    char *strend;
    char *last;
    
    last = end_of_line(str);
    strend = find_next(str, '"');
    if (!strend || (last && strend > last)) {
	strend = last;
	if (!strend) {
	    DXSetError(ERROR_INVALID_DATA, 
		       "missing name or mismatched quotes");
	    return NULL;
	}
    }
    
    newbuf = DXAllocate(strend - str + 1);
    if (!newbuf)
	return NULL;

    strncpy(newbuf, str, strend-str);
    newbuf[strend-str] = '\0';

    return newbuf;
}

/* allocate space for and make a copy of the next string up to the
 * next ';' or end of line, including looking for attributes and squeezing
 * any intervening spaces from them.  (should this be necessary?
 * why doesn't the exec code handle spaces?)
 */
static char *AllocVariable(char *str)
{
    char *newbuf;
    char *strend;
    char *cp, *dest;
    
    strend = find_token_end(str);
    if (!strend)
	return NULL;
    
    cp = strend;
    while (*cp == ' ')
	cp++;
    if(*cp  == '[') {
        strend = strchr(cp, ']');
        if(!strend)
            return NULL;
        strend++;
    }

    newbuf = DXAllocate(strend - str + 1);
    if (!newbuf)
	return NULL;
    
    for (cp = str, dest = newbuf; cp < strend; cp++)
	if (!isspace(*cp))
	    *dest++ = *cp;
    
    *dest = '\0';

    return newbuf;
}

static int IDKeyword(char *str)
{
    if (!str || str[0] == '\0')
	return T_NONE;
    
    if (!strncmp(str, "MODULE", sizeof("MODULE")-1))
	return T_MODULE;
    
    if (!strncmp(str, "CATEGORY", sizeof("CATEGORY")-1))
	return T_CATEGORY;
    
    if (!strncmp(str, "DESCRIPTION", sizeof("DESCRIPTION")-1))
	return T_DESCRIPTION;
    
    if (!strncmp(str, "INPUT", sizeof("INPUT")-1))
	return T_INPUT;
    
    if (!strncmp(str, "REPEAT", sizeof("REPEAT")-1))
	return T_REPEAT;
    
    if (!strncmp(str, "OUTPUT", sizeof("OUTPUT")-1))
	return T_OUTPUT;
    
    if (!strncmp(str, "OPTIONS", sizeof("OPTIONS")-1))
	return T_OPTIONS;
    
    if (!strncmp(str, "FLAGS", sizeof("FLAGS")-1))
	return T_FLAGS;
    
    if (!strncmp(str, "OUTBOARD", sizeof("OUTBOARD")-1))
	return T_OUTBOARD;
    
    if (!strncmp(str, "LOADABLE", sizeof("LOADABLE")-1))
	return T_LOADABLE;
    
    return T_ERROR;
}

static int IDFlag(char *str)
{
    if (!str || str[0] == '\0')
	return 0;

#if 0
    if (!strncmp(str, "SERIAL", sizeof("SERIAL")-1))
	return MODULE_SERIAL;

    if (!strncmp(str, "SEQUENCED", sizeof("SEQUENCED")-1))
	return MODULE_SEQUENCED;
#endif

    if (!strncmp(str, "PIN", sizeof("PIN")-1))
	return MODULE_PIN;

#if 0
    if (!strncmp(str, "ASSIGN", sizeof("ASSIGN")-1))
	return MODULE_ASSIGN;
#endif

    if (!strncmp(str, "SIDE_EFFECT", sizeof("SIDE_EFFECT")-1))
	return MODULE_SIDE_EFFECT;

    if (!strncmp(str, "LOOP", sizeof("LOOP")-1))
        return MODULE_LOOP;

#if 0
    if (!strncmp(str, "JOIN", sizeof("JOIN")-1))
	return MODULE_JOIN;
#endif

    if (!strncmp(str, "ERR_CONT", sizeof("ERR_CONT")-1))
	return MODULE_ERR_CONT;

    if (!strncmp(str, "REROUTABLE", sizeof("REROUTABLE")-1))
	return MODULE_REROUTABLE;

    if (!strncmp(str, "REACH", sizeof("REACH")-1))
	return MODULE_REACH;

    if (!strncmp(str, "OUTBOARD", sizeof("OUTBOARD")-1))
	return MODULE_OUTBOARD;

    if (!strncmp(str, "PERSISTENT", sizeof("PERSISTENT")-1))
	return MODULE_PERSISTENT;

    if (!strncmp(str, "ASYNC", sizeof("ASYNC")-1))
	return MODULE_ASYNC;
    
    if (!strncmp(str, "ASYNCHRONOUS", sizeof("ASYNCHRONOUS")-1))
	return MODULE_ASYNC;
    
    if (!strncmp(str, "ASYNCLOCAL", sizeof("ASYNCLOCAL")-1))
	return MODULE_ASYNCLOCAL;
    
    return 0;
}

/*
 * parse the string into lines, and call DXAddModuleV for each new
 * module definition.
 */
static Error ExParseMDF(char *str)
{
    int lineno = 0;
    int modflag;
    int repcount;
    int loadable = 0;     /* see the comment below in the loadable section */
    int id, rc;
    int argtype = -1;
    char *tempc;
    char *nextc, *cp;
    struct moddef *mp = NULL;


    /* start at the first non-comment line in the string */
    nextc = nextline(str, 0, &lineno);
    
    while ((id = IDKeyword(nextc)) != T_NONE) {
	switch (id) {
	  case T_MODULE:
	    /* output previous module definition */
	    if (mp) {
		
		if (!callmdf(mp, 1))
		    goto error;

		delmod(mp);
	    }
	    /* start new definition */
	    mp = newmod();
	    if (!mp)
		goto error;
	    
	    nextc = find_next_token(nextc, 0);
	    if (!nextc) {
		DXSetError(ERROR_INVALID_DATA, 
			   "missing module name, line %d", lineno);
		goto error;
	    }
	    mp->m_name = AllocToken(nextc);
	    if (!IsGoodIdentifier(mp->m_name)) {
		DXSetError(ERROR_INVALID_DATA, 
			   "module names must start with a letter and contain "
			   "only letters and numbers, line %d", lineno);
		goto error;
	    }
	    
#if 0  /* reload == 0, so that's the default */
	    /* does it already exist? */
            if ((node *)_dxf_ExMacroSearch (mp->m_name) != NULL)
		mp->m_pflags |= PF_RELOAD;
#endif

	    break;
	    
	  /* get name only; ignore type, default & description. */
	  case T_INPUT:
	  case T_OUTPUT:
	    nextc = find_next_token(nextc, 1);
	    if (!nextc) {
		DXSetError(ERROR_INVALID_DATA, 
			   "missing %s parameter name, line %d", 
			   (id==T_INPUT) ? "input" : "output", lineno);
		goto error;
	    }
	    
	    tempc = AllocVariable(nextc);
	    if (!tempc)
		goto error;
	    
	    if (!addarg(mp, (id==T_INPUT) ? INPUTARG : OUTPUTARG, tempc, NULL))
		goto error;

	    /* save this for later, if handling repeats */
	    argtype = id;
	    break;
	    
	  case T_ERROR:
	    /* could this be more helpful? */
	    DXSetError(ERROR_INVALID_DATA, 
		       "unrecognized keyword on line %d", lineno);
	    return ERROR;
	    
	  case T_OUTBOARD:
	    /* get the exec and host name */
	    tempc = find_next(nextc, '"');
	    if (!tempc) {
		nextc = find_next_token(nextc, 1);
		if (!nextc) {
		    DXSetError(ERROR_INVALID_DATA, 
			       "missing outboard execution name, line %d", 
			       lineno);
		    goto error;
		}
		mp->m_exec = AllocToken(nextc);
	    } 
	    else
		mp->m_exec = AllocQuote(++tempc);

	    if (!mp->m_exec) {
		DXSetError(ERROR_INVALID_DATA, 
			   "missing outboard execution name, line %d", 
			   lineno);
		goto error;
	    }

	    tempc = find_next(nextc, ';');
	    if (tempc) {
		nextc = tempc;
		tempc = find_next_token(nextc, 1);
		if (tempc) {
		    mp->m_host = AllocToken(tempc);
		    nextc = tempc;
		}
	    }

	    mp->m_flags |= MODULE_OUTBOARD;
	    mp->m_pflags |= PF_OUTBOARD;
	    break;
	    
	  case T_LOADABLE:
	    /* get the executable filename */
	    tempc = find_next(nextc, '"');
	    if (!tempc) {
		nextc = find_next_token(nextc, 1);
		if (!nextc) {
		    DXSetError(ERROR_INVALID_DATA, 
			       "missing loadable module filename, line %d", 
			       lineno);
		    goto error;
		}
		mp->m_loadfile = AllocToken(nextc);
	    } 
	    else
		mp->m_loadfile = AllocQuote(++tempc);

	    if (!mp->m_loadfile) {
		DXSetError(ERROR_INVALID_DATA, 
			   "missing loadable module filename, line %d", 
			   lineno);
		goto error;
	    }

	    mp->m_pflags |= PF_LOADABLE;
	    break;
	    
	  case T_FLAGS:
	    tempc = find_next_token(nextc, 0);
	    while ((modflag = IDFlag(tempc)) != F_NONE) {
		mp->m_flags |= modflag;
		nextc = tempc;
		tempc = find_next_token(tempc, 0);
	    }
	    break;

	  case T_REPEAT:
	    nextc = find_next_token(nextc, 0);
	    if (!nextc) {
		DXSetError(ERROR_INVALID_DATA, 
			   "missing repeat count, line %d", lineno);
		goto error;
	    }
	    repcount = atoi(nextc);
	    if (argtype == T_INPUT) {
		if (repcount <= 0 || repcount > mp->m_nin) {
		    DXSetError(ERROR_INVALID_DATA,
			       "invalid input repeat count, line %d", lineno);
		    goto error;
		}
		if (!argdup(mp, INPUTARG, repcount)) {
		    DXAddMessage("line %d", lineno);
		    goto error;
		}

	    } else if (argtype == T_OUTPUT) {
		if (repcount <= 0 || repcount > mp->m_nout) {
		    DXSetError(ERROR_INVALID_DATA,
			       "invalid output repeat count, line %d", lineno);
		    goto error;
		}
		if (!argdup(mp, OUTPUTARG, repcount)) {
		    DXAddMessage("line %d", lineno);
		    goto error;
		}
		
	    } else {
		DXSetError(ERROR_INVALID_DATA,
			   "misplaced REPEAT line, line %d", lineno);
	    }
	    break;

	  case T_CATEGORY:
	  case T_DESCRIPTION:
	  case T_OPTIONS:
	    /* ignore rest of line */
	    break;
	}

	nextc = nextline(nextc, 1, &lineno);
    }

  done:

    /* at end-of-string, finish current definition if there is one */
    if (mp) {
	if (!callmdf(mp, 1))
	    goto error;
	
	delmod(mp);
    }
    
    return OK;

  error:
    if (mp)
	delmod(mp);

    return ERROR;
}

/* if file, read into memory and then parse the same way a string obj
 * would be parsed.
 */
Error DXLoadMDFFile(char *filename)
{
    int fd;
    int len, rlen;
    int rc = ERROR;
    char *foundname = NULL;
    char *cp = NULL;
    
    if (_dxf_fileSearch(filename, &foundname, "mdf", "DXMODULES") == ERROR)
	return ERROR;

    fd = open(foundname, O_RDONLY);
    if (fd < 0) {
	DXSetError(ERROR_INVALID_DATA, "cannot open %s as MDF file", foundname);
	goto error;
    }

    len = lseek(fd, 0, 2);   /* find length of file */
    if (len <= 0) {
	DXSetError(ERROR_INVALID_DATA, "error reading from %s", foundname);
	goto error;
    }
    lseek(fd, 0, 0);

    cp = (char *)DXAllocate(len+1);
    if (!cp)
	goto error;

    rlen = read(fd, cp, len);
    if (rlen != len) {
	DXSetError(ERROR_INVALID_DATA, "error reading from %s", foundname);
	goto error;
    }
    cp[len] = '\0';

    close (fd);

    rc = ExParseMDF(cp);

  error:
    DXFree((Pointer)cp);
    DXFree((Pointer)foundname);
    return rc;
}

Error DXLoadMDFString(char *cp)
{
    return ExParseMDF(cp);
}

