/*
 * tech.c --
 *
 * Read in a technology file.
 *
 *     *********************************************************************
 *     * 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/utils/tech.c,v 1.1.1.1 2008/02/03 20:43:50 tim Exp $";
#endif  /* not lint */

#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <ctype.h>

/*
 * C99 compat
 * Mind: tcltk/tclmagic.h must be included prior to all the other headers
 */
#include "tcltk/tclmagic.h"

#include "database/database.h"
#include "utils/magic.h"
#include "utils/geometry.h"
#include "utils/utils.h"
#include "utils/tech.h"
#include "textio/textio.h"
#include "windows/windows.h"
#include "utils/malloc.h"

/* C99 compat */
#include "utils/heap.h"
#include "cif/cif.h"
#include "cif/cif.h"
#include "cif/CIFint.h"
#include "drc/drc.h"
#include "mzrouter/mzrouter.h"
#include "wiring/wiring.h"
#include "lef/lef.h"
#include "router/router.h"
#include "irouter/irouter.h"
#include "garouter/garouter.h"
#include "extract/extract.h"
#include "plow/plow.h"

/* Cannot include tcltk/tclmagic.h for a clash with utils/magic.h
extern int Tcl_printf(FILE *, const char *, va_list); */

global int  TechFormatVersion;
global bool TechOverridesDefault;

/* Define a file stack so that "include" calls can be nested */

typedef struct FStack		/* Linked FILE * pointers */
{
    FILE *file;
    char *filename;		/* Keep file name of parent file */
    int  linenum;		/* Keep line number count at the include line */
    struct FStack *next;        /* Pointer to another linked rectangle */
} filestack;

int techLineNumber;
char *TechFileName = NULL;

#define	iseol(c)	((c) == EOF || (c) == '\n')

/*
 * Each client of the technology module must make itself known by
 * a call to TechAddClient().  These calls provide both the names
 * of the sections of the technology file, as well as the procedures
 * to be invoked with lines in these sections.
 *
 * The following table is used to record clients of the technology
 * module.
 */

    typedef struct tC
    {
	bool		(*tc_proc)();	/* Procedure to be called for each
					 * line in section.
					 */
	void		(*tc_init)();	/* Procedure to be called before any
					 * lines in a section are processed.
					 */
	void		(*tc_final)();	/* Procedure to be called after all
					 * lines in section have been processed.
					 */
	struct tC	*tc_next;	/* Next client in section */
    } techClient;

    typedef struct
    {
	char		*ts_name;	/* Name of section */
	char		*ts_alias;	/* Alternative name of section */
	techClient	*ts_clients;	/* Pointer to list of clients */
	bool		 ts_read;	/* Flag: TRUE if section was read */
	bool		 ts_optional;	/* Flag: TRUE if section is optional */
	SectionID	 ts_thisSect;	/* SectionID of this section */
	SectionID	 ts_prevSects;	/* Mask of sections that must be
					 * read in before this one.  The
					 * mask is constructed from the
					 * section identifiers set by
					 * TechAddClient().
					 */
    } techSection;

#define	MAXSECTIONS	(8 * sizeof (int))	/* Not easily changeable */
#define	MAXARGS		30
#define MAXLINESIZE	1024

#define	SectionToMaskBit(s)		(1 << (s))
#define SectionMaskHasSection(m, s)	(m & SectionToMaskBit(s))

int techSectionNum;			/* ID of next new section */
SectionID techSectionMask;		/* Mask of sections already read */

techSection techSectionTable[MAXSECTIONS];
techSection *techSectionFree;		/* Pointer to next free section */
techSection *techCurrentSection;	/* Pointer to current section */

techSection *techFindSection();

/*
 * ----------------------------------------------------------------------------
 *
 * TechSectionGetMask --
 *
 * Get the SectionID mask for a specific section (specified by name).  The
 * returned mask is inverted;  that is, it is a mask containing bits
 * representing all the client sections except for the one sepcified.
 * This return value can be passed to TechLoad to re-read a specific
 * section.
 *
 * Results:
 *	Returns the inverted mask for the selected section ID.
 *
 * Side effects:
 *	If "depend" is non-NULL, the SectionID to which it points will be
 *	set to a mask representing the mask of sections which depend on
 *	the indicated section; that is, those sections which will be
 *	invalidated if the indicated section is altered in any way.
 *
 * ----------------------------------------------------------------------------
 */

SectionID
TechSectionGetMask(sectionName, depend)
    char *sectionName;
    SectionID *depend;
{
    techSection *tsp, *thissect;
    SectionID invid = 0;
    SectionID selected;

    thissect = techFindSection(sectionName);
    if (thissect == NULL) return -1;

    selected = thissect->ts_thisSect;

    for (tsp = techSectionTable; tsp < techSectionFree; tsp++)
    {
	if (tsp != thissect)
	{
	    invid |= tsp->ts_thisSect;
	    if (tsp->ts_prevSects & thissect->ts_thisSect)
		if (depend != NULL) *depend = tsp->ts_thisSect;
	}
    }
    return invid;
}

/*
 * ----------------------------------------------------------------------------
 *
 * TechInit --
 *
 * Initialize the technology module.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Initializes the technology read-in module.
 *	This function must be called before any other functions in
 *	this module are called.  It is called exactly once at the start
 *	of a magic session.
 *
 * ----------------------------------------------------------------------------
 */

void
TechInit()
{
    techCurrentSection = (techSection *) NULL;
    techSectionFree = techSectionTable;
    techSectionNum = 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * TechAddAlias --
 *
 *	Add an alternative name (alias) for a technology file section which
 *	may be used in place of the primary name.
 *
 *	This has been added mainly to handle sections which have been
 *	expanded beyond their original definition such that the section
 *	name is no longer appropriate.  Case in point:  the "images"
 *	section is broader in scope than the "contact" section, but
 *	because contacts are a subset of images in version 7.3, it is
 *	preferable to have an "images" section instead of a "contacts"
 *	section, with allowances for backwards compatibility.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Allocates string memory.
 *
 * ----------------------------------------------------------------------------
 */

void
TechAddAlias(primaryName, alias)
    char *primaryName;
    char *alias;
{
    techSection *tsp;

    tsp = techFindSection(primaryName);
    if (tsp == (techSection *) NULL)
    {
	TxError("Unknown technology file section \"%s\" requested.\n",
		primaryName);
    }
    else
    {
	if (tsp->ts_alias != NULL)
	    freeMagic(tsp->ts_alias);
	tsp->ts_alias = StrDup((char **)NULL, alias);
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * changePlanesFunc() ---
 *
 * This function hacks the existing layout database in case a tech file
 * is loaded which contains more or fewer planes than the exisiting
 * technology.  This is doing nothing fancy; it is simply making sure
 * that all memory allocation is accounted for.
 *
 * As a note for future implementation, it would be helpful to keep the
 * old plane name definitions around and try to match up the old and new
 * planes, so that it is possible to load a technology file which matches
 * the existing technology except for the addition or subtraction of one
 * or more planes (e.g., extra metal layer option) without completely
 * invalidating an existing layout.
 *
 * As written, this function is inherently dangerous.  It is intended for
 * use when loading a new tech file when there is no layout, just empty
 * tile planes.
 * ----------------------------------------------------------------------------
 */

int
changePlanesFunc(cellDef, arg)
    CellDef *cellDef;
    int *arg;
{
    int oldnumplanes = *arg;
    int pNum;

    if (oldnumplanes < DBNumPlanes)
    {
	/* New planes to be added */
	for (pNum = oldnumplanes; pNum < DBNumPlanes; pNum++)
	{
	    cellDef->cd_planes[pNum] = DBNewPlane((ClientData) TT_SPACE);
	}
    }
    else
    {
	/* Old planes to be subtracted */
	for (pNum = DBNumPlanes; pNum < oldnumplanes; pNum++)
	{
	    if (cellDef->cd_planes[pNum] != NULL)
	    {
		DBFreePaintPlane(cellDef->cd_planes[pNum]);
		TiFreePlane(cellDef->cd_planes[pNum]);
		cellDef->cd_planes[pNum] = (Plane *) NULL;
	    }
	}
    }
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * TechAddClient --
 *
 * Add a client to the technology module.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Identifies "sectionName" as a valid name for a section of a .tech
 *	file, and specifies that init() is the procedure to be called when
 *	a new technology is loaded, proc() as the procedure to be called
 *	for each line in the given section, and final() as the procedure to
 *      be called after the last line in the given section.
 *
 *	The init() procedure takes no arguments.
 *	The proc() procedure should be of the following form:
 *		bool
 *		proc(sectionName, argc, argv)
 *			char *sectionName;
 *			int argc;
 *			char *argv[];
 *		{
 *		}
 *	The final() procedure takes no arguments.
 *
 *	The argument prevSections should be a mask of the SectionID's
 *	of all sections that must be read in before this one.
 *
 *	If the argument 'pSectionID' is non-NULL, it should point to
 *	an int that will be set to the sectionID of this section.
 *
 *	It is legal for several procedures to be associated with a given
 *	sectionName; this is accomplished through successive calls to
 *	TechAddClient with the same sectionName.  The procedures will
 *	be invoked in the order in which they were handed to TechAddClient().
 *
 *	If the procedure given is NULL for init(), proc(), or final(), no
 *	procedure is invoked.
 *
 * ----------------------------------------------------------------------------
 */

void
TechAddClient(sectionName, init, proc, final, prevSections, pSectionID, opt)
    char *sectionName;
    void (*init)();
    bool (*proc)();
    void (*final)();
    SectionID prevSections;
    SectionID *pSectionID;
    bool opt; /* optional section */
{
    techSection *tsp;
    techClient *tcp, *tcl;

    tsp = techFindSection(sectionName);
    if (tsp == (techSection *) NULL)
    {
	tsp = techSectionFree++;
	ASSERT(tsp < &techSectionTable[MAXSECTIONS], "TechAddClient");
	tsp->ts_name = StrDup((char **) NULL, sectionName);
	tsp->ts_alias = NULL;
	tsp->ts_clients = (techClient *) NULL;
	tsp->ts_thisSect = SectionToMaskBit(techSectionNum);
	tsp->ts_prevSects = (SectionID) 0;
	tsp->ts_optional = opt;
	techSectionNum++;
    }

    tsp->ts_prevSects |= prevSections;
    if (pSectionID)
	*pSectionID = tsp->ts_thisSect;

    tcp = (techClient *) mallocMagic(sizeof (techClient));
    ASSERT(tcp != (techClient *) NULL, "TechAddClient");
    tcp->tc_init = init;
    tcp->tc_proc = proc;
    tcp->tc_final = final;
    tcp->tc_next = (techClient *) NULL;

    if (tsp->ts_clients == (techClient *) NULL)
	tsp->ts_clients = tcp;
    else
    {
	for (tcl = tsp->ts_clients; tcl->tc_next; tcl = tcl->tc_next)
	    /* Nothing */;
	tcl->tc_next = tcp;
    }
}

/*
 * ----------------------------------------------------------------------------
 * TechLoad --
 *
 * Initialize technology description information from a file.
 *
 * Results:
 *	TRUE if technology is successfully initialized (all required
 *	sections present and error free); FALSE otherwise.  Unrecognized
 *	sections cause an error message to be printed, but do not otherwise
 *	affect the result returned by TechLoad().
 *
 * Side effects:
 *	Calls technology initialization routines of other modules
 *	to initialize technology-specific information.
 *
 * ----------------------------------------------------------------------------
 */

bool
TechLoad(filename, initmask)
    char *filename;
    SectionID initmask;
{
    FILE *tf;
    techSection *tsp;
    techClient *tcp;
    char suffix[20], line[MAXLINESIZE], *realname;
    char *argv[MAXARGS];
    SectionID mask, badMask;
    int argc, s, repeatcount = 0;
    off_t repeatpos;
    bool retval, skip;
    filestack *fstack, *newstack;
    filestack topfile;

    fstack = NULL;
    techLineNumber = 0;
    badMask = (SectionID) 0;
    int saveNumPlanes;

    int changePlanesFunc();	/* forward declaration */
    int checkForPaintFunc();	/* forward declaration */

    if (initmask == -1)
    {
	TxError("Invalid technology file section requested.\n");
	return (FALSE);
    }

    /* TECH_VERSION in the filename is deprecated as of magic version	*/
    /* 7.2.27;  TECH_VERSION is no longer defined in the utils/Makefile.*/
    /* It has been changed to TECH_FORMAT_VERSION, left at version 27,	*/
    /* and placed in utils/tech.h.  It is needed for backward		*/
    /* compatibility with the old *.tech27 file format.			*/

    (void) sprintf(suffix, ".tech");

    /* If NULL is passed to argument "filename", this is a reload and	*/
    /* we should read TechFileName verbatim.				*/

    if (filename == NULL)
    {
	if (TechFileName != NULL)
	{
	    tf = PaOpen(TechFileName, "r", (char *)NULL, ".", SysLibPath, &realname);
	    if (tf == (FILE *) NULL)
	    {
		TxError("Could not find file '%s' in any of these "
			"directories:\n         %s\n",
			TechFileName, SysLibPath);
		return (FALSE);
	    }
	}
	else
	{
	    TxError("Invalid technology file load.\n");
	    return (FALSE);
	}
    }
    else
    {
	char *sptr, *dptr;

	tf = (FILE *)NULL;

	/* Added 1/20/2015 to correspond to change to PaLockOpen();	*/
	/* Always strip suffix from filename when suffix is specified.	*/

	sptr = strrchr(filename, '/');
	if (sptr == NULL)
	    sptr = filename;
	else
	    sptr++;

	/* If the filename is ".tech", then remove the extension and	*/
	/* process like it was not there at all.			*/
	dptr = strrchr(sptr, '.');
	if ((dptr != NULL) && !strcmp(dptr, suffix))
	    *dptr = '\0';

	/* If a non-standard extension was used, then honor it */
	if ((dptr != NULL) && (*dptr != '\0'))
	{
	    tf = PaOpen(filename, "r", (char *)NULL, ".", SysLibPath, &realname);

	    /* If that didn't work, fall back to trying the filename	*/
	    /* with the suffix.  This is needed for some ill-considered	*/
	    /* names like "SCN4M_SUBM.20.tech".				*/
	}

	if (tf == (FILE *)NULL)
	    tf = PaOpen(filename, "r", suffix, ".", SysLibPath, &realname);

	if (tf == (FILE *) NULL)
	{
	    /* Try looking for tech files from the last version to	*/
	    /* put the version number into the filename itself.	*/

	    (void) sprintf(suffix, ".tech%d", TECH_FORMAT_VERSION);

	    tf = PaOpen(filename, "r", suffix, ".", SysLibPath, &realname);
	    if (tf == (FILE *) NULL)
	    {
		TxError("Could not find file '%s.tech' in any of these "
			"directories:\n         %s\n",
			filename, SysLibPath);
		return (FALSE);
	    }
	}
	StrDup(&TechFileName, realname);

	// In case filename is not a temporary string, put it back the
	// way it was.
	if (dptr != NULL) *dptr = '.';
    }

    topfile.file = tf;
    topfile.filename = NULL;
    topfile.linenum = 0;
    topfile.next = NULL;
    fstack = &topfile;

    // If TechLoad is called with initmask == -2, test that the file
    // exists and is readable, and that the first non-comment line
    // is the keyword "tech".

    if (initmask == -2)
    {
	argc = techGetTokens(line, sizeof line, &fstack, argv);
	fclose(tf);
	if (argc != 1) return (FALSE);
	if (strcmp(argv[0], "tech")) return (FALSE);
	return (TRUE);
    }

    /*
     * Mark all sections as being unread.
     */
    techSectionMask = initmask;
    for (tsp = techSectionTable; tsp < techSectionFree; tsp++)
    {
	tsp->ts_read = FALSE;
    }

    /*
     * Run section initializations if this is not a reload.
     * CIF istyle, CIF ostyle, and extract sections need calls
     * to the init functions which clean up memory devoted to
     * remembering all the styles.
     */

    if (filename != NULL)
    {
#ifdef CIF_MODULE
	CIFTechInit();
	CIFReadTechInit();
#endif
	ExtTechInit();
	DRCTechInit();
#ifdef ROUTE_MODULE
	MZTechInit();
#endif

            /* Changing number of planes requires handling on every     */
            /* celldef.  So we need to save the original number of      */
            /* planes to see if it shrinks or expands.                  */


	saveNumPlanes = DBNumPlanes;
    }

    /*
     * Sections in a technology file begin with a single line containing
     * the keyword identifying the section, and end with a single line
     * containing the keyword "end".
     */

    retval = TRUE;
    skip = FALSE;
    while ((argc = techGetTokens(line, sizeof line, &fstack, argv)) >= 0)
    {
	/* Check for end-of-loop */
	if ((argc == 1) && (!strcmp(argv[0], "endrepeat")))
	{
	    if (repeatcount > 0)
	    {
		repeatcount--;
		fseek(fstack->file, repeatpos, SEEK_SET);
	    }
	    continue;
	}
	/* "repeat <number>" reads the lines until "endrepeat" <number> times */
	else if ((argc == 2) && (!strcmp(argv[0], "repeat")))
	{
	
	    if (!StrIsInt(argv[1]))
	    {
		TechError("Error: \"repeat\" with invalid count %s\n", argv[1]);
		repeatcount = 0;
	    }
	    else
	    {
		repeatcount = atoi(argv[1]) - 1;
		repeatpos = ftell(fstack->file);
	    }
	    continue;
	}

	/* Check for file inclusions (can be nested) */
	if ((argc > 1) && (!strcmp(argv[0], "include")))
	{
	    char *sptr, *increalname;

	    tf = PaOpen(argv[1], "r", suffix, ".", SysLibPath, &increalname);
	    if (tf != NULL)
	    {
		newstack = (filestack *)mallocMagic(sizeof(filestack));
		newstack->file = tf;
		newstack->filename = TechFileName;
		newstack->linenum = techLineNumber;
		newstack->next = fstack;
		fstack = newstack;
		techLineNumber = 0;
		TechFileName = StrDup((char **)NULL, increalname);
		continue;
	    }

	    /* Check the directory from which the tech file	*/
	    /* itself was read.					*/

	    if ((sptr = strrchr(TechFileName, '/')) != NULL)
	    {
		*sptr = '\0';
		tf = PaOpen(argv[1], "r", suffix, TechFileName, NULL, &increalname);
		*sptr = '/';
		if (tf != NULL)
		{
		    newstack = (filestack *)mallocMagic(sizeof(filestack));
		    newstack->file = tf;
		    newstack->filename = TechFileName;
		    newstack->linenum = techLineNumber;
		    newstack->next = fstack;
		    fstack = newstack;
		    techLineNumber = 0;
		    TechFileName = StrDup((char **)NULL, increalname);
		    continue;
		}
	    }
	    TechError("Warning: Couldn't find include file %s\n", argv[1]);
	}

	if (!skip && techCurrentSection == NULL)
	{
	    if (argc != 1)
	    {
		TechError("Bad section header line\n");
		goto skipsection;
	    }

	    tsp = techFindSection(argv[0]);
	    if (tsp == (techSection *) NULL)
	    {
		TechError("Unrecognized section name: %s\n", argv[0]);
		goto skipsection;
	    }
	    else if (initmask & tsp->ts_thisSect)
	    {
		skip = TRUE;
		continue;
	    }
	    if ((mask = (tsp->ts_prevSects & ~techSectionMask)))
	    {
		techSection *sp;

		TechError("Section %s appears too early.\n", argv[0]);
		TxError("\tMissing prerequisite sections:\n");
		for (sp = techSectionTable; sp < techSectionFree; sp++)
		    if (mask & sp->ts_thisSect)
			TxError("\t\t%s\n", sp->ts_name);
		goto skipsection;
	    }
	    techCurrentSection = tsp;

	    /* Invoke initialization routines for all clients that
	     * provided them.
	     */

	    for (tcp = techCurrentSection->ts_clients;
		    tcp != NULL;
		    tcp = tcp->tc_next)
	    {
		if (tcp->tc_init)
		    (void) (*tcp->tc_init)();
	    }
	    continue;
	}

	/* At the end of the section, invoke the finalization routine
	 * of the client's, if there is one.
	 */

	if (argc == 1 && strcmp(argv[0], "end") == 0)
	{
	    if (!skip)
	    {
		techSectionMask |= techCurrentSection->ts_thisSect;
		techCurrentSection->ts_read = TRUE;
		for (tcp = techCurrentSection->ts_clients;
			tcp != NULL;
			tcp = tcp->tc_next)
		{
		    if (tcp->tc_final)
			(*tcp->tc_final)();
		}
	    }
	    techCurrentSection = (techSection *) NULL;
	    skip = FALSE;
	    continue;
	}

	if (!skip)
	    for (tcp = techCurrentSection->ts_clients;
			tcp != NULL;
			tcp = tcp->tc_next)
		if (tcp->tc_proc)
		{
		    if (!(*tcp->tc_proc)(techCurrentSection->ts_name,argc,argv))
		    {
			retval = FALSE;
			badMask |= techCurrentSection->ts_thisSect;
		    }
		}
	continue;

skipsection:
	TxError("[Skipping to \"end\"]\n");
	skip = TRUE;
    }

    if (badMask)
    {
	TxError("The following sections of %s contained errors:\n", TechFileName);
	for (s = 0; s < techSectionNum; s++)
	    if (SectionMaskHasSection(badMask, s))
		TxError("    %s\n", techSectionTable[s].ts_name);
    }

    for (tsp = techSectionTable; tsp < techSectionFree; tsp++)
    {
	if (!(initmask & tsp->ts_thisSect))
	{
	    if (!tsp->ts_read && !tsp->ts_optional)
	    {
		TxError("Section \"%s\" was missing from %s.\n",
			tsp->ts_name, TechFileName);
		retval = FALSE;
	    }
	}
    }

    /* In case we hit an error in an included file. . . */
    while ((fstack != NULL) && (fstack != &topfile))
    {
	fclose(fstack->file);
	freeMagic(fstack->filename);
	free_magic1_t mm1 = freeMagic1_init();
	freeMagic1(&mm1, fstack);
	fstack = fstack->next;
	freeMagic1_end(&mm1);
    }
    if (fstack) fclose(fstack->file);

    /* Note:  If filename is NULL, then individual sections are	*/
    /* being reloaded, and it is the responsibility of the	*/
    /* calling routine to invoke any exit function specific to	*/
    /* that section (e.g., DRCTechScale() when loading a new	*/
    /* DRC style).						*/

    if ((filename != NULL) && (retval == TRUE))
    {
	/* If the tech file insists that the grid should be set	*/
	/* to the minimum, then enforce it.			*/
	if (CIFCurStyle && (CIFCurStyle->cs_flags & CWF_MINIMUM_GRID))
	{
	    DBLambda[0] = 1;
	    DBLambda[1] = CIFCurStyle->cs_scaleFactor / CIFCurStyle->cs_gridLimit;
	}

	/* If internal scalefactor is not the default 1:1, then we  */
	/* need to scale the techfile numbers accordingly.          */

	if ((DBLambda[0] != 1) || (DBLambda[1] != 1))
	{
	    int d = DBLambda[0];
	    int n = DBLambda[1];

	    CIFTechInputScale(d, n, TRUE);
	    CIFTechOutputScale(d, n);
	    DRCTechScale(d, n);
	    ExtTechScale(d, n);
	    WireTechScale(d, n);
#ifdef LEF_MODULE
	    LefTechScale(d, n);
	    LefTechSetDefaults();
#endif
#ifdef ROUTE_MODULE
	    RtrTechScale(d, n);
#endif
	    TxPrintf("Scaled tech values by %d / %d to"
			" match internal grid scaling\n", n, d);

	    /* Check if we're below the scale set by cifoutput gridlimit */
	    if (CIFTechLimitScale(1, 1))
		TxError("WARNING:  Current grid scale is smaller"
		" than the minimum for the process!\n");
	}

	/* Post-technology reading routines */

#ifdef ROUTE_MODULE
	MZAfterTech();
	IRAfterTech();
	GAMazeInitParms();
#endif
	PlowAfterTech();

	if (DBCellSrDefs(0, checkForPaintFunc, (ClientData)&saveNumPlanes))
	{
	    if (saveNumPlanes != DBNumPlanes)
		TxError("Warning:  Number of planes has changed.  ");
	    TxError("Existing layout may be invalid.\n");
	}
	if (saveNumPlanes != DBNumPlanes)
	    DBCellSrDefs(0, changePlanesFunc, (ClientData) &saveNumPlanes);
    }
    else if (retval == FALSE)
    {
	/* On error, remove any existing technology file name */
	DBNumPlanes = saveNumPlanes;
	freeMagic(TechFileName);
	TechFileName = NULL;
    }

    return (retval);
}

/*
 * ----------------------------------------------------------------------------
 *
 * TechError --
 *
 * Print an error message referring to a given line number in the
 * technology module.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Prints an error message.
 *
 * ----------------------------------------------------------------------------
 */

void
TechPrintLine()
{
    char *section;

    if (techCurrentSection)
	section = techCurrentSection->ts_name;
    else
	section = "(none)";

    TxError("%s: line %d: section %s:\n\t",
		TechFileName, techLineNumber, section);
}

void
TechError(const char *fmt, ...)
{
    va_list args;

    TechPrintLine();
    va_start(args, fmt);
    Vfprintf(stderr, fmt, args);
    va_end(args);
}


/* ================== Functions local to this module ================== */

/*
 * ----------------------------------------------------------------------------
 *
 * techFindSection --
 *
 * Return a pointer to the entry in techSectionTable for the section
 * of the given name.
 *
 * Results:
 *	A pointer to the new entry, or NULL if none could be found.
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

techSection *
techFindSection(sectionName)
    char *sectionName;
{
    techSection *tsp;

    for (tsp = techSectionTable; tsp < techSectionFree; tsp++)
    {
	if (!strcmp(tsp->ts_name, sectionName))
	    return (tsp);
	else if (tsp->ts_alias != NULL)
	{
	    if (!strcmp(tsp->ts_alias, sectionName))
		return (tsp);
	}
    }
    return ((techSection *) NULL);
}

/*
 * ----------------------------------------------------------------------------
 *
 * techGetTokens --
 *
 * Read a line from the technology file and split it up into tokens.
 * Blank lines are ignored.  Lines ending in backslash are joined
 * to their successor lines.
 * We assume that all macro definition and comment elimination has
 * been done by the C preprocessor.
 *
 * Results:
 *	Returns the number of tokens into which the line was split, or
 *	-1 on end of file.  Never returns 0.
 *
 * Side effects:
 *	Copies the line just read into 'line'.  The trailing newline
 *	is turned into a '\0'.  The line is broken into tokens which
 *	are then placed into argv.
 *
 * ----------------------------------------------------------------------------
 */

int
techGetTokens(line, size, fstack, argv)
    char *line;			/* Character array into which line is read */
    int size;			/* Size of character array */
    filestack **fstack;		/* Open technology file on top of stack */
    char *argv[];		/* Vector of tokens built by techGetTokens() */
{
    char *get, *put, *getp;
    bool inquote;
    int argc = 0;
    int currspace;		/* chars remaining before end of line[size] */
    FILE *file;			/* Current technology file */

    file = (*fstack)->file;

    /* Read one line into the buffer, joining lines when they end
     * in backslashes.
     */

    /* Code revision (MDG, Stanford):  Prevent the 1024-character limit due */
    /* to unconditional decrement of size.  Long comment lists could cause  */
    /* infinite looping.  New code interprets first non-space character '#' */
    /* as a comment character, rather than requiring it to be in the first  */
    /* column.								    */

    /* Code revision (RTE, Open Circuit Design):  Handle DOS-style CR/LF    */
    /* Code revision (RTE, Open Circuit Design):  Handle "include" files    */

start:
     get = line;
     currspace = size;
     while (currspace > 0)
     {
	techLineNumber += 1;
	while (fgets(get, currspace, file) == NULL) {
	    if ((*fstack)->next != NULL)
	    {
		fclose((*fstack)->file);
		freeMagic(TechFileName);
		TechFileName = (*fstack)->filename;
		techLineNumber = (*fstack)->linenum;
		*fstack = (*fstack)->next;
		file = (*fstack)->file;
	    }
	    else
		return (-1);
	}
	getp = get;
	while(isspace(*getp)) getp++;
	if (*getp == '#') continue;
	for (put = get; *put != '\n'; put++) currspace -= 1;
	if (put != get)
	{
	    put--;
	    if (*put == 0xd) put--;	/* Handle DOS-style CR/LF */
	    if (*put == '\\')
	    {
		get = put;
		continue;
	    }
	    put++;
	}
	*put= '\0';
	break;
    }
    if (currspace == 0) TechError("long line truncated\n");

    get = put = line;

    while (*get != '\0')
    {
	/* Skip leading blanks */

	while (isspace(*get)) get++;

	/* Beginning of the token is here. */

	argv[argc] = put = get;
	if (*get == '"')
	{
	    get++;
	    inquote = TRUE;
	} else inquote = FALSE;

	/*
	 * Grab up characters to the end of the token.  Any character
	 * preceded by a backslash is taken literally.
	 */

	while (*get != '\0')
	{
	    if (inquote)
	    {
		if (*get == '"') break;
	    }
	    else if (isspace(*get)) break;

	    if (*get == '\\')	/* Process quoted characters literally */
	    {
		get += 1;
		if (*get == '\0') break;
	    }

	    /* Copy into token receiving area */
	    *put++ = *get++;
	}

	/*
	 * If we got no characters in the token, we must have been at
	 * the end of the line.
	 */
	if (get == argv[argc])
	    break;

	/* Terminate the token and advance over the terminating character. */

	if (*get != '\0') get++;	/* Careful!  could be at end of line! */
	*put++ = '\0';
	argc++;
    }

    if (argc == 0)
	goto start;

    return (argc);
}
