/*
 * Electric(tm) VLSI Design System
 *
 * File: iosuei.c
 * Input/output tool: SUE (Schematic User Environment) reader
 * 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 "config.h"
#if IOSUE

#include "global.h"
#include "edialogs.h"
#include "egraphics.h"
#include "eio.h"
#include "tech.h"
#include "tecart.h"
#include "tecgen.h"
#include "tecschem.h"
#include "network.h"
#include "usr.h"

/* #define SHOWORIGINAL 1 */		/* uncomment to show original nodes */

#define E0     (WHOLE/8)	/*  0.125 */
#define S1     (T1+E0)		/*  1.875 */
#define F3     (K3+H0+E0)	/*  3.625 */
#define S6     (T6+E0)		/*  6.875 */
#define Q11    (K11+Q0)		/* 11.25 */

/*************** SUE EQUIVALENCES ***************/

typedef struct
{
	char   *portname;
	INTBIG  xoffset;
	INTBIG  yoffset;
} SUEEXTRAWIRE;

typedef struct
{
	char         *suename;
	NODEPROTO   **intproto;
	INTBIG        netateoutput;
	INTBIG        rotation;
	INTBIG        xoffset;
	INTBIG        yoffset;
	INTBIG        detailbits;
	INTBIG        numextrawires;
	SUEEXTRAWIRE *extrawires;
} SUEEQUIV;

SUEEXTRAWIRE io_suetransistorwires[] =
{
	{"d",    K3,   0},
	{"s",   -K3,   0},
	{"g",     0,  H4}
};

SUEEXTRAWIRE io_sueresistorwires[] =
{
	{"a",   -K3,   0},
	{"b",    K3,   0}
};

SUEEXTRAWIRE io_suecapacitorwires[] =
{
	{"a",     0,  T1},
	{"b",     0, -T1}
};

SUEEXTRAWIRE io_suesourcewires[] =
{
	{"minus", 0, -Q1},
	{"plus",  0,  H1}
};

SUEEXTRAWIRE io_suetwoportwires[] =
{
	{"a",  -Q11,  F3},
	{"b",  -Q11, -F3},
	{"x",   Q11,  F3},
	{"y",   Q11, -F3}
};

SUEEQUIV io_sueequivs[] =
{
	/* name         primitive          NEG ANG    X   Y  BITS         WIR EXTRA-WIRES */
	{"pmos10",     &sch_transistorprim, 0, 900, -K2,  0, TRANPMOS,     3, io_suetransistorwires},
	{"pmos4",      &sch_transistorprim, 0, 900, -K2,  0, TRANPMOS,     3, io_suetransistorwires},
	{"pmos",       &sch_transistorprim, 0, 900, -K2,  0, TRANPMOS,     3, io_suetransistorwires},
	{"nmos10",     &sch_transistorprim, 0, 900, -K2,  0, TRANNMOS,     3, io_suetransistorwires},
	{"nmos4",      &sch_transistorprim, 0, 900, -K2,  0, TRANNMOS,     3, io_suetransistorwires},
	{"nmos",       &sch_transistorprim, 0, 900, -K2,  0, TRANNMOS,     3, io_suetransistorwires},
	{"capacitor",  &sch_capacitorprim,  0,   0,   0,  0, 0,            2, io_suecapacitorwires},
	{"resistor",   &sch_resistorprim,   0, 900,   0,  0, 0,            2, io_sueresistorwires},
	{"inductor",   &sch_inductorprim,   0,   0,   0,  0, 0,            0, 0},
	{"cccs",       &sch_twoportprim,    0,   0,  Q1,-S6, TWOPCCCS,     4, io_suetwoportwires},
	{"ccvs",       &sch_twoportprim,    0,   0,  Q1,-S6, TWOPCCVS,     4, io_suetwoportwires},
	{"vcvs",       &sch_twoportprim,    0,   0,  Q1,-S6, TWOPVCVS,     4, io_suetwoportwires},
	{"vccs",       &sch_twoportprim,    0,   0, -S1,-K5, TWOPVCCS,     0, 0},
	{0, 0, 0, 0, 0, 0, 0, 0}
};

/*************** SUE WIRES ***************/

#define NOSUEWIRE ((SUEWIRE *)-1)

typedef struct Isuewire
{
	INTBIG           x[2], y[2];
	NODEINST        *ni[2];
	PORTPROTO       *pp[2];
	ARCPROTO        *proto;
	struct Isuewire *nextsuewire;
} SUEWIRE;

SUEWIRE *io_suefreewire = NOSUEWIRE;

/*************** SUE NETWORKS ***************/

#define NOSUENET ((SUENET *)-1)

typedef struct Isuenet
{
	INTBIG          x, y;
	char           *label;
	struct Isuenet *nextsuenet;
} SUENET;

SUENET *io_suefreenet = NOSUENET;

/*************** MISCELLANEOUS ***************/

#define MAXLINE       300			/* maximum characters on an input line */
#define MAXKEYWORDS    50			/* maximum keywords on an input line */
#define MAXICONPOINTS  25			/* maximum coordinates on an "icon_line" line */

static INTBIG io_suelineno;
static INTBIG io_suefilesize;
static char   io_suelastline[MAXLINE];
static char   io_sueorigline[MAXLINE];
static char   io_suecurline[MAXLINE];
static char **io_suedirectories;
       INTBIG io_suenumdirectories = 0;
static FILE  *io_suefilein;

/* prototypes for local routines */
static BOOLEAN    io_sueadddirectory(char *directory);
static void       io_suecleardirectories(void);
static char      *io_suefindbusname(ARCINST *ai);
static BOOLEAN    io_suefindnode(INTBIG *x, INTBIG *y, INTBIG ox, INTBIG oy, NODEPROTO *np,
					NODEINST **ni, PORTPROTO **pp, NODEINST *notthisnode, INTBIG lambda);
static NODEINST  *io_suefindpinnode(INTBIG x, INTBIG y, NODEPROTO *np, PORTPROTO **thepp);
static void       io_suefreenets(SUENET *firstsn);
static void       io_suefreewires(SUEWIRE *firstsw);
static INTBIG     io_suegetnextline(char **keywords, INTBIG curlydepth);
static NODEPROTO *io_suegetnodeproto(LIBRARY *lib, char *protoname);
static void       io_suekillnet(SUENET *sn);
static void       io_suekillwire(SUEWIRE *sw);
static INTBIG     io_suemakex(INTBIG x, INTBIG lambda);
static INTBIG     io_suemakey(INTBIG y, INTBIG lambda);
static PORTPROTO *io_suenewexport(NODEPROTO *facet, NODEINST *ni, PORTPROTO *pp, char *thename);
static SUENET    *io_suenewnet(void);
static SUEWIRE   *io_suenewwire(void);
static void       io_sueplacenets(SUENET *firstsuenet, NODEPROTO *facet);
static void       io_sueparseparameters(char **keywords, INTBIG count, INTBIG *x, INTBIG *y, INTBIG lambda,
					INTBIG *rot, INTBIG *trn, INTBIG *type, char **thename, char **thelabel, char **thetext);
static void       io_sueplacewires(SUEWIRE *firstsuewire, SUENET *firstsuenet, NODEPROTO *facet, INTBIG lambda);
static NODEPROTO *io_suereadfile(LIBRARY *lib, char *facetname);
static NODEPROTO *io_suereadfromdisk(LIBRARY *lib, char *name);
static char      *io_suesearchbusname(ARCINST *ai);
static PORTPROTO *io_suewiredport(NODEINST *ni, INTBIG *x, INTBIG *y, INTBIG ox, INTBIG oy);

/*
 * Routine to free all memory associated with this module.
 */
void io_freesuememory(void)
{
	SUEWIRE *sw;
	SUENET *sn;

	while (io_suefreewire != NOSUEWIRE)
	{
		sw = io_suefreewire;
		io_suefreewire = io_suefreewire->nextsuewire;
		efree((char *)sw);
	}
	while (io_suefreenet != NOSUENET)
	{
		sn = io_suefreenet;
		io_suefreenet = io_suefreenet->nextsuenet;
		efree((char *)sn);
	}
	io_suecleardirectories();
}

BOOLEAN io_readsuelibrary(LIBRARY *lib)
{
	REGISTER INTBIG len, i, filecount;
	REGISTER NODEPROTO *topfacet;
	extern DIALOG us_progressdialog;
	char facetname[300], dirname[300], topdirname[300], truesuefile[300],
		*filename, **filelist;

	/* open the file */
	io_suefilein = xopen(lib->libfile, io_filetypesue, "", &filename);
	if (io_suefilein == 0)
	{
		ttyputerr(_("File %s not found"), lib->libfile);
		return(TRUE);
	}

	/* determine the facet name */
	strcpy(truesuefile, filename);
	strcpy(facetname, filename);
	len = strlen(facetname);
	if (namesame(&facetname[len-4], ".sue") == 0)
		facetname[len-4] = 0;
	for(i = strlen(facetname)-1; i>0; i--)
		if (facetname[i] == DIRSEP) break;
	i++;
	strcpy(facetname, &facetname[i]);

	/* initialize the number of directories that need to be searched */
	io_suecleardirectories();

	/* determine the current directory */
	strcpy(topdirname, filename);
	len = strlen(topdirname);
	for(i = len-1; i>0; i--)
		if (topdirname[i] == DIRSEP) break;
	topdirname[i+1] = 0;
	if (io_sueadddirectory(topdirname)) return(TRUE);

	/* find all subdirectories that start with "suelib_" and include them in the search */
	filecount = filesindirectory(topdirname, &filelist);
	for(i=0; i<filecount; i++)
	{
		if (namesamen(filelist[i], "suelib_", 7) != 0) continue;
		strcpy(dirname, topdirname);
		strcat(dirname, filelist[i]);
		if (fileexistence(dirname) != 2) continue;
		strcat(dirname, DIRSEPSTR);
		if (io_sueadddirectory(dirname)) return(TRUE);
	}

	/* see if the current directory is inside of a SUELIB */
	len = strlen(topdirname);
	for(i = len-2; i>0; i--)
		if (topdirname[i] == DIRSEP) break;
	i++;
	if (namesamen(&topdirname[i], "suelib_", 7) == 0)
	{
		topdirname[i] = 0;
		filecount = filesindirectory(topdirname, &filelist);
		for(i=0; i<filecount; i++)
		{
			if (namesamen(filelist[i], "suelib_", 7) != 0) continue;
			strcpy(dirname, topdirname);
			strcat(dirname, filelist[i]);
			if (fileexistence(dirname) != 2) continue;
			strcat(dirname, DIRSEPSTR);
			if (io_sueadddirectory(dirname)) return(TRUE);
		}
	}

	/* prepare for input */
	io_suefilesize = filesize(io_suefilein);
	if (DiaInitDialog(&us_progressdialog))
	{
		xclose(io_suefilein);
		return(TRUE);
	}
	io_suelineno = 0;

	/* read the file */
	DiaPercent(1, 0);
	DiaSetText(2, _("Reading SUE file..."));
	topfacet = io_suereadfile(lib, facetname);
	if (topfacet != NONODEPROTO)
		lib->curnodeproto = topfacet;
	(void)asktool(net_tool, "total-re-number");

	/* clean up */
	DiaDoneDialog();
	xclose(io_suefilein);
	ttyputmsg(_("%s read"), truesuefile);

	return(FALSE);
}

/*
 * Routine to read the SUE file in "f"/
 */
NODEPROTO *io_suereadfile(LIBRARY *lib, char *facetname)
{
	char *keywords[MAXKEYWORDS], *thename, *thelabel, *thetext, *namestring;
	char suevarname[200], *pt, *cpt, *startkey, save;
	char **argnames, **argvalues, **newargnames, **newargvalues;
	INTBIG outline[MAXICONPOINTS*2], x, y, type, dx, dy, px, py, pinx, piny, xoff, yoff,
		numargs, namestrlen, rot, trn, rotation;
	REGISTER INTBIG count, i, lx, hx, ly, hy, curly, xshrink, yshrink,
		varcount, varindex, varoffset, pos, invertoutput, lambda, detailbits,
		keycount, cx, cy, p1x, p1y, p2x, p2y, start, extent, numextrawires, len,
		iconx, icony, newaddr, newtype, xpos, ypos;
	REGISTER BOOLEAN halvesize, placeicon, varissize, isparam;
	XARRAY trans;
	double rextent, rstart;
	REGISTER NODEPROTO *facet, *schemfacet, *iconfacet, *proto, *np;
	REGISTER PORTPROTO *pp, *ppt;
	PORTPROTO *npp;
	REGISTER VARIABLE *var;
	REGISTER NODEINST *ni;
	NODEINST *nni;
	REGISTER ARCINST *ai;
	REGISTER SUEWIRE *sw, *firstsuewire;
	REGISTER SUENET *sn, *firstsuenet;
	REGISTER SUEEXTRAWIRE *extrawires;

	firstsuewire = NOSUEWIRE;
	firstsuenet = NOSUENET;
	schemfacet = iconfacet = facet = NONODEPROTO;
	io_suelastline[0] = 0;
	numargs = 0;
	namestrlen = 0;
	lambda = lib->lambda[sch_tech->techindex];
	for(;;)
	{
	   /* get the next line of text */
		count = io_suegetnextline(keywords, 0);
		if (count < 0) break;
		if (count == 0) continue;

		/* handle "proc" for defining views */
		if (namesame(keywords[0], "proc") == 0)
		{
			/* write any wires from the last proc */
			if (facet != NONODEPROTO)
			{
				io_sueplacewires(firstsuewire, firstsuenet, facet, lambda);
				io_suefreewires(firstsuewire);
				io_sueplacenets(firstsuenet, facet);
				io_suefreenets(firstsuenet);
				firstsuewire = NOSUEWIRE;
				firstsuenet = NOSUENET;
			}

			if (count < 2)
			{
				ttyputerr(_("Facet %s, line %ld: 'proc' is missing arguments: %s"),
					facetname, io_suelineno, io_sueorigline);
				continue;
			}

			if (namesamen(keywords[1], "SCHEMATIC_", 10) == 0)
			{
				/* create the schematic facet */
				(void)initinfstr();
				if (namesame(&keywords[1][10], "[get_file_name]") == 0)
					(void)addstringtoinfstr(facetname); else
						(void)addstringtoinfstr(&keywords[1][10]);
				(void)addstringtoinfstr("{sch}");
				schemfacet = facet = us_newnodeproto(returninfstr(), lib);
				placeicon = FALSE;
			} else if (namesamen(keywords[1], "ICON_", 5) == 0)
			{
				/* create the icon facet */
				(void)initinfstr();
				if (namesame(&keywords[1][5], "[get_file_name]") == 0)
					(void)addstringtoinfstr(facetname); else
						(void)addstringtoinfstr(&keywords[1][5]);
				(void)addstringtoinfstr("{ic}");
				iconfacet = facet = us_newnodeproto(returninfstr(), lib);
			} else
			{
				ttyputerr(_("Facet %s, line %ld: unknown 'proc' statement: %s"),
					facetname, io_suelineno, io_sueorigline);
			}
			continue;
		}

		/* handle "make" for defining components */
		if (namesame(keywords[0], "make") == 0)
		{
			if (count < 2)
			{
				ttyputerr(_("Facet %s, line %ld: 'make' is missing arguments: %s"),
					facetname, io_suelineno, io_sueorigline);
				continue;
			}

			/* extract parameters */
			io_sueparseparameters(&keywords[2], count-2, &x, &y, lambda, &rot, &trn,
				&type, &thename, &thelabel, &thetext);

			/* save the name string */
			if (thename != 0)
			{
				len = strlen(thename) + 1;
				if (len > namestrlen)
				{
					/* LINTED "namestring" used in proper order */
					if (namestrlen > 0) efree(namestring);
					namestring = (char *)emalloc(len, el_tempcluster);
					namestrlen = len;
				}
				strcpy(namestring, thename);
				thename = namestring;
			}

			/* ignore self-references */
			if (namesame(keywords[1], facetname) == 0)
			{
				if (x != 0 || y != 0)
				{
					/* queue icon placement */
					iconx = x;   icony = y;
					placeicon = TRUE;
				}
				continue;
			}

			/* special case for network names: queue them */
			if (namesame(keywords[1], "name_net_m") == 0 ||
				namesame(keywords[1], "name_net_s") == 0 ||
				namesame(keywords[1], "name_net") == 0)
			{
				sn = io_suenewnet();
				sn->x = x;
				sn->y = y;
				(void)allocstring(&sn->label, thename, io_tool->cluster);
				sn->nextsuenet = firstsuenet;
				firstsuenet = sn;
				continue;
			}

			/* first check for special names */
			proto = NONODEPROTO;
			invertoutput = 0;
			rotation = 0;
			xoff = yoff = 0;
			xshrink = yshrink = 0;
			detailbits = 0;
			numextrawires = 0;
			extrawires = 0;
			type = 0;
			if (namesame(keywords[1], "inout") == 0)
			{
				proto = sch_offpageprim;
				makeangle(rot, trn, trans);
				xform(K2, 0, &xoff, &yoff, trans);
				xoff = muldiv(xoff, lambda, WHOLE);
				yoff = muldiv(yoff, lambda, WHOLE);
				type = BIDIRPORT;
			} else if (namesame(keywords[1], "input") == 0)
			{
				proto = sch_offpageprim;
				makeangle(rot, trn, trans);
				xform(-K2, 0, &xoff, &yoff, trans);
				xoff = muldiv(xoff, lambda, WHOLE);
				yoff = muldiv(yoff, lambda, WHOLE);
				type = INPORT;
			} else if (namesame(keywords[1], "output") == 0)
			{
				proto = sch_offpageprim;
				makeangle(rot, trn, trans);
				xform(K2, 0, &xoff, &yoff, trans);
				xoff = muldiv(xoff, lambda, WHOLE);
				yoff = muldiv(yoff, lambda, WHOLE);
				type = OUTPORT;
			} else if (namesame(keywords[1], "rename_net") == 0)
			{
				proto = sch_wirepinprim;
			} else if (namesame(keywords[1], "global") == 0)
			{
				if (net_buswidth(thename) > 1) proto = sch_buspinprim; else
				{
					proto = sch_wirepinprim;
					if (namesame(thename, "gnd") == 0)
					{
						makeangle(rot, trn, trans);
						xform(0, -K2, &xoff, &yoff, trans);
						xoff = muldiv(xoff, lambda, WHOLE);
						yoff = muldiv(yoff, lambda, WHOLE);
						proto = sch_gndprim;
						type = GNDPORT;
					}
					if (namesame(thename, "vdd") == 0)
					{
						proto = sch_pwrprim;
						type = PWRPORT;
					}
				}
			} else if (namesame(keywords[1], "join_net") == 0)
			{
				proto = sch_wireconprim;
				xshrink = -K2;
				makeangle(rot, trn, trans);
				xform(Q1, 0, &xoff, &yoff, trans);
				xoff = muldiv(xoff, lambda, WHOLE);
				yoff = muldiv(yoff, lambda, WHOLE);
			}

			/* now check for internal associations to known primitives */
			if (proto == NONODEPROTO)
			{
				for(i=0; io_sueequivs[i].suename != 0; i++)
					if (namesame(keywords[1], io_sueequivs[i].suename) == 0) break;
				if (io_sueequivs[i].suename != 0)
				{
					proto = *io_sueequivs[i].intproto;
					invertoutput = io_sueequivs[i].netateoutput;
					rotation = io_sueequivs[i].rotation;
					makeangle(rot, trn, trans);
					xform(io_sueequivs[i].xoffset, io_sueequivs[i].yoffset, &xoff, &yoff, trans);
					xoff = muldiv(xoff, lambda, WHOLE);
					yoff = muldiv(yoff, lambda, WHOLE);
					rot += rotation;
					if (rot >= 3600) rot -= 3600;
					detailbits = io_sueequivs[i].detailbits;
					numextrawires = io_sueequivs[i].numextrawires;
					extrawires = io_sueequivs[i].extrawires;
#ifdef SHOWORIGINAL
					defaultnodesize(proto, &px, &py);
					lx = x - px/2 + xoff;   hx = lx + px;
					ly = y - py/2 + yoff;   hy = ly + py;
					ni = newnodeinst(proto, lx, hx, ly, hy, trn, rot, facet);
					if (ni == NONODEINST) continue;
					ni->userbits |= detailbits;
					endobjectchange((INTBIG)ni, VNODEINST);
					proto = NONODEPROTO;
					invertoutput = 0;
					rotation = 0;
					xoff = yoff = 0;
					detailbits = 0;
					numextrawires = 0;
					extrawires = 0;
#endif
				}
			}

			/* now check for references to facets */
			if (proto == NONODEPROTO)
			{
				/* find node or read it from disk */
				proto = io_suegetnodeproto(lib, keywords[1]);
				if (proto == NONODEPROTO)
					proto = io_suereadfromdisk(lib, keywords[1]);

				/* set proper offsets for the facet */
				if (proto != NONODEPROTO)
				{
					np = iconview(proto);
					if (np != NONODEPROTO) proto = np;
					xoff = (proto->lowx + proto->highx) / 2;
					yoff = (proto->lowy + proto->highy) / 2;
					makeangle(rot, trn, trans);
					xform(xoff, yoff, &xoff, &yoff, trans);
				}
			}

			/* ignore "title" specifications */
			if (namesamen(keywords[1], "title_", 6) == 0) continue;

			/* stop now if SUE node is unknown */
			if (proto == NONODEPROTO)
			{
				ttyputmsg(_("Cannot make '%s' in facet %s"), keywords[1], describenodeproto(facet));
				continue;
			}

			/* create the instance */
			defaultnodesize(proto, &px, &py);
			px -= muldiv(xshrink, lambda, WHOLE);
			py -= muldiv(yshrink, lambda, WHOLE);
			lx = x - px/2 + xoff;   hx = lx + px;
			ly = y - py/2 + yoff;   hy = ly + py;
			ni = newnodeinst(proto, lx, hx, ly, hy, trn, rot, facet);
			if (ni == NONODEINST) continue;
			ni->userbits |= detailbits;
			ni->temp1 = invertoutput;
			if (proto->primindex == 0 && proto->cellview == el_iconview)
				ni->userbits |= NEXPAND;
			endobjectchange((INTBIG)ni, VNODEINST);

			/* add any extra wires to the node */
			for(i=0; i<numextrawires; i++)
			{
				for(pp = proto->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
					if (namesame(pp->protoname, extrawires[i].portname) == 0) break;
				if (pp == NOPORTPROTO) continue;
				portposition(ni, pp, &x, &y);
				makeangle(ni->rotation, ni->transpose, trans);
				px = muldiv(extrawires[i].xoffset, lambda, WHOLE);
				py = muldiv(extrawires[i].yoffset, lambda, WHOLE);
				xform(px, py, &dx, &dy, trans);
				defaultnodesize(sch_wirepinprim, &px, &py);
				pinx = x + dx;   piny = y + dy;
				nni = io_suefindpinnode(pinx, piny, facet, &npp);
				if (nni == NONODEINST)
				{
					lx = pinx - px/2;   hx = lx + px;
					ly = piny - py/2;   hy = ly + py;
					nni = newnodeinst(sch_wirepinprim, lx, hx, ly, hy, 0, 0, facet);
					if (nni == NONODEINST) continue;
					npp = nni->proto->firstportproto;
				}
				ai = newarcinst(sch_wirearc, 0, us_makearcuserbits(sch_wirearc),
					ni, pp, x, y, nni, npp, pinx, piny, facet);
				if (ai == NOARCINST)
				{
					ttyputerr(_("Error adding extra wires to node %s"), keywords[1]);
					break;
				}
			}

			/* handle names assigned to the node */
			if (thename != 0)
			{
				/* export a port if this is an input, output, inout */
				if (proto == sch_offpageprim && thename != 0)
				{
					pp = proto->firstportproto;
					if (namesame(keywords[1], "output") == 0) pp = pp->nextportproto;
					ppt = io_suenewexport(facet, ni, pp, thename);
					if (ppt == NOPORTPROTO)
					{
						ttyputmsg(_("Facet %s, line %ld, could not create port %s"), 
							facetname, io_suelineno, thename);
					} else
					{
						defaulttextdescript(ppt->textdescript, NOGEOM);
						defaulttextsize(1, ppt->textdescript);
						ppt->userbits = (ppt->userbits & ~STATEBITS) | type;
						endobjectchange((INTBIG)ppt, VPORTPROTO);
					}
				} else
				{
					/* just name the node */
					var = setvalkey((INTBIG)ni, VNODEINST, el_node_name_key, (INTBIG)thename,
						VSTRING|VDISPLAY);
					if (var != NOVARIABLE)
						defaulttextsize(3, var->textdescript);
					net_setnodewidth(ni);
				}
			}

			/* count the variables */
			varcount = 0;
			for(i=2; i<count; i += 2)
			{
				if (keywords[i][0] != '-') continue;
				if (namesame(keywords[i], "-origin") == 0 ||
					namesame(keywords[i], "-orient") == 0 ||
					namesame(keywords[i], "-type") == 0 ||
					namesame(keywords[i], "-name") == 0) continue;
				varcount++;
			}

			/* add variables */
			varindex = 1;
			for(i=2; i<count; i += 2)
			{
				if (keywords[i][0] != '-') continue;
				if (namesame(keywords[i], "-origin") == 0 ||
					namesame(keywords[i], "-orient") == 0 ||
					namesame(keywords[i], "-type") == 0 ||
					namesame(keywords[i], "-name") == 0) continue;
				varissize = FALSE;
				halvesize = FALSE;
				isparam = FALSE;
				if (namesame(&keywords[i][1], "w") == 0)
				{
					strcpy(suevarname, "ATTR_width");
					varissize = TRUE;
					xpos = 2;
					ypos = -4;
				} else if (namesame(&keywords[i][1], "l") == 0)
				{
					strcpy(suevarname, "ATTR_length");
					varissize = TRUE;
					xpos = -2;
					ypos = -4;
					halvesize = TRUE;
				} else
				{
					sprintf(suevarname, "ATTR_%s", &keywords[i][1]);
					for(pt = suevarname; *pt != 0; pt++) if (*pt == ' ')
					{
						ttyputmsg(_("Facet %s, line %ld, bad variable name (%s)"), 
							facetname, io_suelineno, suevarname);
						break;
					}
					xpos = 0;
					pos = (ni->highy - ni->lowy) / 2 - varindex * varoffset;
					ypos = pos * 4 / lambda;
					isparam = TRUE;
				}
				newtype = VSTRING;
				if (varissize)
				{
					newaddr = atola(keywords[i+1]) * WHOLE / lib->lambda[el_curtech->techindex];
					if (newaddr != 0) newtype = VFRACT; else
						newaddr = (INTBIG)keywords[i+1];
				} else
				{
					(void)initinfstr();
					if (keywords[i][1] == 'W' && keywords[i][2] != 0)
					{
						(void)addstringtoinfstr(&keywords[i][2]);
						(void)addstringtoinfstr(":");
					}
					(void)addstringtoinfstr(keywords[i+1]);
					newaddr = (INTBIG)returninfstr();
				}
				var = setval((INTBIG)ni, VNODEINST, suevarname, newaddr,
					newtype|VDISPLAY);
				if (var != NOVARIABLE)
				{
					defaulttextdescript(var->textdescript, ni->geom);
					varoffset = (ni->highy - ni->lowy) / (varcount+1);
					varindex++;
					TDSETOFF(var->textdescript, xpos, ypos);
					if (halvesize)
						TDSETSIZE(var->textdescript, TDGETSIZE(var->textdescript)/2);
					if (isparam)
					{
						TDSETISPARAM(var->textdescript, VTISPARAMETER);
						TDSETDISPPART(var->textdescript, VTDISPLAYNAMEVALUE);
					}
				}
			}
			continue;
		}

		/* handle "make_wire" for defining arcs */
		if (namesame(keywords[0], "make_wire") == 0)
		{
			sw = io_suenewwire();
			sw->x[0] = io_suemakex(atoi(keywords[1]), lambda);
			sw->y[0] = io_suemakey(atoi(keywords[2]), lambda);
			sw->x[1] = io_suemakex(atoi(keywords[3]), lambda);
			sw->y[1] = io_suemakey(atoi(keywords[4]), lambda);
			sw->nextsuewire = firstsuewire;
			firstsuewire = sw;
			continue;
		}

		/* handle "icon_term" for defining ports in icons */
		if (namesame(keywords[0], "icon_term") == 0)
		{
			io_sueparseparameters(&keywords[1], count-1, &x, &y, lambda, &rot, &trn,
				&type, &thename, &thelabel, &thetext);
			strcpy(suevarname, thename);

			proto = sch_buspinprim;
			defaultnodesize(proto, &px, &py);
			lx = x - px/2;   hx = lx + px;
			ly = y - py/2;   hy = ly + py;
			ni = newnodeinst(proto, lx, hx, ly, hy, trn, rot%3600, facet);
			if (ni == NONODEINST) continue;
			endobjectchange((INTBIG)ni, VNODEINST);

			ppt = io_suenewexport(facet, ni, proto->firstportproto, suevarname);
			if (ppt == NOPORTPROTO)
			{
				ttyputmsg(_("Facet %s, line %ld, could not create port %s"), 
					facetname, io_suelineno, suevarname);
			} else
			{
				defaulttextdescript(ppt->textdescript, NOGEOM);
				defaulttextsize(1, ppt->textdescript);
				ppt->userbits = (ppt->userbits & ~STATEBITS) | type;
				endobjectchange((INTBIG)ppt, VPORTPROTO);
			}
			continue;
		}

		/* handle "icon_arc" for defining icon curves */
		if (namesame(keywords[0], "icon_arc") == 0)
		{
			if (count != 9)
			{
				ttyputerr(_("Facet %s, line %ld: needs 9 arguments, has %ld: %s"),
					facetname, io_suelineno, count, io_sueorigline);
				continue;
			}
			start = 0;   extent = 359;
			p1x = io_suemakex(atoi(keywords[1]), lambda);
			p1y = io_suemakey(atoi(keywords[2]), lambda);
			p2x = io_suemakex(atoi(keywords[3]), lambda);
			p2y = io_suemakey(atoi(keywords[4]), lambda);
			if (namesame(keywords[5], "-start") == 0) start = atoi(keywords[6]);
			if (namesame(keywords[7], "-extent") == 0) extent = atoi(keywords[8]);
			lx = mini(p1x, p2x);   hx = maxi(p1x, p2x);
			ly = mini(p1y, p2y);   hy = maxi(p1y, p2y);

			ni = newnodeinst(art_circleprim, lx, hx, ly, hy, 0, 0, facet);
			if (ni == NONODEINST) continue;
			if (extent != 359)
			{
				if (extent < 0)
				{
					start += extent;
					extent = -extent;
				}
				rextent = extent+1;   rextent = rextent * EPI / 180.0;
				rstart = start * EPI / 180.0;
				setarcdegrees(ni, rstart, rextent);
			}
			endobjectchange((INTBIG)ni, VNODEINST);
			continue;
		}

		/* handle "icon_line" for defining icon outlines */
		if (namesame(keywords[0], "icon_line") == 0)
		{
			for(i=1; i<count; i++)
			{
				if ((i%2) != 0) outline[i-1] = io_suemakex(atoi(keywords[i]), lambda); else
					outline[i-1] = io_suemakey(atoi(keywords[i]), lambda);
			}
			keycount = count / 2;

			/* determine bounds of icon */
			lx = hx = outline[0];
			ly = hy = outline[1];
			for(i=1; i<keycount; i++)
			{
				if (outline[i*2]   < lx) lx = outline[i*2];
				if (outline[i*2]   > hx) hx = outline[i*2];
				if (outline[i*2+1] < ly) ly = outline[i*2+1];
				if (outline[i*2+1] > hy) hy = outline[i*2+1];
			}
			ni = newnodeinst(art_openedpolygonprim, lx, hx, ly, hy, 0, 0, facet);
			if (ni == NONODEINST) return(NONODEPROTO);
			cx = (lx + hx) / 2;  cy = (ly + hy) / 2;
			for(i=0; i<keycount; i++)
			{
				outline[i*2] -= cx;   outline[i*2+1] -= cy;
			}
			(void)setvalkey((INTBIG)ni, VNODEINST, el_trace_key, (INTBIG)outline,
				VINTEGER|VISARRAY|((keycount*2)<<VLENGTHSH));
			endobjectchange((INTBIG)ni, VNODEINST);
			continue;
		}

		/* handle "icon_setup" for defining variables */
		if (namesame(keywords[0], "icon_setup") == 0)
		{
			/* extract parameters */
			if (namesame(keywords[1], "$args") != 0)
			{
				ttyputerr(_("Facet %s, line %ld: has unrecognized 'icon_setup'"),
					facetname, io_suelineno);
				continue;
			}
			pt = keywords[2];
			if (*pt == '{') pt++;
			for(;;)
			{
				while (*pt == ' ') pt++;
				if (*pt == '}' || *pt == 0) break;

				/* collect up to a space or close curly */
				startkey = pt;
				curly = 0;
				for(;;)
				{
					if (curly == 0)
					{
						if (*pt == 0 || *pt == ' ' || *pt == '}') break;
					}
					if (*pt == '{') curly++;
					if (*pt == '}') curly--;
					if (*pt == 0) break;
					pt++;
				}
				save = *pt;
				*pt = 0;

				/* parse the keyword pair in "startkey" */
				i = numargs+1;
				newargnames = (char **)emalloc(i * (sizeof (char *)), el_tempcluster);
				newargvalues = (char **)emalloc(i * (sizeof (char *)), el_tempcluster);
				for(i=0; i<numargs; i++)
				{
					/* LINTED "argnames" used in proper order */
					newargnames[i] = argnames[i];

					/* LINTED "argvalues" used in proper order */
					newargvalues[i] = argvalues[i];
				}
				if (numargs > 0)
				{
					efree((char *)argnames);
					efree((char *)argvalues);
				}
				argnames = newargnames;
				argvalues = newargvalues;
				startkey++;
				for(cpt = startkey; *cpt != 0; cpt++) if (*cpt == ' ') break;
				if (*cpt != 0) *cpt++ = 0;
				(void)allocstring(&argnames[numargs], startkey, el_tempcluster);
				while (*cpt == ' ') cpt++;
				if (*cpt == '{') cpt++;
				startkey = cpt;
				for(cpt = startkey; *cpt != 0; cpt++) if (*cpt == '}') break;
				if (*cpt != 0) *cpt++ = 0;
				(void)allocstring(&argvalues[numargs], startkey, el_tempcluster);
				numargs++;

				*pt = save;
			}
			continue;
		}

		/* handle "icon_property" for defining icon strings */
		if (namesame(keywords[0], "icon_property") == 0)
		{
			/* extract parameters */
			io_sueparseparameters(&keywords[1], count-1, &x, &y, lambda, &rot, &trn,
				&type, &thename, &thelabel, &thetext);
			if (thelabel == 0) continue;

			/* substitute parameters */
			(void)initinfstr();
			for(pt = thelabel; *pt != 0; pt++)
			{
				if (*pt == '$')
				{
					for(i=0; i<numargs; i++)
						if (namesamen(&pt[1], argnames[i], strlen(argnames[i])) == 0) break;
					if (i < numargs)
					{
						(void)addstringtoinfstr(argvalues[i]);
						pt += strlen(argnames[i]);
						continue;
					}
				}
				(void)addtoinfstr(*pt);
			}
			thelabel = returninfstr();

			ni = newnodeinst(gen_invispinprim, x, x, y, y, 0, 0, facet);
			if (ni == NONODEINST) continue;
			var = setvalkey((INTBIG)ni, VNODEINST, art_messagekey, (INTBIG)thelabel, VSTRING|VDISPLAY);
			if (var != NOVARIABLE)
			{
				defaulttextsize(2, var->textdescript);
				TDSETPOS(var->textdescript, VTPOSCENT);
			}
			endobjectchange((INTBIG)ni, VNODEINST);
			continue;
		}

		/* handle "make_text" for placing strings */
		if (namesame(keywords[0], "make_text") == 0)
		{
			/* extract parameters */
			io_sueparseparameters(&keywords[1], count-1, &x, &y, lambda, &rot, &trn,
				&type, &thename, &thelabel, &thetext);
			if (thetext == 0) continue;

			ni = newnodeinst(gen_invispinprim, x, x, y, y, 0, 0, facet);
			if (ni == NONODEINST) continue;
			var = setvalkey((INTBIG)ni, VNODEINST, art_messagekey, (INTBIG)thetext, VSTRING|VDISPLAY);
			if (var != NOVARIABLE)
			{
				defaulttextsize(2, var->textdescript);
				TDSETPOS(var->textdescript, VTPOSCENT);
			}
			endobjectchange((INTBIG)ni, VNODEINST);
			continue;
		}

		/* ignore known keywords */
		if (namesame(keywords[0], "icon_title") == 0 ||
			namesame(keywords[0], "make_line") == 0 ||
			namesame(keywords[0], "}") == 0)
		{
			continue;
		}

		ttyputerr(_("Facet %s, line %ld: unknown keyword (%s): %s"), facetname,
			io_suelineno, keywords[0], io_sueorigline);
	}

	/* place an icon instance in the schematic if requested */
	if (placeicon && schemfacet != NONODEPROTO &&
		iconfacet != NONODEPROTO)
	{
		px = iconfacet->highx - iconfacet->lowx;
		py = iconfacet->highy - iconfacet->lowy;
		lx = iconx - px/2;   hx = lx + px;
		ly = icony - py/2;   hy = ly + py;
		ni = newnodeinst(iconfacet, lx, hx, ly, hy, 0, 0, schemfacet);
		if (ni != NONODEINST)
		{
			ni->userbits |= NEXPAND;
			endobjectchange((INTBIG)ni, VNODEINST);
		}
	}

	for(i=0; i<numargs; i++)
	{
		efree(argnames[i]);
		efree(argvalues[i]);
	}
	if (numargs > 0)
	{
		efree((char *)argnames);
		efree((char *)argvalues);
	}
	if (namestrlen > 0) efree(namestring);

	/* cleanup the current facet */
	if (facet != NONODEPROTO)
	{
		io_sueplacewires(firstsuewire, firstsuenet, facet, lambda);
		io_suefreewires(firstsuewire);
		io_sueplacenets(firstsuenet, facet);
		io_suefreenets(firstsuenet);
	}

	/* make sure facets are the right size */
	if (schemfacet != NONODEPROTO) (*el_curconstraint->solve)(schemfacet);
	if (iconfacet != NONODEPROTO) (*el_curconstraint->solve)(iconfacet);

	/* return the facet */
	if (schemfacet != NONODEPROTO) return(schemfacet);
	return(iconfacet);

}

/*
 * Routine to parse the "count" parameters in "keywords" and fill in the values
 * that are found.  Fills in:
 * "-origin"  placed into "x" and "y"
 * "-orient"  placed into "rot" and "trn"
 * "-type"    placed into "type"
 * "-name"    placed into "thename".
 * "-label"   placed into "thelabel".
 * "-text"    placed into "thetext".
 */
void io_sueparseparameters(char **keywords, INTBIG count, INTBIG *x, INTBIG *y, INTBIG lambda,
	INTBIG *rot, INTBIG *trn, INTBIG *type, char **thename, char **thelabel, char **thetext)
{
	char *pt;
	REGISTER INTBIG textloc, i;

	*x = *y = 0;
	*rot = 0;
	*trn = 0;
	*type = 0;
	*thename = 0;
	*thelabel = 0;
	*thetext = 0;
	for(i=0; i<count; i += 2)
	{
		if (namesame(keywords[i], "-origin") == 0)
		{
			pt = keywords[i+1];
			if (*pt == '{') pt++;
			*x = atoi(pt);
			while (*pt != ' ' && *pt != '\t' && *pt != 0) pt++;
			while (*pt == ' ' || *pt == '\t') pt++;
			*y = atoi(pt);
		}
		if (namesame(keywords[i], "-orient") == 0)
		{
			if (namesame(keywords[i+1], "R90")  == 0) { *rot = 900;  } else
			if (namesame(keywords[i+1], "R270") == 0) { *rot = 2700; } else
			if (namesame(keywords[i+1], "RXY")  == 0) { *rot = 1800; } else
			if (namesame(keywords[i+1], "RY")   == 0) { *rot = 900;  *trn = 1; } else
			if (namesame(keywords[i+1], "R90X") == 0) { *rot = 0;    *trn = 1; } else
			if (namesame(keywords[i+1], "R90Y") == 0) { *rot = 1800; *trn = 1; } else
			if (namesame(keywords[i+1], "RX")   == 0) { *rot = 2700; *trn = 1; }
		}
		if (namesame(keywords[i], "-type") == 0)
		{
			if (namesame(keywords[i+1], "input") == 0) *type = INPORT; else
			if (namesame(keywords[i+1], "output") == 0) *type = OUTPORT; else
			if (namesame(keywords[i+1], "inout") == 0) *type = BIDIRPORT;
		}
		if (namesame(keywords[i], "-name") == 0 ||
			namesame(keywords[i], "-label") == 0 ||
			namesame(keywords[i], "-text") == 0)
		{
			if (namesame(keywords[i], "-name") == 0) textloc = 0; else
				if (namesame(keywords[i], "-label") == 0) textloc = 1; else
					textloc = 2;
			(void)initinfstr();
			pt = keywords[i+1];
			if (pt[0] != '{') (void)addstringtoinfstr(pt); else
			{
				pt++;
				for(;;)
				{
					while (*pt != 0)
					{
						if (*pt == '}') break;
						(void)addtoinfstr(*pt);
						pt++;
					}
					if (*pt == '}') break;
					(void)addtoinfstr(' ');

					count = io_suegetnextline(keywords, 1);
					if (count < 0) break;
					if (count == 0) continue;
					pt = keywords[0];
					i = -1;
				}
			}
			switch (textloc)
			{
				case 0: *thename = returninfstr();    break;
				case 1: *thelabel = returninfstr();   break;
				case 2: *thetext = returninfstr();    break;
			}
		}
	}
	*x = io_suemakex(*x, lambda);
	*y = io_suemakey(*y, lambda);
	*rot = (3600 - *rot) % 3600;
}

/*
 * Routine to find facet "protoname" in library "lib".
 */
NODEPROTO *io_suegetnodeproto(LIBRARY *lib, char *protoname)
{
	REGISTER CELL *c;

	for(c = lib->firstcell; c != NOCELL; c = c->nextcell)
		if (namesame(protoname, c->cellname) == 0) break;
	if (c == NOCELL) return(NONODEPROTO);
	return(c->firstincell);
}

/*
 * Routine to create a port called "thename" on port "pp" of node "ni" in facet "facet".
 * The name is modified if it already exists.
 */
PORTPROTO *io_suenewexport(NODEPROTO *facet, NODEINST *ni, PORTPROTO *pp, char *thename)
{
	REGISTER PORTPROTO *ppt;
	REGISTER INTBIG i;
	REGISTER char *portname, *pt;
	char numbuf[20];

	portname = thename;
	for(i=0; ; i++)
	{
		for(ppt = facet->firstportproto; ppt != NOPORTPROTO; ppt = ppt->nextportproto)
			if (namesame(portname, ppt->protoname) == 0) break;
		if (ppt == NOPORTPROTO)
		{
			ppt = newportproto(facet, ni, pp, portname);
			break;
		}

		/* make space for modified name */
		(void)initinfstr();
		for(pt = thename; *pt != 0 && *pt != '['; pt++)
			(void)addtoinfstr(*pt);
		sprintf(numbuf, "-%ld", i);
		(void)addstringtoinfstr(numbuf);
		for( ; *pt != 0; pt++)
			(void)addtoinfstr(*pt);
		portname = returninfstr();
	}
	return(ppt);
}

/* Routine to convert SUE X coordinate "x" to Electric coordinates */
INTBIG io_suemakex(INTBIG x, INTBIG lambda)
{
	return(x * lambda / 8);
}

/* Routine to convert SUE Y coordinate "y" to Electric coordinates */
INTBIG io_suemakey(INTBIG y, INTBIG lambda)
{
	return(-y * lambda / 8);
}

/*
 * Routine to place all SUE nets into the facet (they are in a linked
 * list headed by "firstsuenet").
 */
void io_sueplacenets(SUENET *firstsuenet, NODEPROTO *facet)
{
	SUENET *sn;
	REGISTER INTBIG pass;
	REGISTER BOOLEAN isbus;
	REGISTER ARCINST *ai, *bestai;
	REGISTER INTBIG cx, cy, dist, bestdist, sea;
	REGISTER GEOM *geom;
	REGISTER char *netname, *busname;

	/* 3 passes: qualified labels, unqualified busses, unqualified wires */
	for(pass=0; pass<3; pass++)
	{
		for(sn = firstsuenet; sn != NOSUENET; sn = sn->nextsuenet)
		{
			/* unqualified labels (starting with "[") happen second */
			if (*sn->label == '[')
			{
				/* unqualified label: pass 2 or 3 only */
				if (pass == 0) continue;
			} else
			{
				/* qualified label: pass 1 only */
				if (pass != 0) continue;
			}

			/* see if this is a bus */
			if (net_buswidth(sn->label) > 1) isbus = TRUE; else isbus = FALSE;

			sea = initsearch(sn->x, sn->x, sn->y, sn->y, facet);
			bestai = NOARCINST;
			for(;;)
			{
				geom = nextobject(sea);
				if (geom == NOGEOM) break;
				if (geom->entryisnode) continue;
				ai = geom->entryaddr.ai;
				if (isbus)
				{
					if (ai->proto != sch_busarc) continue;
				} else
				{
					if (ai->proto == sch_busarc) continue;
				}
				cx = (ai->end[0].xpos + ai->end[1].xpos) / 2;
				cy = (ai->end[0].ypos + ai->end[1].ypos) / 2;
				dist = computedistance(cx, cy, sn->x, sn->y);

				/* LINTED "bestdist" used in proper order */
				if (bestai == NOARCINST || dist < bestdist)
				{
					bestai = ai;
					bestdist = dist;
				}
			}
			if (bestai != NOARCINST)
			{
				if (pass == 1)
				{
					/* only allow busses */
					if (bestai->proto != sch_busarc) continue;
				} else if (pass == 2)
				{
					/* disallow busses */
					if (bestai->proto == sch_busarc) continue;
				}
				netname = sn->label;
				if (*netname == '[')
				{
					/* find the proper name of the network */
					busname = io_suefindbusname(bestai);
					if (busname != 0)
					{
						(void)initinfstr();
						(void)addstringtoinfstr(busname);
						(void)addstringtoinfstr(netname);
						netname = returninfstr();
					}
				}
				us_setarcname(bestai, netname);
			}
		}
	}
}

/*
 * Routine to start at "ai" and search all wires until it finds a named bus.
 * Returns zero if no bus name is found.
 */
char *io_suefindbusname(ARCINST *ai)
{
	REGISTER ARCINST *oai;
	REGISTER char *busname, *pt;
	REGISTER VARIABLE *var;
	static char pseudobusname[50];
	REGISTER INTBIG index, len;

	for(oai = ai->parent->firstarcinst; oai != NOARCINST; oai = oai->nextarcinst)
		oai->temp1 = 0;
	busname = io_suesearchbusname(ai);
	if (busname == 0)
	{
		for(index=1; ; index++)
		{
			sprintf(pseudobusname, "NET%ld", index);
			len = strlen(pseudobusname);
			for(oai = ai->parent->firstarcinst; oai != NOARCINST; oai = oai->nextarcinst)
			{
				var = getvalkey((INTBIG)oai, VARCINST, VSTRING, el_arc_name_key);
				if (var == NOVARIABLE) continue;
				pt = (char *)var->addr;
				if (namesame(pseudobusname, pt) == 0) break;
				if (namesamen(pseudobusname, pt, len) == 0 &&
					pt[len] == '[') break;
			}
			if (oai == NOARCINST) break;
		}
		busname = pseudobusname;
	}
	return(busname);
}

char *io_suesearchbusname(ARCINST *ai)
{
	REGISTER ARCINST *oai;
	REGISTER char *busname;
	REGISTER INTBIG i;
	REGISTER NODEINST *ni;
	REGISTER PORTARCINST *pi;
	REGISTER PORTEXPINST *pe;
	REGISTER PORTPROTO *pp;
	REGISTER VARIABLE *var;

	ai->temp1 = 1;
	if (ai->proto == sch_busarc)
	{
		var = getvalkey((INTBIG)ai, VARCINST, VSTRING, el_arc_name_key);
		if (var != NOVARIABLE)
		{
			(void)initinfstr();
			for(busname = (char *)var->addr; *busname != 0; busname++)
			{
				if (*busname == '[') break;
				(void)addtoinfstr(*busname);
			}
			return(returninfstr());
		}
	}
	for(i=0; i<2; i++)
	{
		ni = ai->end[i].nodeinst;
		if (ni->proto != sch_wirepinprim && ni->proto != sch_buspinprim &&
			ni->proto != sch_offpageprim) continue;
		if (ni->proto == sch_buspinprim || ni->proto == sch_offpageprim)
		{
			/* see if there is an arrayed port here */
			for(pe = ni->firstportexpinst; pe != NOPORTEXPINST; pe = pe->nextportexpinst)
			{
				pp = pe->exportproto;
				for(busname = pp->protoname; *busname != 0; busname++)
					if (*busname == '[') break;
				if (*busname != 0)
				{
					(void)initinfstr();
					for(busname = pp->protoname; *busname != 0; busname++)
					{
						if (*busname == '[') break;
						(void)addtoinfstr(*busname);
					}
					return(returninfstr());
				}
			}
		}
		for(pi = ni->firstportarcinst; pi != NOPORTARCINST; pi = pi->nextportarcinst)
		{
			oai = pi->conarcinst;
			if (oai->temp1 != 0) continue;
			busname = io_suesearchbusname(oai);
			if (busname != 0) return(busname);
		}
	}
	return(0);
}

/*
 * Routine to place all SUE wires into the facet (they are in a linked
 * list headed by "firstsuewire").
 */
void io_sueplacewires(SUEWIRE *firstsuewire, SUENET *firstsuenet, NODEPROTO *facet, INTBIG lambda)
{
	SUEWIRE *sw, *osw;
	SUENET *sn;
	REGISTER INTBIG i, j, wid, px, py, lx, hx, ly, hy, sea, bits;
	REGISTER BOOLEAN propagatedbus, isbus;
	INTBIG xsize, ysize, x, y, ox, oy;
	REGISTER NODEPROTO *proto;
	REGISTER GEOM *geom;
	REGISTER PORTEXPINST *pe;
	NODEINST *ni;
	REGISTER ARCPROTO *ap;
	REGISTER NODEINST *bottomni, *oni;
	PORTPROTO *pp;
	REGISTER PORTPROTO *bottompp, *opp;
	REGISTER ARCINST *ai;

	/* mark all wire ends as "unassigned", all wire types as unknown */
	for(sw = firstsuewire; sw != NOSUEWIRE; sw = sw->nextsuewire)
	{
		sw->ni[0] = sw->ni[1] = NONODEINST;
		sw->proto = NOARCPROTO;
	}

	/* examine all network names and assign wire types appropriately */
	for(sn = firstsuenet; sn != NOSUENET; sn = sn->nextsuenet)
	{
		for(sw = firstsuewire; sw != NOSUEWIRE; sw = sw->nextsuewire)
		{
			for(i=0; i<2; i++)
			{
				if (sw->x[i] == sn->x && sw->y[i] == sn->y)
				{
					if (net_buswidth(sn->label) > 1) sw->proto = sch_busarc; else
						sw->proto = sch_wirearc;
				}
			}
		}
	}

	/* find connections that are exactly on existing nodes */
	for(sw = firstsuewire; sw != NOSUEWIRE; sw = sw->nextsuewire)
	{
		for(i=0; i<2; i++)
		{
			if (sw->ni[i] != NONODEINST) continue;
			for(ni = facet->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
			{
				pp = io_suewiredport(ni, &sw->x[i], &sw->y[i], sw->x[1-i], sw->y[1-i]);
				if (pp == NOPORTPROTO) continue;
				sw->ni[i] = ni;
				sw->pp[i] = pp;

				/* determine whether this port is a bus */
				isbus = FALSE;
				bottomni = ni;   bottompp = pp;
				while (bottomni->proto->primindex == 0)
				{
					bottomni = bottompp->subnodeinst;
					bottompp = bottompp->subportproto;
				}
				if (bottomni->proto == sch_wireconprim) continue;
				if (bottomni->proto == sch_buspinprim) isbus = TRUE;
				if (!isbus && ni->proto == sch_offpageprim)
				{
					/* see if there is a bus port on this primitive */
					for(pe = ni->firstportexpinst; pe != NOPORTEXPINST; pe = pe->nextportexpinst)
					{
						if (net_buswidth(pe->exportproto->protoname) > 1) isbus = TRUE;
					}
				}

				if (isbus)
				{
					sw->proto = sch_busarc;
				} else
				{
					if (sw->proto == NOARCPROTO)
						sw->proto = sch_wirearc;
				}
			}
		}
	}

	/* now iteratively extend bus wires to connections with others */
	propagatedbus = TRUE;
	while (propagatedbus)
	{
		propagatedbus = FALSE;
		for(sw = firstsuewire; sw != NOSUEWIRE; sw = sw->nextsuewire)
		{
			if (sw->proto != sch_busarc) continue;
			for(osw = firstsuewire; osw != NOSUEWIRE; osw = osw->nextsuewire)
			{
				if (osw->proto != NOARCPROTO) continue;
				for(i=0; i<2; i++)
				{
					for(j=0; j<2; j++)
					{
						if (sw->x[i] == osw->x[j] && sw->y[i] == osw->y[j])
						{
							/* common point found: continue the bus request */
							osw->proto = sch_busarc;
							propagatedbus = TRUE;
						}
					}
				}
			}
		}
	}

	/* now make pins where wires meet */
	for(sw = firstsuewire; sw != NOSUEWIRE; sw = sw->nextsuewire)
	{
		for(i=0; i<2; i++)
		{
			if (sw->ni[i] != NONODEINST) continue;
			if (sw->proto == sch_busarc) proto = sch_buspinprim; else
				proto = sch_wirepinprim;

			/* look at all other wires at this point and figure out type of pin to make */
			for(osw = firstsuewire; osw != NOSUEWIRE; osw = osw->nextsuewire)
			{
				if (osw == sw) continue;
				for(j=0; j<2; j++)
				{
					if (sw->x[i] != osw->x[j] || sw->y[i] != osw->y[j]) continue;
					if (osw->ni[j] != NONODEINST)
					{
						sw->ni[i] = osw->ni[j];
						sw->pp[i] = osw->pp[j];
						break;
					}
					if (osw->proto == sch_busarc) proto = sch_buspinprim;
				}
				if (sw->ni[i] != NONODEINST) break;
			}

			/* make the pin if it doesn't exist */
			if (sw->ni[i] == NONODEINST)
			{
				/* common point found: make a pin */
				defaultnodesize(proto, &xsize , &ysize);
				sw->ni[i] = newnodeinst(proto, sw->x[i] - xsize/2,
					sw->x[i] + xsize/2, sw->y[i] - ysize/2,
					sw->y[i] + ysize/2, 0, 0, facet);
				endobjectchange((INTBIG)sw->ni[i], VNODEINST);
				sw->pp[i] = proto->firstportproto;
			}

			/* put that node in all appropriate locations */
			for(osw = firstsuewire; osw != NOSUEWIRE; osw = osw->nextsuewire)
			{
				if (osw == sw) continue;
				for(j=0; j<2; j++)
				{
					if (sw->x[i] != osw->x[j] || sw->y[i] != osw->y[j]) continue;
					if (osw->ni[j] != NONODEINST) continue;
					osw->ni[j] = sw->ni[i];
					osw->pp[j] = sw->pp[i];
				}
			}
		}
	}

	/* make pins at all of the remaining wire ends */
	for(sw = firstsuewire; sw != NOSUEWIRE; sw = sw->nextsuewire)
	{
		for(i=0; i<2; i++)
		{
			if (sw->ni[i] != NONODEINST) continue;
			if (!io_suefindnode(&sw->x[i], &sw->y[i], sw->x[1-i], sw->y[1-i], facet,
				&sw->ni[i], &sw->pp[i], sw->ni[1-i], lambda))
			{
				if (sw->proto == sch_busarc) proto = sch_buspinprim; else
					proto = sch_wirepinprim;
				defaultnodesize(proto, &xsize , &ysize);
				sw->ni[i] = newnodeinst(proto, sw->x[i] - xsize/2,
					sw->x[i] + xsize/2, sw->y[i] - ysize/2, sw->y[i] + ysize/2,
					0, 0, facet);
				endobjectchange((INTBIG)sw->ni[i], VNODEINST);
				sw->pp[i] = sw->ni[i]->proto->firstportproto;
			}
		}
	}

	/* now make the connections */
	for(sw = firstsuewire; sw != NOSUEWIRE; sw = sw->nextsuewire)
	{
		if (sw->proto == NOARCPROTO) sw->proto = sch_wirearc;
		wid = defaultarcwidth(sw->proto);

		/* if this is a bus, make sure it can connect */
		if (sw->proto == sch_busarc)
		{
			for(i=0; i<2; i++)
			{
				for(j=0; sw->pp[i]->connects[j] != NOARCPROTO; j++)
					if (sw->pp[i]->connects[j] == sch_busarc) break;
				if (sw->pp[i]->connects[j] == NOARCPROTO)
				{
					/* this end cannot connect: fake the connection */
					px = (sw->x[0] + sw->x[1]) / 2;
					py = (sw->y[0] + sw->y[1]) / 2;
					defaultnodesize(sch_buspinprim, &xsize , &ysize);
					lx = px - xsize/2;   hx = lx + xsize;
					ly = py - ysize/2;   hy = ly + ysize;
					ni = newnodeinst(sch_buspinprim, lx, hx, ly, hy, 0, 0, facet);
					if (ni == NONODEINST) break;
					endobjectchange((INTBIG)ni, VNODEINST);
					pp = ni->proto->firstportproto;
					ai = newarcinst(gen_unroutedarc, defaultarcwidth(gen_unroutedarc),
						us_makearcuserbits(gen_unroutedarc), ni, pp, px, py,
							sw->ni[i], sw->pp[i], sw->x[i], sw->y[i], facet);
					if (ai == NOARCINST)
					{
						ttyputerr(_("Error making fake connection"));
						break;
					}
					endobjectchange((INTBIG)ai, VARCINST);
					sw->ni[i] = ni;
					sw->pp[i] = pp;
					sw->x[i] = px;
					sw->y[i] = py;
				}
			}
		}

		ai = newarcinst(sw->proto, wid, us_makearcuserbits(sw->proto),
			sw->ni[0], sw->pp[0], sw->x[0], sw->y[0],
			sw->ni[1], sw->pp[1], sw->x[1], sw->y[1], facet);
		if (ai == NOARCINST)
		{
			ttyputerr(_("Error placing wire"));
			continue;
		}

		/* negate the wire if requested */
		if (sw->ni[0]->temp1 != 0 &&
			strcmp(sw->pp[0]->protoname, "y") == 0)
		{
			ai->userbits |= ISNEGATED;
		} else if (sw->ni[1]->temp1 != 0 &&
			strcmp(sw->pp[1]->protoname, "y") == 0)
		{
			ai->userbits |= ISNEGATED | REVERSEEND;
		}
		endobjectchange((INTBIG)ai, VARCINST);
	}

	/* now look for implicit connections where "offpage" connectors touch */
	for(ni = facet->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		if (ni->proto != sch_offpageprim) continue;
		if (ni->firstportarcinst != NOPORTARCINST) continue;
		pp = ni->proto->firstportproto->nextportproto;
		portposition(ni, pp, &x, &y);
		sea = initsearch(x, x, y, y, facet);
		for(;;)
		{
			geom = nextobject(sea);
			if (geom == NOGEOM) break;
			if (!geom->entryisnode) continue;
			oni = geom->entryaddr.ni;
			if (oni == ni) continue;
			for(opp = oni->proto->firstportproto; opp != NOPORTPROTO; opp = opp->nextportproto)
			{
				portposition(oni, opp, &ox, &oy);
				if (ox != x || oy != y) continue;
				for(i=0; i<3; i++)
				{
					switch (i)
					{
						case 0: ap = sch_busarc;      break;
						case 1: ap = sch_wirearc;     break;
						case 2: ap = gen_unroutedarc; break;
					}
					for(j=0; pp->connects[j] != NOARCPROTO; j++)
						if (pp->connects[j] == ap) break;
					if (pp->connects[j] == NOARCPROTO) continue;
					for(j=0; opp->connects[j] != NOARCPROTO; j++)
						if (opp->connects[j] == ap) break;
					if (opp->connects[j] == NOARCPROTO) continue;
					break;
				}

				wid = defaultarcwidth(ap);
				bits = us_makearcuserbits(ap);
				ai = newarcinst(ap, wid, bits, ni, pp, x, y, oni, opp, x, y, facet);
				if (ai != NOARCINST)
					endobjectchange((INTBIG)ai, VARCINST);
				break;
			}
			if (opp != NOPORTPROTO) break;
		}
	}
}

/*
 * Routine to find the port on node "ni" that attaches to the wire from (x,y) to (ox,oy).
 * Returns NOPORTPROTO if not found.
 */
PORTPROTO *io_suewiredport(NODEINST *ni, INTBIG *x, INTBIG *y, INTBIG ox, INTBIG oy)
{
	REGISTER PORTPROTO *pp, *bestpp;
	REGISTER INTBIG dist, bestdist;
	INTBIG px, py;
	static POLYGON *poly = NOPOLYGON;

	/* make sure there is a polygon */
	if (poly == NOPOLYGON) poly = allocstaticpolygon(4, io_tool->cluster);
	for(pp = ni->proto->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
	{
		shapeportpoly(ni, pp, poly, FALSE);
		if (isinside(*x, *y, poly)) return(pp);
	}
	if ((ni->lowx+ni->highx) / 2 != *x ||
		(ni->lowy+ni->highy) / 2 != *y) return(NOPORTPROTO);

	/* find port that is closest to OTHER end */
	bestdist = MAXINTBIG;
	bestpp = NOPORTPROTO;
	for(pp = ni->proto->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
	{
		portposition(ni, pp, &px, &py);
		dist = computedistance(px, py, ox, oy);
		if (dist > bestdist) continue;
		bestdist = dist;
		bestpp = pp;
	}
	portposition(ni, bestpp, x, y);
	return(bestpp);
}

/*
 * Routine to find the pin at (x, y) and return it.
 */
NODEINST *io_suefindpinnode(INTBIG x, INTBIG y, NODEPROTO *np, PORTPROTO **thepp)
{
	REGISTER GEOM *geom;
	REGISTER INTBIG sea;
	REGISTER NODEINST *ni;
	REGISTER PORTPROTO *pp;
	INTBIG px, py;

	*thepp = NOPORTPROTO;
	sea = initsearch(x, x, y, y, np);
	for(;;)
	{
		geom = nextobject(sea);
		if (geom == NOGEOM) break;
		if (!geom->entryisnode) continue;
		ni = geom->entryaddr.ni;

		/* find closest port */
		for(pp = ni->proto->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
		{
			/* make sure there is a polygon */
			portposition(ni, pp, &px, &py);
			if (px == x && py == y)
			{
				*thepp = pp;
				termsearch(sea);
				return(ni);
			}
		}
	}
	return(NONODEINST);
}

/*
 * Routine to find the node at (x, y) and return it.
 */
BOOLEAN io_suefindnode(INTBIG *x, INTBIG *y, INTBIG ox, INTBIG oy,
	NODEPROTO *np, NODEINST **rni, PORTPROTO **rpp, NODEINST *notthisnode, INTBIG lambda)
{
	REGISTER GEOM *geom;
	REGISTER INTBIG sea;
	REGISTER NODEINST *ni, *bestni;
	INTBIG plx, phx, ply, phy;
	REGISTER INTBIG dist, bestdist, thisx, thisy, bestx, besty, slop;
	REGISTER PORTPROTO *pp, *bestpp;
	static POLYGON *poly = NOPOLYGON;

	slop = lambda * 10;
	sea = initsearch(*x-slop, *x+slop, *y-slop, *y+slop, np);
	bestpp = NOPORTPROTO;
	for(;;)
	{
		geom = nextobject(sea);
		if (geom == NOGEOM) break;
		if (!geom->entryisnode) continue;
		ni = geom->entryaddr.ni;
		if (ni == notthisnode) continue;

		/* ignore pins */
		if (ni->proto == sch_wirepinprim) continue;

		/* find closest port */
		for(pp = ni->proto->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
		{
			/* make sure there is a polygon */
			if (poly == NOPOLYGON) poly = allocstaticpolygon(4, io_tool->cluster);

			/* get the polygon describing the port */
			shapeportpoly(ni, pp, poly, FALSE);
			getbbox(poly, &plx, &phx, &ply, &phy);

			/* find out if the line crosses the polygon */
			if (*x == ox)
			{
				/* line is vertical: look for intersection with polygon */
				if (ox < plx || ox > phx) continue;
				thisx = ox;
				thisy = (ply+phy)/2;
			} else if (*y == oy)
			{
				/* line is horizontal: look for intersection with polygon */
				if (oy < ply || oy > phy) continue;
				thisx = (plx+phx)/2;
				thisy = oy;
			} else
			{
				if (!isinside(ox, oy, poly)) continue;
				thisx = ox;
				thisy = oy;
			}

			dist = computedistance(ox, oy, thisx, thisy);

			/* LINTED "bestdist" used in proper order */
			if (bestpp == NOPORTPROTO || dist < bestdist)
			{
				bestpp = pp;
				bestni = ni;
				bestdist = dist;
				bestx = thisx;
				besty = thisy;
			}
		}
	}

	/* report the hit */
	if (bestpp == NOPORTPROTO) return(FALSE);
	*rni = bestni;   *rpp = bestpp;
	*x = bestx;      *y = besty;
	return(TRUE);
}

/*
 * Routine to find the SUE file "name" on disk, and read it into library "lib".
 * Returns NONODEPROTO if the file is not found or not read properly.
 */
NODEPROTO *io_suereadfromdisk(LIBRARY *lib, char *name)
{
	REGISTER INTBIG i, savelineno, filepos, savefilesize;
	char suevarname[200], subfilename[300], savesuelastline[MAXLINE], saveorigline[MAXLINE],
		lastprogressmsg[MAXLINE], savecurline[MAXLINE], *truename;
	REGISTER FILE *f, *savefp;
	REGISTER NODEPROTO *proto;

	/* look for another "sue" file that describes this facet */
	for(i=0; i<io_suenumdirectories; i++)
	{
		strcpy(subfilename, io_suedirectories[i]);
		strcat(subfilename, name);
		strcat(subfilename, ".sue");
		f = xopen(subfilename, io_filetypesue, "", &truename);
		if (f != 0)
		{
			savefp = io_suefilein;
			io_suefilein = f;
			for(i=0; i<MAXLINE; i++) savecurline[i] = io_suecurline[i];
			strcpy(saveorigline, io_sueorigline);
			strcpy(savesuelastline, io_suelastline);
			strcpy(lastprogressmsg, DiaGetText(2));
			strcpy(suevarname, _("Reading "));
			strcat(suevarname, name);
			strcat(suevarname, "...");
			DiaSetText(2, suevarname);
			DiaPercent(1, 0);

			strcpy(subfilename, name);
			savefilesize = io_suefilesize;
			savelineno = io_suelineno;
			io_suefilesize = filesize(io_suefilein);
			io_suelineno = 0;
			(void)io_suereadfile(lib, subfilename);
			xclose(io_suefilein);
			io_suefilein = savefp;
			io_suefilesize = savefilesize;
			io_suelineno = savelineno;
			strcpy(io_suelastline, savesuelastline);
			strcpy(io_sueorigline, saveorigline);
			for(i=0; i<MAXLINE; i++) io_suecurline[i] = savecurline[i];
			filepos = xtell(io_suefilein);
			DiaSetText(2, lastprogressmsg);
			DiaPercent(1, filepos*100/io_suefilesize);

			/* now try to find the facet in the library */
			proto = io_suegetnodeproto(lib, subfilename);
			return(proto);
		}
	}
	return(NONODEPROTO);
}

/*
 * Routine to read the next line from file "io_suefilein" and break
 * it up into space-separated keywords.  Returns the number
 * of keywords (-1 on EOF)
 */
INTBIG io_suegetnextline(char **keywords, INTBIG curlydepth)
{
	char *pt;
	REGISTER INTBIG filepos, keypos, lineno;
	REGISTER BOOLEAN inblank;

	for(lineno=0; ; lineno++)
	{
		if (io_suelastline[0] == 0)
		{
			if (xfgets(io_suelastline, MAXLINE, io_suefilein)) return(-1);
			io_suelineno++;
			if ((io_suelineno%50) == 0)
			{
				filepos = xtell(io_suefilein);
				DiaPercent(1, filepos*100/io_suefilesize);
			}
		}
		if (lineno == 0)
		{
			/* first line: use it */
			strcpy(io_suecurline, io_suelastline);
		} else
		{
			/* subsequent line: use it only if a continuation */
			if (io_suelastline[0] != '+') break;
			strcat(io_suecurline, &io_suelastline[1]);
		}
		io_suelastline[0] = 0;
	}
	strcpy(io_sueorigline, io_suecurline);

	/* parse the line */
	inblank = TRUE;
	keypos = 0;
	for(pt=io_suecurline; *pt != 0; pt++)
	{
		if (*pt == '{') curlydepth++;
		if (*pt == '}') curlydepth--;
		if ((*pt == ' ' || *pt == '\t') && curlydepth == 0)
		{
			*pt = 0;
			inblank = TRUE;
		} else
		{
			if (inblank)
			{
				keywords[keypos++] = pt;
			}
			inblank = FALSE;
		}
	}
	return(keypos);
}

/*
 * Routine to add "directory" to the list of places where SUE files may be found.
 */
BOOLEAN io_sueadddirectory(char *directory)
{
	REGISTER INTBIG newnumdir, i;
	char **newdir;

	newnumdir = io_suenumdirectories + 1;
	newdir = (char **)emalloc(newnumdir * (sizeof (char *)), io_tool->cluster);
	if (newdir == 0) return(TRUE);
	for(i=0; i<io_suenumdirectories; i++)
		newdir[i] = io_suedirectories[i];
	newdir[io_suenumdirectories] = (char *)emalloc(strlen(directory)+1, io_tool->cluster);
	if (newdir[io_suenumdirectories] == 0) return(TRUE);
	strcpy(newdir[io_suenumdirectories], directory);
	if (io_suenumdirectories > 0) efree((char *)io_suedirectories);
	io_suedirectories = newdir;
	io_suenumdirectories = newnumdir;
	return(FALSE);
}

void io_suecleardirectories(void)
{
	REGISTER INTBIG i;

	for(i=0; i<io_suenumdirectories; i++)
		efree((char *)io_suedirectories[i]);
	if (io_suenumdirectories > 0) efree((char *)io_suedirectories);
	io_suenumdirectories = 0;
}

/*************************** OBJECT MANAGEMENT ***************************/

SUEWIRE *io_suenewwire(void)
{
	SUEWIRE *sw;

	if (io_suefreewire != NOSUEWIRE)
	{
		sw = io_suefreewire;
		io_suefreewire = sw->nextsuewire;
	} else
	{
		sw = (SUEWIRE *)emalloc(sizeof(SUEWIRE), io_tool->cluster);
	}
	return(sw);
}

void io_suekillwire(SUEWIRE *sw)
{
	sw->nextsuewire = io_suefreewire;
	io_suefreewire = sw;
}

void io_suefreewires(SUEWIRE *firstsw)
{
	SUEWIRE *sw, *nextsw;

	for(sw = firstsw; sw != NOSUEWIRE; sw = nextsw)
	{
		nextsw = sw->nextsuewire;
		io_suekillwire(sw);
	}
}

SUENET *io_suenewnet(void)
{
	SUENET *sn;

	if (io_suefreenet != NOSUENET)
	{
		sn = io_suefreenet;
		io_suefreenet = sn->nextsuenet;
	} else
	{
		sn = (SUENET *)emalloc(sizeof(SUENET), io_tool->cluster);
	}
	sn->label = 0;
	return(sn);
}

void io_suekillnet(SUENET *sn)
{
	sn->nextsuenet = io_suefreenet;
	io_suefreenet = sn;
	if (sn->label != 0) efree(sn->label);
}

void io_suefreenets(SUENET *firstsn)
{
	SUENET *sn, *nextsn;

	for(sn = firstsn; sn != NOSUENET; sn = nextsn)
	{
		nextsn = sn->nextsuenet;
		io_suekillnet(sn);
	}
}

#endif  /* IOSUE - at top */
