/*
 * Electric(tm) VLSI Design System
 *
 * File: usrparse.c
 * User interface aid: command parsing and execution
 * 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 "usr.h"
#include "usrdiacom.h"
#include "database.h"
#include "edialogs.h"
#include "tecschem.h"

#define	MAXLINE 400		/* maximum length of command file input lines */

/* working memory for "us_parsecommand()" */
char *us_parseownbuild[MAXPARS];
INTSML us_parsemaxownbuild = -1;

/* working memory for "us_evaluatevariable()" */
static INTBIG *us_evaluatevarlist;
static INTBIG  us_evaluatevarlimit = 0;

DIALOGHOOK *us_firstdialoghook = NODIALOGHOOK;

/* global variables for parsing */
INTSML   us_pacurchar;					/* current character on current line */
char    *us_pattyline;					/* current line image */
char    *us_paambiguous;				/* used by "us_pagetword" for "?" message */
INTSML   us_patruelength;				/* size of above two buffers */
INTSML   us_paparamcount;				/* number of parameters read in */
char    *us_palinestart;				/* first character position on line */
COMCOMP *us_paparamtype[MAXKEYWORD];	/* expected type of each parameter */
INTSML   us_pakeycount;					/* number of keywords allowed in parse */
char   **us_paparamstart;				/* character position of each parameter */
KEYWORD *us_pathiskey;					/* the current keyword list */

/* local variables for parsing */
INTSML   us_paparamadded[MAXKEYWORD];	/* number of parameters added here */
INTSML   us_pathisind;					/* pointer into the current keyword list */

/* List Dialog */
DIALOGITEM us_listdialogitems[] =
{
 /*  1 */ {0, {160,216,184,280}, BUTTON, "OK"},
 /*  2 */ {0, {96,216,120,280}, BUTTON, "Cancel"},
 /*  3 */ {0, {56,8,200,206}, SCROLL, ""},
 /*  4 */ {0, {8,8,54,275}, MESSAGE, ""}
};
DIALOG us_listdialog = {{50,75,259,369}, "", 4, us_listdialogitems};

/* prototypes for local routines */
INTSML us_addusercom(USERCOM*, char*);
USERCOM *us_fleshcommand(USERCOM*);
void us_dopopupmenu(POPUPMENU*);
void us_domacro(char*, INTSML, char*[]);
INTSML us_expandvarlist(INTBIG **varlist, INTBIG *varlimit);

/*
 * Routine to free all memory associated with this module.
 */
void us_freeparsememory(void)
{
	REGISTER USERCOM *u;
	REGISTER INTBIG i;
	REGISTER MACROPACK *p;
	REGISTER DIALOGHOOK *dh;

	while (us_usercomfree != NOUSERCOM)
	{
		u = us_usercomfree;
		us_usercomfree = us_usercomfree->nextcom;
		efree((char *)u);
	}

	for(i=0; i<=us_parsemaxownbuild; i++)
		efree((char *)us_parseownbuild[i]);

	/* free macro packages */
	while (us_macropacktop != NOMACROPACK)
	{
		p = us_macropacktop;
		us_macropacktop = us_macropacktop->nextmacropack;
		efree((char *)p->packname);
		efree((char *)p);
	}

	if (us_evaluatevarlimit > 0) efree((char *)us_evaluatevarlist);

	/* free hooks to dialogs in other tools */
	while (us_firstdialoghook != NODIALOGHOOK)
	{
		dh = us_firstdialoghook;
		us_firstdialoghook = dh->nextdialoghook;
		if (dh->terminputkeyword != 0) efree((char *)dh->terminputkeyword);
		efree((char *)dh);
	}
}

/*
 * routine to allocate a new user command from the pool (if any) or memory
 */
USERCOM *us_allocusercom(void)
{
	REGISTER USERCOM *u;

	if (us_usercomfree == NOUSERCOM)
	{
		u = (USERCOM *)emalloc((sizeof (USERCOM)), us_aid->cluster);
		if (u == 0) return(NOUSERCOM);
	} else
	{
		/* take usercom from free list */
		u = us_usercomfree;
		us_usercomfree = (USERCOM *)u->nextcom;
	}
	u->count = 0;
	u->comname = 0;
	u->nodeglyph = NONODEPROTO;
	u->arcglyph = NOARCPROTO;
	u->message = 0;
	u->nextcom = NOUSERCOM;
	return(u);
}

/*
 * routine to return user command "u" to the pool of free commands
 */
void us_freeusercom(USERCOM *u)
{
	REGISTER INTSML i;

	u->nextcom = us_usercomfree;
	for(i=0; i<u->count; i++) efree(u->word[i]);
	if (u->comname != 0) efree(u->comname);
	if (u->message != 0) efree(u->message);
	us_usercomfree = u;
}

/*
 * routine to establish a new macro package with name "name"
 */
MACROPACK *us_newmacropack(char *name)
{
	REGISTER MACROPACK *p;

	p = (MACROPACK *)emalloc((sizeof (MACROPACK)), us_aid->cluster);
	if (p == 0) return(NOMACROPACK);
	if (allocstring(&p->packname, name, us_aid->cluster) != 0) return(NOMACROPACK);
	p->nextmacropack = us_macropacktop;
	us_macropacktop = p;
	return(p);
}

/*
 * routine to add user command "us" to macro "mac".  Returns nonzero upon error
 */
INTSML us_addusercom(USERCOM *us, char *mac)
{
	REGISTER INTSML i, parnumber, parsed, len, count, total, j, tot, hasspace;
	REGISTER char *pt, **manystrings;
	COMCOMP *comarray[MAXPARS];
	char *build[MAXPARS], line[30];
	REGISTER VARIABLE *var;
	extern COMCOMP us_macparamp;

	var = us_getmacro(mac);
	if (var == NOVARIABLE) return(1);
	len = (INTSML)getlength(var);

	/* make room for one more command */
	manystrings = (char **)emalloc(((len+1) * (sizeof (char *))), el_tempcluster);
	if (manystrings == 0) return(1);

	/* load the existing commands */
	for(i=0; i<len; i++)
		(void)allocstring(&manystrings[i], ((char **)var->addr)[i], el_tempcluster);

	/* build the new command */
	parsed = 0;
	(void)initinfstr();
	(void)addstringtoinfstr(us->comname);
	(void)addtoinfstr(' ');
	for(i=0; i<us->count; i++)
	{
		/* look for parameter substitution */
		hasspace = 0;
		for(pt = us->word[i]; *pt != 0; pt++)
		{
			if (*pt == ' ' || *pt == '\t') hasspace++;
			if (*pt != '%') continue;
			if (!isdigit(pt[1])) continue;

			/* formal parameter found: determine command completion for it */
			parnumber = pt[1] - '0';
			count = us_parsecommand(manystrings[2], build);
			total = (INTSML)maxi(count, parnumber);
			if (parsed == 0)
			{
				tot = us_fillcomcomp(us, comarray);
				parsed++;
			}
			(void)initinfstr();
			for(j=0; j<total; j++)
			{
				if (j == parnumber-1 && tot > i && comarray[i] != NOCOMCOMP)
				{
					(void)sprintf(line, "0%lo", (UINTBIG)comarray[i]);
					(void)addstringtoinfstr(line);
				} else if (j < count) (void)addstringtoinfstr(build[j]); else
				{
					(void)sprintf(line, "0%lo", (UINTBIG)&us_macparamp);
					(void)addstringtoinfstr(line);
				}
				(void)addtoinfstr(' ');
			}
			(void)reallocstring(&manystrings[2], returninfstr(), el_tempcluster);
		}

		if (hasspace == 0) (void)addstringtoinfstr(us->word[i]); else
		{
			/* must quote this parameter: it has spaces */
			(void)addtoinfstr('"');
			for(pt = us->word[i]; *pt != 0; pt++)
			{
				if (*pt == '"') (void)addtoinfstr('"');
				(void)addtoinfstr(*pt);
			}
			(void)addtoinfstr('"');
		}
		(void)addtoinfstr(' ');
	}

	manystrings[len] = returninfstr();
	(void)setvalkey((INTBIG)us_aid, VAID, var->key, (INTBIG)manystrings,
		VSTRING|VISARRAY|((len+1)<<VLENGTHSH)|VDONTSAVE);
	for(i=0; i<len; i++) efree(manystrings[i]);
	efree((char *)manystrings);
	return(0);
}

/*
 * routine to execute user command "item".  The command is echoed if "print"
 * is nonzero.  If "macro" is nonzero, the command is saved in the
 * macro.  If "fromuser" is nonzero, the command was issued from the
 * terminal and is saved in the 1-command stack "us_lastcom"
 */
void us_execute(USERCOM *item, INTBIG print, INTSML macro, INTSML fromuser)
{
	REGISTER INTSML i, j, runit, act, ismacend, count, wasmacro;
	REGISTER USERCOM *uc, *ucnew;
	char *userpars[MAXPARS];
	REGISTER VARIABLE *var, *varmacro;

	/* for segmentation fault correction */
	us_state &= ~COMMANDFAILED;

	/* stop if interrupted */
	if (stopping("Execution")) return;

	/* see if the status display should be cleared */
	if ((us_state&DIDINPUT) != 0)
	{
		ttynewcommand();
		us_state &= ~DIDINPUT;
	}

	/* see if the command is valid */
	act = item->active;
	if (act < 0)
	{
		us_unknowncommand();
		return;
	}

	/* copy the command */
	uc = us_allocusercom();
	if (uc != NOUSERCOM)
	{
		uc->active = act;
		(void)allocstring(&uc->comname, item->comname, us_aid->cluster);
		uc->menu = item->menu;
		uc->count = item->count;
		for(j=0; j<item->count; j++)
			if (allocstring(&uc->word[j], item->word[j], us_aid->cluster) != 0)
		{
			uc = NOUSERCOM;
			break;
		}
	}
	if (uc == NOUSERCOM)
	{
		/*
		 * when there is no memory, things are basically wedged.  This
		 * little hack lets the command be executed but may cause all
		 * kinds of hell in other places.
		 */
		ttyputerr("No memory for command execution!");
		uc = item;
	}

	/* if this is a macro definition, see if the command is to be executed */
	runit = 1;
	ismacend = 0;
	wasmacro = 0;
	if (macro != 0)
	{
		var = getvalkey((INTBIG)us_aid, VAID, VSTRING, us_macrobuilding);
		if (var != NOVARIABLE)
		{
			/* see if macro was begun with the "no-execute" option */
			varmacro = us_getmacro((char *)var->addr);
			if (varmacro != NOVARIABLE)
			{
				wasmacro++;
				count = us_parsecommand(((char **)varmacro->addr)[0], userpars);
				for(i=0; i<count; i++)
					if (namesame(userpars[i], "noexecute") == 0)
				{
					runit = 0;
					break;
				}
			}

			/* see if a "macdone" was encountered */
			if ((us_state&MACROTERMINATED) != 0) runit = 0;

			/* always execute the "macend" command */
			if (act < us_longcount &&
				strcmp(us_lcommand[act].name, "macend") == 0) ismacend = runit = 1;
		}
	}

	ucnew = NOUSERCOM;
	if (runit)
	{
		/* expand "%" macros, "$" variables, and "^" quotes in command line */
		ucnew = us_fleshcommand(uc);

		/* print the command if requested */
		if (print != 0)
		{
			i = initinfstr();
			i += addtoinfstr('-');
			i += addstringtoinfstr(ucnew->comname);
			i += us_appendargs(ucnew);
			if (i != 0) ttyputerr("Memory low!");
			ttyputmsg("%s", returninfstr());
		}

		/* handle normal commands */
		if (act < us_longcount)
		{
			/* make copy of parameter pointers in case user changes it */
			for(i=0; i<ucnew->count; i++) userpars[i] = ucnew->word[i];

			/* execute the command */
			(*us_lcommand[act].routine)(ucnew->count, userpars);
		} else
		{
			/* if command is a popup menu, execute it differently */
			if (ucnew->menu != NOPOPUPMENU) us_dopopupmenu(ucnew->menu); else
			{
				/* presume macro */
				us_domacro(ucnew->comname, ucnew->count, ucnew->word);
			}
		}
	} else ttyputmsg("(command not executed)");
	if (ucnew != uc && ucnew != NOUSERCOM) us_freeusercom(ucnew);

	/* save the command in a macro if requested */
	if (wasmacro != 0 && (us_state&COMMANDFAILED) == 0 && ismacend == 0)
	{
		var = getvalkey((INTBIG)us_aid, VAID, VSTRING, us_macrobuilding);
		if (var != NOVARIABLE)
			if (us_addusercom(uc, (char *)var->addr) != 0)
				ttyputerr("No memory to save macro command");
	}

	/* if command had an error, set flag to not save it */
	if ((us_state&COMMANDFAILED) != 0) fromuser = 0;

	/* if command was "iterate", don't save it in iteration stack */
	if (fromuser != 0)
	{
		if (act < us_longcount && strcmp(us_lcommand[act].name, "iterate") == 0) fromuser = 0;
	}

	/* if command is to be saved for iteration, do so */
	if (fromuser != 0)
	{
		if (us_lastcom != NOUSERCOM) us_freeusercom(us_lastcom);
		us_lastcom = uc;
	} else us_freeusercom(uc);
}

/*
 * routine to convert command "uc" into a fully fleshed-out command (with all
 * "%" macros, "$" variables, and "^" quoted characters properly converted).
 */
USERCOM *us_fleshcommand(USERCOM *uc)
{
	REGISTER USERCOM *ucnew;
	REGISTER INTSML i, j, l, letter, aindex, indexsave, hasparams, count, chr;
	REGISTER VARIABLE *var, *varrunning, *varbuilding;
	VARIABLE localvar;
	REGISTER char *pp, *closeparen, *pars;
	char *qual, *tmpstring, *params[MAXPARS];
	INTBIG objaddr, objtype;

	/* look for macro/variable/quote expansion possibilities */
	hasparams = 0;
	for(j=0; j<uc->count; j++)
	{
		for(pp = uc->word[j]; *pp != 0; pp++)
			if (*pp == '%' || *pp == '$' || *pp == '^') hasparams++;
		if (hasparams != 0) break;
	}
	if (hasparams == 0) return(uc);

	/* make a new user command */
	ucnew = us_allocusercom();
	if (ucnew == NOUSERCOM) return(uc);
	ucnew->active = uc->active;
	(void)allocstring(&ucnew->comname, uc->comname, us_aid->cluster);
	ucnew->menu = uc->menu;
	ucnew->count = uc->count;

	/* run through each parameter */
	i = 0;
	for(j=0; j<ucnew->count; j++)
	{
		/* copy the original parameter */
		i += allocstring(&tmpstring, uc->word[j], el_tempcluster);

		/* expand macro if it has parameters */
		for(i += initinfstr(), pp = tmpstring; *pp != 0; pp++)
		{
			/* handle escape character */
			if (*pp == '^')
			{
				i += addtoinfstr(*pp++);
				if (*pp != 0) i += addtoinfstr(*pp);
				continue;
			}

			/* if not a command interpreter macro character, just copy it */
			if (*pp != '%') { i += addtoinfstr(*pp);  continue; }
			pp++;

			/* handle command interpreter variables */
			if (isalpha(*pp))
			{
				/* handle array indices */
				aindex = -1;
				if (pp[1] == '[') for(l=2; pp[l] != 0; l++) if (pp[l] == ']')
				{
					aindex = (INTSML)myatoi(&pp[2]);
					indexsave = pp[1];
					pp[1] = 0;
					break;
				}

				var = getval((INTBIG)us_aid, VAID, -1, us_commandvarname(*pp));
				if (var != NOVARIABLE) i += addstringtoinfstr(describevariable(var, aindex, -1));
				if (aindex >= 0)
				{
					pp[1] = (char)indexsave;
					pp += l;
				}
				continue;
			}

			/* if "%" form is parameter substitution, do that */
			if (isdigit(*pp))
			{
				/* substitute macro parameter */
				varrunning = getvalkey((INTBIG)us_aid, VAID, VSTRING, us_macrorunning);
				varbuilding = getvalkey((INTBIG)us_aid, VAID, VSTRING, us_macrobuilding);
				if (varrunning != NOVARIABLE && (varbuilding == NOVARIABLE ||
					namesame((char *)varrunning->addr, (char *)varbuilding->addr) != 0))
				{
					/* find the number of parameters to this macro */
					var = us_getmacro((char *)varrunning->addr);
					if (var == NOVARIABLE) count = 0; else
						count = us_parsecommand(((char **)var->addr)[1], params);
					letter = *pp - '1';
					if (letter < count)
					{
						/* remove enclosing quotes on parameter */
						pars = params[letter];
						if (pars[0] == '"' && pars[strlen(pars)-1] == '"')
						{
							pars[strlen(pars)-1] = 0;
							pars++;
						}

						/* add the parameter, removing quotes */
						for(; *pars != 0; pars++)
						{
							if (pars[0] == '"' && pars[1] == '"') pars++;
							i += addtoinfstr(*pars);
						}

						/* skip the default clause, if any */
						if (pp[1] == '[')
							for(pp += 2; *pp != ']' && *pp != 0; pp++)
								;
						continue;
					}
				}

				/* use the default clause, if any */
				if (pp[1] == '[')
					for(pp += 2; *pp != ']' && *pp != 0; pp++) i += addtoinfstr(*pp);
				continue;
			}

			/* give up on substitution: leave the construct as is */
			i += addtoinfstr(*pp);
		}
		if (reallocstring(&tmpstring, returninfstr(), el_tempcluster) != 0) i++;

		/* substitute any "$" variable references in the user command */
		for(i += initinfstr(), pp = tmpstring; *pp != 0; pp++)
		{
			/* handle escape character */
			if (*pp == '^')
			{
				i += addtoinfstr(*pp++);
				if (*pp != 0) i += addtoinfstr(*pp);
				continue;
			}

			/* if not a variable character, just copy it */
			if (*pp != '$') { i += addtoinfstr(*pp);  continue; }

			/* if variable expression is in parenthesis, determine extent */
			pp++;
			closeparen = 0;
			if (*pp == '(')
			{
				for(l=0; pp[l] != 0; l++) if (pp[l] == ')') break;
				if (pp[l] == ')')
				{
					closeparen = &pp[l];
					pp[l] = 0;
					pp++;
				}
			}

			/* get index of variable (if specified) */
			aindex = -1;
			l = strlen(pp);
			if (pp[l-1] == ']') for(l--; l >= 0; l--) if (pp[l] == '[')
			{
				aindex = (INTSML)myatoi(&pp[l+1]);
				break;
			}
			if (us_evaluatevariable(pp, &objaddr, &objtype, &qual) == 0)
			{
				if (*qual != 0) var = getval(objaddr, objtype, -1, qual); else
				{
					localvar.addr = objaddr;
					localvar.type = objtype;
					var = &localvar;
				}
				if (var != NOVARIABLE) i += addstringtoinfstr(describevariable(var, aindex, -1));
			}
			if (closeparen != 0)
			{
				*closeparen = ')';
				pp = closeparen;
			} else break;
		}
		if (reallocstring(&tmpstring, returninfstr(), el_tempcluster) != 0) i++;

		/* remove any "^" escape references in the user command */
		for(i += initinfstr(), pp = tmpstring; *pp != 0; pp++)
		{
			if (*pp != '^') i += addtoinfstr(*pp); else
			{
				pp++;
				if (*pp == 0)
				{
					ttyputerr("HEY! '^' at end of line");
					break;
				}
				chr = *pp;

				/* check for numeric equivalent of the form "^010" */
				if (chr == '0')
				{
					pp++;
					chr = 0;
					while (*pp >= '0' && *pp <= '7')
					{
						chr = (chr * 8) + (*pp - '0');
						pp++;
					}
				}
				i += addtoinfstr((char)chr);
			}
		}
		if (allocstring(&ucnew->word[j], returninfstr(), us_aid->cluster) != 0) i++;
		efree(tmpstring);
	}
	if (i != 0) ttyputerr("Memory low!");
	return(ucnew);
}

/*
 * routine to expand a variable path in the string "name" and return a full
 * description of how to find that variable by setting "objaddr" and "objtype"
 * to the address and type of the object containing the variable "qual".
 * The routine returns nonzero if the variable path is nonsensical.
 */
INTSML us_evaluatevariable(char *name, INTBIG *objaddr, INTBIG *objtype, char **qual)
{
	REGISTER INTSML i, len;
	INTBIG lx, hx, ly, hy, x, y;
	REGISTER char *pp, save, *type;
	REGISTER NODEPROTO *np;
	REGISTER NODEINST *ni;
	REGISTER ARCINST *ai;
	REGISTER ARCPROTO *ap;
	REGISTER LIBRARY *lib;
	REGISTER PORTPROTO *port;
	REGISTER HIGHLIGHT *high;
	REGISTER TECHNOLOGY *tech;
	REGISTER VARIABLE *var, *highvar;
	REGISTER VIEW *view;
	REGISTER CELL *c;
	REGISTER NETWORK *net;
	REGISTER WINDOWPART *win;
	GEOM *fromgeom, *togeom;
	PORTPROTO *fromport, *toport;
	HIGHLIGHT thishigh;
	REGISTER INTBIG varcount, *list;

	/* look for specifier with ":" */
	for(pp = name;
		*pp != 0 && *pp != ':' && *pp != '.' && *pp != ')' && *pp != '['; pp++)
			;
	if (*pp == ':')
	{
		i = pp - name;
		type = name;
		for(name = ++pp;
			*name != 0 && *name != '.' && *name != ')' && *name != '['; name++)
				;
		save = *name;    *name = 0;
		if (namesamen(type, "technology", i) == 0 && i >= 1)
		{
			*objtype = VTECHNOLOGY;
			if (strcmp(pp, "~") == 0) tech = el_curtech; else
				tech = gettechnology(pp);
			*name = save;
			if (tech == NOTECHNOLOGY) return(1);
			*objaddr = (INTBIG)tech;
		} else if (namesamen(type, "library", i) == 0 && i >= 1)
		{
			*objtype = VLIBRARY;
			if (strcmp(pp, "~") == 0) lib = el_curlib; else lib = getlibrary(pp);
			*name = save;
			if (lib == NOLIBRARY) return(1);
			*objaddr = (INTBIG)lib;
		} else if (namesamen(type, "aid", i) == 0 && i >= 2)
		{
			*objtype = VAID;
			if (strcmp(pp, "~") == 0) i = us_aid->aidindex; else
				for(i=0; i<el_maxaid; i++)
					if (namesame(el_aids[i].aidname, pp) == 0) break;
			*name = save;
			if (i >= el_maxaid) return(1);
			*objaddr = (INTBIG)&el_aids[i];
		} else if (namesamen(type, "arc", i) == 0 && i >= 2)
		{
			*objtype = VARCPROTO;
			if (strcmp(pp, "~") == 0) ap = us_curarcproto; else
				for(ap = el_curtech->firstarcproto; ap != NOARCPROTO; ap = ap->nextarcproto)
					if (namesame(ap->protoname, pp) == 0) break;
			*name = save;
			if (ap == NOARCPROTO) return(1);
			*objaddr = (INTBIG)ap;
		} else if (namesamen(type, "node", i) == 0 && i >= 2)
		{
			*objtype = VNODEPROTO;
			if (strcmp(pp, "~") == 0) np = us_curnodeproto; else
				np = getnodeproto(pp);
			*name = save;
			if (np == NONODEPROTO) return(1);
			*objaddr = (INTBIG)np;
		} else if (namesamen(type, "primitive", i) == 0 && i >= 1)
		{
			*objtype = VNODEPROTO;
			if (strcmp(pp, "~") == 0) np = us_curnodeproto; else
				np = getnodeproto(pp);
			if (np != NONODEPROTO && np->primindex == 0) np = NONODEPROTO;
			*name = save;
			if (np == NONODEPROTO) return(1);
			*objaddr = (INTBIG)np;
		} else if (namesamen(type, "facet", i) == 0 && i >= 1)
		{
			*objtype = VNODEPROTO;
			if (strcmp(pp, "~") == 0) np = getcurfacet(); else
			{
				np = getnodeproto(pp);
				if (np != NONODEPROTO && np->primindex != 0) np = NONODEPROTO;
			}
			*name = save;
			if (np == NONODEPROTO) return(1);
			*objaddr = (INTBIG)np;
		} else if (namesamen(type, "cell", i) == 0 && i >= 1)
		{
			*objtype = VCELL;
			if (strcmp(pp, "~") != 0) c = getcell(pp); else
			{
				np = getcurfacet();
				if (np == NONODEPROTO) c = NOCELL; else
					c = np->cell;
			}
			*name = save;
			if (c == NOCELL) return(1);
			*objaddr = (INTBIG)c;
		} else if (namesamen(type, "view", i) == 0 && i >= 1)
		{
			*objtype = VVIEW;
			view = getview(pp);
			*name = save;
			if (view == NOVIEW) return(1);
			*objaddr = (INTBIG)view;
		} else if (namesamen(type, "window", i) == 0 && i >= 1)
		{
			*objtype = VWINDOWPART;
			if (strcmp(pp, "~") == 0) win = el_curwindowpart; else
			{
				for(win = el_topwindowpart; win != NOWINDOWPART; win = win->nextwindowpart)
					if (namesame(pp, win->location) == 0) break;
			}
			*name = save;
			if (win == NOWINDOWPART) return(1);
			*objaddr = (INTBIG)win;
		} else if (namesamen(type, "net", i) == 0 && i >= 2)
		{
			*objtype = VNETWORK;
			np = getcurfacet();
			if (np == NONODEPROTO) return(1);
			net = getnetwork(pp, np);
			*name = save;
			if (net == NONETWORK) return(1);
			*objaddr = (INTBIG)net;
		} else
		{
			*name = save;
			return(1);
		}
		if (*name == '.') name++;
	} else
	{
		if (*name == '~')
		{
			name++;

			/* handle cases that do not need current selection */
			if (*name == 'x' || *name == 'y')
			{
				/* special case when evaluating cursor coordinates */
				if (us_demandxy(&x, &y)) return(1);
				gridalign(&x, &y, us_alignment);
				*objtype = VINTEGER;
				if (*name == 'x') *objaddr = x; else *objaddr = y;
				name++;
			} else if (*name == 't')
			{
				/* figure out the current technology */
				*objtype = VSTRING;
				*objaddr = (INTBIG)"";
				np = el_curlib->curnodeproto;
				if (np != NONODEPROTO && (np->cellview->viewstate&TEXTVIEW) == 0)
					*objaddr = (INTBIG)us_techname(np);
				name++;
			} else if (*name == 'c')
			{
				list = us_getallhighlighted();
				for(i=0; list[i+1] != 0; i += 2) ;
				*objtype = VINTEGER;
				*objaddr = i / 2;
				name++;
			} else if (*name == 'h')
			{
				name++;
				if (us_getareabounds(&lx, &hx, &ly, &hy) == NONODEPROTO) return(1);
				*objtype = VINTEGER;
				switch (*name)
				{
					case 'l': *objaddr = lx;   name++;   break;
					case 'r': *objaddr = hx;   name++;   break;
					case 'b': *objaddr = ly;   name++;   break;
					case 't': *objaddr = hy;   name++;   break;
					default:  *objtype = VUNKNOWN;       break;
				}
			} else if (*name == 'o')
			{
				name++;
				if (us_gettwoobjects(&fromgeom, &fromport, &togeom, &toport) != 0) return(1);
				*objaddr = (INTBIG)togeom->entryaddr.blind;
				if (togeom->entrytype == OBJNODEINST) *objtype = VNODEINST; else
					*objtype = VARCINST;
			} else if (namesamen(name, "nodes", 5) == 0)
			{
				/* construct a list of the nodes in this facet */
				name += 5;
				np = getcurfacet();
				if (np == NONODEPROTO) return(1);
				varcount = 0;
				for(ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
				{
					if (varcount >= us_evaluatevarlimit)
						if (us_expandvarlist(&us_evaluatevarlist, &us_evaluatevarlimit) != 0) return(1);
					us_evaluatevarlist[varcount*2] = (INTBIG)ni;
					us_evaluatevarlist[varcount*2+1] = VNODEINST;
					varcount++;
				}
				*objaddr = (INTBIG)us_evaluatevarlist;
				*objtype = VGENERAL | VISARRAY | ((varcount*2) << VLENGTHSH) | VDONTSAVE;
			} else if (namesamen(name, "arcs", 4) == 0)
			{
				/* construct a list of the arcs in this facet */
				name += 4;
				np = getcurfacet();
				if (np == NONODEPROTO) return(1);
				varcount = 0;
				for(ai = np->firstarcinst; ai != NOARCINST; ai = ai->nextarcinst)
				{
					if (varcount >= us_evaluatevarlimit)
						if (us_expandvarlist(&us_evaluatevarlist, &us_evaluatevarlimit) != 0) return(1);
					us_evaluatevarlist[varcount*2] = (INTBIG)ai;
					us_evaluatevarlist[varcount*2+1] = VARCINST;
					varcount++;
				}
				*objaddr = (INTBIG)us_evaluatevarlist;
				*objtype = VGENERAL | VISARRAY | ((varcount*2) << VLENGTHSH) | VDONTSAVE;
			} else if (namesamen(name, "ports", 5) == 0)
			{
				/* construct a list of the ports in this facet */
				name += 5;
				np = getcurfacet();
				if (np == NONODEPROTO) return(1);
				varcount = 0;
				for(port = np->firstportproto; port != NOPORTPROTO; port = port->nextportproto)
				{
					if (varcount >= us_evaluatevarlimit)
						if (us_expandvarlist(&us_evaluatevarlist, &us_evaluatevarlimit) != 0) return(1);
					us_evaluatevarlist[varcount*2] = (INTBIG)port;
					us_evaluatevarlist[varcount*2+1] = VPORTPROTO;
					varcount++;
				}
				*objaddr = (INTBIG)us_evaluatevarlist;
				*objtype = VGENERAL | VISARRAY | ((varcount*2) << VLENGTHSH) | VDONTSAVE;
			} else if (namesamen(name, "nets", 4) == 0)
			{
				/* construct a list of the networks in this facet */
				name += 4;
				np = getcurfacet();
				if (np == NONODEPROTO) return(1);
				varcount = 0;
				for(net = np->firstnetwork; net != NONETWORK; net = net->nextnetwork)
				{
					if (varcount >= us_evaluatevarlimit)
						if (us_expandvarlist(&us_evaluatevarlist, &us_evaluatevarlimit) != 0) return(1);
					us_evaluatevarlist[varcount*2] = (INTBIG)net;
					us_evaluatevarlist[varcount*2+1] = VNETWORK;
					varcount++;
				}
				*objaddr = (INTBIG)us_evaluatevarlist;
				*objtype = VGENERAL | VISARRAY | ((varcount*2) << VLENGTHSH) | VDONTSAVE;
			} else if (namesamen(name, "facets", 6) == 0)
			{
				/* construct a list of the facets in this library */
				name += 6;
				varcount = 0;
				for(np = el_curlib->firstnodeproto; np != NONODEPROTO; np = np->nextnodeproto)
				{
					if (varcount >= us_evaluatevarlimit)
						if (us_expandvarlist(&us_evaluatevarlist, &us_evaluatevarlimit) != 0) return(1);
					us_evaluatevarlist[varcount*2] = (INTBIG)np;
					us_evaluatevarlist[varcount*2+1] = VNODEPROTO;
					varcount++;
				}
				*objaddr = (INTBIG)us_evaluatevarlist;
				*objtype = VGENERAL | VISARRAY | ((varcount*2) << VLENGTHSH) | VDONTSAVE;
			} else if (namesamen(name, "libs", 4) == 0)
			{
				/* construct a list of the libraries */
				name += 4;
				varcount = 0;
				for(lib = el_curlib; lib != NOLIBRARY; lib = lib->nextlibrary)
				{
					if (varcount >= us_evaluatevarlimit)
						if (us_expandvarlist(&us_evaluatevarlist, &us_evaluatevarlimit) != 0) return(1);
					us_evaluatevarlist[varcount*2] = (INTBIG)lib;
					us_evaluatevarlist[varcount*2+1] = VLIBRARY;
					varcount++;
				}
				*objaddr = (INTBIG)us_evaluatevarlist;
				*objtype = VGENERAL | VISARRAY | ((varcount*2) << VLENGTHSH) | VDONTSAVE;
			} else
			{
				/* basic "~" with nothing after it requires the current selection, so get it */
				list = us_getallhighlighted();

				/* if there are no objects selected, stop now */
				if (list[1] == 0) return(1);

				highvar = getvalkey((INTBIG)us_aid, VAID, VSTRING|VISARRAY, us_highlighted);
				if (highvar == NOVARIABLE) return(1);
				if (us_makehighlight(((char **)highvar->addr)[0], &thishigh) != 0) return(1);
				high = &thishigh;

				/* copy the selected object to the parameters */
				*objaddr = list[0];
				*objtype = list[1];

				/* get variable name if one is selected */
				if ((high->status&HIGHTYPE) == HIGHTEXT && high->fromvar != NOVARIABLE)
				{
					*qual = makename(high->fromvar->key);
					if (*name == 0 || *name == ')' || *name == '[') return(0);
					return(1);
				}
			}
			if (*name == '.') name++;
		} else
		{
			np = getcurfacet();
			if (np == NONODEPROTO) return(1);
			*objaddr = (INTBIG)np;
			*objtype = VNODEPROTO;
		}
	}

	/* add in qualifiers */
	save = *name;
	for(pp = name; *name != 0;)
	{
		/* advance to the end of the next qualifying name */
		while (*name != 0 && *name != '.' && *name != ')' && *name != '[') name++;

		/* save the terminating character */
		save = *name;   *name = 0;

		/* special case if this is an array */
		if (((*objtype) & VISARRAY) != 0 && ((*objtype)&VTYPE) == VGENERAL)
		{
			len = (((*objtype) & VLENGTH) >> VLENGTHSH) / 2;
			for(i=0; i<len; i++)
			{
				/* climb down the context chain */
				var = getval(((INTBIG *)*objaddr)[i*2], ((INTBIG *)*objaddr)[i*2+1], -1, pp);

				/* if chain cannot be climbed, path is invalid */
				if (var == NOVARIABLE) ((INTBIG *)*objaddr)[i*2+1] = VUNKNOWN; else
				{
					/* keep on going down the path */
					((INTBIG *)*objaddr)[i*2] = var->addr;
					((INTBIG *)*objaddr)[i*2+1] = var->type;
				}
			}
			*name++ = save;
		} else
		{
			/* if this is the end, return now */
			if (save != '.') break;

			/* climb down the context chain */
			var = getval(*objaddr, *objtype, -1, pp);
			*name++ = save;

			/* if chain cannot be climbed, path is invalid */
			if (var == NOVARIABLE) return(1);

			/* keep on going down the path */
			*objaddr = var->addr;   *objtype = var->type;
		}

		pp = name;
	}

	/* get the final qualifying name */
	if (((*objtype)&VTYPE) == VGENERAL) *qual = ""; else
	{
		(void)initinfstr();
		(void)addstringtoinfstr(pp);
		*qual = returninfstr();
	}
	*name = save;
	return(0);
}

/*
 * routine to maintain a static list of address/type pairs
 */
INTSML us_expandvarlist(INTBIG **varlist, INTBIG *varlimit)
{
	INTBIG *newlist, newlimit, i;

	newlimit = *varlimit * 2;
	if (newlimit <= 0) newlimit = *varlimit + 10;
	newlist = (INTBIG *)emalloc(newlimit * 2 * SIZEOFINTBIG, us_aid->cluster);
	if (newlist == 0) return(1);
	for(i=0; i < (*varlimit)*2; i++) newlist[i] = (*varlist)[i];
	if (*varlimit != 0) efree((char *)*varlist);
	*varlist = newlist;
	*varlimit = newlimit;
	return(0);
}

/*
 * routine to ensure that the INTBIG arrays "addr" and "type" are at least "len"
 * long (current size is "limit").  Reallocates if not.  Returns nonzero on error.
 */
INTSML us_expandaddrtypearray(INTBIG *limit, INTBIG **addr, INTBIG **type, INTBIG len)
{
	if (len <= *limit) return(0);

	if (*limit > 0) { efree((char *)*addr);   efree((char *)*type); }
	*limit = 0;
	*addr = (INTBIG *)emalloc(len * SIZEOFINTBIG, us_aid->cluster);
	if (*addr == 0) return(1);
	*type = (INTBIG *)emalloc(len * SIZEOFINTBIG, us_aid->cluster);
	if (*type == 0) return(1);
	*limit = len;
	return(0);
}

/*
 * routine to execute popup menu "pm"
 */
void us_dopopupmenu(POPUPMENU *pm)
{
	REGISTER INTSML i;
	REGISTER POPUPMENUITEM *mi, *miret;
	POPUPMENU *cpm;
	REGISTER USERCOM *uc;
	INTSML butstate;

	/* initialize the popup menu */
	for(i=0; i<pm->total; i++)
	{
		mi = &pm->list[i];
		if (mi->maxlen < 0) mi->value = 0; else
		{
			mi->value = (char *)emalloc((mi->maxlen+1), el_tempcluster);
			if (mi->value == 0)
			{
				ttyputerr("No memory");
				return;
			}
			(void)strcpy(mi->value, "*");
		}
	}

	/* display and select from the menu */
	butstate = 1;
	cpm = pm;
	miret = us_popupmenu(&cpm, &butstate, 1, -1, -1, 0);

	/* now analyze the results */
	for(i=0; i<pm->total; i++)
	{
		mi = &pm->list[i];
		if (mi->changed != 0)
		{
			/* build the new command with the argument */
			uc = mi->response;
			(void)initinfstr();
			(void)addstringtoinfstr(uc->comname);
			(void)us_appendargs(uc);
			(void)addtoinfstr(' ');
			(void)addstringtoinfstr(mi->value);
			uc = us_makecommand(returninfstr());
			if (uc != NOUSERCOM)
			{
				us_execute(uc, (miret == mi ? 0 : 1), 0, 0);
				us_freeusercom(uc);
			}
		}
		if (mi->maxlen >= 0) efree(mi->value);
	}

	/* now execute the selected command if it wasn't already done */
	if (miret == NOPOPUPMENUITEM || miret == 0) return;
	if (miret->changed != 0) return;
	uc = miret->response;
	if (uc == NOUSERCOM) return;
	us_execute(uc, 0, 0, 0);
}

/*
 * routine to execute macro "mac" with parameters in the "count" entries of
 * array "pp"
 * DJY: Modified 10/87 to save old macro information before executing new macro
 * This is necessary if recursive macro calls are used.
 */
void us_domacro(char *mac, INTSML count, char *pp[])
{
	REGISTER INTSML i, verbose, total;
	REGISTER USERCOM *uc;
	char *runsave, *build[MAXPARS], *paramsave;
	REGISTER char *pt, **varmacroaddr;
	REGISTER INTSML len;
	REGISTER INTBIG varmacrokey;
	REGISTER VARIABLE *varmacro, *varbuilding, *varrunning;

	/* make sure the macro being executed is not currently being defined */
	varbuilding = getvalkey((INTBIG)us_aid, VAID, VSTRING, us_macrobuilding);
	if (varbuilding != NOVARIABLE && namesame((char *)varbuilding->addr, mac) == 0)
	{
		us_abortcommand("End macro definitions before executing them");
		return;
	}

	/* get the macro to be run */
	varmacro = us_getmacro(mac);
	if (varmacro == NOVARIABLE || (len = (INTSML)getlength(varmacro)) < 4)
	{
		us_abortcommand("Cannot find macro '%s'", mac);
		return;
	}
	varmacroaddr = (char **)varmacro->addr;
	varmacrokey = varmacro->key;

	/* find out whether the macro should be verbose */
	total = us_parsecommand(varmacroaddr[0], build);
	verbose = 0;
	for(i=0; i<total; i++) if (namesame(build[i], "verbose") == 0) verbose++;

	/* save former parameters to this macro, set new ones */
	pt = varmacroaddr[1];
	if (*pt != 0) (void)allocstring(&paramsave, pt, el_tempcluster); else
		paramsave = 0;
	(void)initinfstr();
	for(i=0; i<count; i++)
	{
		if (i != 0) (void)addtoinfstr(' ');
		(void)addtoinfstr('"');
		for(pt = pp[i]; *pt != 0; pt++)
		{
			if (*pt == '"') (void)addtoinfstr('^');
			(void)addtoinfstr(*pt);
		}
		(void)addtoinfstr('"');
	}
	(void)setindkey((INTBIG)us_aid, VAID, varmacrokey, 1, (INTBIG)returninfstr());

	/* save currently running macro, set this as current */
	varrunning = getvalkey((INTBIG)us_aid, VAID, VSTRING, us_macrorunning);
	if (varrunning == NOVARIABLE) runsave = 0; else
		(void)allocstring(&runsave, (char *)varrunning->addr, el_tempcluster);
	(void)setvalkey((INTBIG)us_aid, VAID, us_macrorunning, (INTBIG)mac, VSTRING|VDONTSAVE);

	/* execute the macro */
	us_state &= ~MACROTERMINATED;
	for(i=3; i<len; i++)
	{
		uc = us_makecommand(varmacroaddr[i]);
		if (uc == NOUSERCOM) continue;
		us_execute(uc, verbose, 0, 0);
		us_freeusercom(uc);
		if ((us_state&MACROTERMINATED) != 0) break;
	}
	us_state &= ~MACROTERMINATED;

	/* restore currently running macro */
	if (runsave == 0) (void)delvalkey((INTBIG)us_aid, VAID, us_macrorunning); else
	{
		(void)setvalkey((INTBIG)us_aid, VAID, us_macrorunning, (INTBIG)runsave, VSTRING|VDONTSAVE);
		efree(runsave);
	}

	/* restore the current macro parameters */
	if (paramsave == 0) (void)setindkey((INTBIG)us_aid, VAID, varmacrokey, 1, (INTBIG)""); else
	{
		(void)setindkey((INTBIG)us_aid, VAID, varmacrokey, 1, (INTBIG)paramsave);
		efree(paramsave);
	}
}

/*
 * routine to cause further single-key commands to be ignored until a
 * carriage-return is typed.
 */
void us_unknowncommand(void)
{
	REGISTER INTSML i;

	if ((us_aid->aidstate&NOKEYLOCK) != 0) return;
	i = ttyquiet(0);
	ttyputmsg("Single-key commands will be ignored until the next RETURN");
	(void)ttyquiet(i);
	us_state |= SKIPKEYS;
}

void us_abortedmsg(void)
{
	if ((us_aid->aidstate & JUSTTHEFACTS) == 0) us_abortcommand("Aborted"); else
	{
		us_state |= COMMANDFAILED;
		if (getvalkey((INTBIG)us_aid, VAID, VSTRING, us_macrobuilding) != NOVARIABLE)
			ttyputmsg("Command ignored from macro definition");
		us_unknowncommand();
	}
}

/*
 * routine to execute the commands in the file pointed to by stream "infile".
 * If "verbose" is zero, execute the commands silently.  "headerstring"
 * is the string that must exist at the start of every line of the command
 * file that is to be processed.
 */
void us_docommands(FILE *infile, INTSML verbose, char *headerstring)
{
	REGISTER INTSML headercount, lastquiet;
	REGISTER char *pp;
	char line[MAXLINE], *savemacro;
	REGISTER USERCOM *uc, *savelastc;
	REGISTER VARIABLE *varbuilding;

	/* see how many characters are in the header string */
	headercount = strlen(headerstring);

	/* shut-up the status terminal if requested */
	if (verbose == 0) lastquiet = ttyquiet(1);

	/* save iteration and macro state */
	savelastc = us_lastcom;      us_lastcom = NOUSERCOM;
	varbuilding = getvalkey((INTBIG)us_aid, VAID, VSTRING, us_macrobuilding);
	if (varbuilding != NOVARIABLE)
	{
		(void)allocstring(&savemacro, (char *)varbuilding->addr, el_tempcluster);
		(void)delvalkey((INTBIG)us_aid, VAID, us_macrobuilding);
	} else savemacro = 0;

	/* loop through the command file */
	for(;;)
	{
		if (stopping("Command file") != 0) break;
		if (xfgets(line, MAXLINE, infile)) break;
		pp = line;   while (*pp == ' ' || *pp == '\t') pp++;
		if (namesamen(pp, headerstring, headercount) != 0) continue;
		pp += headercount;

		/* skip initial blanks and tabs */
		while (*pp == ' ' || *pp == '\t') pp++;

		/* ignore comment lines */
		if (*pp == 0 || *pp == ';') continue;

		/* parse the line into separate strings */
		uc = us_makecommand(pp);
		if (uc == NOUSERCOM) break;
		us_execute(uc, verbose, 1, 1);
		us_freeusercom(uc);
	}

	/* restore the world as it was */
	if (us_lastcom != NOUSERCOM) us_freeusercom(us_lastcom);
	us_lastcom = savelastc;
	if (savemacro != 0)
	{
		(void)setvalkey((INTBIG)us_aid, VAID, us_macrobuilding, (INTBIG)savemacro, VSTRING|VDONTSAVE);
		efree(savemacro);
	}
	if (verbose == 0) (void)ttyquiet(lastquiet);
}

/*
 * routine to parse the line in "pp" into space-separated parameters and
 * to store them in the array "build".  Returns the number of strings put
 * into "build".
 */
INTSML us_parsecommand(char *pp, char *build[])
{
	REGISTER char *ppsave, c;
	REGISTER INTSML count, inquote;

	/* parse the line into up to MAXPARS fields (command plus parameters) */
	for(count=0; count<MAXPARS; count++)
	{
		/* skip the delimiters before the keyword */
		while (*pp == ' ' || *pp == '\t') pp++;
		if (*pp == 0) break;

		/* remember the start of the keyword */
		ppsave = pp;

		/* scan to the end of the keyword (considering quotation) */
		inquote = 0;
		for(;;)
		{
			if (*pp == '"') inquote = 1 - inquote;
			if (*pp == 0) break;
			if (inquote == 0 && (*pp == ' ' || *pp == '\t')) break;
			pp++;
		}

		/* save the keyword */
		c = *pp;   *pp = 0;
		if (count > us_parsemaxownbuild)
		{
			(void)allocstring(&us_parseownbuild[count], ppsave, us_aid->cluster);
			us_parsemaxownbuild = count;
		} else (void)reallocstring(&us_parseownbuild[count], ppsave, us_aid->cluster);
		build[count] = us_parseownbuild[count];
		*pp = c;
	}
	return(count);
}

/*
 * routine to parse the string in "line" into a user command which is
 * allocated and returned (don't forget to free it when done).  Returns
 * NOUSERCOM upon error.
 */
USERCOM *us_buildcommand(INTSML count, char *par[])
{
	REGISTER USERCOM *uc;
	REGISTER INTSML i, inquote;
	REGISTER char *in, *out;
	extern COMCOMP us_userp;

	/* build the command structure */
	i = parse(par[0], &us_userp, 1);
	uc = us_allocusercom();
	if (uc == NOUSERCOM) return(NOUSERCOM);
	uc->active = i;
	uc->menu = us_getpopupmenu(par[0]);
	(void)allocstring(&uc->comname, par[0], us_aid->cluster);
	uc->count = count-1;
	for(i=1; i<count; i++)
	{
		if (allocstring(&uc->word[i-1], par[i], us_aid->cluster) != 0) return(NOUSERCOM);

		/* remove quotes from the keyword */
		inquote = 0;
		for(in = out = uc->word[i-1]; *in != 0; in++)
		{
			if (in[0] == '"')
			{
				if (inquote == 0)
				{
					inquote = 1;
					continue;
				}
				if (in[1] == '"')
				{
					*out++ = '"';
					in++;
					continue;
				}
				inquote = 0;
				continue;
			}
			*out++ = *in;
		}
		*out = 0;
	}
	return(uc);
}

/*
 * routine to parse the command in the string "line" and return a user
 * command object with that command.  This essentially combines
 * "us_parsecommand" and "us_buildcommand" into one routine.  Returns NOUSERCOM
 * if the command cannot be parsed.  The command object must be freed when done.
 */
USERCOM *us_makecommand(char *line)
{
	char *build[MAXPARS];
	REGISTER INTSML count;
	REGISTER USERCOM *uc;

	count = us_parsecommand(line, build);
	if (count <= 0) return(NOUSERCOM);
	uc = us_buildcommand(count, build);
	if (uc == NOUSERCOM) ttyputerr("No memory for command");
	return(uc);
}

/*
 * routine to obtain the variable with the macro called "name".  Returns
 * NOVARIABLE if not found.
 */
VARIABLE *us_getmacro(char *name)
{
	(void)initinfstr();
	(void)addstringtoinfstr("USER_macro_");
	(void)addstringtoinfstr(name);
	return(getval((INTBIG)us_aid, VAID, VSTRING|VISARRAY, returninfstr()));
}

/*
 * routine to return the macro package named "name"
 */
MACROPACK *us_getmacropack(char *name)
{
	REGISTER MACROPACK *pack;

	for(pack = us_macropacktop; pack != NOMACROPACK; pack = pack->nextmacropack)
		if (namesame(pack->packname, name) == 0) return(pack);
	return(NOMACROPACK);
}

/*
 * routine to return address of popup menu whose name is "name"
 */
POPUPMENU *us_getpopupmenu(char *name)
{
	REGISTER POPUPMENU *pm;

	for(pm = us_firstpopupmenu; pm != NOPOPUPMENU; pm = pm->nextpopupmenu)
		if (namesame(name, pm->name) == 0) return(pm);
	return(NOPOPUPMENU);
}

/*
 * routine to append the parameters of user command "uc" to the infinite string.
 * returns nonzero if there is a memory error
 */
INTSML us_appendargs(USERCOM *uc)
{
	REGISTER INTSML k;

	for(k=0; k<uc->count; k++)
	{
		if (addtoinfstr(' ') != 0) return(1);
		if (addstringtoinfstr(uc->word[k]) != 0) return(1);
	}
	return(0);
}

/*
 * routine to copy the string "str" to an infinite string and return it.
 * This is needed when a local string is being returned from a subroutine and
 * it will be destroyed on return.  It is also used to preserve strings from
 * dialogs which get destroyed when the dialog is closed.
 */
char *us_putintoinfstr(char *str)
{
	(void)initinfstr();
	(void)addstringtoinfstr(str);
	return(returninfstr());
}

/********************** COMMON STUFF FROM THE TERMINAL MODULE **********************/

/*
 * Routine to declare a dialog routine in "routine" that will be called when
 * "us_specialparse()" is given the command-completion module "getlinecomcomp".
 * Also, if "terminputkeyword" is not zero, this is the keyword for the
 * "terminal input" command that invokes this dialog routine.
 */
void DiaDeclareHook(char *terminputkeyword, COMCOMP *getlinecomp, void (*routine)(void))
{
	DIALOGHOOK *dh;

	dh = (DIALOGHOOK *)emalloc(sizeof (DIALOGHOOK), us_aid->cluster);
	if (dh == 0) return;
	if (terminputkeyword == 0) dh->terminputkeyword = 0; else
		(void)allocstring(&dh->terminputkeyword, terminputkeyword, us_aid->cluster);
	dh->getlinecomp = getlinecomp;
	dh->routine = routine;
	dh->nextdialoghook = us_firstdialoghook;
	us_firstdialoghook = dh;
}

/*
 * Routine to find the command-completion object associated with the "terminal input"
 * keyword in "keyword".
 */
COMCOMP *us_getcomcompfromkeyword(char *keyword)
{
	DIALOGHOOK *dh;

	for(dh = us_firstdialoghook; dh != NODIALOGHOOK; dh = dh->nextdialoghook)
	{
		if (dh->terminputkeyword == 0) continue;
		if (namesame(keyword, dh->terminputkeyword) == 0) return(dh->getlinecomp);
	}
	return(NOCOMCOMP);
}

INTSML us_specialparse(char *prompt, COMCOMP *parameter, INTSML keycount, char *paramstart[])
{
	INTBIG itemHit;
	INTSML filetype, multiplefiles;
	REGISTER char *pt, *next;
	DIALOGHOOK *dh;
	extern COMCOMP us_arrayxp, us_quitp, us_editfacetp, us_spreaddp, us_artlookp,
		us_defnodexsp, us_lambdachp, us_replacep, us_visiblelayersp, us_portlp,
		us_copyfacetp, us_showp, us_gridalip, us_defarcsp, us_portep, us_menup,
		us_yesnop, us_viewn1p, us_noyesp, us_colorpvaluep, us_viewfp, us_nodetptlp,
		us_colorreadp, us_colorwritep, us_varvep, us_nodetp, us_showop, us_librarywp,
		us_windowrnamep, us_technologyup, us_technologyedlp, us_librarydp, us_helpp,
		us_technologyctdp, us_textdsp, us_librarywriteformatp, us_colorhighp,
		us_technologyclsrp, us_technologyclscp, us_technologyclsecp, us_technologyclip,
		us_technologycnsp, us_technologyclgp, us_technologyclsp, us_showdp, us_createxp,
		us_nodetcaip, us_bindgkeyp, us_sizep, us_sizewp, us_defnodep, us_showup,
		us_librarykp, us_findobjap, us_window3dp, us_gridp, us_technologytp,
		us_renamecp, us_findnnamep, us_findexportp, us_noyesalwaysp, us_varop,
		us_copyfacetdp;

	for(dh = us_firstdialoghook; dh != NODIALOGHOOK; dh = dh->nextdialoghook)
		if (parameter == dh->getlinecomp)
	{
		(*dh->routine)();
		return(0);
	}
	if (parameter == &us_window3dp)           return(us_3ddepthdlog(paramstart));
	if (parameter == &us_gridalip)            return(us_alignmentdlog(paramstart));
	if (parameter == &us_nodetcaip)           return(us_annularringdlog(paramstart));
	if (parameter == &us_sizewp)              return(us_arcsizedlog(paramstart));
	if (parameter == &us_technologycnsp)      return(us_areadlog(prompt, paramstart, 0));
	if (parameter == &us_arrayxp)             return(us_arraydlog(paramstart));
	if (parameter == &us_artlookp)            return(us_artlookdlog(paramstart));
	if (parameter == &us_varop)               return(us_attributesdlog(paramstart));
	if (parameter == &us_technologyclscp)     return(us_capacitancedlog(paramstart, 0));
	if (parameter == &us_copyfacetp)          return(us_copyfacetdlog(prompt, paramstart));
	if (parameter == &us_createxp)            return(us_createtodlog(paramstart));
	if (parameter == &us_defarcsp)            return(us_defarcdlog(paramstart));
	if (parameter == &us_defnodexsp)          return(us_defnodedlog(paramstart));
	if (parameter == &us_textdsp)             return(us_deftextdlog(prompt, paramstart));
	if (parameter == &us_technologyedlp)      return(us_dependentlibdlog(paramstart));
	if (parameter == &us_editfacetp)          return(us_editfacetdlog(prompt, paramstart));
	if (parameter == &us_defnodep)            return(us_facetdlog(paramstart));
	if (parameter == &us_showdp)              return(us_facetlist(prompt, paramstart, 1));
	if (parameter == &us_showup)              return(us_facetlist(prompt, paramstart, 0));
	if (parameter == &us_viewfp)              return(us_frameoptionsdlog(paramstart));
	if (parameter == &us_technologyclsp)      return(us_gaindlog(prompt, paramstart, 0));
	if (parameter == &us_gridp)               return(us_griddlog(paramstart));
	if (parameter == &us_helpp)               return(us_helpdlog(prompt, paramstart));
	if (parameter == &us_colorhighp)          return(us_highlayerlog(paramstart));
	if (parameter == &us_copyfacetdp)         return(us_iconstyledlog(paramstart));
	if (parameter == &us_technologyclsecp)    return(us_inductancedlog(paramstart, 0));
	if (parameter == &us_lambdachp)           return(us_lambdadlog(paramstart));
	if (parameter == &us_librarydp)           return(us_librarypathdlog(paramstart));
	if (parameter == &us_menup)               return(us_menudlog(paramstart));
	if (parameter == &us_viewn1p)             return(us_newviewdlog(paramstart));
	if (parameter == &us_sizep)               return(us_nodesizedlog(paramstart));
	if (parameter == &us_noyesp)              return(us_noyesdlog(prompt, paramstart));
	if (parameter == &us_noyesalwaysp)        return(us_noyesalwaysdlog(prompt, paramstart));
	if (parameter == &us_librarykp)           return(us_oldlibrarydlog(paramstart));
	if (parameter == &us_librarywp)           return(us_optionsavingdlog(paramstart));
	if (parameter == &us_colorpvaluep)        return(us_patterndlog(paramstart));
	if (parameter == &us_nodetptlp)           return(us_placetextdlog(paramstart));
	if (parameter == &us_librarywriteformatp) return(us_plotoptionsdlog(paramstart));
	if (parameter == &us_portlp)              return(us_portdisplaydlog(paramstart));
	if (parameter == &us_portep)              return(us_portdlog(paramstart));
	if (parameter == &us_bindgkeyp)           return(us_quickkeydlog(paramstart));
	if (parameter == &us_quitp)               return(us_quitdlog(prompt, paramstart));
	if (parameter == &us_renamecp)            return(us_renamedlog(paramstart));
	if (parameter == &us_replacep)            return(us_replacedlog(paramstart));
	if (parameter == &us_technologyclsrp)     return(us_resistancedlog(prompt, paramstart, 0));
	if (parameter == &us_findobjap)           return(us_selectoptdlog(paramstart));
	if (parameter == &us_findnnamep)          return(us_selectportnetdlog(paramstart, 0));
	if (parameter == &us_findexportp)         return(us_selectportnetdlog(paramstart, 1));
	if (parameter == &us_showp)               return(us_showdlog(paramstart, 0));
	if (parameter == &us_showop)              return(us_showdlog(paramstart, 1));
	if (parameter == &us_spreaddp)            return(us_spreaddlog(paramstart));
	if (parameter == &us_technologyup)        return(us_technologydlog(prompt, paramstart));
	if (parameter == &us_technologytp)        return(us_techoptdlog(paramstart));
	if (parameter == &us_technologyctdp)      return(us_techvarsdlog(paramstart));
	if (parameter == &us_nodetp)              return(us_tracedlog(paramstart));
	if (parameter == &us_varvep)              return(us_variablesdlog(paramstart));
	if (parameter == &us_technologyclip)      return(us_vccsdlog(paramstart, 0));
	if (parameter == &us_visiblelayersp)      return(us_visiblelayersdlog(prompt, paramstart));
	if (parameter == &us_technologyclgp)      return(us_widlendlog(paramstart, 0));
	if (parameter == &us_windowrnamep)        return(us_windowviewdlog(paramstart));
	if (parameter == &us_yesnop)              return(us_yesnodlog(prompt, paramstart));
	if (parameter == &us_colorwritep || parameter == &us_colorreadp)
	{
		/* copy "prompt" to a temporary so that it can be modified */
		(void)initinfstr();
		(void)addstringtoinfstr(prompt);
		prompt = returninfstr();

		/* file dialog prompts have the form "type/prompt" */
		filetype = FILETYPETEXT;
		multiplefiles = 0;
		for(pt = prompt; *pt != 0; pt++) if (*pt == '/') break;
		if (*pt == '/')
		{
			*pt = 0;
			if (namesamen(prompt, "multi-", 6) == 0)
			{
				multiplefiles = 1;
				prompt += 6;
			}
			filetype = getfiletype(prompt);
			if (filetype == 0) filetype = FILETYPETEXT;
			*pt++ = '/';
			prompt = pt;
		}
		if (parameter == &us_colorwritep)
		{
			/* file output dialog prompts have the form "type/prompt|default" */
			filetype |= FILETYPEWRITE;
			pt = prompt;
			while (*pt != 0 && *pt != '|') pt++;
			if (*pt == 0)
			{
				next = (char *)fileselect(prompt, filetype, "");
			} else
			{
				*pt = 0;
				next = (char *)fileselect(prompt, filetype, &pt[1]);
				*pt = '|';
			}
			if (next == 0 || *next == 0) return(0);
			setdefaultcursortype(WAITCURSOR);
			paramstart[0] = next;
			return(1);
		} else
		{
			/* do dialog to get file name */
			if (multiplefiles != 0)
			{
				/* allow selection of multiple files */
				next = multifileselectin(prompt, filetype);
			} else
			{
				/* allow selection of one file */
				next = (char *)fileselect(prompt, filetype, "");
			}
			if (next == 0 || *next == 0) return(0);
			setdefaultcursortype(WAITCURSOR);
			paramstart[0] = next;
			return(1);
		}
	}

	/* the general case: display the dialog box */
	if (DiaInitDialog(&us_listdialog) != 0) return(0);
	DiaInitTextDialog(3, parameter->toplist, parameter->nextcomcomp, DiaNullDlogDone,
		0, SCSELMOUSE|SCSELKEY|SCDOUBLEQUIT);
	DiaSetText(4, prompt);

	for(;;)
	{
		itemHit = DiaNextHit();
		if (itemHit == OK || itemHit == CANCEL) break;
	}
	paramstart[0] = us_putintoinfstr(DiaGetScrollLine(3, DiaGetCurLine(3)));
	DiaDoneDialog();
	if (itemHit == CANCEL) return(0);
	return(1);
}

/*
 * routine to examine the command in "uc" and fill the command completion array
 * "comarray" with appropriate entries for parsing the string.  Returns the
 * number of valid entries in the array.
 */
INTSML us_fillcomcomp(USERCOM *uc, COMCOMP *comarray[])
{
	REGISTER INTSML j, k, com, pos, curtotal, count;
	REGISTER char *pt;
	char *build[MAXPARS];
	COMCOMP *nextparams[MAXPARS+5];
	REGISTER VARIABLE *var;

	comarray[0] = NOCOMCOMP;
	com = uc->active;

	/* if the command is not valid, exit now */
	if (com < 0) return(0);

	/* handle normal commands */
	if (com < us_longcount)
	{
		/* initialize the command completion array with the default parameters */
		for(curtotal=0; curtotal<us_lcommand[com].params; curtotal++)
			comarray[curtotal] = us_lcommand[com].par[curtotal];
	} else
	{
		/* ignore popup menus */
		if (uc->menu != NOPOPUPMENU) return(0);

		/* handle macros */
		var = us_getmacro(uc->comname);
		if (var == NOVARIABLE) return(0);
		pt = ((char **)var->addr)[2];
		count = us_parsecommand(pt, build);
		for(curtotal=0; curtotal<count; curtotal++)
			comarray[curtotal] = (COMCOMP *)myatoi(build[curtotal]);
	}
	comarray[curtotal] = NOCOMCOMP;

	/* now run through the parameters, inserting whatever is appropriate */
	for(pos=0; comarray[pos] != NOCOMCOMP; pos++)
	{
		if (pos >= uc->count) break;

		/* load the necessary global and get the next parameters */
		us_pathiskey = comarray[pos]->ifmatch;
		k = (*comarray[pos]->params)(uc->word[pos], nextparams, ' ');

		/* clip to the number of allowable parameters */
		if (k+curtotal >= MAXPARS-1) k = MAXPARS-2-curtotal;

		/* spread open the list */
		for(j = MAXPARS-k-1; j >= curtotal; j--) comarray[j+k] = comarray[j];

		/* fill in the parameter types */
		for(j = 0; j < k; j++) comarray[curtotal+j] = nextparams[j];
		curtotal += k;
	}
	return(curtotal);
}

/*
 * internal routine to read words.  Input characters come from the routine
 * "input"; output strings are written to the routine "output", backups of
 * one character are displayed with "erase", and deletion of the entire line
 * are done with the routine "kill".  If the display scrolls (in which case
 * informative inter-line messages require re-typing of the line) then
 * "scrolling" is nonzero.  Returns nonzero when the last word is read
 */
INTSML us_pagetword(INTSML (*input)(COMCOMP**, INTSML*), void (*output)(char*),
	void (*erase)(void), void (*kill)(void), INTSML scrolling)
{
	REGISTER INTSML c, i, j, k, nofill, noshoall, isbreak, inquote, bst, extra, w;
	REGISTER INTSML (*setparams)(char*, COMCOMP*[], char), (*toplist)(char**);
	REGISTER void (*backupto)(INTSML, char*[]);
	REGISTER char *initialline, *pt, realc, *breakch, *(*nextinlist)(void);
	char *compare;
	INTSML reset;
	REGISTER COMCOMP *thiscomcomp;
	COMCOMP *nextcomcomp, *nextparams[100];
	static COMCOMP nullcomcomp = {NOKEYWORD, NOTOPLIST, NONEXTLIST, NOPARAMS, NOBACKUP,
		0, " \t", "", ""};

	/* continue parsing word number "us_paparamcount" */
	thiscomcomp = NOCOMCOMP;
	nextcomcomp = us_paparamtype[us_paparamcount];
	initialline = us_paparamstart[us_paparamcount];
	us_pattyline[us_pacurchar] = 0;
	inquote = 0;

	for(;;)
	{
		c = (*input)(&nextcomcomp, &reset);
		if (c == EOF) return(1);

		if (reset != 0)
		{
			/* reset to the beginning-of-line state */
			us_paparamcount = 0;
			initialline = us_paparamstart[us_paparamcount];
			us_pacurchar = initialline - us_pattyline;
			us_paparamtype[us_paparamcount] = nextcomcomp;
			us_paparamtype[us_paparamcount+1] = NOCOMCOMP;
			us_pattyline[us_pacurchar] = 0;
			inquote = 0;
		}

		/* set new command completion if it changed */
		if (nextcomcomp == NOCOMCOMP) return(1);
		if (nextcomcomp != thiscomcomp)
		{
			thiscomcomp = nextcomcomp;
			if ((thiscomcomp->interpret&NOSHOALL) != 0) noshoall = 1; else
				noshoall = 0;
			if ((thiscomcomp->interpret&NOFILL) != 0) nofill = 1; else
				nofill = 0;
			us_pathiskey = thiscomcomp->ifmatch;
			toplist = thiscomcomp->toplist;
			nextinlist = thiscomcomp->nextcomcomp;
			setparams = thiscomcomp->params;
			backupto = thiscomcomp->backupto;
			breakch = thiscomcomp->breakchrs;
		}

		/* backup one character if the erase character is typed */
		if (c == us_erasech)
		{
			/* ignore if at the start of the line */
			if (&us_pattyline[us_pacurchar] == initialline && us_paparamcount == 0) continue;

			/* back up one character */
			if (us_pattyline[--us_pacurchar] == '"') inquote = 1 - inquote;
			us_pattyline[us_pacurchar] = 0;
			(*erase)();

			/* special case if just backed up over a word boundary */
			if (&us_pattyline[us_pacurchar] < initialline)
			{
				/* remove any added parameters from the last keyword */
				k = us_paparamadded[us_paparamcount-1];
				for(j=0; j<k; j++)
					us_paparamtype[us_paparamcount+j] = us_paparamtype[us_paparamcount+j+k];

				/* let the user code know about this */
				us_paparamcount--;
				(*backupto)(us_paparamcount, us_paparamstart);
				return(0);
			}
			continue;
		}

		/* erase the line if the kill character is typed */
		if (c == us_killch)
		{
			(*kill)();
			while(&us_pattyline[us_pacurchar] > us_palinestart) us_pacurchar--;
			us_paparamcount = 0;
			us_paparamtype[us_paparamcount+1] = NOCOMCOMP;
			(*backupto)(us_paparamcount, us_paparamstart);
			return(0);
		}

		/* provide help if requested */
		if (c == '?' && inquote == 0)
		{
			/* special case for variables */
			if (*initialline == '$')
			{
				toplist = us_topofvars;   nextinlist = us_nextvars;
			}

			/* initialize scrolled list of choices if not scrolling in text window */
			if (scrolling == 0)
				DiaLoadTextDialog(5, DiaNullDlogList, DiaNullDlogItem, DiaNullDlogDone, -1);

			compare = initialline;
			i = (*toplist)(&compare);
			if (*thiscomcomp->noise != 0)
			{
				(void)initinfstr();
				(void)addstringtoinfstr(thiscomcomp->noise);
				if (thiscomcomp->def != 0)
				{
					(void)addstringtoinfstr(" (");
					(void)addstringtoinfstr(thiscomcomp->def);
					(void)addtoinfstr(')');
				}
				if (i != 0) (void)addtoinfstr(':');
				if (scrolling == 0) DiaStuffLine(5, returninfstr()); else
				{
					(*output)("\n");
					(*output)(returninfstr());
					(*output)("\n");
				}
			}

			if (*compare != 0 || noshoall == 0)
			{
				for(i=0; (pt = (*nextinlist)()); )
				{
					j = stringmatch(pt, compare);
					if (j == -1) continue;
					if (j == -2) j = strlen(pt);
					if (j > i) i = j;
				}
				if (i == 0 && *compare != 0) ttybeep(); else
				{
					us_paambiguous[0] = 0;
					compare = initialline;
					for((void)(*toplist)(&compare); (pt = (*nextinlist)()); )
					{
						j = stringmatch(pt, compare);
						if (j == -1) continue;
						if (j == i || j == -2)
						{
							if (scrolling == 0) DiaStuffLine(5, pt); else
							{
								if ((INTBIG)(strlen(us_paambiguous)+strlen(pt)) > MESSAGESWIDTH-1)
								{
									(*output)(us_paambiguous);
									(*output)("\n");
									us_paambiguous[0] = 0;
								}
								(void)strcat(us_paambiguous, " ");
								(void)strcat(us_paambiguous, pt);
							}
						}
					}
					if (*us_paambiguous != 0)
					{
						(*output)(us_paambiguous);
						(*output)("\n");
					}
				}
			}
			if (scrolling == 0) DiaSelectLine(5, -1); else
			{
				for(j=0; j<us_pacurchar; j++) if (us_pattyline[j] == 0)
					us_paambiguous[j] = ' '; else us_paambiguous[j] = us_pattyline[j];
				us_paambiguous[us_pacurchar] = 0;
				(*output)(us_paambiguous);
			}
			continue;
		}

		/* check for quoted strings */
		if (c == '"') inquote = 1 - inquote;

		/* see if this is a break character */
		isbreak = 0;
		if (inquote == 0)
			for(pt = breakch; *pt != 0; pt++)
				if (c == *pt) { isbreak++;  break; }
		if (isbreak != 0) { realc = (char)c;   c = ' '; }

		/* ignore break characters at the start of a keyword */
		if ((c == 033 || isbreak != 0) && *initialline == 0) continue;

		/* fill out keyword if a break character is reached */
		if (c == 033 || isbreak != 0 || c == '\n' || c == '\r')
		{
			/* special case for variables */
			if (*initialline == '$')
			{
				toplist = us_topofvars;   nextinlist = us_nextvars;
			}

			compare = initialline;
			if (*compare != 0)
			{
				extra = i = bst = 0;
				(void)(*toplist)(&compare);
				for(w=0; (pt = (*nextinlist)()); w++)
				{
					j = stringmatch(pt, compare);
					if (j == -1) continue;
					if (j == -2)
					{
						i = 10000;   extra = 0;   bst = w;   break;
					}
					if (j > i)
					{
						extra = strlen(strcpy(us_paambiguous, &pt[j]));
						i = j;   bst = w;
					} else if (j == i)
					{
						bst = -1;
						for(j=0; j<extra; j++) if (pt[i+j] != us_paambiguous[j]) extra = j;
					}
				}

				/* fill out the keyword if possible */
				if ((nofill == 0 || c == 033) && extra != 0)
				{
					us_paambiguous[extra] = 0;
					if (us_pacurchar+extra >= us_patruelength)
						us_expandttybuffers((INTSML)(us_pacurchar+extra+10));
					(void)strcat(&us_pattyline[us_pacurchar], us_paambiguous);
					us_pacurchar += extra;
					(*output)(us_paambiguous);
				}

				/* print an error bell if there is ambiguity */
				if (i == 0) bst = -1;
				if (bst < 0 && c == 033)
				{
					ttybeep();
					continue;
				}

				/* if there were no words to match, pretend the first fit */
				if (w == 0 && i == 0) bst = 0;
			} else bst = -1;

			/* if an escape was typed, stop after filling in the keyword */
			if (c == 033) continue;

			/* if normal break character was typed, advance to next keyword */
			if (isbreak != 0)
			{
				/* find out number of parameters to this keyword */
				k = (*setparams)(initialline, nextparams, realc);

				/* clip to the number of allowable parameters */
				if (k+us_paparamcount >= us_pakeycount) k = us_pakeycount-1-us_paparamcount;

				/* spread open the list */
				for(j = us_pakeycount-k; j >= us_paparamcount; j--)
					us_paparamtype[j+k+1] = us_paparamtype[j+1];

				/* fill in the parameter types */
				for(j = 0; j < k; j++)
					us_paparamtype[us_paparamcount+j+1] = nextparams[j];
				us_paparamadded[us_paparamcount] = k;
				us_paparamcount++;
				if (us_paparamtype[us_paparamcount] == NOCOMCOMP)
				{
					us_paparamtype[us_paparamcount] = &nullcomcomp;
					us_paparamtype[us_paparamcount+1] = NOCOMCOMP;
				}
				pt = &us_pattyline[us_pacurchar];
				if (us_pacurchar >= us_patruelength)
					us_expandttybuffers((INTSML)(us_pacurchar+10));
				*pt = realc;   pt[1] = 0;
				us_pacurchar++;
				(*output)(pt);
				us_paparamstart[us_paparamcount] = &us_pattyline[us_pacurchar];
				return(0);
			}

			/* if this is the end of the line, signal so */
			if (c == '\n' || c == '\r') return(1);
			break;
		}

		/* normal character: add it to the input string */
		if (us_pacurchar >= us_patruelength) us_expandttybuffers((INTSML)(us_pacurchar+10));
		pt = &us_pattyline[us_pacurchar];
		*pt = (char)c;   pt[1] = 0;
		us_pacurchar++;
		(*output)(pt);
	}
	return(0);
}

/*
 * Routine to expand the buffers "us_pattyline" and "us_paambiguous" to "amt" long
 */
void us_expandttybuffers(INTSML amt)
{
	REGISTER char *newline, *newamb;
	REGISTER INTSML i;

	/* stop now if request is already satisfied */
	if (amt <= us_patruelength) return;

	/* allocate new buffers */
	newline = (char *)emalloc(((amt+1) * (sizeof (char))), us_aid->cluster);
	if (newline == 0) return;
	newamb = (char *)emalloc(((amt+1) * (sizeof (char))), us_aid->cluster);
	if (newamb == 0) return;

	/* preserve any old buffers */
	if (us_pattyline != 0)
	{
		for(i=0; i<us_patruelength; i++)
		{
			newline[i] = us_pattyline[i];
			newamb[i] = us_paambiguous[i];
		}
		if (us_paparamstart != 0)
			for(i=0; i<=us_paparamcount; i++)
				us_paparamstart[i] = us_paparamstart[i] - us_pattyline + newline;
		efree(us_pattyline);
		efree(us_paambiguous);
	}

	/* set the new buffers */
	us_pattyline = newline;
	us_paambiguous = newamb;
	us_patruelength = amt;
}

/*
 * internal routines to search keyword lists for command completion:
 */
INTSML us_patoplist(char **a)
{
	us_pathisind = 0;
	if (us_pathiskey == NOKEYWORD) return(0);
	if (us_pathiskey[0].name == 0) return(0);
	return(1);
}

char *us_panextinlist(void)
{
	if (us_pathiskey == NOKEYWORD) return(0);
	return(us_pathiskey[us_pathisind++].name);
}

INTSML us_paparams(char *word, COMCOMP *arr[], char breakc)
{
	REGISTER INTSML i, count, ind;

	if (*word == 0 || us_pathiskey == NOKEYWORD) return(0);
	for(ind=0; us_pathiskey[ind].name != 0; ind++)
		if (namesame(word, us_pathiskey[ind].name) == 0) break;
	if (us_pathiskey[ind].name == 0) return(0);
	count = us_pathiskey[ind].params;
	for(i=0; i<count; i++) arr[i] = us_pathiskey[ind].par[i];
	return(count);
}

void us_pabackupto(INTSML a, char *b[]) {}
