/*
 * EFvisit.c -
 *
 * Procedures to traverse and output flattened nodes, capacitors,
 * transistors, resistors, and Distances.
 *
 *     *********************************************************************
 *     * Copyright (C) 1985, 1990 Regents of the University of California. *
 *     * Permission to use, copy, modify, and distribute this              *
 *     * software and its documentation for any purpose and without        *
 *     * fee is hereby granted, provided that the above copyright          *
 *     * notice appear in all copies.  The University of California        *
 *     * makes no representations about the suitability of this            *
 *     * software for any purpose.  It is provided "as is" without         *
 *     * express or implied warranty.  Export of this software outside     *
 *     * of the United States of America may require an export license.    *
 *     *********************************************************************
 */

#ifndef lint
static char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-8.0/extflat/EFvisit.c,v 1.5 2010/08/10 00:18:45 tim Exp $";
#endif  /* not lint */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include "utils/magic.h"
#include "utils/geometry.h"
#include "utils/geofast.h"
#include "utils/hash.h"
#include "utils/malloc.h"
#include "utils/utils.h"
#include "extflat/extflat.h"
#include "database/database.h"  /* before EFint.h for ArrayInfo */
#include "extflat/EFint.h"      /* also has ArrayInfo */
#include "tiles/tile.h"
#include "extract/extract.h"

/* C99 compat */
#include "textio/textio.h"

/* Root of the tree being flattened */
extern Def *efFlatRootDef;
extern Use efFlatRootUse;
extern HierContext efFlatContext;

extern void efDevFixLW();
extern void efHNOutPrefix();

bool efDevKilled();

/*
 * ----------------------------------------------------------------------------
 *
 * EFVisitSubcircuits --
 *
 * Visit all of the "defined" subcircuits in the circuit.
 * This is meant to provide a generic functionality similar to
 * the transistor/resistor/capacitor extraction.  It assumes that the
 * end-user has an existing description of the extracted subcircuit,
 * such as a characterized standard cell, and that magic is not to
 * attempt an extraction itself, but only to call the predefined
 * subcircuit, matching nodes to the subcircuit's port list.
 *
 * For each def encountered which has the DEF_SUBCIRCUIT flag set,
 * call the user-supplied procedure (*subProc)(), which should be of
 * the following form:
 *
 * 	(*subProc)(use, hierName, is_top)
 *	    Use *use;
 *	    HierName *hierName;
 *	    bool is_top;
 *	{
 *	}
 *
 * is_top will be TRUE for the top-level cell, and FALSE for all
 * other cells.  The procedure should return 0 normally, or 1 to abort
 * the search.
 *
 * Results:
 *	Returns 0 if terminated normally, or 1 if the search
 *	was aborted.
 *
 * Side effects:
 *	Whatever (*subProc)() does.
 *
 * ----------------------------------------------------------------------------
 */

int
EFVisitSubcircuits(
    int (*subProc)(),
    ClientData cdata)
{
    CallArg ca;
    HierContext *hc;
    int efVisitSubcircuits();   /* Forward declaration */

    /* If the top-level def is defined as a subcircuit, call topProc */

    hc = &efFlatContext;
    if (hc->hc_use->use_def->def_flags & DEF_SUBCIRCUIT)
	if ((*subProc)(hc->hc_use, hc->hc_hierName, TRUE))
	    return 1;

    /* For each subcell of the top-level def that is defined as */
    /* a subcircuit, call subProc.				*/

    ca.ca_proc = subProc;
    ca.ca_cdata = cdata;

    if (efHierSrUses(hc, efVisitSubcircuits, (ClientData) &ca))
	return 1;

    return 0;
}

/*
 * Procedure to visit recursively all subcircuits in the design.
 * Does all the work of EFVisitSubcircuits() above.
 *
 * Results:
 *	Returns 0 to keep efHierSrUses going.
 *
 * Side effects:
 *	Calls the client procedure (*ca->ca_proc)().
 */

int
efVisitSubcircuits(
    HierContext *hc,
    CallArg *ca)
{
    /* Look for children of this def which are defined	*/
    /* as subcircuits via the DEF_SUBCIRCUIT flag.	*/

    if (hc->hc_use->use_def->def_flags & DEF_SUBCIRCUIT)
    {
	if ((*ca->ca_proc)(hc->hc_use, hc->hc_hierName, NULL))
	    return 1;
	else
	    return 0;
    }

    /* Recursively visit subcircuits in our children last. */

    if (efHierSrUses(hc, efVisitSubcircuits, (ClientData) ca))
	return 1;

    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * EFGetLengthAndWidth --
 *
 *	Estimate length and width for a device from area and perimeter values.
 *	Mostly this routine is meant to handle the older "fet" record.
 *	Newer "device" types should have length and width properly determined
 *	already, and we just return those values from the device structure.
 *
 * Results:
 *	None
 *
 * Side Effects:
 *	Values substituted for length and width.
 *
 * ----------------------------------------------------------------------------
 */

void
EFGetLengthAndWidth(
    Dev *dev,
    int *lptr,
    int *wptr)
{
    DevTerm *gate, *source, *drain;
    int area, perim, l, w;

    switch (dev->dev_class)
    {
	case DEV_MOSFET:
	case DEV_ASYMMETRIC:
	case DEV_BJT:
	case DEV_SUBCKT:
	case DEV_VERILOGA:
	case DEV_MSUBCKT:
	case DEV_RSUBCKT:
	case DEV_DSUBCKT:
	case DEV_CSUBCKT:
	case DEV_DIODE:
	case DEV_PDIODE:
	case DEV_NDIODE:
	case DEV_CAP:
	case DEV_CAPREV:
	case DEV_RES:
	    l = dev->dev_length;
	    w = dev->dev_width;
	    break;

	case DEV_FET:
	    area = dev->dev_area;
	    perim = dev->dev_perim;

	    gate = &dev->dev_terms[0];

	    /*
	     * L, W, and flat coordinates of a point inside the channel.
	     * Handle FETs with two terminals (capacitors) separately.
	     */

	    if (dev->dev_nterm == 2)
	    {
		/* Convert area to type double to avoid overflow in	*/
		/* extreme cases.					*/
		l = perim - (int)sqrt((double)(perim * perim) - 16 * (double)area);
		l >>= 2;
		w = area / l;
	    }
	    else
	    {
		source = drain = &dev->dev_terms[1];
		if (dev->dev_nterm >= 3)
		    drain = &dev->dev_terms[2];
		l = gate->dterm_length / 2;
		w = (source->dterm_length + drain->dterm_length) / 2;
	    }
	    if (gate->dterm_attrs) efDevFixLW(gate->dterm_attrs, &l, &w);
	    break;

	default:
	    l = w = 0;
	    break;
    }

    *lptr = l;
    *wptr = w;
}

/*
 * ----------------------------------------------------------------------------
 *
 * EFVisitDevs --
 *
 * Visit all the devs in the circuit.
 * Must be called after EFFlatBuild().
 * For each dev in the circuit, call the user-supplied procedure
 * (*devProc)(), which should be of the following form
 *   see also typedef cb_extflat_visitdevs_t:
 *
 *	int (*devProc)(
 *	    Dev *dev,
 *	    HierContext *hc,
 *	    float scale,
 *	    Transform *trans,
 *	    ClientData cdata)
 *	{
 *	}
 *
 * The procedure should return 0 normally, or 1 to abort the
 * search.
 *
 * We ensure that no devs connected to killed nodes are passed
 * to this procedure.
 *
 * Results:
 *	Returns 0 if terminated normally, or 1 if the search
 *	was aborted.
 *
 * Side effects:
 *	Whatever (*devProc)() does.
 *
 * ----------------------------------------------------------------------------
 */

int
EFVisitDevs(
    const cb_extflat_visitdevs_t devProc,
    ClientData cdata)
{
    CallArg ca;

    ca.ca_proc = (int (*)()) devProc;
    ca.ca_cdata = cdata;
    return efVisitDevs(&efFlatContext, (ClientData) &ca);
}

/*
 * Procedure to visit recursively all devs in the design.
 * Does all the work of EFVisitDevs() above.
 *
 * Results:
 *	Returns 0 to keep efHierSrUses going.
 *
 * Side effects:
 *	Calls the client procedure (*ca->ca_proc)().
 */

int
efVisitDevs(
    HierContext *hc,
    CallArg *ca)
{
    Def *def = hc->hc_use->use_def;
    Dev *dev;
    float scale;
    Transform t;
    HashSearch hs;
    HashEntry *he;

    if (def->def_flags & DEF_SUBCIRCUIT) return 0;

    /* Recursively visit devs in our children first */
    if (efHierSrUses(hc, efVisitDevs, (ClientData) ca))
	return 1;

    scale = (efScaleChanged && def->def_scale != 1.0) ? def->def_scale : 1.0;
    t = hc->hc_trans;

    /* Visit our own devices */

    HashStartSearch(&hs);
    while ((he = HashNext(&def->def_devs, &hs)))
    {
	dev = (Dev *)HashGetValue(he);
	if (efDevKilled(dev, hc->hc_hierName))
	    continue;

	const cb_extflat_visitdevs_t ca_visitdevs_proc = (cb_extflat_visitdevs_t) ca->ca_proc; /* FIXME temporary */
	if ((*ca_visitdevs_proc)(dev, hc, scale, &t, ca->ca_cdata)) /* @invoke cb_extflat_visitdevs_t */
	    return 1;
    }
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * efDevKilled --
 *
 * Check all of the nodes to which the dev 'dev' is connected (its
 * hierarchical prefix is hc->hc_hierName).  If any of these nodes
 * have been killed, then the dev is also killed.
 *
 * Results:
 *	TRUE if the dev is connected to a killed node, FALSE if it's ok.
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

bool
efDevKilled(
    Dev *dev,
    HierName *prefix)
{
    HierName *suffix;
    HashEntry *he;
    EFNodeName *nn;
    int n;

    for (n = 0; n < dev->dev_nterm; n++)
    {
	suffix = dev->dev_terms[n].dterm_node->efnode_name->efnn_hier;
	he = EFHNConcatLook(prefix, suffix, "kill");
	if (he  && (nn = (EFNodeName *) HashGetValue(he))
		&& (nn->efnn_node->efnode_flags & EF_KILLED))
	    return TRUE;
    }

    return FALSE;
}

/*
 * ----------------------------------------------------------------------------
 *
 * efDevFixLW --
 *
 * Called for any devs that have gate attributes; these attributes may
 * specify the L and W of the dev explicitly.  The attributes will be
 * of the form ext:l=value or ext:w=value, where value is either numerical
 * or symbolic; if symbolic the symbol must have been defined via efSymAdd().
 * If the value is symbolic but wasn't defined by efSymAdd(), it's ignored.
 * The variables *pL and *pW are changed to reflect the new L and W as
 * appropriate.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	See above.
 *
 * ----------------------------------------------------------------------------
 */

void
efDevFixLW(
    char *attrs,
    int *pL,
    int *pW)
{
    char *cp, *ep;
    char attrName, savec;
    int value;

    cp = attrs;
    while (cp && *cp)
    {
	if (*cp != 'e' || strncmp(cp, "ext:", 4) != 0)
	    goto skip;

	cp += 4;
	if (*cp && cp[1] == '=')
	{
	    switch (*cp)
	    {
		case 'w': case 'W':
		    attrName = 'w';
		    goto both;
		case 'l': case 'L':
		    attrName = 'l';
		both:
		    cp += 2;
		    for (ep = cp; *ep && *ep != ','; ep++)
			/* Nothing */;
		    savec = *ep;
		    *ep = '\0';
		    if (StrIsInt(cp)) value = atoi(cp);
		    else if (!efSymLook(cp, &value)) goto done;

		    if (attrName == 'w')
			*pW = value;
		    else if (attrName == 'l')
			*pL = value;

		done:
		    *ep = savec;
	    }
	}

skip:
	/* Skip to next attribute */
	while (*cp && *cp++ != ',')
	    /* Nothing */;
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * EFVisitResists --
 *
 * Visit all the resistors in the circuit.
 * Must be called after EFFlatBuild().
 * For each resistor in the circuit, call the user-supplied procedure
 * (*resProc)(), which should be of the following form, where hn1 and
 * hn2 are the HierNames of the two nodes connected by the resistor
 *   see also typedef cb_extflat_visitresists_t:
 *
 *	int (*resProc)(
 *	    const HierName *hn1,
 *	    const HierName *hn2,
 *	    float resistance,
 *	    ClientData cdata)
 *	{
 *	}
 *
 * The procedure should return 0 normally, or 1 to abort the
 * search.
 *
 * We ensure that no resistors connected to killed nodes are passed
 * to this procedure.
 *
 * Results:
 *	Returns 0 if terminated normally, or 1 if the search
 *	was aborted.
 *
 * Side effects:
 *	Whatever (*resProc)() does.
 *
 * ----------------------------------------------------------------------------
 */

int
EFVisitResists(
    const cb_extflat_visitresists_t resProc,
    ClientData cdata)
{
    CallArg ca;

    ca.ca_proc = (int (*)()) resProc;
    ca.ca_cdata = cdata;
    return efVisitResists(&efFlatContext, &ca);
}

/*
 * Procedure to visit recursively all resistors in the design.
 * Does all the work of EFVisitResists() above.
 *
 * Results:
 *	Returns 0 to keep efHierSrUses going.
 *
 * Side effects:
 *	Calls the client procedure (*ca->ca_proc)().
 */

extern int efVisitSingleResist(HierContext *hc, const char *name1, const char *name2,
                               Connection *res, ClientData cdata); /* @typedef cb_extflat_hiersrarray_t (CallArg*) */

int
efVisitResists(
    HierContext *hc,
    CallArg *ca)
{
    Def *def = hc->hc_use->use_def;
    Connection *res;

    /* Ignore subcircuits */
    if (def->def_flags & DEF_SUBCIRCUIT) return 0;

    /* Recursively visit resistors in our children first */
    if (efHierSrUses(hc, efVisitResists, (ClientData) ca))
	return 1;

    /* Visit our own resistors */
    for (res = def->def_resistors; res; res = res->conn_next)
    {
	/* Special case for speed if no arraying info */
	if (res->conn_1.cn_nsubs == 0)
	{
	    if (efVisitSingleResist(hc, res->conn_name1, res->conn_name2,
			res, ca))
		return 1;
	}
	else if (efHierSrArray(hc, res, efVisitSingleResist, (ClientData) ca))
	    return 1;
    }

    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * efVisitSingleResist --
 *
 * Visit a resistor of res->conn_res milliohms between the nodes
 * 'name1' and 'name2' (text names, not hierarchical names).  Don't
 * process the resistor if either terminal is a killed node.
 *
 * Results:
 *	Whatever the user-supplied procedure (*ca->ca_proc)() returns
 *	(type int).
 *
 * Side effects:
 *	Calls the user-supplied procedure.
 *
 * ----------------------------------------------------------------------------
 */

/* @typedef cb_extflat_hiersrarray_t (CallArg*) */
int
efVisitSingleResist(
    HierContext *hc,		/* Contains hierarchical pathname to cell */
    const char *name1,
    const char *name2,		/* Names of nodes connecting to resistor */
    Connection *res,		/* Contains resistance to add */
    ClientData cdata)
{
    CallArg *ca = (CallArg *)CD2PTR(cdata);
    const EFNode *n1, *n2;
    HashEntry *he;

    if ((he = EFHNLook(hc->hc_hierName, name1, "resist(1)")) == NULL)
	return 0;
    n1 = ((const EFNodeName *) HashGetValue(he))->efnn_node;
    if (n1->efnode_flags & EF_KILLED)
	return 0;

    if ((he = EFHNLook(hc->hc_hierName, name2, "resist(2)")) == NULL)
	return 0;
    n2 = ((const EFNodeName *) HashGetValue(he))->efnn_node;
    if (n2->efnode_flags & EF_KILLED)
	return 0;

    /* Do nothing if the nodes aren't different */
    if (n1 == n2)
	return 0;

    const cb_extflat_visitresists_t ca_visitresists_proc = (cb_extflat_visitresists_t) ca->ca_proc; /* FIXME temporary */
    return (*ca_visitresists_proc)(n1->efnode_name->efnn_hier,
		n2->efnode_name->efnn_hier,
		res->conn_res, ca->ca_cdata); /* @invoke cb_extflat_visitresists_t */
}

/*
 * ----------------------------------------------------------------------------
 *
 * EFVisitCaps --
 *
 * Visit all the capacitors built up by efFlatCaps.
 * Calls the user-provided procedure (*capProc)()
 * which should be of the following format:
 *
 *	(*capProc)(hierName1, hierName2, cap, cdata)
 *	    HierName *hierName1, *hierName2;
 *	    EFCapValue cap;
 *	    ClientData cdata;
 *	{
 *	}
 *
 * Here cap is the capacitance in attofarads.
 *
 * Results:
 *	Returns 1 if the client procedure returned 1;
 *	otherwise returns 0.
 *
 * Side effects:
 *	Calls the user-provided procedure (*capProc)().
 *
 * ----------------------------------------------------------------------------
 */

int
EFVisitCaps(
    int (*capProc)(),
    ClientData cdata)
{
    HashSearch hs;
    HashEntry *he;
    EFCoupleKey *ck;
    EFCapValue cap;

    HashStartSearch(&hs);
    while ((he = HashNext(&efCapHashTable, &hs)))
    {
	cap = CapHashGetValue(he);
	ck = (EFCoupleKey *) he->h_key.h_words;
	if ((*capProc)(ck->ck_1->efnode_name->efnn_hier,
		       ck->ck_2->efnode_name->efnn_hier,
		       (double) cap, cdata))
	    return 1;
    }

    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * EFVisitNodes --
 *
 * Procedure to visit all flat nodes in the circuit.
 * For each node, calls the procedure (*nodeProc)(),
 * which should be of the following form:
 *
 *	(*nodeProc)(node, r, c, cdata)
 *	    EFNode *node;
 *          int r;
 *          EFCapValue c;
 *	    ClientData cdata;
 *	{
 *	}
 *
 * Where 'r' and 'c' are the lumped resistance estimate
 * and capacitance to ground, in milliohms and attofarads
 * respectively.  When either falls below the threshold
 * for output, they are passed as 0.
 *
 * Results:
 *	Returns 1 if (*nodeProc)() returned 1 to abort the
 *	search; otherwise, returns 0.
 *
 * Side effects:
 *	Calls (*nodeProc)().
 *
 * ----------------------------------------------------------------------------
 */

int
EFVisitNodes(
    int (*nodeProc)(),
    ClientData cdata)
{
    EFNode *node;
    EFNodeName *nn;
    HierName *hierName;
    EFCapValue cap;
    int res;

    for (node = (EFNode *) efNodeList.efnode_next;
	    node && (node != &efNodeList);
	    node = (EFNode *) node->efnode_next)
    {
	res = EFNodeResist(node);
	cap = node->efnode_cap;
	hierName = (HierName *) node->efnode_name->efnn_hier;
	if (EFCompat)
	{
	    if (EFHNIsGND(hierName))
		cap = 0;
	}
	else
	{
	    if (node->efnode_flags & EF_GLOB_SUBS_NODE)
		cap = 0;
	}
	if (efWatchNodes)
	{
	    for (nn = node->efnode_name; nn; nn = nn->efnn_next)
		if (HashLookOnly(&efWatchTable, (char *) nn->efnn_hier))
		{
		    TxPrintf("Equivalent nodes:\n");
		    for (nn = node->efnode_name; nn; nn = nn->efnn_next)
			TxPrintf("\t%s\n", EFHNToStr(nn->efnn_hier));
		    break;
		}
	}

	if (node->efnode_flags & EF_KILLED)
	    continue;

	if ((*nodeProc)(node, res, (double) cap, cdata))
	    return 1;
    }

    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * EFNodeResist --
 *
 * The input to this procedure is a pointer to a EFNode.
 * Its resistance is computed from the area and perimeter stored
 * in the array efnode_pa.
 *
 * We approximate the resistive region as a collection of rectangles
 * of width W and length L, one for each set of layers having a different
 * sheet resistivity.  We do so by noting that for a rectangle,
 *
 *		Area = L * W
 *		Perimeter = 2 * (L + W)
 *
 * Solving the two simultaneous equations for L yields the following
 * quadratic:
 *
 *		2 * (L**2) - Perimeter * L + 2 * Area = 0
 *
 * Solving this quadratic for L, the longer dimension, we get
 *
 *		L = (Perimeter + S) / 4
 *
 * where
 *
 *		S = sqrt( (Perimeter**2) - 16 * Area )
 *
 * The smaller dimension is W, ie,
 *
 *		W = (Perimeter - S) / 4
 *
 * The resistance is L / W squares:
 *
 *			Perimeter + S
 *		R =	-------------
 *			Perimeter - S
 *
 * Results:
 *	Returns the resistance.
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

int
EFNodeResist(
    EFNode *node)
{
    int n, perim, area;
    float s, fperim;
    double v, dresist;
    int resist;

    resist = 0;
    for (n = 0; n < efNumResistClasses; n++)
    {
	area = node->efnode_pa[n].pa_area;
	perim = node->efnode_pa[n].pa_perim;
	if (area > 0 && perim > 0)
	{
	    v = (double) perim * (double) perim - 16.0 * area;

	    /* Approximate by one square if v < 0; shouldn't happen! */
	    if (v < 0.0) s = 0.0; else s = sqrt(v);

	    fperim = (float) perim;
	    dresist = ((double)fperim + (double)s)/((double)fperim - (double)s) * efResists[n];
	    if (dresist + (double) resist > (double) INT_MAX)
		resist = INT_MAX;
	    else
		resist += dresist;
	}
    }
    return (resist);
}

/*
 * ----------------------------------------------------------------------------
 *
 * EFLookDist --
 *
 * Look for the Distance between two points given by their HierNames.
 *
 * Results:
 *	TRUE if a distance was found, FALSE if not.
 *
 * Side effects:
 *	Sets *pMinDist and *pMaxDist to the min and max distances
 *	if found.
 *
 * ----------------------------------------------------------------------------
 */

bool
EFLookDist(
    HierName *hn1,
    HierName *hn2,
    int *pMinDist,
    int *pMaxDist)
{
    Distance distKey, *dist;
    HashEntry *he;

    if (EFHNBest(hn1, hn2))
    {
	distKey.dist_1 = hn1;
	distKey.dist_2 = hn2;
    }
    else
    {
	distKey.dist_1 = hn2;
	distKey.dist_2 = hn1;
    }
    he = HashLookOnly(&efDistHashTable, (char *) &distKey);
    if (he == NULL)
	return FALSE;

    dist = (Distance *) HashGetValue(he);
    *pMinDist = dist->dist_min;
    *pMaxDist = dist->dist_max;
    return TRUE;
}

/*
 * ----------------------------------------------------------------------------
 *
 * EFHNOut --
 *
 * Output a hierarchical node name.
 * The flags in EFOutputFlags control whether global (!) or local (#)
 * suffixes are to be trimmed.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Writes to the files 'outf'.
 *
 * ----------------------------------------------------------------------------
 */

void
EFHNOut(
    HierName *hierName,
    FILE *outf)
{
    bool trimGlob, trimLocal, convComma, convBrackets;
    char *cp, c;

    if (hierName->hn_parent) efHNOutPrefix(hierName->hn_parent, outf);
    if (EFOutputFlags)
    {
	cp = hierName->hn_name;
	trimGlob = (EFOutputFlags & EF_TRIMGLOB);
	trimLocal = (EFOutputFlags & EF_TRIMLOCAL);
	convComma = (EFOutputFlags & EF_CONVERTCOMMA);
	convBrackets = (EFOutputFlags & EF_CONVERTBRACKETS);
	while ((c = *cp++))
	{
	    if (*cp)
	    {
		if (c == ',')
		{
		    if (convComma)
			putc('|', outf);
		}
		else if (convBrackets && ((c == '[') || (c == ']')))
		    putc('_', outf);
		else
		    putc(c, outf);
	    }
	    else switch (c)
	    {
		case '!':
		    if (!trimGlob)
			(void) putc(c, outf);
		    break;
		case '#':
		    if (trimLocal)
			break;
		default:
		    (void) putc(c, outf);
		    break;
	    }
	}
    }
    else (void) fputs(hierName->hn_name, outf);
}

void
efHNOutPrefix(
    HierName *hierName,
    FILE *outf)
{
    char *cp, c;

    if (hierName->hn_parent)
	efHNOutPrefix(hierName->hn_parent, outf);

    cp = hierName->hn_name;
    while ((c = *cp++))
	putc(c, outf);
    putc('/', outf);
}
