/* -*- tab-width: 4 -*-
 *
 * 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 "edialogs.h"
#include "usr.h"
#include "tecgen.h"

#define PUREUNITGIGA    0		/* giga:  x 1000000000 */
#define PUREUNITMEGA    1		/* mega:  x 1000000 */
#define PUREUNITKILO    2		/* kilo:  x 1000 */
#define PUREUNITNONE    3		/* -:     x 1 */
#define PUREUNITMILLI   4		/* milli: / 1000 */
#define PUREUNITMICRO   5		/* micro: / 1000000 */
#define PUREUNITNANO    6		/* nano:  / 1000000000 */
#define PUREUNITPICO    7		/* pico:  / 1000000000000 */
#define PUREUNITFEMTO   8		/* femto: / 1000000000000000 */

#define NUMDESCRIPTIONBUFS 4

static char  *db_keywordbuffer;
static INTBIG db_keywordbufferlength = 0;

/* working memory for "db_tryfile()" */
static char  *db_tryfilename;
static INTBIG db_tryfilenamelen = 0;

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

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

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

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

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

/* working memory for types of files */
typedef struct
{
	char   *extension;
	char   *winfilter;
	INTBIG  mactype;
	BOOLEAN binary;
	char   *shortname;
	char   *longname;
} FILETYPES;
static INTBIG     db_filetypecount = 0;
static FILETYPES *db_filetypeinfo;

/* the infinite string package */
#define INFSTRCOUNT     8				/* number of infinite strings */
#define INFSTRDEFAULT 200				/* default infinite string length */
#define NOINFSTR ((INFSTR *)-1)
typedef struct Iinfstr
{
	char  *infstr;						/* the string address */
	INTBIG infstrlength;				/* the length of the string */
	INTBIG infstrptr;					/* the location of the string end */
	struct Iinfstr *nextinfstr;			/* next in linked list */
} INFSTR;
static BOOLEAN db_firstinf = FALSE;		/* initialization flag */
static INFSTR *db_firstfreeinfstr;		/* first free infinite string block */
static INFSTR *db_lastfreeinfstr;		/* last free infinite string block */
static void   *db_infstrmutex = 0;		/* mutex for infinite string modules */

/* prototypes for local routines */
static BOOLEAN db_beginsearch(char**);
static void    db_makestringvar(INTBIG, INTBIG, INTBIG, INTBIG, void*);
static void    db_addstring(char*, void*);
static FILE   *db_tryfile(char*, char*, char*, char*, char**);
static void    db_shuffle(char*, char*);
static INTBIG  db_insensitivechar(INTBIG c);
static char   *db_makeunits(float value, INTBIG units);
static INTBIG  db_getpureunit(INTBIG unittype, INTBIG unitscale);

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

	if (db_pluralbufsize > 0) efree(db_pluralbuffer);
	if (db_keywordbufferlength != 0) efree(db_keywordbuffer);
	if (db_tryfilenamelen != 0) efree(db_tryfilename);

	while (db_firstfreeinfstr != NOINFSTR)
	{
		inf = db_firstfreeinfstr;
		db_firstfreeinfstr = inf->nextinfstr;
		efree((char *)inf->infstr);
		efree((char *)inf);
	}

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

	/* free file type memory */
	for(i=0; i<db_filetypecount; i++)
	{
		efree((char *)db_filetypeinfo[i].extension);
		efree((char *)db_filetypeinfo[i].winfilter);
		efree((char *)db_filetypeinfo[i].shortname);
		efree((char *)db_filetypeinfo[i].longname);
	}
	if (db_filetypecount > 0) efree((char *)db_filetypeinfo);
}

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

/*
 * routine to parse a lambda value of the form "nn.dd[u | " | cm | mil | mm | cu | nm]"
 * 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, INTBIG lambda)
{
	REGISTER INTBIG hipart, lonum, loden, retval, units;
	REGISTER INTBIG neg;
	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] == 'n' && ptr[1] == 'm') units = (units & ~DISPLAYUNITS) | DISPUNITMMIC;

	/* convert to database units */
	if (lambda == 0) lambda = el_curlib->lambda[el_curtech->techindex];
	scale = db_getcurrentscale(el_units&INTERNALUNITS, units, lambda);
	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 INTBIG 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 INTBIG 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 convert a HUGE integer (64 bits) to a string.
 * This routine does the work by hand, but it can be done with
 * special "printf" format conversions, of which these are known:
 *    Windows: "%I64d"
 *    Sun:     "%PRIx64"
 *    Linux:   "%lld"
 */
char *hugeinttoa(INTHUGE a)
{
	static char ret[NUMDESCRIPTIONBUFS][40];
	static INTBIG which = 0;
	REGISTER char *curbuf, digit;
	REGISTER INTBIG i, neg;

	/* code cannot be called by multiple procesors: uses globals */
	NOT_REENTRANT;

	curbuf = ret[which++];
	if (which >= NUMDESCRIPTIONBUFS) which = 0;

	if (a >= 0) neg = 0; else
	{
		neg = 1;
		a = -a;
	}

	curbuf[i=39] = 0;
	while (i > 0)
	{
		digit = (char)(a % 10);
		curbuf[--i] = '0' + digit;
		a /= 10;
		if (a == 0) break;
	}
	if (neg != 0)
		curbuf[--i] = '-';
	return(&curbuf[i]);
}

char *explainduration(float duration)
{
	static char elapsed[200];
	char temp[50];
	INTBIG hours, minutes;

	/* code cannot be called by multiple procesors: uses globals */
	NOT_REENTRANT;

	elapsed[0] = 0;
	if (duration >= 3600.0)
	{
		hours = (INTBIG)(duration / 3600.0f);
		duration -= (float)(hours * 3600);
		sprintf(temp, "%ld hours, ", hours);
		strcat(elapsed, temp);
	}
	if (duration >= 60.0)
	{
		minutes = (INTBIG)(duration / 60.0f);
		duration -= (float)(minutes * 60);
		sprintf(temp, "%ld minutes, ", minutes);
		strcat(elapsed, temp);
	}
	sprintf(temp, "%g seconds", duration);
	strcat(elapsed, temp);
	return(elapsed);
}

/*
 * Routine to get the pure unit to use for electrical unit type "unittype", scale
 * "unitscale".
 */
INTBIG db_getpureunit(INTBIG unittype, INTBIG unitscale)
{
	switch (unittype)
	{
		case VTUNITSRES:		/* resistance */
			switch (unitscale)
			{
				case INTRESUNITKOHM: return(PUREUNITKILO);
				case INTRESUNITMOHM: return(PUREUNITMEGA);
				case INTRESUNITGOHM: return(PUREUNITGIGA);
			}
			break;

		case VTUNITSCAP:		/* capacitance */
			switch (unitscale)
			{
				case INTCAPUNITMFARAD: return(PUREUNITMILLI);
				case INTCAPUNITUFARAD: return(PUREUNITMICRO);
				case INTCAPUNITNFARAD: return(PUREUNITNANO);
				case INTCAPUNITPFARAD: return(PUREUNITPICO);
				case INTCAPUNITFFARAD: return(PUREUNITFEMTO);
			}
			break;

		case VTUNITSIND:		/* inductance */
			switch (unitscale)
			{
				case INTINDUNITMHENRY: return(PUREUNITMILLI);
				case INTINDUNITUHENRY: return(PUREUNITMICRO);
				case INTINDUNITNHENRY: return(PUREUNITNANO);
			}
			break;

		case VTUNITSCUR:		/* current */
			switch (unitscale)
			{
				case INTCURUNITMAMP: return(PUREUNITMILLI);
				case INTCURUNITUAMP: return(PUREUNITMICRO);
			}
			break;

		case VTUNITSVOLT:		/* voltage */
			switch (unitscale)
			{
				case INTVOLTUNITKVOLT: return(PUREUNITKILO);
				case INTVOLTUNITMVOLT: return(PUREUNITMILLI);
				case INTVOLTUNITUVOLT: return(PUREUNITMICRO);
			}
			break;

		case VTUNITSTIME:		/* time */
			switch (unitscale)
			{
				case INTTIMEUNITMSEC: return(PUREUNITMILLI);
				case INTTIMEUNITUSEC: return(PUREUNITMICRO);
				case INTTIMEUNITNSEC: return(PUREUNITNANO);
				case INTTIMEUNITPSEC: return(PUREUNITPICO);
				case INTTIMEUNITFSEC: return(PUREUNITFEMTO);
			}
			break;
	}
	return(PUREUNITNONE);
}

/*
 * Routine to express "value" as a string in "unittype" electrical units.
 * The scale of the units is in "unitscale".
 */
char *displayedunits(float value, INTBIG unittype, INTBIG unitscale)
{
	static char line[100];
	REGISTER INTBIG pureunit;
	char *postfix;

	/* code cannot be called by multiple procesors: uses globals */
	NOT_REENTRANT;

	pureunit = db_getpureunit(unittype, unitscale);
	switch (pureunit)
	{
		case PUREUNITGIGA:		/* giga:  x 1000000000 */
			value /= 1000000000.0f;
			postfix = "g";
			break;
		case PUREUNITMEGA:		/* mega:  x 1000000 */
			value /= 1000000.0f;
			postfix = "meg";		/* SPICE wants "x" */
			break;
		case PUREUNITKILO:		/* kilo:  x 1000 */
			value /= 1000.0f;
			postfix = "k";
			break;
		case PUREUNITNONE:		/* -:     x 1 */
			postfix = "";
			break;
		case PUREUNITMILLI:		/* milli: / 1000 */
			value *= 1000.0f;
			postfix = "m";
			break;
		case PUREUNITMICRO:		/* micro: / 1000000 */
			value *= 1000000.0f;
			postfix = "u";
			break;
		case PUREUNITNANO:		/* nano:  / 1000000000 */
			value *= 1000000000.0f;
			postfix = "n";
			break;
		case PUREUNITPICO:		/* pico:  / 1000000000000 */
			value *= 1000000000000.0f;
			postfix = "p";
			break;
		case PUREUNITFEMTO:		/* femto: / 1000000000000000 */
			value *= 1000000000000000.0f;
			postfix = "f";
			break;
	}
	sprintf(line, "%g%s", value, postfix);
	return(line);
}

/*
 * Routine to convert the string "str" to electrical units of type "unittype",
 * presuming that the scale is in "unitscale".
 */
float figureunits(char *str, INTBIG unittype, INTBIG unitscale)
{
	float value;
	REGISTER INTBIG len, pureunit;

	len = strlen(str);
	value = (float)atof(str);
	pureunit = db_getpureunit(unittype, unitscale);
	if (len > 1)
	{
		if (namesame(&str[len-1], "g") == 0) pureunit = PUREUNITGIGA;
		if (namesame(&str[len-1], "k") == 0) pureunit = PUREUNITKILO;
		if (namesame(&str[len-1], "m") == 0) pureunit = PUREUNITMILLI;
		if (namesame(&str[len-1], "u") == 0) pureunit = PUREUNITMICRO;
		if (namesame(&str[len-1], "n") == 0) pureunit = PUREUNITNANO;
		if (namesame(&str[len-1], "p") == 0) pureunit = PUREUNITPICO;
		if (namesame(&str[len-1], "f") == 0) pureunit = PUREUNITFEMTO;
	}
	if (len > 3)
	{
		if (namesame(&str[len-3], "meg") == 0) pureunit = PUREUNITMEGA;
	}
	switch (pureunit)
	{
		case PUREUNITGIGA:		/* giga:  x 1000000000 */
			value *= 1000000000.0f;
			break;
		case PUREUNITMEGA:		/* mega:  x 1000000 */
			value *= 1000000.0f;
			break;
		case PUREUNITKILO:		/* kilo:  x 1000 */
			value *= 1000.0f;
			break;
		case PUREUNITMILLI:		/* milli: / 1000 */
			value /= 1000.0f;
			break;
		case PUREUNITMICRO:		/* micro: / 1000000 */
			value /= 1000000.0f;
			break;
		case PUREUNITNANO:		/* nano:  / 1000000000 */
			value /= 1000000000.0f;
			break;
		case PUREUNITPICO:		/* pico:  / 1000000000000 */
			value /= 1000000000000.0f;
			break;
		case PUREUNITFEMTO:		/* femto: / 1000000000000000 */
			value /= 1000000000000000.0f;
			break;
	}
	return(value);
}

/*
 * Routine to parse the version of Electric in "version" into three fields:
 * the major version number, minor version, and a detail version number.
 * The detail version number can be letters.  If it is omitted, it is
 * assumed to be 999.  If it is a number, it is beyond 1000.  For example:
 *    "6.02a"     major=6, minor=2, detail=1       (a Prerelease)
 *    "6.02z"     major=6, minor=2, detail=26      (a Prerelease)
 *    "6.02aa"    major=6, minor=2, detail=27      (a Prerelease)
 *    "6.02az"    major=6, minor=2, detail=52      (a Prerelease)
 *    "6.02ba"    major=6, minor=2, detail=53      (a Prerelease)
 *    "6.02"      major=6, minor=2, detail=999     (a Release)
 *    "6.02.1"    major=6, minor=2, detail=1001    (a PostRelease, update)
 */
void parseelectricversion(char *version, INTBIG *major, INTBIG *minor, INTBIG *detail)
{
	REGISTER char *pt;

	/* parse the version fields */
	pt = version;
	*major = atoi(pt);
	while (isdigit(*pt) != 0) pt++;
	if (*pt++ != '.') { *minor = *detail = 0;   return; }
	*minor = atoi(pt);
	while (isdigit(*pt) != 0) pt++;
	if (*pt == 0) { *detail = 999;   return; }
	if (*pt == '.')
	{
		*detail = atoi(&pt[1]) + 1000;
	} else
	{
		*detail = 0;
		while (isalpha(*pt))
		{
			*detail = (*detail * 26) + tolower(*pt) - 'a' + 1;
			pt++;
		}
	}
#if 0		/* for debugging the version number */
	ttyputmsg("Version '%s' is numbered %ld.%ld.%ld", version, *major, *minor, *detail);
#endif
}

/*
 * 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 INTBIG wantversion, save, saidtech, saidlib;
	REGISTER LIBRARY *lib, *l;
	REGISTER VIEW *wantview, *v;
	REGISTER char *pt, *line;
	REGISTER void *infstr;

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

	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;
#ifdef HASHCELLNAMES
	c = db_findcellname(line, lib);
#else
	for(c = lib->firstcell; c != NOCELL; c = c->nextcell)
		if (namesame(line, c->cellname) == 0) break;
#endif
	*pt = (char)save;
	if (c == NOCELL) return(NONODEPROTO);

	wantversion = -1;
	wantview = el_unknownview;
	if (save == ';')
	{
		wantversion = 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->prevversion; onp != NONODEPROTO; onp = onp->prevversion)
					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->prevversion; np != NONODEPROTO; np = np->prevversion)
		if (np->version == wantversion) return(np);
	return(NONODEPROTO);
}

/*
 * routine to find cell "cellname".  Returns NOCELL if it cannot be found
 */
CELL *getcell(char *cellname)
{
	REGISTER CELL *cell;
#ifdef HASHCELLNAMES
	cell = db_findcellname(cellname, el_curlib);
	return(cell);
#else
	static COMCOMP db_cellp = {NOKEYWORD, topofcells, nextcells, NOPARAMS,
		0, " \t", M_("cell"), 0};
	REGISTER INTBIG i, j;

	i = parse(cellname, &db_cellp, FALSE);
	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);
#endif
}

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

	i = parse(techname, &db_technologyp, FALSE);
	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,
	0, " \t", M_("view"), 0};
VIEW *getview(char *viewname)
{
	REGISTER INTBIG i, j;
	REGISTER VIEW *v;

	i = parse(viewname, &db_viewp, FALSE);
	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 INTBIG i, 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;
		}
	}

	/* see if a global name is specified */
	pt = _("Global");
	k = strlen(pt);
	if (namesamen(netname, pt, k) == 0 && netname[k] == '-')
	{
		for(i=0; i<facet->globalnetcount; i++)
			if (namesame(&netname[k+1], facet->globalnetnames[i]) == 0)
				return(facet->globalnetworks[i]);
	}
	return(NONETWORK);
}

/*
 * routine to find network "netname" in facet "facet".  Returns NONETWORK
 * if it cannot be found.  This routine allows for variations of the network
 * name that may occur due to simulation, VHDL compilation, etc.
 */
NETWORK *getcomplexnetwork(char *name, NODEPROTO *np)
{
	REGISTER INTBIG len, i, l, k, c1, c2, addr;
	REGISTER NETWORK *net;
	REGISTER char *pt;

	/* try the direct approach */
	net = getnetwork(name, np);
	if (net != NONETWORK) return(net);

	if (name[0] == 'N' && name[1] == 'E' && name[2] == 'T')
	{
		addr = atoi(&name[3]);
		for(net = np->firstnetwork; net != NONETWORK; net = net->nextnetwork)
			if (net == (NETWORK *)addr) return(net);
	}

	/* if the name ends with "NV", try removing that */
	len = strlen(name);
	if (name[len-2] == 'N' && name[len-1] == 'V')
	{
		name[len-2] = 0;
		net = getnetwork(name, np);
		name[len-2] = 'N';
		if (net != NONETWORK) return(net);
	}

	/* check for prefix "v(" and "l(" HSPICE prefixes (see also "simwindow.c:sim_window_findtrace()")*/
	if ((name[0] == 'l' || name[0] == 'v') && name[1] == '(')
	{
		net = getnetwork(&name[2], np);
		if (net != NONETWORK) return(net);
	}

	/* if there are underscores, see if they match original names */
	for(pt = name; *pt != 0; pt++) if (*pt == '_') break;
	if (*pt != 0)
	{
		len = strlen(name);
		for(net = np->firstnetwork; net != NONETWORK; net = net->nextnetwork)
		{
			pt = net->netname;
			for(k=0; k<net->namecount; k++)
			{
				l = strlen(pt);
				if (l == len)
				{
					for(i=0; i<len; i++)
					{
						c1 = tolower(name[i]);
						c2 = tolower(pt[i]);
						if (c1 == c2) continue;
						if (c1 == '_' && !isalnum(c2)) continue;
						break;
					}
					if (i >= len) 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;
	REGISTER void *infstr;

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

	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;
	REGISTER INTBIG i, j;

	if (facet->portprotohashtablesize > 0)
	{
		i = db_namehash(portname) % facet->portprotohashtablesize;
		for(j=1; j<=facet->portprotohashtablesize; j += 2)
		{
			pp = facet->portprotohashtable[i];
			if (pp == NOPORTPROTO) break;
			if (namesame(portname, pp->protoname) == 0) return(pp);
			i += j;
			if (i >= facet->portprotohashtablesize) i -= facet->portprotohashtablesize;
		}
	} else
	{
		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 tool "toolname".  Returns NOTOOL if it cannot be found
 */
TOOL *gettool(char *toolname)
{
	REGISTER INTBIG i;

	for(i=0; i<el_maxtools; i++)
		if (namesame(toolname, el_tools[i].toolname) == 0) return(&el_tools[i]);
	return(NOTOOL);
}

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

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

	truecolorname = _(colorname);
	for(i=0; db_colors[i].name != 0; i++)
		if (namesame(truecolorname, _(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 true if the color is unknown.
 */
BOOLEAN ecolorname(INTBIG color, char **colorname, char **colorsymbol)
{
	REGISTER INTBIG 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(FALSE);
	}
	return(TRUE);
}

/*
 * 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.
 */
INTBIG parse(char *keyword, COMCOMP *list, BOOLEAN noise)
{
	REGISTER INTBIG i, j;
	BOOLEAN (*toplist)(char**);
	INTBIG w, bst;
	REGISTER char *pp, *(*nextinlist)(void);
	REGISTER void *infstr;

	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)
	{
		infstr = initinfstr();
		(void)(*toplist)(&keyword);
		for( ; (pp = (*nextinlist)()) != 0; )
		{
			if ((j = stringmatch(pp, keyword)) < 0) continue;
			if (j < i) continue;
			addtoinfstr(infstr, ' ');
			addstringtoinfstr(infstr, pp);
		}
		ttyputerr(_("%s ambiguous:%s"), keyword, returninfstr(infstr));
	}
	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.
 */
INTBIG stringmatch(char *keyword, char *input)
{
	REGISTER INTBIG 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 *************************/

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

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

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

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

BOOLEAN db_beginsearch(char **a)
{
	INTBIG 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(TRUE);
}

char *nextfile(void)
{
	char *pt;
	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)
		{
			strcpy(testfile, pt);
			strcat(testfile, DIRSEPSTR);
			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;
BOOLEAN topoftechs(char **c) { db_postechcomcomp = el_technologies; return(TRUE); }
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;
BOOLEAN topofcells(char **c) { db_poscellcomcomp = el_curlib->firstcell; return(TRUE); }
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;
BOOLEAN topofviews(char **c) { db_posviewcomcomp = el_views; return(TRUE); }
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;
BOOLEAN topoflibs(char **c)
{
	db_poslibcomcomp = el_curlib;
	return(TRUE);
}
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 tool names
 */
static INTBIG db_poscomcomp;
BOOLEAN topoftools(char **c) { db_poscomcomp = 0; return(TRUE); }
char *nexttools(void)
{
	if (db_poscomcomp >= el_maxtools) return(0);
	return(el_tools[db_poscomcomp++].toolname);
}

/*
 * routines to do command completion on facet names
 */
static NODEPROTO *db_posnodeprotos;
BOOLEAN 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(TRUE);
}
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;
BOOLEAN 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(TRUE);
}
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 INTBIG db_posinnet;
static char *db_posinname;

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

	db_posnets = NONETWORK;
	np = getcurfacet();
	if (np == NONODEPROTO) return(FALSE);
	db_posnets = np->firstnetwork;
	if (db_posnets == NONETWORK) return(FALSE);
	db_posinnet = 0;
	db_posinname = db_posnets->netname;
	return(TRUE);
}
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 full name of node "ni", including its local name.
 * Technology considerations are ignored.
 */
char *ntdescribenodeinst(NODEINST *ni)
{
	REGISTER TECHNOLOGY *curtech;
	REGISTER char *ret;

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

	if (ni->proto->primindex == 0) return(describenodeinst(ni));
	curtech = el_curtech;
	el_curtech = ni->proto->tech;
	ret = describenodeinst(ni);
	el_curtech = curtech;
	return(ret);
}

/* routine to return the name of nodeinst "ni" */
char *describenodeinst(NODEINST *ni)
{
	REGISTER char *name, *protoname;
	REGISTER VARIABLE *var;
	REGISTER INTBIG 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_key);
	if (var == NOVARIABLE) return(protoname);

	/* code cannot be called by multiple procesors: uses globals */
	NOT_REENTRANT;

	/* get output buffer */
	db_descninodeswitch++;
	if (db_descninodeswitch >= NUMDESCRIPTIONBUFS) 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 INTBIG len;

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

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

	/* code cannot be called by multiple procesors: uses globals */
	NOT_REENTRANT;

	/* get output buffer */
	db_descaiarcswitch++;
	if (db_descaiarcswitch >= NUMDESCRIPTIONBUFS) 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);

	/* code cannot be called by multiple procesors: uses globals */
	NOT_REENTRANT;

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

	/* code cannot be called by multiple procesors: uses globals */
	NOT_REENTRANT;

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

	if (np->primindex != 0)
	{
		len = strlen(np->primname) + strlen(np->tech->techname) + 2;
	} else
	{
		/* compute size of buffer */
		if (np->cell->cellname == 0) return("***BOGUS***");
		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, ";%ld", 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 INTBIG len;

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

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

	/* code cannot be called by multiple procesors: uses globals */
	NOT_REENTRANT;

	/* get output buffer */
	db_descaparcswitch++;
	if (db_descaparcswitch >= NUMDESCRIPTIONBUFS) 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->entryisnode) return(describenodeinst(geom->entryaddr.ni));
	return(describearcinst(geom->entryaddr.ai));
}

/*
 * routine to convert network "net" into a string
 */
char *describenetwork(NETWORK *net)
{
	REGISTER char *pt;
	REGISTER NODEPROTO *np;
	static char gennetname[50];
	REGISTER INTBIG i;
	REGISTER void *infstr;

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

	if (net->globalnet >= 0)
	{
		infstr = initinfstr();
		addstringtoinfstr(infstr, _("Global"));
		addtoinfstr(infstr, '-');
		if (net->globalnet == 0) addstringtoinfstr(infstr, _("Power")); else
		if (net->globalnet == 1) addstringtoinfstr(infstr, _("Ground")); else
		{
			np = net->parent;
			if (net->globalnet >= np->globalnetcount)
				addstringtoinfstr(infstr, _("UNKNOWN")); else
					addstringtoinfstr(infstr, np->globalnetnames[net->globalnet]);
		}
		return(returninfstr(infstr));
	}
	if (net->namecount == 0)
	{
		sprintf(gennetname, _("UNNAMED%ld"), (INTBIG)net);
		return(gennetname);
	}
	if (net->namecount == 1) return(net->netname);

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

char *describeportbits(PORTPROTO *pp)
{
	switch (pp->userbits&STATEBITS)
	{
		case INPORT:     return(_("Input"));
		case OUTPORT:    return(_("Output"));
		case BIDIRPORT:  return(_("Bidirectional"));
		case PWRPORT:    return(_("Power"));
		case GNDPORT:    return(_("Ground"));
		case CLKPORT:    return(_("Clock"));
		case C1PORT:     return(_("Clock Phase 1"));
		case C2PORT:     return(_("Clock Phase 2"));
		case C3PORT:     return(_("Clock Phase 3"));
		case C4PORT:     return(_("Clock Phase 4"));
		case C5PORT:     return(_("Clock Phase 5"));
		case C6PORT:     return(_("Clock Phase 6"));
		case REFOUTPORT: return(_("Reference Output"));
		case REFINPORT:  return(_("Reference Input"));
	}
	return("Unknown");
}

/*
 * routine to name the variable at "addr" of type "type".  It is assumed
 * to be an object that can hold other variables
 */
char *describeobject(INTBIG addr, INTBIG type)
{
	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 LIBRARY *lib;
	REGISTER TECHNOLOGY *tech;
	REGISTER TOOL *tool;
	REGISTER RTNODE *rtn;
	REGISTER CELL *c;
	REGISTER VIEW *v;
	REGISTER WINDOWPART *win;
	REGISTER WINDOWFRAME *wf;
	REGISTER GRAPHICS *gra;
	REGISTER CONSTRAINT *con;
	REGISTER POLYGON *poly;
	REGISTER void *infstr;

	infstr = initinfstr();
	switch (type&VTYPE)
	{
		case VNODEINST:
			ni = (NODEINST *)addr;
			formatinfstr(infstr, "NodeInstance(%s)", describenodeinst(ni));
			break;
		case VNODEPROTO:
			np = (NODEPROTO *)addr;
			if (np->primindex == 0) formatinfstr(infstr, "Facet(%s)", describenodeproto(np)); else
				formatinfstr(infstr, "Primitive(%s)", describenodeproto(np));
			break;
		case VPORTARCINST:
			pi = (PORTARCINST *)addr;
			if (pi == NOPORTARCINST) addstringtoinfstr(infstr, "PortArcInstance(NULL)"); else
				formatinfstr(infstr, "PortArcInstance(%ld)", (INTBIG)pi);
			break;
		case VPORTEXPINST:
			pe = (PORTEXPINST *)addr;
			if (pe == NOPORTEXPINST) addstringtoinfstr(infstr, "PortExpInstance(NULL)"); else
				formatinfstr(infstr, "PortExpInstance(%ld)", (INTBIG)pe);
			break;
		case VPORTPROTO:
			pp = (PORTPROTO *)addr;
			if (pp == NOPORTPROTO) addstringtoinfstr(infstr, "PortPrototype(NULL)"); else
				formatinfstr(infstr, "PortPrototype(%s)", pp->protoname);
			break;
		case VARCINST:
			ai = (ARCINST *)addr;
			formatinfstr(infstr, "ArcInstance(%s)", describearcinst(ai));
			break;
		case VARCPROTO:
			ap = (ARCPROTO *)addr;
			formatinfstr(infstr, "ArcPrototype(%s)", describearcproto(ap));
			break;
		case VGEOM:
			geom = (GEOM *)addr;
			if (geom == NOGEOM) addstringtoinfstr(infstr, "Geom(NULL)"); else
				formatinfstr(infstr, "Geom(%ld)", (INTBIG)geom);
			break;
		case VLIBRARY:
			lib = (LIBRARY *)addr;
			if (lib == NOLIBRARY) addstringtoinfstr(infstr, "Library(NULL)"); else
				formatinfstr(infstr, "Library(%s)", lib->libname);
			break;
		case VTECHNOLOGY:
			tech = (TECHNOLOGY *)addr;
			if (tech == NOTECHNOLOGY) addstringtoinfstr(infstr, "Technology(NULL)"); else
				formatinfstr(infstr, "Technology(%s)", tech->techname);
			break;
		case VTOOL:
			tool = (TOOL *)addr;
			if (tool == NOTOOL) addstringtoinfstr(infstr, "Tool(NULL)"); else
				formatinfstr(infstr, "Tool(%s)", tool->toolname);
			break;
		case VRTNODE:
			rtn = (RTNODE *)addr;
			if (rtn == NORTNODE) addstringtoinfstr(infstr, "Rtnode(NULL)"); else
				formatinfstr(infstr, "Rtnode(%ld)", (INTBIG)rtn);
			break;
		case VCELL:
			c = (CELL *)addr;
			if (c == NOCELL) addstringtoinfstr(infstr, "Cell(NULL)"); else
				formatinfstr(infstr, "Cell(%s)", c->cellname);
			break;
		case VVIEW:
			v = (VIEW *)addr;
			if (v == NOVIEW) addstringtoinfstr(infstr, "View(NULL)"); else
				formatinfstr(infstr, "View(%s)", v->viewname);
			break;
		case VWINDOWPART:
			win = (WINDOWPART *)addr;
			if (win != NOWINDOWPART) formatinfstr(infstr, "WindowPart(%s)", win->location); else
				formatinfstr(infstr, "WindowPart(%ld)", (INTBIG)win);
			break;
		case VGRAPHICS:
			gra = (GRAPHICS *)addr;
			if (gra == NOGRAPHICS) addstringtoinfstr(infstr, "Graphics(NULL)"); else
				formatinfstr(infstr, "Graphics(%ld)", (INTBIG)gra);
			break;
		case VCONSTRAINT:
			con = (CONSTRAINT *)addr;
			if (con == NOCONSTRAINT) addstringtoinfstr(infstr, "Constraint(NULL)"); else
				formatinfstr(infstr, "Constraint(%s)", con->conname);
			break;
		case VWINDOWFRAME:
			wf = (WINDOWFRAME *)addr;
			formatinfstr(infstr, "WindowFrame(%ld)", (INTBIG)wf);
			break;
		case VPOLYGON:
			poly = (POLYGON *)addr;
			formatinfstr(infstr, "Polygon(%ld)", (INTBIG)poly);
			break;
		default:
			addstringtoinfstr(infstr, "UNKNOWN(?)");
			break;
	}
	return(returninfstr(infstr));
}

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

	/* code cannot be called by multiple procesors: uses globals */
	NOT_REENTRANT;

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

	/* determine scaling */
	if (lambda == 0) lambda = el_curlib->lambda[el_curtech->techindex];
	scale = db_getcurrentscale(el_units&INTERNALUNITS, el_units&DISPLAYUNITS, lambda);
	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, "nm");  break;
	}
	return(cur);
}

/*
 * routine to convert a fractional number to ascii
 */
#define FRTOANUMBUFS 10

char *frtoa(INTBIG i)
{
	static INTBIG latoaswitch = 0;
	static char output[FRTOANUMBUFS][30];
	REGISTER INTBIG fra;
	char temp[3];
	REGISTER char *pp, *cur, *start;

	/* code cannot be called by multiple procesors: uses globals */
	NOT_REENTRANT;

	/* get output buffer */
	start = cur = &output[latoaswitch++][0];
	if (latoaswitch >= FRTOANUMBUFS) 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 determine whether or not the string in "pp" is a number.
 * Returns true if it is.
 */
BOOLEAN isanumber(char *pp)
{
	INTBIG xflag, founddigits;

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

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

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

	return(FALSE);
}

/*
 * routine to convert relative or absolute font values to absolute, in the
 * window "w"
 */
INTBIG truefontsize(INTBIG font, WINDOWPART *w, TECHNOLOGY *tech)
{
	REGISTER INTBIG pixperlam, lambda, height;
	REGISTER LIBRARY *lib;

	/* keep special font codes */
	if (font == TXTEDITOR || font == TXTMENU) return(font);

	/* absolute font sizes are easy */
	if ((font&TXTPOINTS) != 0) return((font&TXTPOINTS) >> TXTPOINTSSH);

	/* detemine default, min, and max size of font */
	if (w->curnodeproto == NONODEPROTO) lib = el_curlib; else
		lib = w->curnodeproto->cell->lib;
	lambda = lib->lambda[tech->techindex];
	if ((font&TXTQLAMBDA) != 0)
	{
		height = TXTGETQLAMBDA(font);
		height = height * lambda / 4;
		pixperlam = applyyscale(w, height);
		return(pixperlam);
	}
	return(applyyscale(w, lambda));
}

/*
 * routine to set the default text descriptor into "td".  This text will be
 * placed on "geom".
 */
void defaulttextdescript(UINTBIG *descript, GEOM *geom)
{
	REGISTER VARIABLE *txtvar;
	REGISTER INTBIG dx, dy, goleft, goright, goup, godown;
	INTBIG *defdescript;
	REGISTER NODEINST *ni;
	REGISTER ARCINST *ai;
	static INTBIG user_default_text_style_key = 0, user_default_text_smart_style_key = 0;

	if (user_default_text_style_key == 0)
		user_default_text_style_key = makekey("USER_default_text_style");
	if (user_default_text_smart_style_key == 0)
		user_default_text_smart_style_key = makekey("USER_default_text_smart_style");

	txtvar = getvalkey((INTBIG)us_tool, VTOOL, VINTEGER|VISARRAY, user_default_text_style_key);
	if (txtvar == NOVARIABLE)
	{
		TDSETPOS(descript, VTPOSCENT);
	} else
	{
		defdescript = (INTBIG *)txtvar->addr;
		TDSETPOS(descript, TDGETPOS(defdescript));
		TDSETSIZE(descript, TDGETSIZE(defdescript));
		TDSETFACE(descript, TDGETFACE(defdescript));
		TDSETITALIC(descript, TDGETITALIC(defdescript));
		TDSETBOLD(descript, TDGETBOLD(defdescript));
		TDSETUNDERLINE(descript, TDGETUNDERLINE(defdescript));
	}
	if (geom != NOGEOM)
	{
		/* set text size */
		if (geom->entryisnode)
		{
			ni = geom->entryaddr.ni;
			if (ni->proto == gen_invispinprim)
			{
				defaulttextsize(2, descript);
			} else
			{
				defaulttextsize(3, descript);
			}
		} else
		{
			defaulttextsize(4, descript);
		}

		/* handle smart text placement relative to attached object */
		txtvar = getvalkey((INTBIG)us_tool, VTOOL, VINTEGER, user_default_text_smart_style_key);
		if (txtvar != NOVARIABLE)
		{
			/* figure out location of object relative to environment */
			dx = dy = 0;
			if (geom->entryisnode)
			{
				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 (TDGETPOS(descript))
				{
					case VTPOSCENT:
					case VTPOSRIGHT:
					case VTPOSLEFT:
						TDSETPOS(descript, VTPOSLEFT);
						break;
					case VTPOSUP:
					case VTPOSUPLEFT:
					case VTPOSUPRIGHT:
						TDSETPOS(descript, VTPOSUPLEFT);
						break;
					case VTPOSDOWN:
					case VTPOSDOWNLEFT:
					case VTPOSDOWNRIGHT:
						TDSETPOS(descript, VTPOSDOWNLEFT);
						break;
				}
			}
			if (goright != 0)
			{
				switch (TDGETPOS(descript))
				{
					case VTPOSCENT:
					case VTPOSRIGHT:
					case VTPOSLEFT:
						TDSETPOS(descript, VTPOSRIGHT);
						break;
					case VTPOSUP:
					case VTPOSUPLEFT:
					case VTPOSUPRIGHT:
						TDSETPOS(descript, VTPOSUPRIGHT);
						break;
					case VTPOSDOWN:
					case VTPOSDOWNLEFT:
					case VTPOSDOWNRIGHT:
						TDSETPOS(descript, VTPOSDOWNRIGHT);
						break;
				}
			}
			if (goup != 0)
			{
				switch (TDGETPOS(descript))
				{
					case VTPOSCENT:
					case VTPOSUP:
					case VTPOSDOWN:
						TDSETPOS(descript, VTPOSUP);
						break;
					case VTPOSRIGHT:
					case VTPOSUPRIGHT:
					case VTPOSDOWNRIGHT:
						TDSETPOS(descript, VTPOSUPRIGHT);
						break;
					case VTPOSLEFT:
					case VTPOSUPLEFT:
					case VTPOSDOWNLEFT:
						TDSETPOS(descript, VTPOSUPLEFT);
						break;
				}
			}
			if (godown != 0)
			{
				switch (TDGETPOS(descript))
				{
					case VTPOSCENT:
					case VTPOSUP:
					case VTPOSDOWN:
						TDSETPOS(descript, VTPOSDOWN);
						break;
					case VTPOSRIGHT:
					case VTPOSUPRIGHT:
					case VTPOSDOWNRIGHT:
						TDSETPOS(descript, VTPOSDOWNRIGHT);
						break;
					case VTPOSLEFT:
					case VTPOSUPLEFT:
					case VTPOSDOWNLEFT:
						TDSETPOS(descript, VTPOSDOWNLEFT);
						break;
				}
			}
		}
	}
}

#define DEFNODETEXTSIZE      TXTSETQLAMBDA(4)
#define DEFARCTEXTSIZE       TXTSETQLAMBDA(4)
#define DEFEXPORTSIZE        TXTSETQLAMBDA(8)
#define DEFNONLAYOUTTEXTSIZE TXTSETQLAMBDA(4)
#define DEFINSTTEXTSIZE      TXTSETQLAMBDA(16)
#define DEFFACETTEXTSIZE     TXTSETQLAMBDA(4)

/*
 * Routine to determine the size of text to use for a particular type of text:
 * 3:  node name
 * 4:  arc name
 * 1:  export label
 * 2:  nonlayout text (text on an invisible pin, facet variables)
 * 5:  facet instance name
 * 6:  facet text
 */
void defaulttextsize(INTBIG texttype, UINTBIG *descript)
{
	REGISTER VARIABLE *var;
	static INTBIG node_size_key = 0, arc_size_key = 0, export_size_key = 0,
		nonlayout_size_key = 0, instance_size_key = 0, facet_size_key = 0;

	if (node_size_key == 0)
		node_size_key = makekey("USER_default_node_text_size");
	if (arc_size_key == 0)
		arc_size_key = makekey("USER_default_arc_text_size");
	if (export_size_key == 0)
		export_size_key = makekey("USER_default_export_text_size");
	if (nonlayout_size_key == 0)
		nonlayout_size_key = makekey("USER_default_nonlayout_text_size");
	if (instance_size_key == 0)
		instance_size_key = makekey("USER_default_instance_text_size");
	if (facet_size_key == 0)
		facet_size_key = makekey("USER_default_facet_text_size");

	TDSETSIZE(descript, TXTSETQLAMBDA(4));
	switch (texttype)
	{
		case 3:
			var = getvalkey((INTBIG)us_tool, VTOOL, -1, node_size_key);
			if (var == NOVARIABLE) TDSETSIZE(descript, DEFNODETEXTSIZE); else
			{
				if ((var->type&VISARRAY) != 0) TDCOPY(descript, (UINTBIG *)var->addr); else
					TDSETSIZE(descript, var->addr);
			}
			break;
		case 4:
			var = getvalkey((INTBIG)us_tool, VTOOL, -1, arc_size_key);
			if (var == NOVARIABLE) TDSETSIZE(descript, DEFARCTEXTSIZE); else
			{
				if ((var->type&VISARRAY) != 0) TDCOPY(descript, (UINTBIG *)var->addr); else
					TDSETSIZE(descript, var->addr);
			}
			break;
		case 1:
			var = getvalkey((INTBIG)us_tool, VTOOL, -1, export_size_key);
			if (var == NOVARIABLE) TDSETSIZE(descript, DEFEXPORTSIZE); else
			{
				if ((var->type&VISARRAY) != 0) TDCOPY(descript, (UINTBIG *)var->addr); else
					TDSETSIZE(descript, var->addr);
			}				
			break;
		case 2:
			var = getvalkey((INTBIG)us_tool, VTOOL, -1, nonlayout_size_key);
			if (var == NOVARIABLE) TDSETSIZE(descript, DEFNONLAYOUTTEXTSIZE); else
			{
				if ((var->type&VISARRAY) != 0) TDCOPY(descript, (UINTBIG *)var->addr); else
					TDSETSIZE(descript, var->addr);
			}
			break;
		case 5:
			var = getvalkey((INTBIG)us_tool, VTOOL, -1, instance_size_key);
			if (var == NOVARIABLE) TDSETSIZE(descript, DEFINSTTEXTSIZE); else
			{
				if ((var->type&VISARRAY) != 0) TDCOPY(descript, (UINTBIG *)var->addr); else
					TDSETSIZE(descript, var->addr);
			}
			break;
		case 6:
			var = getvalkey((INTBIG)us_tool, VTOOL, -1, facet_size_key);
			if (var == NOVARIABLE) TDSETSIZE(descript, DEFFACETTEXTSIZE); else
			{
				if ((var->type&VISARRAY) != 0) TDCOPY(descript, (UINTBIG *)var->addr); else
					TDSETSIZE(descript, var->addr);
			}
			break;
	}
}

/*
 * Routine to get the X offset field of text descriptor words "td".  This routine
 * implements the macro "TDGETXOFF".
 */
INTBIG gettdxoffset(UINTBIG *td)
{
	REGISTER INTBIG offset, scale;

	offset = (td[0] & VTXOFF) >> VTXOFFSH;
	if ((td[0]&VTXOFFNEG) != 0) offset = -offset;
	scale = TDGETOFFSCALE(td) + 1;
	return(offset * scale);
}

/*
 * Routine to get the Y offset field of text descriptor words "td".  This routine
 * implements the macro "TDGETYOFF".
 */
INTBIG gettdyoffset(UINTBIG *td)
{
	REGISTER INTBIG offset, scale;

	offset = (td[0] & VTYOFF) >> VTYOFFSH;
	if ((td[0]&VTYOFFNEG) != 0) offset = -offset;
	scale = TDGETOFFSCALE(td) + 1;
	return(offset * scale);
}

/*
 * Routine to set the X offset field of text descriptor words "td" to "value".
 * This routine implements the macro "TDSETOFF".
 */
void settdoffset(UINTBIG *td, INTBIG x, INTBIG y)
{
	REGISTER INTBIG scale;

	td[0] = td[0] & ~(VTXOFF|VTYOFF|VTXOFFNEG|VTYOFFNEG);
	if (x < 0)
	{
		x = -x;
		td[0] |= VTXOFFNEG;
	}
	if (y < 0)
	{
		y = -y;
		td[0] |= VTYOFFNEG;
	}
	scale = maxi(x,y) >> VTOFFMASKWID;
	x /= (scale + 1);
	y /= (scale + 1);
	td[0] |= (x << VTXOFFSH);
	td[0] |= (y << VTYOFFSH);
	TDSETOFFSCALE(td, scale);
}

/*
 * Routine to validate the variable offset (x,y) and clip or grid it
 * to acceptable values.
 */
void propervaroffset(INTBIG *x, INTBIG *y)
{
	REGISTER INTBIG scale, realx, realy, negx, negy;

	negx = negy = 1;
	realx = *x;   realy = *y;
	if (realx < 0) { realx = -realx;   negx = -1; }
	if (realy < 0) { realy = -realy;   negy = -1; }
	if (realx > 077777) realx = 077777;
	if (realy > 077777) realy = 077777;
	scale = (maxi(realx, realy) >> VTOFFMASKWID) + 1;
	realx /= scale;
	realy /= scale;
	*x = realx * scale * negx;
	*y = realy * scale * negy;
}

/*
 * Routine to clear the text descriptor array in "t".
 * This routine implements the macro "TDCLEAR".
 */
void tdclear(UINTBIG *t)
{
	REGISTER INTBIG i;

	for(i=0; i<TEXTDESCRIPTSIZE; i++) t[i] = 0;
}

/*
 * Routine to copy the text descriptor array in "s" to "d".
 * This routine implements the macro "TDCOPY".
 */
void tdcopy(UINTBIG *d, UINTBIG *s)
{
	REGISTER INTBIG i;

	for(i=0; i<TEXTDESCRIPTSIZE; i++) d[i] = s[i];
}

/*
 * Routine to compare the text descriptor arrays in "t1" to "t2"
 * and return true if they are different.
 * This routine implements the macro "TDDIFF".
 */
BOOLEAN tddiff(UINTBIG *t1, UINTBIG *t2)
{
	REGISTER INTBIG i;

	for(i=0; i<TEXTDESCRIPTSIZE; i++)
		if (t1[i] != t2[i]) return(TRUE);
	return(FALSE);
}

/*
 * 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).  If the variable is displayable and the name
 * is to be displayed, that part is added.
 */
char *describedisplayedvariable(VARIABLE *var, INTBIG aindex, INTBIG purpose)
{
	REGISTER char *name, *parstr;
	REGISTER INTBIG len;
	REGISTER VARIABLE *parval;
	REGISTER void *infstr;

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

	infstr = initinfstr();

	/* add variable name if requested */
	if ((var->type&VDISPLAY) != 0)
	{
		if ((var->type&VISARRAY) == 0) len = 1; else
			len = getlength(var);
		name = truevariablename(var);
		switch (TDGETDISPPART(var->textdescript))
		{
			case VTDISPLAYNAMEVALUE:
				if (len > 1 && aindex >= 0)
				{
					formatinfstr(infstr, "%s[%ld]=", name, aindex);
				} else
				{
					formatinfstr(infstr, "%s=", name);
				}
				break;
			case VTDISPLAYNAMEVALINH:
				parval = getparentvalkey(var->key, 1);
				if (parval == NOVARIABLE) parstr = "?"; else
					parstr = describevariable(parval, -1, purpose);
				if (len > 1 && aindex >= 0)
				{
					formatinfstr(infstr, "%s[%ld]=%s;def=", name, aindex, parstr);
				} else
				{
					formatinfstr(infstr, "%s=%s;def=", name, parstr);
				}
				break;
			case VTDISPLAYNAMEVALINHALL:
				parval = getparentvalkey(var->key, 0);
				if (parval == NOVARIABLE) parstr = "?"; else
					parstr = describevariable(parval, -1, purpose);
				if (len > 1 && aindex >= 0)
				{
					formatinfstr(infstr, "%s[%ld]=%s;def=", name, aindex, parstr);
				} else
				{
					formatinfstr(infstr, "%s=%s;def=", name, parstr);
				}
				break;
		}
	}
	addstringtoinfstr(infstr, describevariable(var, aindex, purpose));
	return(returninfstr(infstr));
}

/*
 * Routine to return the true name of variable "var".  Leading "ATTR_" or "ATTRP_"
 * markers are removed.
 */
char *truevariablename(VARIABLE *var)
{
	REGISTER char *name;
	REGISTER INTBIG i, len;

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

/*
 * 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, INTBIG aindex, INTBIG purpose)
{
	REGISTER INTBIG i, len, *addr, units;
	REGISTER void *infstr;

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

	infstr = initinfstr();

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

			/* 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)
						db_makestringvar(addr[aindex+1], addr[aindex], purpose, units, infstr);
				} else
				{
					/* normal array indexing */
					if (aindex < len)
						switch ((var->type&VTYPE))
					{
						case VCHAR:
							db_makestringvar(var->type,
								((INTBIG)((char *)addr)[aindex]), purpose, units, infstr);
							break;

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

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

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

					/* 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)
							db_makestringvar(addr[i+1], addr[i], purpose, units, infstr);
						i++;
					} else
					{
						/* normal array indexing */
						switch ((var->type&VTYPE))
						{
							case VCHAR:
								db_makestringvar(var->type,
									((INTBIG)((char *)addr)[i]), purpose, units, infstr);
								break;

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

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

							default:
								db_makestringvar(var->type, addr[i], purpose, units, infstr);
								break;
						}
					}
				}
				addtoinfstr(infstr, ']');
			}
		} else db_makestringvar(var->type, var->addr, purpose, units, infstr);
	}
	return(returninfstr(infstr));
}

/*
 * 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 true
 * if there is a memory allocation error.
 */
void db_makestringvar(INTBIG type, INTBIG addr, INTBIG purpose, INTBIG units, void *infstr)
{
	char line[100];
	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 TOOL *tool;
	REGISTER NETWORK *net;
	REGISTER CELL *cell;
	REGISTER VIEW *view;
	REGISTER WINDOWPART *win;
	REGISTER GRAPHICS *gra;
	REGISTER VARIABLE *var;
	REGISTER CONSTRAINT *con;
	REGISTER WINDOWFRAME *wf;
	REGISTER POLYGON *poly;

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

char *db_makeunits(float value, INTBIG units)
{
	static char line[100];

	switch (units)
	{
		case VTUNITSRES:
			return(displayedunits(value, units,
				(us_electricalunits&INTERNALRESUNITS) >> INTERNALRESUNITSSH));
		case VTUNITSCAP:
			return(displayedunits(value, units,
				(us_electricalunits&INTERNALCAPUNITS) >> INTERNALCAPUNITSSH));
		case VTUNITSIND:
			return(displayedunits(value, units,
				(us_electricalunits&INTERNALINDUNITS) >> INTERNALINDUNITSSH));
		case VTUNITSCUR:
			return(displayedunits(value, units,
				(us_electricalunits&INTERNALCURUNITS) >> INTERNALCURUNITSSH));
		case VTUNITSVOLT:
			return(displayedunits(value, units,
				(us_electricalunits&INTERNALVOLTUNITS) >> INTERNALVOLTUNITSSH));
		case VTUNITSTIME:
			return(displayedunits(value, units,
				(us_electricalunits&INTERNALTIMEUNITS) >> INTERNALTIMEUNITSSH));
	}
	sprintf(line, "%g", value);
	return(line);
}

/*
 * 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.
 */
void db_addstring(char *str, void *infstr)
{
	while (*str != 0)
	{
		if (*str == '[' || *str == ']' || *str == '"' || *str == '^')
			addtoinfstr(infstr, '^');
		addtoinfstr(infstr, *str++);
	}
}

/*
 * Routine to describe a simple variable "var".  There can be no arrays, code,
 * or pointers to Electric objects in this variable.
 */
char *describesimplevariable(VARIABLE *var)
{
	static char line[50];

	/* code cannot be called by multiple procesors: uses globals */
	NOT_REENTRANT;

	switch (var->type&VTYPE)
	{
		case VSTRING:
			return((char *)var->addr);
		case VINTEGER:
			sprintf(line, "%ld", var->addr);
			return(line);
		case VFRACT:
			strcpy(line, frtoa(var->addr));
			return(line);
		case VSHORT:
			sprintf(line, "%ld", var->addr&0xFFFF);
			return(line);
		case VBOOLEAN:
			sprintf(line, "%ld", var->addr&0xFF);
			return(line);
		case VFLOAT:
		case VDOUBLE:
			sprintf(line, "%g", castfloat(var->addr));
			return(line);
	}
	return("");
}

/*
 * Routine to determine the variable type of "pt"; either an integer, float,
 * or string; and return the type and value in "type" and "addr".  If "units"
 * is nonzero, this value is converted into that type of unit.
 */
void getsimpletype(char *pt, INTBIG *type, INTBIG *addr, INTBIG units)
{
	REGISTER char *opt;
	REGISTER INTBIG i;
	float f;

	if (!isanumber(pt))
	{
		*type = VSTRING;
		*addr = (INTBIG)pt;
		return;
	}
	switch (units)
	{
		case VTUNITSRES:
			*addr = castint(figureunits(pt, VTUNITSRES,
				(us_electricalunits&INTERNALRESUNITS) >> INTERNALRESUNITSSH));
			*type = VFLOAT;
			return;
		case VTUNITSCAP:
			*addr = castint(figureunits(pt, VTUNITSCAP,
				(us_electricalunits&INTERNALCAPUNITS) >> INTERNALCAPUNITSSH));
			*type = VFLOAT;
			return;
		case VTUNITSIND:
			*addr = castint(figureunits(pt, VTUNITSIND,
				(us_electricalunits&INTERNALINDUNITS) >> INTERNALINDUNITSSH));
			*type = VFLOAT;
			return;
		case VTUNITSCUR:
			*addr = castint(figureunits(pt, VTUNITSCUR,
				(us_electricalunits&INTERNALCURUNITS) >> INTERNALCURUNITSSH));
			*type = VFLOAT;
			return;
		case VTUNITSVOLT:
			*addr = castint(figureunits(pt, VTUNITSVOLT,
				(us_electricalunits&INTERNALVOLTUNITS) >> INTERNALVOLTUNITSSH));
			*type = VFLOAT;
			return;
		case VTUNITSTIME:
			*addr = castint(figureunits(pt, VTUNITSTIME,
				(us_electricalunits&INTERNALTIMEUNITS) >> INTERNALTIMEUNITSSH));
			*type = VFLOAT;
			return;
	}

	*type = VINTEGER;
	*addr = atoi(pt);
	for(opt = pt; *opt != 0; opt++) if (*opt == '.' || *opt == 'e' || *opt == 'E')
	{
		f = (float)atof(pt);
		i = (INTBIG)(f * WHOLE);
		if (i / WHOLE == f)
		{
			*type = VFRACT;
			*addr = i;
		} else
		{
			*type = VFLOAT;
			*addr = castint(f);
		}
		break;
	}
}

/*
 * routine to make an abbreviation for the string "pt" in upper case if
 * "upper" is true
 */
char *makeabbrev(char *pt, BOOLEAN upper)
{
	REGISTER void *infstr;

	/* generate an abbreviated name for this prototype */
	infstr = initinfstr();
	while (*pt != 0)
	{
		if (isalpha(*pt))
		{
			if (isupper(*pt))
			{
				if (upper) addtoinfstr(infstr, *pt); else
					addtoinfstr(infstr, (char)(tolower(*pt)));
			} else
			{
				if (!upper) addtoinfstr(infstr, *pt); else
					addtoinfstr(infstr, (char)(toupper(*pt)));
			}
			while (isalpha(*pt)) pt++;
		}
		while (!isalpha(*pt) && *pt != 0) pt++;
	}
	return(returninfstr(infstr));
}

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

/************************* INTERNATIONALIZATION *************************/

char *db_stoppingreason[] =
{
	N_("Contour gather"),		/* STOPREASONCONTOUR */
	N_("DRC"),					/* STOPREASONDRC */
	N_("Playback"),				/* STOPREASONPLAYBACK */
	N_("Binary"),				/* STOPREASONBINARY */
	N_("CIF"),					/* STOPREASONCIF */
	N_("DXF"),					/* STOPREASONDXF */
	N_("EDIF"),					/* STOPREASONEDIF */
	N_("VHDL"),					/* STOPREASONVHDL */
	N_("Compaction"),			/* STOPREASONCOMPACT */
	N_("ERC"),					/* STOPREASONERC */
	N_("Check-in"),				/* STOPREASONCHECKIN */
	N_("Lock wait"),			/* STOPREASONLOCK */
	N_("Network comparison"),	/* STOPREASONNCC */
	N_("Port exploration"),		/* STOPREASONPORT */
	N_("Routing"),				/* STOPREASONROUTING */
	N_("Silicon compiler"),		/* STOPREASONSILCOMP */
	N_("Display"),				/* STOPREASONDISPLAY */
	N_("Simulation"),			/* STOPREASONSIMULATE */
	N_("Deck generation"),		/* STOPREASONDECK */
	N_("SPICE"),				/* STOPREASONSPICE */
	N_("Check"),				/* STOPREASONCHECK */
	N_("Array"),				/* STOPREASONARRAY */
	N_("Iteration"),			/* STOPREASONITERATE */
	N_("Replace"),				/* STOPREASONREPLACE */
	N_("Spread"),				/* STOPREASONSPREAD */
	N_("Execution"),			/* STOPREASONEXECUTE */
	N_("Command file"),			/* STOPREASONCOMFILE */
	N_("Selection"),			/* STOPREASONSELECT */
	N_("Tracking"),				/* STOPREASONTRACK */
	N_("Network evaluation"),	/* STOPREASONNETWORK */
	0
};

/*
 * Routine to translate internal strings in the database.
 */
void db_inittranslation(void)
{
	REGISTER INTBIG i;

	/* pretranslate the reasons for stopping */
	for(i=0; db_stoppingreason[i] != 0; i++)
		db_stoppingreason[i] = _(db_stoppingreason[i]);
}

/*
 * Routine to ensure that dialog "dia" is translated.
 */
void DiaTranslate(DIALOG *dia)
{
	REGISTER INTBIG j;
	DIALOGITEM *item;

	if (dia->translated != 0) return;
	dia->translated = 1;
	if (dia->movable != 0) dia->movable = _(dia->movable);
	for(j=0; j<dia->items; j++)
	{
		item = &dia->list[j];
		switch (item->type&ITEMTYPE)
		{
			case BUTTON:
			case DEFBUTTON:
			case CHECK:
			case RADIO:
			case MESSAGE:
			case EDITTEXT:
				if (*item->msg != 0)
					item->msg = _(item->msg);
				break;
		}
	}
}

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

	/* code cannot be called by multiple procesors: uses globals */
	NOT_REENTRANT;

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

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

/*
 * this routine dynamically allocates a string to hold "str" and places
 * that string at "*addr".  The routine returns true if the allocation
 * cannot be done.  Memory is allocated from virtual cluster "cluster".
 */
#ifdef DEBUGMEMORY
BOOLEAN _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;
		(void)db_error(DBNOMEM|DBALLOCSTRING);
		return(TRUE);
	}
	(void)strcpy(*addr, str);
	return(FALSE);
}
#else
BOOLEAN allocstring(char **addr, char *str, CLUSTER *cluster)
{
	*addr = (char *)emalloc((strlen(str)+1), cluster);
	if (*addr == 0)
	{
		*addr = NOSTRING;
		(void)db_error(DBNOMEM|DBALLOCSTRING);
		return(TRUE);
	}
	(void)strcpy(*addr, str);
	return(FALSE);
}
#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 true if the allocation
 * cannot be done.  Memory is allocated from virtual cluster "cluster".
 */
#ifdef DEBUGMEMORY
BOOLEAN _reallocstring(char **addr, char *str, CLUSTER *cluster, char *module, INTBIG line)
{
	efree(*addr);
	return(_allocstring(addr, str, cluster, module, line));
}
#else
BOOLEAN 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
 */
INTBIG namesame(char *pt1, char *pt2)
{
	REGISTER INTBIG c1, c2;

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

INTBIG 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(c1-c2);
		if (c1 == 0) break;
	}
	return(0);
}

/*
 * Routine to compare two names "name1" and "name2" and return an
 * integer giving their sorting order (0 if equal, nonzero according
 * to order) in the same manner as any other string compare EXCEPT
 * that it considers numbers properly, so that the string "in10" comes
 * after the string "in9".
 */
INTBIG namesamenumeric(char *name1, char *name2)
{
	REGISTER char *pt1, *pt2, pt1save, pt2save, *number1, *number2,
		*nameend1, *nameend2;
	REGISTER INTBIG pt1index, pt2index, pt1number, pt2number;
	REGISTER INTBIG compare;

	number1 = 0;
	for(pt1 = name1; *pt1 != 0; pt1++)
	{
		if (*pt1 == '[') break;
		if (isdigit(*pt1) == 0) number1 = 0; else
		{
			if (number1 == 0) number1 = pt1;
		}
	}
	for(pt1 = name1; *pt1 != 0; pt1++)
		if (*pt1 == '[' && isdigit(pt1[1])) break;
	number2 = 0;
	for(pt2 = name2; *pt2 != 0; pt2++)
	{
		if (*pt2 == '[') break;
		if (isdigit(*pt2) == 0) number2 = 0; else
		{
			if (number2 == 0) number2 = pt2;
		}
	}
	for(pt2 = name2; *pt2 != 0; pt2++)
		if (*pt2 == '[' && isdigit(pt2[1])) break;
	nameend1 = pt1;   nameend2 = pt2;
	pt1number = pt2number = 0;
	pt1index = pt2index = 0;
	if (number1 != 0 && number2 != 0)
	{
		pt1number = myatoi(number1);
		pt2number = myatoi(number2);
		if (pt1number != pt2number) compare = 0; else
		{
			/* make sure the text is the same if the numbers are the same */
			pt1save = *nameend1;   *nameend1 = 0;
			pt2save = *nameend2;   *nameend2 = 0;
			compare = namesame(name1, name2);
			*nameend2 = pt2save;   *nameend1 = pt1save;
		}
		if (compare == 0)
		{
			nameend1 = number1;
			nameend2 = number2;
		}
	}
	if (*pt1 == '[') pt1index = myatoi(&pt1[1]);
	if (*pt2 == '[') pt2index = myatoi(&pt2[1]);

	pt1save = *nameend1;   *nameend1 = 0;
	pt2save = *nameend2;   *nameend2 = 0;
	compare = namesame(name1, name2);
	*nameend2 = pt2save;   *nameend1 = pt1save;
	if (compare != 0) return(compare);
	if (pt1number != pt2number) return(pt1number - pt2number);
	return(pt1index - pt2index);
}

/******************** 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;
	REGISTER INTBIG len;

	/* code cannot be called by multiple procesors: uses globals */
	NOT_REENTRANT;

	/* 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++;
	}
	len = pt2 - pt;
	if (len+1 > db_keywordbufferlength)
	{
		if (db_keywordbufferlength != 0) efree(db_keywordbuffer);
		db_keywordbufferlength = 0;
		db_keywordbuffer = (char *)emalloc(len+1, el_tempcluster);
		if (db_keywordbuffer == 0)
		{
			ttyputnomemory();
			return(NOSTRING);
		}
		db_keywordbufferlength = len+1;
	}
	(void)strncpy(db_keywordbuffer, pt, len);
	db_keywordbuffer[len] = 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 the ability to build an arbitrarily large string.
 * The first routine called must be "initinfstr()" which returns a "string object".
 * After that, make any number of calls to "addtoinfstr()", "addstringtoinfstr()",
 * or "formatinfstr()".  At any time, call "getinfstr()" to return the string.
 * When done, call "doneinfstr".
 */

/*
 * Routine to initialize a new infinite string.  Returns the address of the
 * infinite string object (zero on error).
 */
void *initinfstr(void)
{
	REGISTER INTBIG i;
	REGISTER INFSTR *inf;

	if (!db_firstinf)
	{
		/* first time through: build a free-list with many blocks */
		if (ensurevalidmutex(&db_infstrmutex, TRUE)) return(0);
		db_firstinf = TRUE;
		db_firstfreeinfstr = NOINFSTR;
		db_lastfreeinfstr = NOINFSTR;
		for(i=0; i<INFSTRCOUNT; i++)
		{
			inf = (INFSTR *)emalloc(sizeof (INFSTR), db_cluster);
			if (inf == 0)
			{
				(void)db_error(DBNOMEM|DBINITINFSTR);
				return(0);
			}
			inf->infstr = (char *)emalloc((INFSTRDEFAULT+1), db_cluster);
			if (inf->infstr == 0)
			{
				(void)db_error(DBNOMEM|DBINITINFSTR);
				return(0);
			}
			inf->infstrlength = INFSTRDEFAULT;

			/* append it to the end of the list */
			inf->nextinfstr = NOINFSTR;
			if (db_lastfreeinfstr == NOINFSTR)
			{
				db_firstfreeinfstr = inf;
			} else
			{
				db_lastfreeinfstr->nextinfstr = inf;
			}
			db_lastfreeinfstr = inf;
		}
	}

	/* see if there is a free one on the list */
	inf = NOINFSTR;
	if (db_multiprocessing) emutexlock(db_infstrmutex);
	if (db_firstfreeinfstr != NOINFSTR)
	{
		inf = db_firstfreeinfstr;
		db_firstfreeinfstr = inf->nextinfstr;
		if (db_firstfreeinfstr == NOINFSTR)
			db_lastfreeinfstr = NOINFSTR;
	}
	if (db_multiprocessing) emutexunlock(db_infstrmutex);

	/* if none in free list, allocate a new one */
	if (inf == NOINFSTR)
	{
		inf = (INFSTR *)emalloc(sizeof (INFSTR), db_cluster);
		if (inf == 0)
		{
			(void)db_error(DBNOMEM|DBINITINFSTR);
			return(0);
		}
	}
	inf->infstrptr = 0;
	inf->infstr[inf->infstrptr] = 0;
	return(inf);
}

void addtoinfstr(void *vinf, char ch)
{
	REGISTER char *str;
	REGISTER INFSTR *inf;

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

void addstringtoinfstr(void *vinf, char *pp)
{
	REGISTER char *str;
	REGISTER INTBIG l, i, ori;
	REGISTER INFSTR *inf;

	if (vinf == 0) return;
	inf = (INFSTR *)vinf;

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

/*
 * Routine to do a variable-arguments "sprintf" to line "line" which
 * is "len" in size.
 */
void evsnprintf(char *line, INTBIG len, char *msg, va_list ap)
{
#ifdef HAVE_VSNPRINTF
	(void)vsnprintf(line, len-1, msg, ap);
#else
#  ifdef WIN32
	(void)_vsnprintf(line, len-1, msg, ap);
#  else
	(void)vsprintf(line, msg, ap);
#  endif
#endif
}

void formatinfstr(void *vinf, char *msg, ...)
{
	va_list ap;
	char line[8192];

	var_start(ap, msg);
	evsnprintf(line, 8192, msg, ap);
	va_end(ap);
	addstringtoinfstr(vinf, line);
}

char *returninfstr(void *vinf)
{
	REGISTER char *retval;
	REGISTER INFSTR *inf;

	if (vinf == 0) return("");
	inf = (INFSTR *)vinf;

	/* get the inifinite string */
	retval = inf->infstr;

	/* append it to the end of the list */
	if (db_multiprocessing) emutexlock(db_infstrmutex);
	inf->nextinfstr = NOINFSTR;
	if (db_lastfreeinfstr == NOINFSTR)
	{
		db_firstfreeinfstr = inf;
	} else
	{
		db_lastfreeinfstr->nextinfstr = inf;
	}
	db_lastfreeinfstr = inf;
	if (db_multiprocessing) emutexunlock(db_infstrmutex);

	/* return the string */
	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;
		if (newlimit < sa->stringcount) newlimit = sa->stringcount;
		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)) 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 true.
 */
void stringarraytotextfacet(void *vsa, NODEPROTO *np, BOOLEAN permanent)
{
	STRINGARRAY *sa;
	REGISTER INTBIG type;

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

/*
 * 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.
 */
time_t 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(time_t curtime)
{
	static char timebuf[30];

	/* code cannot be called by multiple procesors: uses globals */
	NOT_REENTRANT;

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

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

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

INTBIG parsemonth(char *monthname)
{
	REGISTER INTBIG mon;
	static char *name[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
		"Aug", "Sep", "Oct", "Nov", "Dec"};

	for (mon=0; mon<12; mon++)
		if (namesame(name[mon], monthname) == 0) break;
	if (mon < 12) return(mon+1);
	return(0);
}

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

INTBIG setupfiletype(char *extension, char *winfilter, INTBIG mactype, BOOLEAN binary,
	char *shortname, char *longname)
{
	FILETYPES *newlist;
	INTBIG oldcount, i;

	oldcount = db_filetypecount;
	db_filetypecount++;
	newlist = (FILETYPES *)emalloc(db_filetypecount * (sizeof (FILETYPES)), db_cluster);
	for(i=0; i<oldcount; i++)
	{
		newlist[i].extension = db_filetypeinfo[i].extension;
		newlist[i].winfilter = db_filetypeinfo[i].winfilter;
		newlist[i].mactype   = db_filetypeinfo[i].mactype;
		newlist[i].binary    = db_filetypeinfo[i].binary;
		newlist[i].shortname = db_filetypeinfo[i].shortname;
		newlist[i].longname  = db_filetypeinfo[i].longname;
	}
	if (oldcount > 0) efree((char *)db_filetypeinfo);
	db_filetypeinfo = newlist;
	(void)allocstring(&db_filetypeinfo[oldcount].extension, extension, db_cluster);
	(void)allocstring(&db_filetypeinfo[oldcount].winfilter, winfilter, db_cluster);
	db_filetypeinfo[oldcount].mactype = mactype;
	db_filetypeinfo[oldcount].binary = binary;
	(void)allocstring(&db_filetypeinfo[oldcount].shortname, shortname, db_cluster);
	(void)allocstring(&db_filetypeinfo[oldcount].longname, longname, db_cluster);
	return(oldcount);
}

/*
 * Routine to return the extension, short name, and long name of file type "filetype".
 */
void describefiletype(INTBIG filetype, char **extension, char **winfilter, INTBIG *mactype,
	BOOLEAN *binary, char **shortname, char **longname)
{
	REGISTER INTBIG index;

	index = filetype & FILETYPE;
	*extension = db_filetypeinfo[index].extension;
	*winfilter = db_filetypeinfo[index].winfilter;
	*mactype   = db_filetypeinfo[index].mactype;
	*binary    = db_filetypeinfo[index].binary;
	*shortname = db_filetypeinfo[index].shortname;
	*longname  = db_filetypeinfo[index].longname;
}

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

	for(i=0; i<db_filetypecount; i++)
		if (namesame(shortname, db_filetypeinfo[i].shortname) == 0) return(i);
	return(0);
}

/*
 * routine to find the actual file name in a path/file specification.  The
 * input path name is in "file" and a pointer to the file name is returned.
 * Note that this routine handles the directory separating characters for
 * all operating systems ('\' on Windows, '/' on UNIX, and ':' on Macintosh)
 * because the strings may be from other systems.
 */
char *skippath(char *file)
{
	REGISTER char *pt;

	pt = &file[strlen(file)-1];
	while (pt != file && pt[-1] != '\\' && pt[-1] != '/' && pt[-1] != ':') pt--;
	return(pt);
}

/*
 * 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"
 * (a descriptor created by "setupfiletype()").  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, INTBIG filetype, char *otherdir, char **truename)
{
	REGISTER FILE *f;
	REGISTER char *pp;
	INTBIG mactype;
	BOOLEAN binary;
	char rarg[3], *extension, *winfilter, *shortname, *longname;

	/* determine extension to use */
	describefiletype(filetype&FILETYPE, &extension, &winfilter, &mactype, &binary, &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 (binary != 0) strcat(rarg, "b");

	/* 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 INTBIG len;
	REGISTER char *pt;

	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);
		len = strlen(db_tryfilename);
		if (db_tryfilename[len-1] != DIRSEP)
		{
			db_tryfilename[len++] = DIRSEP;
			db_tryfilename[len] = 0;
		}
	}
	(void)strcat(db_tryfilename, pp);
	if (extension != 0)
	{
		(void)strcat(db_tryfilename, ".");
		(void)strcat(db_tryfilename, extension);
	}

	/* now extend this to the full path name */
	pt = fullfilename(db_tryfilename);
	len = strlen(pt) + 1;
	if (len > db_tryfilenamelen)
	{
		efree(db_tryfilename);
		db_tryfilenamelen = 0;
		db_tryfilename = (char *)emalloc(len, db_cluster);
		if (db_tryfilename == 0) return(NULL);
		db_tryfilenamelen = len;
	}
	strcpy(db_tryfilename, pt);
	*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 (a descriptor created by "setupfiletype()").  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, INTBIG filetype, char *prompt, char **truename)
{
	REGISTER FILE *f;
	REGISTER char *pt, *next;
	char warg[3], *extension, *winfilter, *shortname, *longname;
	static char truenamelocal[256];
	INTBIG mactype;
	BOOLEAN binary;

	/* determine extension to use */
	describefiletype(filetype&FILETYPE, &extension, &winfilter, &mactype, &binary, &shortname, &longname);

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

	/* determine argument to "fopen" */
	strcpy(warg, "w");
	if (binary) strcat(warg, "b");
	f = fopen(pt, warg);
#ifdef	MACOS
	if (f != NULL)
	{
		void mac_settypecreator(char*, INTBIG, INTBIG);

		if (mactype == 'TEXT')
		{
			mac_settypecreator(pt, 'TEXT', 'ttxt');
		} else
		{
			mac_settypecreator(pt, mactype, 'Elec');
		}
	}
#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"
 */
BOOLEAN xeof(FILE *f)
{
	if (feof(f) != 0) return(TRUE);
	return(FALSE);
}

/*
 * Routine to seek to position "pos", nature "nat", on stream "f"
 */
void xseek(FILE *f, INTBIG pos, INTBIG 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"
 */
void xputs(char *s, FILE *f)
{
	(void)fputs(s, f);
}

/*
 * Routine to read "count" elements of size "size" from stream "f" into the buffer
 * at "buf".  Returns the number of objects read (ideally, "size").
 */
INTBIG xfread(char *buf, INTBIG size, INTBIG count, FILE *f)
{
#if 0		/* limits reads to BUFSIZE */
	REGISTER INTBIG amtjustread, amttoread, totalamtread;

	totalamtread = 0;
	while (count > 0)
	{
		amttoread = mini(count, BUFSIZ/size);
		amtjustread = fread(buf, size, amttoread, f);
		totalamtread += amtjustread;
		if (amtjustread != amttoread)
		{
			if (ferror(f) != 0) clearerr(f); else
			{
				if (feof(f) != 0) break;
			}
		}
		count -= amtjustread;
		buf += amtjustread*size;
	}
	return(totalamtread);
#else
	REGISTER INTBIG ret;

	for(;;)
	{
		ret = fread(buf, size, count, f);
		if (ret == count || feof(f) != 0) break;
		clearerr(f);
	}
	return(ret);
#endif
}

/*
 * 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)
{
	REGISTER INTBIG ret;

	for(;;)
	{
		ret = fwrite(buf, size, count, f);
		if (ret == count || feof(f) != 0) break;
		clearerr(f);
	}
	return(ret);
}

/*
 * 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 false if sucessful, true if end-of-file is
 * reached.
 */
BOOLEAN xfgets(char *line, INTBIG limit, FILE *file)
{
	REGISTER char *pp;
	REGISTER INTBIG c, total;

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

/******************** 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)
{
	INTBIG ic, i, k, temp, n1, n2, nr1, nr2;
	UINTBIG 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)
{
	INTBIG i, ic, k, temp;
	UINTBIG 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;
	}
}
