/*
 * Electric(tm) VLSI Design System
 *
 * File: dbtext.c
 * Database text and file support module
 * Written by: Steven M. Rubin, Static Free Software
 *
 * Copyright (c) 2000 Static Free Software.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Electric(tm); see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, Mass 02111-1307, USA.
 *
 * Static Free Software
 * 4119 Alpine Road
 * Portola Valley, California 94028
 * info@staticfreesoft.com
 */

#include "global.h"
#include "database.h"
#include "egraphics.h"
#include "usr.h"
#include <time.h>
#ifdef TM_IN_SYS_TIME
#  include <sys/time.h>
#endif

char  *db_keywordbuffer;
INTSML db_keywordbufferlength = 0;

char  *db_tryfilename;
INTSML db_tryfilenamelen = 0;

/* working memory for "describenodeproto()" */
INTSML db_descnpnodeswitch = 0;
char  *db_descnpoutput[2];
INTSML db_descnpoutputsize[2] = {0, 0};

/* working memory for "describenodeinst()" */
INTSML db_descninodeswitch = 0;
char *db_descnioutput[2];
INTSML db_descnioutputsize[2] = {0, 0};

/* working memory for "describearcinst()" */
INTSML db_descaiarcswitch = 0;
char *db_descaioutput[2];
INTSML db_descaioutputsize[2] = {0, 0};

/* working memory for "describearcproto()" */
INTSML db_descaparcswitch = 0;
char *db_descapoutput[2];
INTSML db_descapoutputsize[2] = {0, 0};

/* working memory for "makeplural()" */
static INTBIG db_pluralbufsize = 0;
static char *db_pluralbuffer;

/* the infinite string package */
#define	INFSTRCOUNT     5	/* number of infinite strings */
#define	INFSTRDEFAULT 200	/* default infinite string length */
#define	NOINFSTR ((INFSTR *)-1)
typedef struct
{
	char *infstr;		/* the string address */
	INTBIG infstrlength;		/* the length of the string */
	INTBIG infstrptr;		/* the location of the string end */
} INFSTR;

INFSTR db_infstrstack[INFSTRCOUNT];	/* a stack of infinite strings */
INFSTR *db_curinf = NOINFSTR;	/* the current infinite string */
INTBIG db_infstrstackbase;	/* base position of the stack in the list */
INTBIG db_infstrstackpos;	/* position within the stack */
INTBIG db_infstrdeepeststack;	/* deepest amount of stacking */
INTSML db_firstinf = 0;		/* initialization flag */

/* prototypes for local routines */
INTSML db_beginsearch(char**);
INTSML db_makestringvar(INTBIG, INTBIG, INTSML);
INTSML db_addstring(char*);
FILE *db_tryfile(char*, char*, char*, char*, char**);
void db_shuffle(char*, char*);
INTBIG db_insensitivechar(INTBIG c);

/*
 * Routine to free all memory associated with this module.
 */
void db_freetextmemory(void)
{
	REGISTER INTBIG i;

	if (db_pluralbufsize > 0) efree(db_pluralbuffer);
	if (db_keywordbufferlength != 0) efree(db_keywordbuffer);
	if (db_tryfilenamelen != 0) efree(db_tryfilename);
	for(i=0; i<INFSTRCOUNT; i++)
		efree((char *)db_infstrstack[i].infstr);

	for(i=0; i<2; i++)
		if (db_descnpoutputsize[i] != 0) efree(db_descnpoutput[i]);
	for(i=0; i<2; i++)
		if (db_descnioutputsize[i] != 0) efree(db_descnioutput[i]);
	for(i=0; i<2; i++)
		if (db_descaioutputsize[i] != 0) efree(db_descaioutput[i]);
	for(i=0; i<2; i++)
		if (db_descapoutputsize[i] != 0) efree(db_descapoutput[i]);
}

/************************* STRING PARSING *************************/

/*
 * routine to parse a lambda value of the form "nn.dd[u | " | cm | hmm | mm]"
 * where the unlabeled number defaults to the current DISPLAYUNITS of the
 * current technology but trailing letters can override.  The input is in
 * the string "pp".
 */
INTBIG atola(char *pp)
{
	REGISTER INTBIG hipart, lonum, loden, retval;
	REGISTER INTSML neg, units;
	REGISTER char *ptr;
	double scale;

	/* determine default scale amount */
	ptr = pp;

	if (*ptr == '-') { neg = -1;   ptr++; } else neg = 1;
	hipart = atoi(ptr);
	while (isdigit(*ptr)) ptr++;
	lonum = 0;   loden = 1;
	if (*ptr == '.')
	{
		ptr++;
		while (isdigit(*ptr)) { lonum = lonum * 10 + (*ptr++ - '0'); loden *= 10; }
	}

	/* determine units */
	units = el_units;
	if (ptr[0] == '"') units = (units & ~DISPLAYUNITS) | DISPUNITINCH; else
	if (ptr[0] == 'c' && ptr[1] == 'm') units = (units & ~DISPLAYUNITS) | DISPUNITCM; else
	if (ptr[0] == 'm' && ptr[1] == 'm') units = (units & ~DISPLAYUNITS) | DISPUNITMM;
	if (ptr[0] == 'm' && ptr[1] == 'i' && ptr[2] == 'l') units = (units & ~DISPLAYUNITS) | DISPUNITMIL;
	if (ptr[0] == 'u') units = (units & ~DISPLAYUNITS) | DISPUNITMIC; else
	if (ptr[0] == 'c' && ptr[1] == 'u') units = (units & ~DISPLAYUNITS) | DISPUNITCMIC; else
	if (ptr[0] == 'm' && ptr[1] == 'u') units = (units & ~DISPLAYUNITS) | DISPUNITMMIC;

	/* convert to database units */
	scale = db_getcurrentscale((INTSML)(el_units&INTERNALUNITS), units);
	retval = rounddouble( ((double)hipart) * scale + ((double)lonum)*scale / ((double)loden) );
	return(retval*neg);
}

/*
 * routine to parse a fixed point value of the form "n.d" where
 * "d" is evaluated to the nearest 120th (the value of WHOLE).
 * The number is returned scaled by a factor of WHOLE.  The input is in
 * the string "pp".
 */
INTBIG atofr(char *pp)
{
	REGISTER INTBIG i, j, k;
	REGISTER INTSML n;

	if (*pp == '-') { n = -1;   pp++; } else n = 1;
	i = atoi(pp) * WHOLE;
	while (isdigit(*pp)) pp++;
	if (*pp++ != '.') return(i*n);
	j = 0;   k = 1;
	while (isdigit(*pp)) { j = j * 10 + (*pp++ - '0'); k *= 10; }
	i += (j*WHOLE + k/2)/k;
	return(i*n);
}

/* routine to convert ascii to integer */
INTBIG myatoi(char *pp)
{
	REGISTER INTBIG num;
	REGISTER INTSML base, sign;

	base = 10;
	num = 0;
	sign = 1;
	if (*pp == '-')
	{
		pp++;
		sign = -1;
	}
	if (*pp == '0')
	{
		pp++;
		base = 8;
		if (*pp == 'x')
		{
			pp++;
			base = 16;
		}
	}
	for(;;)
	{
		if ((*pp >= 'a' && *pp <= 'f') || (*pp >= 'A' && *pp <= 'F'))
		{
			if (base != 16) break;
			num = num * 16;
			if (*pp >= 'a' && *pp <= 'f') num += *pp++ - 'a' + 10; else
				num += *pp++ - 'A' + 10;
			continue;
		} else if (isdigit(*pp))
		{
			if (*pp >= '8' && base == 8) break;
			num = num * base + *pp++ - '0';
			continue;
		}
		break;
	}
	return(num * sign);
}

/*
 * routine to determine which node prototype is referred to by "line"
 * and return that nodeproto.  The routine returns NONODEPROTO if the
 * prototype cannot be determined.
 */
NODEPROTO *getnodeproto(char *initline)
{
	REGISTER NODEPROTO *np, *onp;
	REGISTER CELL *c;
	REGISTER TECHNOLOGY *tech, *t;
	REGISTER INTSML wantversion, save, saidtech, saidlib;
	REGISTER LIBRARY *lib, *l;
	REGISTER VIEW *wantview, *v;
	REGISTER char *pt, *line;

	/* make a copy of the argument so that it can be modified */
	(void)initinfstr();
	(void)addstringtoinfstr(initline);
	line = returninfstr();

	tech = el_curtech;   lib = el_curlib;
	saidtech = saidlib = 0;
	for(pt = line; *pt != 0; pt++) if (*pt == ':') break;
	if (*pt != ':') pt = line; else
	{
		*pt = 0;
		t = gettechnology(line);
		if (t != NOTECHNOLOGY)
		{
			tech = t;
			saidtech++;
		}
		l = getlibrary(line);
		if (l != NOLIBRARY)
		{
			lib = l;
			saidlib++;
		}
		*pt++ = ':';
		line = pt;
	}

	/* try primitives in the technology */
	if (saidlib == 0 || saidtech != 0)
	{
		for(np = tech->firstnodeproto; np != NONODEPROTO; np = np->nextnodeproto)
			if (namesame(line, np->primname) == 0) return(np);
	}

	/* look for version numbers and view types */
	for(pt = line; *pt != 0; pt++) if (*pt == ';' || *pt == '{') break;
	save = *pt;
	*pt = 0;
	for(c = lib->firstcell; c != NOCELL; c = c->nextcell)
		if (namesame(line, c->cellname) == 0) break;
	*pt = (char)save;
	if (c == NOCELL) return(NONODEPROTO);

	wantversion = -1;
	wantview = el_unknownview;
	if (save == ';')
	{
		wantversion = (INTSML)myatoi(pt+1);
		for(pt++; *pt != 0; pt++) if (*pt == '{') break;
		save = *pt;
	}
	if (save == '{')
	{
		line = pt = (pt + 1);
		for(; *pt != 0; pt++) if (*pt == '}') break;
		if (*pt != '}') return(NONODEPROTO);
		*pt = 0;
		for(v = el_views; v != NOVIEW; v = v->nextview)
			if (namesame(v->sviewname, line) == 0 || namesame(v->viewname, line) == 0) break;
		*pt = '}';
		if (v == NOVIEW) return(NONODEPROTO);
		wantview = v;
	}

	/* find desired view of facet */
	for(np = c->firstincell; np != NONODEPROTO; np = np->nextincell)
		if (np->cellview == wantview) break;
	if (np == NONODEPROTO && wantview == el_unknownview)
	{
		/* if a version number was specified, let that guide the search */
		if (wantversion > 0)
		{
			for(np = c->firstincell; np != NONODEPROTO; np = np->nextincell)
			{
				for(onp = np->lastversion; onp != NONODEPROTO; onp = onp->lastversion)
					if (onp->version == wantversion) return(onp);
			}
		}

		/* first find a layout or schematic view */
		for(np = c->firstincell; np != NONODEPROTO; np = np->nextincell)
			if (np->cellview == el_layoutview || np->cellview == el_schematicview) return(np);

		/* take any view */
		np = c->firstincell;
		if (np == NONODEPROTO) return(NONODEPROTO);
	}

	/* get desired version */
	if (wantversion < 0) return(np);
	for(np = np->lastversion; np != NONODEPROTO; np = np->lastversion)
		if (np->version == wantversion) return(np);
	return(NONODEPROTO);
}

/*
 * routine to find cell "cellname".  Returns NOCELL if it cannot be found
 */
static COMCOMP db_cellp = {NOKEYWORD, topofcells, nextcells, NOPARAMS,
	NOBACKUP, 0, " \t", "cell", 0};
CELL *getcell(char *cellname)
{
	REGISTER INTSML i, j;
	REGISTER CELL *cell;

	i = parse(cellname, &db_cellp, 0);
	if (i < 0) return(NOCELL);
	for(j=0, cell = el_curlib->firstcell; cell != NOCELL; cell = cell->nextcell, j++)
		if (j == i) return(cell);
	return(NOCELL);
}

/*
 * routine to find technology "techname".  Returns NOTECHNOLOGY if it cannot
 * be found
 */
static COMCOMP db_technologyp = {NOKEYWORD, topoftechs, nexttechs, NOPARAMS,
	NOBACKUP, 0, " \t", "technology", 0};
TECHNOLOGY *gettechnology(char *techname)
{
	REGISTER INTSML i;
	REGISTER TECHNOLOGY *tech;

	i = parse(techname, &db_technologyp, 0);
	if (i < 0) return(NOTECHNOLOGY);
	for(tech = el_technologies; tech != NOTECHNOLOGY; tech = tech->nexttechnology)
		if (tech->techindex == i) return(tech);
	return(NOTECHNOLOGY);
}

/*
 * routine to find view "viewname".  Returns NOVIEW if it cannot be found
 */
static COMCOMP db_viewp = {NOKEYWORD, topofviews, nextviews, NOPARAMS,
	NOBACKUP, 0, " \t", "view", 0};
VIEW *getview(char *viewname)
{
	REGISTER INTSML i, j;
	REGISTER VIEW *v;

	i = parse(viewname, &db_viewp, 0);
	if (i < 0) return(NOVIEW);
	for(j=0, v = el_views; v != NOVIEW; v = v->nextview, j++)
		if (j == i) return(v);
	return(NOVIEW);
}

/*
 * routine to find network "netname" in facet "facet".  Returns NONETWORK
 * if it cannot be found
 */
NETWORK *getnetwork(char *netname, NODEPROTO *facet)
{
	REGISTER INTSML k;
	REGISTER char *pt;
	REGISTER NETWORK *net;

	for(net = facet->firstnetwork; net != NONETWORK; net = net->nextnetwork)
	{
		pt = net->netname;
		for(k=0; k<net->namecount; k++)
		{
			if (namesame(netname, pt) == 0) return(net);
			pt += strlen(pt) + 1;
		}
	}
	return(NONETWORK);
}

/*
 * routine to determine which arc prototype is referred to by "line"
 * and return that arcproto.  The routine returns NOARCPROTO if the prototype
 * cannot be determined.
 */
ARCPROTO *getarcproto(char *initline)
{
	REGISTER ARCPROTO *ap;
	REGISTER TECHNOLOGY *tech, *t;
	REGISTER char *pt, *line;

	/* make a copy of the argument so that it can be modified */
	(void)initinfstr();
	(void)addstringtoinfstr(initline);
	line = returninfstr();

	tech = el_curtech;
	for(pt = line; *pt != 0; pt++) if (*pt == ':') break;
	if (*pt != ':') pt = line; else
	{
		*pt = 0;
		t = gettechnology(line);
		if (t != NOTECHNOLOGY) tech = t;
		*pt++ = ':';
	}
	for(ap = tech->firstarcproto; ap != NOARCPROTO; ap = ap->nextarcproto)
		if (namesame(pt, ap->protoname) == 0) return(ap);
	return(NOARCPROTO);
}

/*
 * routine to find portproto "portname" on facet "facet".  Returns NOPORTPROTO
 * if it cannot be found
 */
PORTPROTO *getportproto(NODEPROTO *facet, char *portname)
{
	REGISTER PORTPROTO *pp;

	for(pp = facet->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
		if (namesame(portname, pp->protoname) == 0) return(pp);
	return(NOPORTPROTO);
}

/*
 * routine to find library "libname".  Returns NOLIBRARY if it cannot be found
 */
LIBRARY *getlibrary(char *libname)
{
	REGISTER LIBRARY *lib;

	for(lib = el_curlib; lib != NOLIBRARY; lib = lib->nextlibrary)
		if (namesame(libname, lib->libname) == 0) return(lib);
	return(NOLIBRARY);
}

/*
 * routine to find aid "aidname".  Returns NOAID if it cannot be found
 */
AIDENTRY *getaid(char *aidname)
{
	REGISTER INTSML i;

	for(i=0; i<el_maxaid; i++)
		if (namesame(aidname, el_aids[i].aidname) == 0) return(&el_aids[i]);
	return(NOAID);
}

struct
{
	char  *name;
	char  *symbol;
	INTSML value;
} db_colors[] =
{
	{"none",           "ALLOFF",  ALLOFF},
	{"overlappable-1", "COLORT1", COLORT1},
	{"overlappable-2", "COLORT2", COLORT2},
	{"overlappable-3", "COLORT3", COLORT3},
	{"overlappable-4", "COLORT4", COLORT4},
	{"overlappable-5", "COLORT5", COLORT5},
	{"white",          "WHITE",   WHITE},
	{"black",          "BLACK",   BLACK},
	{"red",            "RED",     RED},
	{"blue",           "BLUE",    BLUE},
	{"green",          "GREEN",   GREEN},
	{"cyan",           "CYAN",    CYAN},
	{"magenta",        "MAGENTA", MAGENTA},
	{"yellow",         "YELLOW",  YELLOW},
	{"gray",           "GRAY",    GRAY},
	{"orange",         "ORANGE",  ORANGE},
	{"purple",         "PURPLE",  PURPLE},
	{"brown",          "BROWN",   BROWN},
	{"light-gray",     "LGRAY",   LGRAY},
	{"dark-gray",      "DGRAY",   DGRAY},
	{"light-red",      "LRED",    LRED},
	{"dark-red",       "DRED",    DRED},
	{"light-green",    "LGREEN",  LGREEN},
	{"dark-green",     "DGREEN",  DGREEN},
	{"light-blue",     "LBLUE",   LBLUE},
	{"dark-blue",      "DBLUE",   DBLUE},
	{NULL, NULL, 0}
};

/*
 * Routine to convert the color name "colorname" to a color.  Returns negative on error.
 */
INTSML getecolor(char *colorname)
{
	REGISTER INTSML i;

	for(i=0; db_colors[i].name != 0; i++)
		if (namesame(colorname, db_colors[i].name) == 0)
			return(db_colors[i].value);
	return(-1);
}

/*
 * Routine to convert color "color" to a full name (i.e. "light-gray") in "colorname" and a
 * symbol name (i.e. "LGRAY") in "colorsymbol".  Returns nonzero if the color is unknown.
 */
INTSML ecolorname(INTSML color, char **colorname, char **colorsymbol)
{
	REGISTER INTSML i;

	for(i=0; db_colors[i].name != 0; i++)
		if (db_colors[i].value == color)
	{
		*colorname = db_colors[i].name;
		*colorsymbol = db_colors[i].symbol;
		return(0);
	}
	return(1);
}

/*
 * routine to parse a set of commands in "list" against the keyword in
 * "keyword" and return the index in the list of the keyword.  A return of
 * -1 indicates failure to parse the command and an error message will be
 * issued if "noise" is nonzero.
 */
INTSML parse(char *keyword, COMCOMP *list, INTSML noise)
{
	REGISTER INTSML i, j;
	INTSML (*toplist)(char**), w, bst;
	REGISTER char *pp, *(*nextinlist)(void);
	extern KEYWORD *us_pathiskey;

	us_pathiskey = list->ifmatch;
	toplist = list->toplist;
	nextinlist = list->nextcomcomp;

	(void)(*toplist)(&keyword);
	for(i=0, w=0; (pp = (*nextinlist)()) != 0; w++)
	{
		j = stringmatch(pp, keyword);
		if (j == -2) return(w);
		if (j < 0) continue;
		if (j > i)
		{
			i = j;   bst = w;
		} else if (j == i) bst = -1;
	}

	/* if nothing found, give an error */
	if (i == 0)
	{
		if (noise != 0) ttyputerr("Unknown command: %s", keyword);
		return(-1);
	}

	/* if there is unambiguous match, return it */
	if (bst >= 0) return(bst);

	/* print ambiguities */
	if (noise != 0)
	{
		(void)initinfstr();
		(void)(*toplist)(&keyword);
		for( ; (pp = (*nextinlist)()) != 0; )
		{
			if ((j = stringmatch(pp, keyword)) < 0) continue;
			if (j < i) continue;
			(void)addtoinfstr(' ');
			(void)addstringtoinfstr(pp);
		}
		ttyputerr("%s ambiguous:%s", keyword, returninfstr());
	}
	return(-1);
}

/*
 * routine to report the amount of match that string "keyword" and "input"
 * have in common.  Returns the number of characters that match.  Returns -2
 * if they are equal, -1 if there are extra characters at the end of "input"
 * which make the match erroneous.  Ignores case distinction.
 */
INTSML stringmatch(char *keyword, char *input)
{
	REGISTER INTSML j;
	REGISTER char c, d;

	for(j=0; (c = input[j]) != 0; j++)
	{
		if (isupper(c)) c = tolower(c);
		d = keyword[j];  if (isupper(d)) d = tolower(d);
		if (c != d) break;
	}
	if (c != 0) return(-1);
	if (keyword[j] == 0) return(-2);
	return(j);
}

/************************* COMMAND COMPLETION CODE *************************/

INTSML  db_filestrlen;
char    db_filekey[100];
char    db_directorypath[256];
char    db_fileextension[10];
INTSML  db_filecount, db_filetotal;
char  **db_filesindir;

void requiredextension(char *extension)
{
	(void)strcpy(db_fileextension, extension);
}

INTSML topoffile(char **a)
{
	db_fileextension[0] = 0;
	return(db_beginsearch(a));
}

INTSML topoflibfile(char **a)
{
	(void)strcpy(db_fileextension, ".elib");
	return(db_beginsearch(a));
}

INTSML db_beginsearch(char **a)
{
	INTSML i;
	static char file[256];
	char *pt;

	/* build the full file name */
	(void)strcpy(file, truepath(*a));
	*a = file;

	/* search for directory specifications */
	for(i=strlen(file)-1; i > 0; i--) if (file[i] == DIRSEP) break;
	if (file[i] == DIRSEP) i++;
	(void)strcpy(db_filekey, &file[i]);
	db_filestrlen = strlen(db_filekey);
	file[i] = 0;
	strcpy(db_directorypath, file);
	db_filecount = 0;
	db_filetotal = filesindirectory(file, &db_filesindir);

	/* advance pointer to after the directory separator */
	pt = *a;
	for(i=strlen(pt)-1; i > 0; i--) if (pt[i] == DIRSEP) break;
	if (i > 0) *a = &pt[i+1];
	return(0);
}

char *nextfile(void)
{
	char *pt, sep[2];
	static char testfile[256];

	for(;;)
	{
		if (db_filecount >= db_filetotal) break;
		pt = db_filesindir[db_filecount];
		db_filecount++;

		/* see if the file is valid */
		if (pt[0] == '.') continue;
		if (strncmp(db_filekey, pt, db_filestrlen) != 0) continue;
		(void)strcpy(testfile, db_directorypath);
		(void)strcat(testfile, pt);
		if (fileexistence(testfile) == 2)
		{
			sep[0] = DIRSEP;   sep[1] = 0;
			strcpy(testfile, pt);
			strcat(testfile, sep);
			return(testfile);
		}
		if (db_fileextension[0] != 0)
		{
			if (strcmp(&pt[strlen(pt)-strlen(db_fileextension)], db_fileextension) != 0)
				continue;
		}
		return(pt);
	}
	return(0);
}

/*
 * routines to do command completion on technology names
 */
static TECHNOLOGY *db_postechcomcomp;
INTSML topoftechs(char **c) { db_postechcomcomp = el_technologies; return(1); }
char *nexttechs(void)
{
	REGISTER char *retname;

	if (db_postechcomcomp == NOTECHNOLOGY) return(0);
	retname = db_postechcomcomp->techname;
	db_postechcomcomp = db_postechcomcomp->nexttechnology;
	return(retname);
}

/*
 * routines to do command completion on technology names
 */
static CELL *db_poscellcomcomp;
INTSML topofcells(char **c) { db_poscellcomcomp = el_curlib->firstcell; return(1); }
char *nextcells(void)
{
	REGISTER char *retname;

	if (db_poscellcomcomp == NOCELL) return(0);
	retname = db_poscellcomcomp->cellname;
	db_poscellcomcomp = db_poscellcomcomp->nextcell;
	return(retname);
}

/*
 * routines to do command completion on view names
 */
static VIEW *db_posviewcomcomp;
INTSML topofviews(char **c) { db_posviewcomcomp = el_views; return(1); }
char *nextviews(void)
{
	REGISTER char *retname;

	if (db_posviewcomcomp == NOVIEW) return(0);
	retname = db_posviewcomcomp->viewname;
	db_posviewcomcomp = db_posviewcomcomp->nextview;
	return(retname);
}

/*
 * routines to do command completion on library names
 */
static LIBRARY *db_poslibcomcomp;
INTSML topoflibs(char **c)
{
	db_poslibcomcomp = el_curlib;
	return(1);
}
char *nextlibs(void)
{
	REGISTER char *retname;

	for(;;)
	{
		if (db_poslibcomcomp == NOLIBRARY) return(0);
		if ((db_poslibcomcomp->userbits&HIDDENLIBRARY) == 0)
		{
			retname = db_poslibcomcomp->libname;
			db_poslibcomcomp = db_poslibcomcomp->nextlibrary;
			break;
		} else
		{
			db_poslibcomcomp = db_poslibcomcomp->nextlibrary;
		}
	}
	return(retname);
}

/*
 * routines to do command completion on aid names
 */
static INTSML db_poscomcomp;
INTSML topofaids(char **c) { db_poscomcomp = 0; return(1); }
char *nextaids(void)
{
	if (db_poscomcomp >= el_maxaid) return(0);
	return(el_aids[db_poscomcomp++].aidname);
}

/*
 * routines to do command completion on facet names
 */
static NODEPROTO *db_posnodeprotos;
INTSML topoffacets(char **c)
{
	REGISTER char *pt;
	REGISTER LIBRARY *lib;

	/* by default, assume the current library */
	db_posnodeprotos = el_curlib->firstnodeproto;

	/* see if a library specification was given */
	for(pt = *c; *pt != 0; pt++) if (*pt == ':') break;
	if (*pt == ':')
	{
		*pt = 0;
		lib = getlibrary(*c);
		*pt++ = ':';
		if (lib != NOLIBRARY)
		{
			*c = pt;
			db_posnodeprotos = lib->firstnodeproto;
		}
	}
	return(1);
}
char *nextfacets(void)
{
	REGISTER char *ret;

	if (db_posnodeprotos != NONODEPROTO)
	{
		ret = describenodeproto(db_posnodeprotos);
		db_posnodeprotos = db_posnodeprotos->nextnodeproto;
		return(ret);
	}
	return(0);
}

/*
 * routines to do command completion on arc names
 */
static ARCPROTO *db_posarcs;
INTSML topofarcs(char **c)
{
	REGISTER char *pt;
	REGISTER TECHNOLOGY *t;

	/* by default, assume the current technology */
	db_posarcs = el_curtech->firstarcproto;

	/* see if a technology specification was given */
	for(pt = *c; *pt != 0; pt++) if (*pt == ':') break;
	if (*pt == ':')
	{
		*pt = 0;
		t = gettechnology(*c);
		*pt++ = ':';
		if (t != NOTECHNOLOGY)
		{
			*c = pt;
			db_posarcs = t->firstarcproto;
		}
	}
	return(1);
}
char *nextarcs(void)
{
	REGISTER char *ret;

	if (db_posarcs != NOARCPROTO)
	{
		ret = describearcproto(db_posarcs);
		db_posarcs = db_posarcs->nextarcproto;
		return(ret);
	}
	return(0);
}

/*
 * routines to do command completion on network names
 */
static NETWORK *db_posnets;
static INTSML db_posinnet;
static char *db_posinname;

INTSML topofnets(char **c)
{
	REGISTER NODEPROTO *np;

	db_posnets = NONETWORK;
	np = getcurfacet();
	if (np == NONODEPROTO) return(0);
	db_posnets = np->firstnetwork;
	if (db_posnets == NONETWORK) return(0);
	db_posinnet = 0;
	db_posinname = db_posnets->netname;
	return(1);
}
char *nextnets(void)
{
	REGISTER char *ret;

	for(;;)
	{
		if (db_posnets == NONETWORK) return(0);
		ret = db_posinname;
		if (db_posinnet >= db_posnets->namecount)
		{
			db_posnets = db_posnets->nextnetwork;
			if (db_posnets == NONETWORK) return(0);
			db_posinnet = 0;
			db_posinname = db_posnets->netname;
		} else
		{
			db_posinname += strlen(db_posinname) + 1;
			db_posinnet++;
			break;
		}
	}
	return(ret);
}

/************************* OUTPUT PREPARATION *************************/

/* routine to return the name of nodeinst "ni" */
char *describenodeinst(NODEINST *ni)
{
	REGISTER char *name, *protoname;
	REGISTER VARIABLE *var;
	REGISTER INTSML len;

	if (ni == NONODEINST) return("***NONODEINST***");

	/* see if there is a local name on the node */
	protoname = describenodeproto(ni->proto);
	var = getvalkey((INTBIG)ni, VNODEINST, VSTRING, el_node_name);
	if (var == NOVARIABLE) return(protoname);

	/* get output buffer */
	db_descninodeswitch++;
	if (db_descninodeswitch >= 2) db_descninodeswitch = 0;

	/* make sure buffer has enough room */
	len = strlen(protoname) + strlen((char *)var->addr) + 3;
	if (len > db_descnioutputsize[db_descninodeswitch])
	{
		if (db_descnioutputsize[db_descninodeswitch] != 0)
			efree(db_descnioutput[db_descninodeswitch]);
		db_descnioutputsize[db_descninodeswitch] = 0;
		db_descnioutput[db_descninodeswitch] = (char *)emalloc(len, db_cluster);
		if (db_descnioutput[db_descninodeswitch] == 0) return("");
		db_descnioutputsize[db_descninodeswitch] = len;
	}

	/* store the name */
	name = db_descnioutput[db_descninodeswitch];
	(void)strcpy(name, protoname);
	(void)strcat(name, "[");
	(void)strcat(name, (char *)var->addr);
	(void)strcat(name, "]");
	return(name);
}

/* routine to return the name of arcinst "ai" */
char *describearcinst(ARCINST *ai)
{
	REGISTER char *name, *pname;
	REGISTER VARIABLE *var;
	REGISTER INTSML len;

	if (ai == NOARCINST) return("***NOARCINST***");

	/* get local arc name */
	pname = describearcproto(ai->proto);
	var = getvalkey((INTBIG)ai, VARCINST, VSTRING, el_arc_name);
	if (var == NOVARIABLE) return(pname);

	/* get output buffer */
	db_descaiarcswitch++;
	if (db_descaiarcswitch >= 2) db_descaiarcswitch = 0;

	/* make sure buffer has enough room */
	len = strlen(pname) + strlen((char *)var->addr) + 3;
	if (len > db_descaioutputsize[db_descaiarcswitch])
	{
		if (db_descaioutputsize[db_descaiarcswitch] != 0)
			efree(db_descaioutput[db_descaiarcswitch]);
		db_descaioutputsize[db_descaiarcswitch] = 0;
		db_descaioutput[db_descaiarcswitch] = (char *)emalloc(len, db_cluster);
		if (db_descaioutput[db_descaiarcswitch] == 0) return("");
		db_descaioutputsize[db_descaiarcswitch] = len;
	}

	name = db_descaioutput[db_descaiarcswitch];
	(void)strcpy(name, pname);
	(void)strcat(name, "[");
	(void)strcat(name, (char *)var->addr);
	(void)strcat(name, "]");
	return(name);
}

/*
 * routine to return the full name of facet "np", including its view type
 * (if any) and version (if not most recent).  Library considerations are
 * ignored.
 */
char *nldescribenodeproto(NODEPROTO *np)
{
	REGISTER LIBRARY *curlib;
	REGISTER char *ret;

	if (np == NONODEPROTO) return("***NONODEPROTO***");

	if (np->primindex != 0) return(np->primname);
	curlib = el_curlib;
	el_curlib = np->cell->lib;
	ret = describenodeproto(np);
	el_curlib = curlib;
	return(ret);
}

/*
 * routine to return the full name of facet "np", including its library name
 * (if different from the current), view type (if any), and version (if not
 * most recent).
 */
char *describenodeproto(NODEPROTO *np)
{
	char line[50];
	REGISTER char *name;
	REGISTER INTSML len;

	if (np == NONODEPROTO) return("***NONODEPROTO***");

	/* simple tests for direct name use */
	if (np->primindex != 0)
	{
		/* if a primitive in the current technology, simply use name */
		if (np->tech == el_curtech) return(np->primname);
	} else
	{
		/* if view unknown, version recent, library current, simply use name */
		if (*np->cellview->sviewname == 0 && np->newestversion == np && np->cell->lib == el_curlib)
			return(np->cell->cellname);
	}

	/* get output buffer */
	db_descnpnodeswitch++;
	if (db_descnpnodeswitch >= 2) db_descnpnodeswitch = 0;

	if (np->primindex != 0)
	{
		len = strlen(np->primname) + strlen(np->tech->techname) + 2;
	} else
	{
		/* compute size of buffer */
		len = strlen(np->cell->cellname) + 1;
		if (np->cell->lib != el_curlib) len += strlen(np->cell->lib->libname) + 1;
		if (np->newestversion != np)
		{
			(void)sprintf(line, ";%d", np->version);
			len += strlen(line);
		}
		if (*np->cellview->sviewname != 0) len += strlen(np->cellview->sviewname) + 2;
	}

	/* make sure buffer has enough room */
	if (len > db_descnpoutputsize[db_descnpnodeswitch])
	{
		if (db_descnpoutputsize[db_descnpnodeswitch] != 0)
			efree(db_descnpoutput[db_descnpnodeswitch]);
		db_descnpoutputsize[db_descnpnodeswitch] = 0;
		db_descnpoutput[db_descnpnodeswitch] = (char *)emalloc(len, db_cluster);
		if (db_descnpoutput[db_descnpnodeswitch] == 0) return("");
		db_descnpoutputsize[db_descnpnodeswitch] = len;
	}

	/* construct complete name */
	name = db_descnpoutput[db_descnpnodeswitch];
	if (np->primindex != 0)
	{
		(void)strcpy(name, np->tech->techname);
		(void)strcat(name, ":");
		(void)strcat(name, np->primname);
	} else
	{
		if (np->cell->lib != el_curlib)
		{
			(void)strcpy(name, np->cell->lib->libname);
			(void)strcat(name, ":");
			(void)strcat(name, np->cell->cellname);
		} else (void)strcpy(name, np->cell->cellname);
		if (np->newestversion != np) (void)strcat(name, line);
		if (*np->cellview->sviewname != 0)
		{
			(void)strcat(name, "{");
			(void)strcat(name, np->cellview->sviewname);
			(void)strcat(name, "}");
		}
	}
	return(name);
}

/* routine to return the name of arcproto "ap" */
char *describearcproto(ARCPROTO *ap)
{
	REGISTER char *name;
	REGISTER INTSML len;

	if (ap == NOARCPROTO) return("***NOARCPROTO***");

	if (ap->tech == el_curtech) return(ap->protoname);

	/* get output buffer */
	db_descaparcswitch++;
	if (db_descaparcswitch >= 2) db_descaparcswitch = 0;

	/* make sure buffer has enough room */
	len = strlen(ap->tech->techname) + strlen(ap->protoname) + 2;
	if (len > db_descapoutputsize[db_descaparcswitch])
	{
		if (db_descapoutputsize[db_descaparcswitch] != 0)
			efree(db_descapoutput[db_descaparcswitch]);
		db_descapoutputsize[db_descaparcswitch] = 0;
		db_descapoutput[db_descaparcswitch] = (char *)emalloc(len, db_cluster);
		if (db_descapoutput[db_descaparcswitch] == 0) return("");
		db_descapoutputsize[db_descaparcswitch] = len;
	}

	name = db_descapoutput[db_descaparcswitch];
	(void)strcpy(name, ap->tech->techname);
	(void)strcat(name, ":");
	(void)strcat(name, ap->protoname);
	return(name);
}

/* routine to return the name of the object whose geometry module is "geom" */
char *geomname(GEOM *geom)
{
	if (geom == NOGEOM) return("***NOGEOM***");
	if (geom->entrytype == OBJNODEINST) return(describenodeinst(geom->entryaddr.ni));
	if (geom->entrytype == OBJARCINST) return(describearcinst(geom->entryaddr.ai));
	return("dummy");
}

/*
 * routine to convert network "net" into a string
 */
char *describenetwork(NETWORK *net)
{
	REGISTER char *pt;
	REGISTER INTSML i;

	if (net == NONETWORK) return("***NONETWORK***");

	if (net->namecount == 0) return("");
	if (net->namecount == 1) return(net->netname);

	(void)initinfstr();
	pt = net->netname;
	for(i=0; i<net->namecount; i++)
	{
		if (i != 0) (void)addtoinfstr('/');
		(void)addstringtoinfstr(pt);
		pt += strlen(pt) + 1;
	}
	return(returninfstr());
}

/*
 * routine to convert a lambda number to ascii
 */
#define	OUTBUFS 8
char *latoa(INTBIG i)
{
	static INTSML latoaswitch = 0;
	static char output[OUTBUFS][20];
	double scale, number;
	REGISTER char *cur;

	/* get output buffer */
	cur = output[latoaswitch++];
	if (latoaswitch >= OUTBUFS) latoaswitch = 0;

	/* determine scaling */
	scale = db_getcurrentscale((INTSML)(el_units&INTERNALUNITS), (INTSML)(el_units&DISPLAYUNITS));
	number = ((double)i) / scale;
	(void)sprintf(cur, "%g", number);

	switch (el_units&DISPLAYUNITS)
	{
		case DISPUNITINCH:   strcat(cur, "\"");  break;
		case DISPUNITCM:     strcat(cur, "cm");  break;
		case DISPUNITMM:     strcat(cur, "mm");  break;
		case DISPUNITMIL:    strcat(cur, "mil"); break;
		case DISPUNITMIC:    strcat(cur, "u");   break;
		case DISPUNITCMIC:   strcat(cur, "cu");  break;
		case DISPUNITMMIC:   strcat(cur, "mu");  break;
	}
	return(cur);
}

/*
 * routine to convert a fractional number to ascii
 */
char *frtoa(INTBIG i)
{
	static INTSML latoaswitch = 0;
	static char output[5][20];
	REGISTER INTBIG fra;
	char temp[3];
	REGISTER char *pp, *cur, *start;

	/* get output buffer */
	start = cur = &output[latoaswitch++][0];
	if (latoaswitch >= 5) latoaswitch = 0;

	/* handle negative values */
	if (i < 0)
	{
		*cur++ = '-';
		i = -i;
	}

	/* get the part to the left of the decimal point */
	(void)sprintf(cur, "%ld", i/WHOLE);

	/* see if there is anything to the right of the decimal point */
	if ((i%WHOLE) != 0)
	{
		(void)strcat(cur, ".");
		fra = i % WHOLE;
		fra = (fra*100 + WHOLE/2) / WHOLE;
		(void)sprintf(temp, "%02ld", fra);
		(void)strcat(cur, temp);
		pp = cur;   while (*pp != 0) pp++;
		while (*--pp == '0') *pp = 0;
		if (*pp == '.') *pp = 0;
	}
	return(start);
}

/*
 * routine to convert "word" to its plural form, depending on the value
 * of "amount" (if "amount" is zero or multiple, pluralize the word).
 */
char *makeplural(char *word, INTBIG amount)
{
	INTBIG needed, len;

	if (amount == 1) return(word);
	len = strlen(word);
	if (len == 0) return(word);
	needed = len + 2;
	if (needed > db_pluralbufsize)
	{
		if (db_pluralbufsize > 0) efree(db_pluralbuffer);
		db_pluralbufsize = 0;
		db_pluralbuffer = (char *)emalloc(needed, db_cluster);
		if (db_pluralbuffer == 0) return(word);
		db_pluralbufsize = needed;
	}
	strcpy(db_pluralbuffer, word);
	if (isupper(word[len-1])) strcat(db_pluralbuffer, "S"); else
		strcat(db_pluralbuffer, "s");
	return(db_pluralbuffer);
}

/*
 * routine to determine whether or not the string in "pp" is a number.
 * Returns nonzero if it is.
 */
INTSML isanumber(char *pp)
{
	INTSML xflag;

	/* ignore the minus sign */
	if (*pp == '+' || *pp == '-') pp++;

	/* special case for hexadecimal prefix */
	if (*pp == '0' && (pp[1] == 'x' || pp[1] == 'X'))
	{
		pp += 2;
		xflag = 1;
	} else xflag = 0;

	/* there must be something to check */
	if (*pp == 0) return(0);

	if (xflag != 0)
	{
		while (isxdigit(*pp)) pp++;
	} else
	{
		while (isdigit(*pp) || *pp == '.') pp++;
	}
	if (*pp == 0) return(1);

	/* handle exponent of floating point numbers */
	if (xflag != 0 || (*pp != 'e' && *pp != 'E')) return(0);
	pp++;
	if (*pp == '+' || *pp == '-') pp++;
	if (*pp == 0) return(0);
	while (isdigit(*pp)) pp++;
	if (*pp == 0) return(1);

	return(0);
}

#define	IDEALPIXPER10LAM   150
#define	PIXINCREMENT        50

/*
 * routine to convert relative or absolute font values to absolute, in the
 * window "w"
 */
INTSML truefontsize(INTSML font, WINDOWPART *w, TECHNOLOGY *tech)
{
	REGISTER INTSML inifont, minf, maxf;
	REGISTER INTBIG pixper10lam;

	/* absolute fonts need no adjustment */
	if ((font >= TXT4P && font <= TXT20P) || font >= TXTEDITOR) return(font);

	/* detemine default, min, and max size of font */
	switch (font)
	{
		case TXTSMALL:  inifont = TXT10P;  minf = TXT4P;  maxf = TXT16P;  break;
		default:        ttyputmsg("Warning: font size is %d", font);
		case TXTMEDIUM: inifont = TXT14P;  minf = TXT6P;  maxf = TXT18P;  break;
		case TXTLARGE:  inifont = TXT16P;  minf = TXT8P;  maxf = TXT20P;  break;
	}
	if (w == NOWINDOWPART) return(inifont);

	/* determine scale of this window */
	pixper10lam = applyxscale(w, tech->deflambda*10);

	/* shrink down as window gets tight */
	if (pixper10lam < IDEALPIXPER10LAM)
	{
		while (pixper10lam < IDEALPIXPER10LAM)
		{
			if (inifont <= minf) break;
			inifont--;
			pixper10lam += PIXINCREMENT;
		}
	} else
	{
		pixper10lam -= PIXINCREMENT;
		while (pixper10lam > IDEALPIXPER10LAM)
		{
			if (inifont >= maxf) break;
			inifont++;
			pixper10lam -= PIXINCREMENT;
		}
	}
	return(inifont);
}

/*
 * routine to return the default text descriptor.  This text will be
 * placed on "geom".
 */
INTBIG defaulttextdescript(GEOM *geom)
{
	REGISTER VARIABLE *txtvar;
	REGISTER INTBIG descript, dx, dy, goleft, goright, goup, godown;
	REGISTER NODEINST *ni;
	REGISTER ARCINST *ai;
	static INTBIG user_default_text_style = 0, user_default_text_size = 0,
		user_default_text_smart_style = 0;

	if (user_default_text_style == 0)
		user_default_text_style = makekey("USER_default_text_style");
	if (user_default_text_size == 0)
		user_default_text_size = makekey("USER_default_text_size");
	if (user_default_text_smart_style == 0)
		user_default_text_smart_style = makekey("USER_default_text_smart_style");

	txtvar = getvalkey((INTBIG)us_aid, VAID, VINTEGER, user_default_text_style);
	if (txtvar == NOVARIABLE) descript = VTPOSCENT; else
		descript = txtvar->addr;
	txtvar = getvalkey((INTBIG)us_aid, VAID, VINTEGER, user_default_text_size);
	if (txtvar == NOVARIABLE) descript |= TXTSMALL << VTSIZESH; else
		descript |= txtvar->addr << VTSIZESH;
	if (geom != NOGEOM)
	{
		/* handle smart text placement relative to attached object */
		txtvar = getvalkey((INTBIG)us_aid, VAID, VINTEGER, user_default_text_smart_style);
		if (txtvar != NOVARIABLE)
		{
			/* figure out location of object relative to environment */
			dx = dy = 0;
			if (geom->entrytype == OBJNODEINST)
			{
				ni = geom->entryaddr.ni;
				if (ni->firstportarcinst != NOPORTARCINST)
				{
					ai = ni->firstportarcinst->conarcinst;
					dx = (ai->end[0].xpos+ai->end[1].xpos)/2 -
						(ni->lowx+ni->highx)/2;
					dy = (ai->end[0].ypos+ai->end[1].ypos)/2 -
						(ni->lowy+ni->highy)/2;
				}
			}

			/* first move placement horizontally */
			goleft = goright = goup = godown = 0;
			if ((txtvar->addr&03) == 1)
			{
				/* place label inside (towards center) */
				if (dx > 0) goright++; else
					if (dx < 0) goleft++;
			} else if ((txtvar->addr&03) == 2)
			{
				/* place label outside (away from center) */
				if (dx > 0) goleft++; else
					if (dx < 0) goright++;
			}

			/* next move placement vertically */
			if (((txtvar->addr>>2)&03) == 1)
			{
				/* place label inside (towards center) */
				if (dy > 0) goup++; else
					if (dy < 0) godown++;
			} else if (((txtvar->addr>>2)&03) == 2)
			{
				/* place label outside (away from center) */
				if (dy > 0) godown++; else
					if (dy < 0) goup++;
			}
			if (goleft != 0)
			{
				switch (descript&VTPOSITION)
				{
					case VTPOSCENT:
					case VTPOSRIGHT:
					case VTPOSLEFT:
						descript = (descript & ~VTPOSITION) | VTPOSLEFT;
						break;
					case VTPOSUP:
					case VTPOSUPLEFT:
					case VTPOSUPRIGHT:
						descript = (descript & ~VTPOSITION) | VTPOSUPLEFT;
						break;
					case VTPOSDOWN:
					case VTPOSDOWNLEFT:
					case VTPOSDOWNRIGHT:
						descript = (descript & ~VTPOSITION) | VTPOSDOWNLEFT;
						break;
				}
			}
			if (goright != 0)
			{
				switch (descript&VTPOSITION)
				{
					case VTPOSCENT:
					case VTPOSRIGHT:
					case VTPOSLEFT:
						descript = (descript & ~VTPOSITION) | VTPOSRIGHT;
						break;
					case VTPOSUP:
					case VTPOSUPLEFT:
					case VTPOSUPRIGHT:
						descript = (descript & ~VTPOSITION) | VTPOSUPRIGHT;
						break;
					case VTPOSDOWN:
					case VTPOSDOWNLEFT:
					case VTPOSDOWNRIGHT:
						descript = (descript & ~VTPOSITION) | VTPOSDOWNRIGHT;
						break;
				}
			}
			if (goup != 0)
			{
				switch (descript&VTPOSITION)
				{
					case VTPOSCENT:
					case VTPOSUP:
					case VTPOSDOWN:
						descript = (descript & ~VTPOSITION) | VTPOSUP;
						break;
					case VTPOSRIGHT:
					case VTPOSUPRIGHT:
					case VTPOSDOWNRIGHT:
						descript = (descript & ~VTPOSITION) | VTPOSUPRIGHT;
						break;
					case VTPOSLEFT:
					case VTPOSUPLEFT:
					case VTPOSDOWNLEFT:
						descript = (descript & ~VTPOSITION) | VTPOSUPLEFT;
						break;
				}
			}
			if (godown != 0)
			{
				switch (descript&VTPOSITION)
				{
					case VTPOSCENT:
					case VTPOSUP:
					case VTPOSDOWN:
						descript = (descript & ~VTPOSITION) | VTPOSDOWN;
						break;
					case VTPOSRIGHT:
					case VTPOSUPRIGHT:
					case VTPOSDOWNRIGHT:
						descript = (descript & ~VTPOSITION) | VTPOSDOWNRIGHT;
						break;
					case VTPOSLEFT:
					case VTPOSUPLEFT:
					case VTPOSDOWNLEFT:
						descript = (descript & ~VTPOSITION) | VTPOSDOWNLEFT;
						break;
				}
			}
		}
	}
	return(descript);
}

/*
 * routine to make a printable string from variable "val", array index
 * "aindex".  If "aindex" is negative, print the entire array.  If "purpose" is
 * zero, the conversion is for human reading and should be easy to understand.
 * If "purpose" is positive, the conversion is for machine reading and should
 * be easy to parse.  If "purpose" is negative, the conversion is for
 * parameter substitution and should be easy to understand but not hard to
 * parse (a combination of the two).
 */
char *describevariable(VARIABLE *var, INTSML aindex, INTSML purpose)
{
	REGISTER INTSML err, i, len;

	if (var == NOVARIABLE)
	{
		if (purpose <= 0) return("***UNKNOWN***"); else return("");
	}

	err = initinfstr();

	/* add variable name if requested */
	if ((var->type&VDISPLAY) != 0 &&
		(var->textdescript&VTDISPLAYPART) == VTDISPLAYNAMEVALUE &&
		(var->type&VISARRAY) == 0)
	{
		char *name;

		name = makename(var->key);
		if (strncmp(name, "ATTR_", 5) == 0)
		{
			name += 5;
		} else if (strncmp(name, "ATTRP_", 6) == 0)
		{
			len = strlen(name);
			for(i=len-1; i>=0; i--) if (name[i] == '_') break;
			name += i;
		}
		(void)addstringtoinfstr(name);
		(void)addtoinfstr('=');
	}

	if ((var->type & (VCODE1|VCODE2)) != 0)
	{
		/* special case for code: it is a string, the type applies to the result */
		err += db_makestringvar(VSTRING, var->addr, purpose);
	} else
	{
		if ((var->type&VISARRAY) != 0)
		{
			/* compute the array length */
			len = (INTSML)getlength(var);

			/* if asking for a single entry, get it */
			if (aindex >= 0)
			{
				/* special case when the variable is a general array of objects */
				if ((var->type&VTYPE) == VGENERAL)
				{
					/* index the array in pairs */
					aindex *= 2;
					if (aindex < len)
					err += db_makestringvar(((INTBIG *)var->addr)[aindex+1],
						((INTBIG *)var->addr)[aindex], purpose);
				} else
				{
					/* normal array indexing */
					if (aindex < len)
						switch ((var->type&VTYPE))
					{
						case VCHAR:
						err += db_makestringvar(var->type,
							((INTBIG)((char *)var->addr)[aindex]), purpose);
						break;

						case VDOUBLE:
						err += db_makestringvar(var->type,
							((INTBIG)((double *)var->addr)[aindex]), purpose);
						break;

						case VSHORT:
						err += db_makestringvar(var->type,
							((INTBIG)((INTSML *)var->addr)[aindex]), purpose);
						break;

						default:
						err += db_makestringvar(var->type, ((INTBIG *)var->addr)[aindex], purpose);
						break;
					}
				}
			} else
			{
				/* in an array, quote strings */
				if (purpose < 0) purpose = 0;
				err += addtoinfstr('[');
				for(i=0; i<len; i++)
				{
					if (i != 0) err += addtoinfstr(',');

					/* special case when the variable is a general array of objects */
					if ((var->type&VTYPE) == VGENERAL)
					{
						/* index the array in pairs */
						if (i+1 < len)
							err += db_makestringvar(((INTBIG *)var->addr)[i+1],
								((INTBIG *)var->addr)[i], purpose);
						i++;
					} else
					{
						/* normal array indexing */
						switch ((var->type&VTYPE))
						{
							case VCHAR:
							err += db_makestringvar(var->type,
								((INTBIG)((char *)var->addr)[i]), purpose);
							break;

							case VDOUBLE:
							err += db_makestringvar(var->type,
								((INTBIG)((double *)var->addr)[i]), purpose);
							break;

							case VSHORT:
							err += db_makestringvar(var->type,
								((INTBIG)((INTSML *)var->addr)[i]), purpose);
							break;

							default:
							err += db_makestringvar(var->type, ((INTBIG *)var->addr)[i], purpose);
							break;
						}
					}
				}
				err += addtoinfstr(']');
			}
		} else err += db_makestringvar(var->type, var->addr, purpose);
	}
	if (err != 0) (void)db_error(DBNOMEM|DBDESCRIBEVARIABLE);
	return(returninfstr());
}

/*
 * routine to make a string from the value in "addr" which has a type in
 * "type".  "purpose" is an indicator of the purpose of this conversion:
 * zero indicates conversion for humans to read, positive indicates
 * conversion for a program to read (more terse) and negative indicates human
 * reading for parameter substitution (don't quote strings).  Returns nonzero
 * if there is a memory allocation error.
 */
INTSML db_makestringvar(INTBIG type, INTBIG addr, INTSML purpose)
{
	char line[100];
	REGISTER INTSML err;
	REGISTER NODEINST *ni;
	REGISTER NODEPROTO *np;
	REGISTER PORTARCINST *pi;
	REGISTER PORTEXPINST *pe;
	REGISTER PORTPROTO *pp;
	REGISTER ARCINST *ai;
	REGISTER ARCPROTO *ap;
	REGISTER GEOM *geom;
	REGISTER RTNODE *rtn;
	REGISTER LIBRARY *lib;
	REGISTER TECHNOLOGY *tech;
	REGISTER AIDENTRY *aid;
	REGISTER NETWORK *net;
	REGISTER CELL *cell;
	REGISTER VIEW *view;
	REGISTER WINDOWPART *win;
	REGISTER GRAPHICS *gra;
	REGISTER VARIABLE *var;
	REGISTER CONSTRAINT *con;
	REGISTER WINDOWFRAME *wf;

	switch (type&VTYPE)
	{
		case VINTEGER:
			(void)sprintf(line, "%ld", addr);
			return(addstringtoinfstr(line));
		case VADDRESS:
			if (purpose == 0) err = addstringtoinfstr("Address="); else
				err = 0;
			(void)sprintf(line, "0%lo", addr);
			err += addstringtoinfstr(line);
			return(err);
		case VCHAR:
			return(addtoinfstr((char)addr));
		case VSTRING:
			if ((char *)addr == NOSTRING || (char *)addr == 0)
				return(addstringtoinfstr("***NOSTRING***"));
			err = 0;
			if (purpose >= 0) err += addtoinfstr('"');
			if (purpose <= 0) err += addstringtoinfstr((char *)addr); else
				err += db_addstring((char *)addr);
			if (purpose >= 0) err += addtoinfstr('"');
			return(err);
		case VFLOAT:
		case VDOUBLE:
			(void)sprintf(line, "%f", castfloat(addr));
			return(addstringtoinfstr(line));
		case VNODEINST:
			ni = (NODEINST *)addr;
			if (ni == NONODEINST) return(addstringtoinfstr("***NONODEINST***"));
			if (purpose == 0)
			{
				var = getvalkey((INTBIG)ni, VNODEINST, VSTRING, el_node_name);
				if (var != NOVARIABLE) return(addstringtoinfstr((char *)var->addr));
				(void)sprintf(line, "node%ld", (INTBIG)ni);
				return(addstringtoinfstr(line));
			}
			(void)sprintf(line, "node%ld", (INTBIG)ni);
			return(addstringtoinfstr(line));
		case VNODEPROTO:
			np = (NODEPROTO *)addr;
			if (purpose <= 0) return(addstringtoinfstr(describenodeproto(np))); else
				return(db_addstring(describenodeproto(np)));
		case VPORTARCINST:
			pi = (PORTARCINST *)addr;
			if (pi == NOPORTARCINST) return(addstringtoinfstr("***NOPORTARCINST***"));
			(void)sprintf(line, "portarc%ld", (INTBIG)pi);
			return(addstringtoinfstr(line));
		case VPORTEXPINST:
			pe = (PORTEXPINST *)addr;
			if (pe == NOPORTEXPINST) return(addstringtoinfstr("***NOPORTEXPINST***"));
			(void)sprintf(line, "portexp%ld", (INTBIG)pe);
			return(addstringtoinfstr(line));
		case VPORTPROTO:
			pp = (PORTPROTO *)addr;
			if (pp == NOPORTPROTO) return(addstringtoinfstr("***NOPORTPROTO***"));
			if (purpose <= 0) return(addstringtoinfstr(pp->protoname)); else
				return(db_addstring(pp->protoname));
		case VARCINST:
			ai = (ARCINST *)addr;
			if (ai == NOARCINST) return(addstringtoinfstr("***NOARCINST***"));
			(void)sprintf(line, "arc%ld", (INTBIG)ai);
			return(addstringtoinfstr(line));
		case VARCPROTO:
			ap = (ARCPROTO *)addr;
			if (purpose <= 0) return(addstringtoinfstr(describearcproto(ap))); else
				return(db_addstring(describearcproto(ap)));
		case VGEOM:
			geom = (GEOM *)addr;
			if (geom == NOGEOM) return(addstringtoinfstr("***NOGEOM***"));
			(void)sprintf(line, "geom%ld", (INTBIG)geom);
			return(addstringtoinfstr(line));
		case VLIBRARY:
			lib = (LIBRARY *)addr;
			if (lib == NOLIBRARY) return(addstringtoinfstr("***NOLIBRARY***"));
			if (purpose <= 0) return(addstringtoinfstr(lib->libname)); else
				return(db_addstring(lib->libname));
		case VTECHNOLOGY:
			tech = (TECHNOLOGY *)addr;
			if (tech == NOTECHNOLOGY) return(addstringtoinfstr("***NOTECHNOLOGY***"));
			if (purpose <= 0) return(addstringtoinfstr(tech->techname)); else
				return(db_addstring(tech->techname));
		case VAID:
			aid = (AIDENTRY *)addr;
			if (aid == NOAID) return(addstringtoinfstr("***NOAID***"));
			if (purpose <= 0) return(addstringtoinfstr(aid->aidname)); else
				return(db_addstring(aid->aidname));
		case VRTNODE:
			rtn = (RTNODE *)addr;
			if (rtn == NORTNODE) return(addstringtoinfstr("***NORTNODE***"));
			(void)sprintf(line, "rtn%ld", (INTBIG)rtn);
			return(addstringtoinfstr(line));
		case VFRACT:
			return(addstringtoinfstr(frtoa(addr)));
		case VNETWORK:
			net = (NETWORK *)addr;
			if (net == NONETWORK) return(addstringtoinfstr("***NONETWORK***"));
			if (net->namecount != 0)
			{
				if (purpose <= 0) return(addstringtoinfstr(describenetwork(net))); else
					return(db_addstring(describenetwork(net)));
			}
			(void)sprintf(line, "network%ld", (INTBIG)net);
			return(addstringtoinfstr(line));
		case VCELL:
			cell = (CELL *)addr;
			if (cell == NOCELL) return(addstringtoinfstr("***NOCELL***"));
			if (purpose <= 0) return(addstringtoinfstr(cell->cellname)); else
				return(db_addstring(cell->cellname));
		case VVIEW:
			view = (VIEW *)addr;
			if (view == NOVIEW) return(addstringtoinfstr("***NOVIEW***"));
			if (purpose <= 0) return(addstringtoinfstr(view->viewname)); else
				return(db_addstring(view->viewname));
		case VWINDOWPART:
			win = (WINDOWPART *)addr;
			if (win == NOWINDOWPART) return(addstringtoinfstr("***NOWINDOWPART***"));
			if (purpose <= 0) return(addstringtoinfstr(win->location)); else
				return(db_addstring(win->location));
		case VGRAPHICS:
			gra = (GRAPHICS *)addr;
			if (gra == NOGRAPHICS) return(addstringtoinfstr("***NOGRAPHICS***"));
			(void)sprintf(line, "gra%ld", (INTBIG)gra);
			return(addstringtoinfstr(line));
		case VSHORT:
			(void)sprintf(line, "%ld", addr&0xFFFF);
			return(addstringtoinfstr(line));
		case VCONSTRAINT:
			con = (CONSTRAINT *)addr;
			if (con == NOCONSTRAINT) return(addstringtoinfstr("***NOCONSTRAINT***"));
			if (purpose <= 0) return(addstringtoinfstr(con->conname)); else
				return(db_addstring(con->conname));
		case VGENERAL:
			(void)sprintf(line, "***%ld-LONG-GENERAL-ARRAY***", ((type&VLENGTH)>>VLENGTHSH) / 2);
			return(addstringtoinfstr(line));
		case VWINDOWFRAME:
			wf = (WINDOWFRAME *)addr;
			if (wf == NOWINDOWFRAME) return(addstringtoinfstr("***NOWINDOWFRAME***"));
			(void)sprintf(line, "wf%ld", (INTBIG)wf);
			return(addstringtoinfstr(line));
	}
	return(0);
}

/*
 * routine to add the string "str" to the infinite string and to quote the
 * special characters '[', ']', '"', and '^'.  The routine returns nonzero
 * if there is memory problem with the infinite string package.
 */
INTSML db_addstring(char *str)
{
	REGISTER INTSML err;

	err = 0;
	while (*str != 0)
	{
		if (*str == '[' || *str == ']' || *str == '"' || *str == '^')
			err += addtoinfstr('^');
		err += addtoinfstr(*str++);
	}
	return(err);
}

/*
 * routine to make an abbreviation for the string "pt" in upper case if
 * "upper" is nonzero
 */
char *makeabbrev(char *pt, INTSML upper)
{
	/* generate an abbreviated name for this prototype */
	(void)initinfstr();
	while (*pt != 0)
	{
		if (isalpha(*pt))
		{
			if (isupper(*pt))
			{
				if (upper != 0) (void)addtoinfstr(*pt); else
					(void)addtoinfstr((char)(tolower(*pt)));
			} else
			{
				if (upper == 0) (void)addtoinfstr(*pt); else
					(void)addtoinfstr((char)(toupper(*pt)));
			}
			while (isalpha(*pt)) pt++;
		}
		while (!isalpha(*pt) && *pt != 0) pt++;
	}
	return(returninfstr());
}

/*
 * routine to report the name of the internal unit in "units".
 */
char *unitsname(INTSML units)
{
	switch (units & INTERNALUNITS)
	{
		case INTUNITHMMIC:   return("half-millimicron");
		case INTUNITHDMIC:   return("half-decimicron");
	}
	return("?");
}

/************************* STRING MANIPULATION *************************/

/*
 * this routine dynamically allocates a string to hold "str" and places
 * that string at "*addr".  The routine returns nonzero if the allocation
 * cannot be done.  Memory is allocated from virtual cluster "cluster".
 */
#ifdef DEBUGMEMORY
INTSML _allocstring(char **addr, char *str, CLUSTER *cluster, char *module, INTBIG line)
{
	*addr = (char *)_emalloc((strlen(str)+1), cluster, module, line);
	if (*addr == 0)
	{
		*addr = NOSTRING;
		return((INTSML)db_error(DBNOMEM|DBALLOCSTRING));
	}
	(void)strcpy(*addr, str);
	return(0);
}
#else
INTSML allocstring(char **addr, char *str, CLUSTER *cluster)
{
	*addr = (char *)emalloc((strlen(str)+1), cluster);
	if (*addr == 0)
	{
		*addr = NOSTRING;
		return((INTSML)db_error(DBNOMEM|DBALLOCSTRING));
	}
	(void)strcpy(*addr, str);
	return(0);
}
#endif

/*
 * this routine assumes that there is a dynamically allocated string at
 * "*addr" and that it is to be replaced with another dynamically allocated
 * string from "str".  The routine returns nonzero if the allocation
 * cannot be done.  Memory is allocated from virtual cluster "cluster".
 */
#ifdef DEBUGMEMORY
INTSML _reallocstring(char **addr, char *str, CLUSTER *cluster, char *module, INTBIG line)
{
	efree(*addr);
	return(_allocstring(addr, str, cluster, module, line));
}
#else
INTSML reallocstring(char **addr, char *str, CLUSTER *cluster)
{
	efree(*addr);
	return(allocstring(addr, str, cluster));
}
#endif

/*
 * Routine to convert character "c" to a case-insensitive character, with
 * special characters all ordered properly before the letters.
 */
INTBIG db_insensitivechar(INTBIG c)
{
	if (isupper(c)) return(tolower(c));
	switch (c)
	{
		case '[':  return(1);
		case '\\': return(2);
		case ']':  return(3);
		case '^':  return(4);
		case '_':  return(5);
		case '`':  return(6);
		case '{':  return(7);
		case '|':  return(8);
		case '}':  return(9);
		case '~':  return(10);
	}
	return(c);
}

/*
 * name matching routine: ignores case
 */
INTSML namesame(char *pt1, char *pt2)
{
	REGISTER INTBIG c1, c2;

	for(;;)
	{
		c1 = db_insensitivechar(*pt1++ & 0377);
		c2 = db_insensitivechar(*pt2++ & 0377);
		if (c1 != c2) return((INTSML)(c1-c2));
		if (c1 == 0) break;
	}
	return(0);
}

INTSML namesamen(char *pt1, char *pt2, INTBIG count)
{
	REGISTER INTBIG c1, c2, i;

	for(i=0; i<count; i++)
	{
		c1 = db_insensitivechar(*pt1++ & 0377);
		c2 = db_insensitivechar(*pt2++ & 0377);
		if (c1 != c2) return((INTSML)(c1-c2));
		if (c1 == 0) break;
	}
	return(0);
}

/******************** STRING PARSING ROUTINES ********************/

/*
 * routine to scan off the next keyword in the string at "*ptin".  The string
 * is terminated by any of the characters in "brk".  The string is returned
 * (-1 if error) and the string pointer is advanced to the break character
 */
char *getkeyword(char **ptin, char *brk)
{
	REGISTER char *pt2, *b, *pt;

	/* skip leading blanks */
	pt = *ptin;
	while (*pt == ' ' || *pt == '\t') pt++;

	/* remember starting point */
	pt2 = pt;
	for(;;)
	{
		if (*pt2 == 0) break;
		for(b = brk; *b != 0; b++) if (*pt2 == *b) break;
		if (*b != 0) break;
		pt2++;
	}
	if (pt2-pt+1 > db_keywordbufferlength)
	{
		if (db_keywordbufferlength != 0) efree(db_keywordbuffer);
		db_keywordbufferlength = 0;
		db_keywordbuffer = (char *)emalloc(pt2-pt+1, el_tempcluster);
		if (db_keywordbuffer == 0)
		{
			ttyputerr("No memory for parse string");
			return(NOSTRING);
		}
		db_keywordbufferlength = pt2-pt+1;
	}
	(void)strncpy(db_keywordbuffer, pt, pt2-pt);
	db_keywordbuffer[pt2-pt] = 0;
	*ptin = pt2;
	return(db_keywordbuffer);
}

/*
 * routine to return the next nonblank character in the string pointed
 * to by "ptin".  The pointer is advanced past that character.
 */
char tonextchar(char **ptin)
{
	REGISTER char *pt, ret;

	/* skip leading blanks */
	pt = *ptin;
	while (*pt == ' ' || *pt == '\t') pt++;
	ret = *pt;
	if (ret != 0) pt++;
	*ptin = pt;
	return(ret);
}

/*
 * routine to return the next nonblank character in the string pointed
 * to by "ptin".  The pointer is advanced to that character.
 */
char seenextchar(char **ptin)
{
	REGISTER char *pt;

	/* skip leading blanks */
	pt = *ptin;
	while (*pt == ' ' || *pt == '\t') pt++;
	*ptin = pt;
	return(*pt);
}

/******************** INFINITE STRING PACKAGE ********************/

/*
 * this package provides temporary storage while building an arbitrarily
 * large string in sequential order.  The first routine called must be
 * "initinfstr" followed by any number of calls to "addtoinfstr" or
 * "addstringtoinfstr" to add a character or a string to the end
 * of the string.  When the string is built, a call to "returninfstr"
 * produces the entire string so far.  The routines "initinfstr",
 * "addtoinfstr" and "addstringtoinfstr" return nonzero upon error.
 */

/*
 * routine to initialize a new infinite string
 */
INTSML initinfstr(void)
{
	REGISTER INTSML i;

	if (db_firstinf == 0)
	{
		for(i=0; i<INFSTRCOUNT; i++)
		{
			db_infstrstack[i].infstr = (char *)emalloc((INFSTRDEFAULT+1), db_cluster);
			if (db_infstrstack[i].infstr == 0)
				return((INTSML)db_error(DBNOMEM|DBINITINFSTR));
			db_infstrstack[i].infstrlength = INFSTRDEFAULT;
		}
		db_firstinf = 1;
		db_infstrstackpos = 0;
		db_infstrstackbase = 0;
		db_infstrdeepeststack = 0;
	}

	/* grab a new infinite string and initialize it */
	if (db_infstrstackpos >= INFSTRCOUNT)
		return((INTSML)db_error(DBRECURSIVE|DBINITINFSTR));
	db_curinf = &db_infstrstack[(db_infstrstackbase+db_infstrstackpos) % INFSTRCOUNT];
	if (db_infstrstackpos > db_infstrdeepeststack) db_infstrdeepeststack = db_infstrstackpos;
	db_infstrstackpos++;
	db_curinf->infstrptr = 0;
	db_curinf->infstr[db_curinf->infstrptr] = 0;
	return(0);
}

INTSML addtoinfstr(char ch)
{
	REGISTER char *str;

	if (db_curinf == NOINFSTR)
		return((INTSML)db_error(DBBADOBJECT|DBADDTOINFSTR));
	if (db_curinf->infstrptr >= db_curinf->infstrlength)
	{
		str = (char *)emalloc((db_curinf->infstrlength*2+1), db_cluster);
		if (str == 0)
			return((INTSML)db_error(DBNOMEM|DBADDTOINFSTR));
		db_curinf->infstrlength *= 2;
		(void)strcpy(str, db_curinf->infstr);
		efree(db_curinf->infstr);
		db_curinf->infstr = str;
	}
	db_curinf->infstr[db_curinf->infstrptr++] = ch;
	db_curinf->infstr[db_curinf->infstrptr] = 0;
	return(0);
}

INTSML addstringtoinfstr(char *pp)
{
	REGISTER char *str;
	REGISTER INTBIG l, i, ori;

	if (db_curinf == NOINFSTR)
		return((INTSML)db_error(DBBADOBJECT|DBADDSTRINGTOINFSTR));
	if (pp == 0) l = 0; else
		l = strlen(pp);
	if (db_curinf->infstrptr+l >= db_curinf->infstrlength)
	{
		ori = db_curinf->infstrlength;
		while (db_curinf->infstrptr+l >= db_curinf->infstrlength)
			db_curinf->infstrlength *= 2;
		str = (char *)emalloc((db_curinf->infstrlength+1), db_cluster);
		if (str == 0)
		{
			db_curinf->infstrlength = ori;
			return((INTSML)db_error(DBNOMEM|DBADDSTRINGTOINFSTR));
		}
		(void)strcpy(str, db_curinf->infstr);
		efree(db_curinf->infstr);
		db_curinf->infstr = str;
	}
	for(i=0; i<l; i++) db_curinf->infstr[db_curinf->infstrptr++] = pp[i];
	db_curinf->infstr[db_curinf->infstrptr] = 0;
	return(0);
}

char *returninfstr(void)
{
	REGISTER char *retval;

	if (db_curinf == NOINFSTR)
	{
		(void)db_error(DBBADOBJECT|DBRETURNINFSTR);
		return("");
	}
	retval = db_curinf->infstr;
	db_infstrstackpos--;
	if (db_infstrstackpos == 0)
	{
		db_infstrstackbase = db_infstrstackbase + 1 + db_infstrdeepeststack;
		db_infstrdeepeststack = 0;
		db_curinf = NOINFSTR;
	} else
		db_curinf = &db_infstrstack[(db_infstrstackbase+db_infstrstackpos-1) % INFSTRCOUNT];
	return(retval);
}

/****************************** FILES IN FACETS ******************************/

typedef struct
{
	char   **strings;
	INTBIG   stringcount;
	INTBIG   stringlimit;
	CLUSTER *cluster;
} STRINGARRAY;

/*
 * Routine to create an object for gathering arrays of strings.
 * Returns a pointer that can be used with "addtostringarray", "keepstringarray", "stringarraytotextfacet", and
 * "killstringarray"
 * Returns zero one error.
 */
void *newstringarray(CLUSTER *cluster)
{
	STRINGARRAY *sa;

	sa = (STRINGARRAY *)emalloc(sizeof (STRINGARRAY), cluster);
	if (sa == 0) return(0);
	sa->stringlimit = 0;
	sa->stringcount = 0;
	sa->cluster = cluster;
	return((void *)sa);
}

void killstringarray(void *vsa)
{
	STRINGARRAY *sa;

	sa = (STRINGARRAY *)vsa;
	if (sa == 0) return;
	clearstrings(sa);
	if (sa->stringlimit > 0) efree((char *)sa->strings);
	efree((char *)sa);
}

void clearstrings(void *vsa)
{
	STRINGARRAY *sa;
	REGISTER INTBIG i;

	sa = (STRINGARRAY *)vsa;
	if (sa == 0) return;
	for(i=0; i<sa->stringcount; i++) efree(sa->strings[i]);
	sa->stringcount = 0;
}

void addtostringarray(void *vsa, char *string)
{
	REGISTER char **newbuf;
	REGISTER INTBIG i, newlimit;
	STRINGARRAY *sa;

	sa = (STRINGARRAY *)vsa;
	if (sa == 0) return;
	if (sa->stringcount >= sa->stringlimit)
	{
		newlimit = sa->stringlimit * 2;
		if (newlimit <= 0) newlimit = 10;
		newbuf = (char **)emalloc(newlimit * (sizeof (char *)), sa->cluster);
		if (newbuf == 0) return;
		for(i=0; i<sa->stringcount; i++) newbuf[i] = sa->strings[i];
		if (sa->stringlimit > 0) efree((char *)sa->strings);
		sa->strings = newbuf;
		sa->stringlimit += 10;
	}
	if (allocstring(&sa->strings[sa->stringcount], string, sa->cluster) != 0) return;
	sa->stringcount++;
}

/*
 * routine called when done adding lines to string array "vsa".  The collection of lines is
 * stored in the "FACET_message" variable on the facet "np".  It is made permanent if
 * "permanent" is nonzero.
 */
void stringarraytotextfacet(void *vsa, NODEPROTO *np, INTSML permanent)
{
	STRINGARRAY *sa;

	sa = (STRINGARRAY *)vsa;
	if (sa == 0) return;
	if (sa->stringcount <= 0) return;
	(void)setvalkey((INTBIG)np, VNODEPROTO, el_facet_message, (INTBIG)sa->strings,
		VSTRING|VISARRAY|(sa->stringcount<<VLENGTHSH));
}

/*
 * routine called when done adding lines to string array "vsa".  The collection of lines is
 * returned to you.
 */
char **getstringarray(void *vsa, INTBIG *count)
{
	STRINGARRAY *sa;

	sa = (STRINGARRAY *)vsa;
	if (sa == 0) { *count = 0;   return(0); }
	*count = sa->stringcount;
	return(sa->strings);
}

/************************* TIME HANDLING *************************/

/*
 * Time functions are centralized here to account for different time
 * systems on different computers.
 */

/*
 * Routine to return the current time in seconds since January 1, 1970.
 */
UINTBIG getcurrenttime(void)
{
	time_t curtime;

	(void)time(&curtime);
	curtime -= machinetimeoffset();
	return(curtime);
}

/*
 * Routine to convert the time "curtime" to a string describing the date.
 * This string does *NOT* have a carriage-return after it.
 */
char *timetostring(UINTBIG curtime)
{
	static char timebuf[30];

	curtime += machinetimeoffset();
	strcpy(timebuf, ctime((time_t *)&curtime));
	timebuf[24] = 0;
	return(timebuf);
}

/*
 * Routine to parse the time in "curtime" and convert it to the year (since 1900),
 * month (0 = January), and day of month.
 */
void parsetime(UINTBIG curtime, INTSML *year, INTSML *month, INTSML *mday,
	INTSML *hour, INTSML *minute, INTSML *second)
{
	struct tm *tm;

	curtime += machinetimeoffset();
	tm = localtime((time_t *)&curtime);
	*month = tm->tm_mon;
	*mday = tm->tm_mday;
	*year = tm->tm_year;
	*hour = tm->tm_hour;
	*minute = tm->tm_min;
	*second = tm->tm_sec;
}

/******************** SUBROUTINES FOR FILE I/O ****************/

struct
{
	char *extension;
	char *filter;
	char *shortname;
	char *longname;
} db_filetypeinfo[] =
{
	{"tab",  "*.tab",		"actel",     "QuickPartTable"},		/* FILETYPEACTELTAB */
	{"arr",  "*.arr",		"arrfile",   "Array"},				/* FILETYPEARRAY */
	{"elib", "*.elib",      "blib",      "Binary library"},		/* FILETYPEBLIB */
	{"",     "*.*",			"cadrc",     "Startup"},			/* FILETYPECADRC */
	{"cif",  "*.cif",		"cif",       "CIF"},				/* FILETYPECIF */
	{"map",  "*.map",		"color",     "Color map"},			/* FILETYPECOLORMAP */
	{"def",  "*.def",		"def",       "DEF"},				/* FILETYPEDEF */
	{"rul",  "*.map",		"drac",      "Dracula"},			/* FILETYPEDRAC */
	{"dxf",  "*.dxf",		"dxf",       "AutoCAD DXF"},		/* FILETYPEDXF */
	{"txt",  "*.txt",		"eagle",     "Eagle netlist"},		/* FILETYPEEAGLE */
	{"edif", "*.edif;*.ed?","edif",      "EDIF"},				/* FILETYPEEDIF */
	{"sim",  "*.sim",		"esim",      "ESIM netlist"},		/* FILETYPEESIM */
	{"inp",  "*.inp",		"fasthenry", "FastHenry netlist"},	/* FILETYPEFASTHENRY */
	{"fpga", "*.fpga",		"fpga",      "FPGA Architecture"},	/* FILETYPEFPGA */
	{"gds",  "*.gds",		"gds",       "GDS II"},				/* FILETYPEGDS */
	{"help", "*.help",		"help",      "Help"},				/* FILETYPEHELP */
	{"hpgl", "*.hpgl",		"hpgl",      "HPGL"},				/* FILETYPEHPGL */
	{"tr0",  "*.tr0",		"hspiceout", "HSPICE output"},		/* FILETYPEHSPICEOUT */
	{"l",    "*.l",			"l",         "L"},					/* FILETYPEL */
	{"lef",  "*.lef",		"lef",       "LEF"},				/* FILETYPELEF */
	{"log",  "*.log",		"log",       "Log"},				/* FILETYPELOG */
	{"mac",  "*.mac",		"macro",     "Macro package"},		/* FILETYPEMACRO */
	{"ntk",  "*.ntk",		"mossim",    "MOSSIM netlist"},		/* FILETYPEMOSSIM */
	{"net",  "*.net",		"netlisp",   "NetLisp netlist"},	/* FILETYPENETLISP */
	{"news", "*.news",		"news",      "News"},				/* FILETYPENEWS */
	{"asc",  "*.asc",		"pad",       "PADS netlist"},		/* FILETYPEPADS */
	{"pal",  "*.pal",		"pal",       "Abel PAL"},			/* FILETYPEPAL */
	{"",     "*.*",			"platab",    "PLA table"},			/* FILETYPEPLATAB */
	{"",     "*.*",			"proj",      "Project"},			/* FILETYPEPROJ */
	{"",     "*.*",			"projusers", "Project Users"},		/* FILETYPEPROJUSERS */
	{"ps",   "*.ps;*.eps",	"ps",        "PostScript"},			/* FILETYPEPS */
	{"net",  "*.net",		"als",       "ALS netlist"},		/* FILETYPEALS */
	{"vec",  "*.vec",		"alsvec",    "ALS vector"},			/* FILETYPEALSVEC */
	{"sci",  "*.sci",		"quisc",     "QUISC netlist"},		/* FILETYPEQUISC */
	{"sim",  "*.sim",		"rsim",      "RSIM netlist"},		/* FILETYPERSIM */
	{"",     "*.*",			"sctab",     "QUISC table"},		/* FILETYPESCTAB */
	{"",     "*.*",			"scsim",     "QUISC simulation"},	/* FILETYPESCSIM */
	{"sdf",  "*.sdf",		"sdf",       "SDF"},				/* FILETYPESDF */
	{"sil",  "*.sil",		"silos",     "SILOS netlist"},		/* FILETYPESILOS */
	{"il",   "*.il",		"skill",     "SKILL commands"},		/* FILETYPESKILL */
	{"spi",  "*.spi;*.spo",	"spice",     "SPICE netlist"},		/* FILETYPESPICE */
	{"spo",  "*.spo",		"spiceout",  "SPICE output"},		/* FILETYPESPICEOUT */
	{"sue",  "*.sue",		"sue",       "SUE"},				/* FILETYPESUE */
	{"tdl",  "*.tdl",		"tegas",     "TEGAS netlist"},		/* FILETYPETEGAS */
	{"",     "*.*",			"tegastab",  "TEGAS table"},		/* FILETYPETEGASTAB */
	{"",     "*.*",			"text",      "Text"},				/* FILETYPETEXT */
	{"txt",  "*.txt",		"tlib",      "Text library"},		/* FILETYPETLIB */
	{"v",    "*.v;*.ver",	"verilog",   "Verilog"},			/* FILETYPEVERILOG */
	{"vhdl", "*.vhdl;*.vhd","vhdl",      "VHDL"},				/* FILETYPEVHDL */
	{0, 0, 0}
};

/*
 * Routine to return the extension, short name, and long name of file type "filetype".
 */
void describefiletype(INTSML filetype, char **extension, char **filter, char **shortname, char **longname)
{
	*extension = db_filetypeinfo[((FILETYPE & filetype) - 1)].extension;
	*filter    = db_filetypeinfo[((FILETYPE & filetype) - 1)].filter;
	*shortname = db_filetypeinfo[((FILETYPE & filetype) - 1)].shortname;
	*longname = db_filetypeinfo[((FILETYPE & filetype) - 1)].longname;
}

/*
 * Routine to return the filetype associated with short name "shortname".
 * Returns zero on error.
 */
INTSML getfiletype(char *shortname)
{
	REGISTER INTSML i;

	for(i=0; db_filetypeinfo[i].extension != 0; i++)
		if (namesame(shortname, db_filetypeinfo[i].shortname) == 0) return(i+1);
	return(0);
}

/*
 * routine to return the size of file whose stream is "f"
 */
INTBIG filesize(FILE *f)
{
	INTBIG savepos, endpos;

	savepos = ftell(f);
	(void)fseek(f, 0L, 2);	/* SEEK_END */
	endpos = ftell(f);
	(void)fseek(f, savepos, 0);	/* SEEK_SET */
	return(endpos);
}

/*
 * routine to open a file whose name is "file".  The type of the file is in "filetype" (whose
 * possible valyes are listed in "global.h").  The file may be in the directory "otherdir".
 * The stream is returned (NULL if it can't be found), and the true name of the file (with
 * extensions and proper directory) is returned in "truename".
 */
FILE *xopen(char *file, INTSML filetype, char *otherdir, char **truename)
{
	REGISTER FILE *f;
	REGISTER char *pp;
	char rarg[3], *extension, *filter, *shortname, *longname;

	/* determine argument to "fopen" */
	if ((filetype&FILETYPEWRITE) != 0) strcpy(rarg, "w"); else
		if ((filetype&FILETYPEAPPEND) != 0) strcpy(rarg, "a"); else
			strcpy(rarg, "r");
	if ((filetype&FILETYPE) == FILETYPELOG || (filetype&FILETYPE) == FILETYPEBLIB ||
		(filetype&FILETYPE) == FILETYPEGDS) strcat(rarg, "b");

	/* determine extension to use */
	describefiletype((INTSML)(filetype&FILETYPE), &extension, &filter, &shortname, &longname);

	/* remove "~" from the file name */
	pp = truepath(file);

	/* add the extension and look for the file */
	if (*extension != 0)
	{
		f = db_tryfile(pp, rarg, extension, (char *)0, truename);
		if (f != NULL) return(f);
	}

	/* try the file directly */
	f = db_tryfile(pp, rarg, (char *)0, (char *)0, truename);
	if (f != NULL) return(f);

	/* if no other directory given, stop now */
	if (otherdir == 0 || *otherdir == 0) return(NULL);

	/* if directory path is in file name, stop now */
	if (*pp == '/') return(NULL);

	/* try the file in the other directory with the extension */
	f = db_tryfile(pp, rarg, extension, otherdir, truename);
	if (f != NULL) return(f);

	/* try the file in the other directory with no extension */
	f = db_tryfile(pp, rarg, (char *)0, otherdir, truename);
	if (f != NULL) return(f);
	return(NULL);
}

/*
 * routine to try to find a file with name "pp", read with argument "rarg",
 * with extension "extension" (if it is nonzero) and in directory "otherdir"
 * (if it is nonzero).  Returns the file descriptor, and a pointer to the
 * actual file name in "truename"
 */
FILE *db_tryfile(char *pp, char *rarg, char *extension, char *otherdir, char **truename)
{
	REGISTER INTSML len;

	len = strlen(pp) + 1;
	if (extension != 0) len += strlen(extension) + 1;
	if (otherdir != 0) len += strlen(otherdir) + 1;
	if (len > db_tryfilenamelen)
	{
		if (db_tryfilenamelen != 0) efree(db_tryfilename);
		db_tryfilenamelen = 0;
		db_tryfilename = (char *)emalloc(len, db_cluster);
		if (db_tryfilename == 0) return(NULL);
		db_tryfilenamelen = len;
	}
	db_tryfilename[0] = 0;
	if (otherdir != 0)
	{
		(void)strcat(db_tryfilename, otherdir);
#ifdef ONUNIX
		(void)strcat(db_tryfilename, "/");
#endif
	}
	(void)strcat(db_tryfilename, pp);
	if (extension != 0)
	{
		(void)strcat(db_tryfilename, ".");
		(void)strcat(db_tryfilename, extension);
	}
	*truename = db_tryfilename;
	return(fopen(db_tryfilename, rarg));
}

/*
 * Routine to create the file "name" and return a stream pointer.  "filetype" is the type
 * of file being created (from the table in "global.h").  If "prompt" is zero, the path
 * is already fully set and the user should not be further querried.  Otherwise,
 * "prompt" is the string to use when describing this file (and the final name selected
 * by the user is returned in "truename").  The routine returns zero on error.  However,
 * to differentiate errors in file creation from user aborts, the "truename" is set to zero
 * if the user aborts the command.
 */
FILE *xcreate(char *name, INTSML filetype, char *prompt, char **truename)
{
	REGISTER FILE *f;
	REGISTER char *pt, *next;
	char warg[3];
	static char truenamelocal[256];

	pt = truepath(name);
	if (prompt != 0 && (us_aid->aidstate&USEDIALOGS) != 0)
	{
		next = (char *)fileselect(prompt, (INTSML)(filetype | FILETYPEWRITE), pt);
		if (next == 0 || *next == 0)
		{
			if (truename != 0) *truename = 0;
			return(NULL);
		}
		setdefaultcursortype(3);		/* the hourglass cursor */
		pt = next;
	}
	if (truename != 0)
	{
		(void)strcpy(truenamelocal, pt);
		*truename = truenamelocal;
	}

	/* determine argument to "fopen" */
	strcpy(warg, "w");
	if ((filetype&FILETYPE) == FILETYPELOG || (filetype&FILETYPE) == FILETYPEBLIB ||
		(filetype&FILETYPE) == FILETYPEGDS) strcat(warg, "b");
	f = fopen(pt, warg);
#ifdef	MACOS
	if (f != NULL)
	{
		void mac_settypecreator(char*, INTBIG, INTBIG);
		switch (filetype)
		{
			case FILETYPEBLIB:
				mac_settypecreator(pt, 'Elec', 'Elec');
				break;
			case FILETYPEGDS:
				mac_settypecreator(pt, '\?\?\?\?', '\?\?\?\?');
				break;
			case FILETYPELOG:
				mac_settypecreator(pt, 'LOG ', 'Elec');
				break;
			default:
				mac_settypecreator(pt, 'TEXT', 'ttxt');
				break;
		}
	}
#endif
	return(f);
}

/*
 * Routine to append to the file "name" and return a stream pointer
 */
FILE *xappend(char *name)
{
	return(fopen(truepath(name), "a"));
}

/*
 * Routine to close stream "f"
 */
void xclose(FILE *f)
{
	(void)fclose(f);
}

/*
 * Routine to flush stream "f"
 */
void xflushbuf(FILE *f)
{
	(void)fflush(f);
}

/*
 * Routine to report the EOF condition on stream "f"
 */
INTSML xeof(FILE *f)
{
	return(feof(f));
}

/*
 * Routine to seek to position "pos", nature "nat", on stream "f"
 */
void xseek(FILE *f, INTBIG pos, INTSML nat)
{
	fseek(f, pos, nat);
}

/*
 * Routine to return the current position in stream "f"
 */
INTBIG xtell(FILE *f)
{
	return(ftell(f));
}

/*
 * Routine to write the formatted message "s" with parameters "p1" through "p9"
 * to stream "f".
 */
void xprintf(FILE *f, char *s, ...)
{
	va_list ap;

	var_start(ap, s);
	(void)vfprintf(f, s, ap);
	va_end(ap);
}

/*
 * Routine to get the next character in stream "f"
 */
INTSML xgetc(FILE *f)
{
	return(getc(f));
}

/*
 * Routine to "unget" the character "c" back to stream "f"
 */
void xungetc(char c, FILE *f)
{
	(void)ungetc(c, f);
}

/*
 * Routine to put the character "c" into stream "f"
 */
void xputc(char c, FILE *f)
{
	putc(c, f);
}

/*
 * Routine to put the string "s" into stream "f"
 */
INTSML xputs(char *s, FILE *f)
{
	return(fputs(s, f));
}

/*
 * Routine to read "count" elements of size "size" from stream "f" into the buffer
 * at "buf".  Returns the number of bytes read.
 */
INTBIG xfread(char *buf, INTBIG size, INTBIG count, FILE *f)
{
	return(fread(buf, size, count, f));
}

/*
 * Routine to write "count" elements of size "size" to stream "f" from the buffer
 * at "buf".  Returns the number of bytes written.
 */
INTBIG xfwrite(char *buf, INTBIG size, INTBIG count, FILE *f)
{
	return(fwrite(buf, size, count, f));
}

/*
 * routine to read a line of text from a file.  The file is in stream "file"
 * and the text is placed in the array "line" which is only "limit" characters
 * long.  The routine returns zero if sucessful, nonzero if end-of-file is
 * reached.
 */
INTSML xfgets(char *line, INTSML limit, FILE *file)
{
	REGISTER char *pp;
	REGISTER INTSML c, total;

	pp = line;
	total = 1;
	for(;;)
	{
		c = xgetc(file);
		if (c == EOF)
		{
			if (pp == line) return(1);
			break;
		}
		*pp = (char)c;
		if (*pp == '\n' || *pp == '\r') break;
		pp++;
		if ((++total) >= limit) break;
	}
	return(*pp = 0);
}

/******************** SUBROUTINES FOR ENCRYPTION ****************/

/*
 * A one-rotor machine designed along the lines of Enigma but considerably trivialized
 */
# define ROTORSZ 256		/* a power of two */
# define MASK    (ROTORSZ-1)
void myencrypt(char *text, char *key)
{
	INTSML ic, i, k, temp, n1, n2, nr1, nr2;
	UINTSML random;
	INTBIG seed;
	char *pt, t1[ROTORSZ], t2[ROTORSZ], t3[ROTORSZ], deck[ROTORSZ];
	static char readable[ROTORSZ] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-";

	/* first setup the machine */
	seed = 123;
	for (i=0; i<13; i++) seed = seed*key[i] + i;
	for(i=0; i<ROTORSZ; i++)
	{
		t1[i] = (char)i;
		t3[i] = 0;
		deck[i] = (char)i;
	}
	for(i=0; i<ROTORSZ; i++)
	{
		seed = 5*seed + key[i%13];
		random = seed % 65521;
		k = ROTORSZ-1 - i;
		ic = (random&MASK) % (k+1);
		random >>= 8;
		temp = t1[k];
		t1[k] = t1[ic];
		t1[ic] = (char)temp;
		if (t3[k] != 0) continue;
		ic = (random&MASK) % k;
		while (t3[ic] != 0) ic = (ic+1) % k;
		t3[k] = (char)ic;
		t3[ic] = (char)k;
	}
	for(i=0; i<ROTORSZ; i++) t2[t1[i]&MASK] = (char)i;

	/* now run the machine */
	n1 = 0;
	n2 = 0;
	nr2 = 0;
	for(pt = text; *pt; pt++)
	{
		nr1 = deck[n1]&MASK;
		nr2 = deck[nr1]&MASK;
		i = t2[(t3[(t1[(*pt+nr1)&MASK]+nr2)&MASK]-nr2)&MASK]-nr1;
		*pt = readable[i&63];
		n1++;
		if (n1 == ROTORSZ)
		{
			n1 = 0;
			n2++;
			if (n2 == ROTORSZ) n2 = 0;
			db_shuffle(deck, key);
		}
	}
}

void db_shuffle(char *deck, char *key)
{
	INTSML i, ic, k, temp;
	UINTSML random;
	static INTBIG seed = 123;

	for(i=0; i<ROTORSZ; i++)
	{
		seed = 5*seed + key[i%13];
		random = seed % 65521;
		k = ROTORSZ-1 - i;
		ic = (random&MASK) % (k+1);
		temp = deck[k];
		deck[k] = deck[ic];
		deck[ic] = (char)temp;
	}
}
