/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: CIF.java
 * Input/output tool: CIF input
 * Original C CIF Parser (front end) by Robert W. Hon, Schlumberger Palo Alto Research
 * and its interface to C Electric (back end) by Robert Winstanley, University of Calgary.
 * Translated into Java by Steven M. Rubin.
 *
 * Copyright (c) 2004, Static Free Software. All rights reserved.
 *
 * 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 3 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.sun.electric.tool.io.input;

import com.sun.electric.database.EditingPreferences;
import com.sun.electric.database.geometry.EPoint;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.hierarchy.Export;
import com.sun.electric.database.hierarchy.Library;
import com.sun.electric.database.id.CellId;
import com.sun.electric.database.prototype.NodeProto;
import com.sun.electric.database.text.TextUtils;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.technology.Layer;
import com.sun.electric.technology.Technology;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.io.IOTool;
import com.sun.electric.util.math.FixpTransform;
import com.sun.electric.util.math.GenMath;
import com.sun.electric.util.math.Orientation;

import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * This class reads files in CIF files.
 */
public class CIF extends Input<Object>
{
    private static final boolean MAKE_EXPORTS = false;
	/** max depth of min/max stack */		private static final int MAXMMSTACK = 50;
	/** max value that can add extra digit */private static final int BIGSIGNED = ((0X7FFFFFFF-9)/10);

	//	specific syntax errors
	private static final int NOERROR    = 100;
	private static final int NUMTOOBIG  = 101;
	private static final int NOUNSIGNED = 102;
	private static final int NOSIGNED   = 103;
	private static final int NOSEMI     = 104;
	private static final int NOPATH     = 105;
	private static final int BADTRANS   = 106;
	private static final int BADUSER    = 107;
	private static final int BADCOMMAND = 108;
	private static final int INTERNAL   = 109;
	private static final int BADDEF     = 110;
	private static final int NOLAYER    = 111;
	private static final int BADCOMMENT = 112;
	private static final int BADAXIS    = 113;
	private static final int NESTDEF    = 114;
	private static final int NESTDD     = 115;
	private static final int NODEFSTART = 116;
	private static final int NESTEND    = 117;
	private static final int NOSPACE    = 118;
	private static final int NONAME     = 119;

	// enumerated types for CIF 2.0 parser
//	private static final int SEMANTICERROR = 0;
	private static final int SYNTAXERROR   = 1;
	private static final int WIRECOM       = 2;
	private static final int BOXCOM        = 3;
	private static final int POLYCOM       = 4;
	private static final int FLASHCOM      = 5;
	private static final int DEFSTART      = 6;
	private static final int DEFEND        = 7;
	private static final int DELETEDEF     = 8;
	private static final int LAYER         = 9;
	private static final int CALLCOM       = 10;
	private static final int COMMENT       = 11;
	private static final int NULLCOMMAND   = 12;
	private static final int USERS         = 13;
	private static final int END           = 14;
	private static final int ENDFILE       = 15;
	private static final int SYMNAME       = 16;
	private static final int INSTNAME      = 17;
	private static final int GEONAME       = 18;
	private static final int LABELCOM      = 19;

	// types for FrontTransformLists
	static class FrontTransformType {}
	private FrontTransformType MIRROR = new FrontTransformType();
	private FrontTransformType TRANSLATE = new FrontTransformType();
	private FrontTransformType ROTATE = new FrontTransformType();

	// error codes for reporting errors
	private static final int FATALINTERNAL = 0;
	private static final int FATALSYNTAX   = 1;
	private static final int FATALSEMANTIC = 2;
	private static final int FATALOUTPUT   = 3;
	private static final int ADVISORY      = 4;
//	private static final int OTHER         = 5;			/* OTHER must be last */

	private static final int TIDENT     = 0;
	private static final int TROTATE    = 1;
	private static final int TTRANSLATE = 2;
	private static final int TMIRROR    = 4;

	// values for BackCIFList->identity
	private static final int CSTART =   0;
	private static final int CEND =     1;
//	private static final int CWIRE =    2;
//	private static final int CFLASH =   3;
	private static final int CBOX =     4;
	private static final int CPOLY =    5;
//	private static final int CCOMMAND = 6;
	private static final int CGNAME =   7;
	private static final int CLABEL =   8;
	private static final int CCALL =    9;

	static class BackCIFCell
	{
		/** bounding box of cell */						int  l, r, t, b;
		/** the address of the CIF cell */				Cell addr;
	};

	static class BackCIFList
	{
		/** specifies the nature of the entry */	int         identity;
		/** the line number in the original CIF */	int         lineNumber;
		/** will point to member's structure */		Object      member;
	};

	static class BackCIFStart
	{
		/** cell index */							int    cIndex;
		/** cell name */							String name;
		/** bounding box of cell */					int    l, r, t, b;
	};

	static class BackCIFBox
	{
		/** the corresponding layer number */		Layer lay;
		/** dimensions of box */					int   length, width;
		/** center point of box */					int   cenX, cenY;
		/** box direction */						int   xRot, yRot;
	};

	static class BackCIFPoly
	{
		/** the corresponding layer number */		Layer  lay;
		/** list of points */						int [] x, y;
		/** number of points in list */				int    lim;
	};

	static class BackCIFGeomName
	{
		/** the corresponding layer number */		Layer  lay;
        /** the name */                             String name;
        /** the central point */                    int cx, cy;
	};

	static class BackCIFLabel
	{
		/** location of label */					int    x, y;
		/** the label */							String label;
	};

	static class BackCIFCall
	{
		/** index of cell called */					int              cIndex;
		/** name of cell called */					String           name;
		/** list of transformations */				BackCIFTransform list;
	};

	// values for the transformation type
	/** mirror in x */	private static final int MIRX  = 1;
	/** mirror in y */	private static final int MIRY  = 2;
	/** translation */	private static final int TRANS = 3;
	/** rotation */		private static final int ROT   = 4;

	static class BackCIFTransform
	{
		/** type of transformation */				int              type;
		/** not required for the mirror types */	int              x, y;
		/** next element in list */					BackCIFTransform next;
	};

	static class FrontTransformEntry
	{
		FrontTransformType kind;
		boolean            xCoord;
		int                xt, yt;
		int                xRot, yRot;
	};

	/** data types for transformation package */
	static class FrontMatrix
	{
		double a11, a12, a21, a22, a31, a32, a33;
		FrontMatrix prev, next;
		int type;
		boolean multiplied;
	};

	/** bounding box */
	static class FrontBBox
	{
		int l, r, b, t;
	};

	static class FrontSymbol
	{
		/** symbol number for this entry */					int symNumber;
		boolean expanded;
		boolean defined;
		boolean dumped;
		/** bb as if this symbol were called by itself */	FrontBBox bounds;
		/** flag for rebuilding bounding box */				boolean boundsValid;
		/** name of this symbol */							String name;
		/** number of calls made by this symbol */			int numCalls;
		/** pointer to linked list of objects */			FrontObjBase guts;
		FrontSymbol(int num)
		{
			bounds = new FrontBBox();
			symNumber = num;
			expanded = false;
			defined = false;
			dumped = false;
			numCalls = 0;
			boundsValid = false;
			name = null;
			guts = null;
		}
	};

	static class FrontLinkedPoint
	{
		Point pValue;
		FrontLinkedPoint pNext;
	};

	static class FrontPath
	{
		FrontLinkedPoint pFirst, pLast;
		int pLength;

		FrontPath()
		{
			pFirst = null;
			pLast = null;
			pLength = 0;
		}
	};

	static class FrontLinkedTransform
	{
		FrontTransformEntry tValue;
		FrontLinkedTransform tNext;
	};

	static class FrontTransformList
	{
		FrontLinkedTransform tFirst, tLast;
		int tLength;

		FrontTransformList()
		{
			tFirst = null;
			tLast = null;
			tLength = 0;
		}
	};

	/** items in item tree */
	static class FrontItem
	{
		/** pointer into symbol structure */	FrontObjBase   what;
	};

	/** hack structure for referencing first fields of any object */
	static class FrontObjBase
	{
		/** bounding box */				FrontBBox bb;
		/** for ll */					FrontObjBase  next;
		/** layer for this object */	Layer layer;

		FrontObjBase()
		{
			bb = new FrontBBox();
		}
	};

	/** symbol call object */
	static class FrontCall extends FrontObjBase
	{
		/** rest is non-critical */				int symNumber;
		int lineNumber;
		FrontSymbol unID;
		FrontMatrix matrix;
		/** transformation list for this call */	FrontTransformList transList;
	};

	static class FrontGeomName extends FrontObjBase
	{
        String name;
	};

	static class FrontLabel extends FrontObjBase
	{
		String name;
		Point pos;
	};

	static class FrontBox extends FrontObjBase
	{
		int length, width;
		Point center;
		int xRot, yRot;
	};

	static class FrontManBox extends FrontObjBase
	{
	};

	static class FrontFlash extends FrontObjBase
	{
		Point center;
		int diameter;
	};

	static class FrontPoly extends FrontObjBase
	{
		/** array of points in path */			Point [] points;
	};

	static class FrontWire extends FrontObjBase
	{
		/** width of wire */					int width;
		/** array of points in wire */			Point [] points;
	};

	/** current transformation */			private BackCIFTransform currentCTrans;
	/** list of front-end objects */		private List<BackCIFList> currentFrontList = null;
	/** current object in list */			private BackCIFList      currentFrontElement;
	/** item list */						private List<FrontItem>  currentItemList;
	/** A/B from DS */						private double           cellScaleFactor;
	/** current symbol being defined */		private FrontSymbol      currentFrontSymbol;
	/** place to save layer during def */	private Layer            backupLayer;
	/** symbol has been named */			private boolean          symbolNamed;
	/** flag for error encountered */		private boolean          errorFound;
	/** what it was */						private int              errorType;
	/** definition in progress flag */		private boolean          isInCellDefinition;
	/** end command flag */					private boolean          endIsSeen;
	/** number of chars in buffer */		private int              charactersRead;
	/** flag to reset buffer */				private boolean          resetInputBuffer;
	/** number of "fatal" errors */			private int              numFatalErrors;
	/** null layer errors encountered */	private boolean          numNullLayerErrors;
	/** ignore statements until DF */		private boolean          ignoreStatements;
	/** 91 pending */						private boolean          namePending;
	/** end command flag */					private boolean          endCommandFound;
	/** current layer */					private Layer            currentLayer;
	/** symbol table */						private Map<Integer,FrontSymbol> symbolTable;
	/** the top of stack */					private FrontMatrix      matrixStackTop;
	/** lookahead character */				private int              nextInputCharacter;
	/** # statements since 91 com */		private boolean          statementsSince91;
	/** min/max stack pointer */			private int              minMaxStackPtr;
	/** min/max stack: left edge */			private int []           minMaxStackLeft;
	/** min/max stack: right edge */		private int []           minMaxStackRight;
	/** min/max stack: bottom edge */		private int []           minMaxStackBottom;
	/** min/max stack: top edge */			private int []           minMaxStackTop;
	/** map from cell numbers to cells */	private Map<Integer,BackCIFCell> cifCellMap;
	/** the current cell */					private BackCIFCell      currentBackCell;
	/** current technology for layers */	private Technology       curTech;
	/** map from layer names to layers */	private Map<String,Layer> cifLayerNames;
	/** set of unknown layers */			private Set<String>      unknownLayerNames;
	/** address of cell being defined */	private Cell             cellBeingBuilt;
	/** name of the current cell */			private String           currentNodeProtoName;
	/** the line being read */				private StringBuilder    inputBuffer;

	private CIFPreferences localPrefs;

	public static class CIFPreferences extends InputPreferences
    {
		public boolean squareWires;

		public CIFPreferences(boolean factory)
		{
			super(factory);
			if (factory)
			{
				squareWires = IOTool.isFactoryCIFInSquaresWires();
			} else
            {
    			squareWires = IOTool.isCIFInSquaresWires();
            }
		}

        @Override
		public Library doInput(URL fileURL, Library lib, Technology tech, EditingPreferences ep, Map<Library,Cell> currentCells, Map<CellId,BitSet> nodesToExpand, Job job)
        {
        	CIF in = new CIF(ep, this);
			if (in.openTextInput(fileURL)) return null;
			lib = in.importALibrary(lib, tech, currentCells);
			in.closeInput();
			return lib;
        }
    }

	/**
	 * Creates a new instance of CIF.
	 */
	CIF(EditingPreferences ep, CIFPreferences ap) {
        super(ep);
        localPrefs = ap;
    }

	/**
	 * Method to import a library from disk.
	 * @param lib the library to fill
     * @param currentCells this map will be filled with currentCells in Libraries found in library file
	 * @return the created library (null on error).
	 */
    @Override
	protected Library importALibrary(Library lib, Technology tech, Map<Library,Cell> currentCells)
	{
        setProgressNote("Reading CIF file");

        // initialize all lists and the searching routines
		cifCellMap = new HashMap<Integer,BackCIFCell>();
		curTech = tech;

		if (initFind()) return null;

		// parse the CIF and create a listing
		if (interpret()) return null;

		// instantiate the CIF as nodes
        setProgressNote("Storing CIF in database...");
		if (listToNodes(lib)) return null;

		// clean up
		doneInterpreter();

		return lib;
	}

	private boolean listToNodes(Library lib)
	{
		cellBeingBuilt = null;
		for(BackCIFList x : currentFrontList)
		{
			currentFrontElement = x;
			if (currentFrontElement.identity == CSTART)
			{
				cellBeingBuilt = nodesStart(lib);
				if (cellBeingBuilt == null) return true;
				continue;
			}
			if (currentFrontElement.identity == CEND)
			{
//				lib.setCurCell(cellBeingBuilt);		// THIS TAKES WAY TOO LONG
				cellBeingBuilt = null;
				continue;
			}
			if (cellBeingBuilt == null)
			{
				// circuitry found at the top level: create a fake cell for it
				cellBeingBuilt = lib.findNodeProto("TOP_LEVEL_UNNAMED{lay}");
				if (cellBeingBuilt == null)
				{
					cellBeingBuilt = Cell.newInst(lib, "TOP_LEVEL_UNNAMED{lay}");
					if (cellBeingBuilt == null) break;
					currentBackCell = makeBackCIFCell(9999);
				}
				currentBackCell.addr = cellBeingBuilt;
			}
			if (currentFrontElement.identity == CBOX)
			{
				if (nodesBox()) return true;
			} else if (currentFrontElement.identity == CPOLY)
			{
				if (nodesPoly()) return true;
			} else if (currentFrontElement.identity == CCALL)
			{
				if (nodesCall()) return true;
			} else if (currentFrontElement.identity == CGNAME)
            {
                if (MAKE_EXPORTS && nodesGeoName()) return true;
            }
		}
		return false;
	}

	private Cell nodesStart(Library lib)
	{
		BackCIFStart cs = (BackCIFStart)currentFrontElement.member;
		BackCIFCell cifCell = makeBackCIFCell(cs.cIndex);
		cifCell.l = cs.l;   cifCell.r = cs.r;
		cifCell.b = cs.b;   cifCell.t = cs.t;
		currentNodeProtoName = cs.name;

		// remove illegal characters
		StringBuffer properName = new StringBuffer();
		for(int i=0; i<currentNodeProtoName.length(); i++)
		{
			char chr = currentNodeProtoName.charAt(i);
			if (Character.isWhitespace(chr) || chr == ':' || chr == ';')
			{
				chr = 'X';
			}
			properName.append(chr);
		}
		currentNodeProtoName = properName.toString();
		cifCell.addr = Cell.newInst(lib, currentNodeProtoName + "{lay}");
		if (cifCell.addr == null)
		{
			System.out.println("Cannot create the cell " + currentNodeProtoName);
			return null;
		}
		return cifCell.addr;
	}

	private boolean nodesCall()
	{
		BackCIFCall cc = (BackCIFCall)currentFrontElement.member;
		BackCIFCell cell = findBackCIFCell(cc.cIndex);
		if (cell == null)
		{
			System.out.println("Referencing an undefined cell");
			return true;
		}
		int rot = 0;
		boolean trans = false;
		int l = cell.l;    int r = cell.r;    int b = cell.b;    int t = cell.t;
		for(BackCIFTransform ctrans = cc.list; ctrans != null; ctrans = ctrans.next)
			switch (ctrans.type)
		{
			case MIRX:
				int temp = l;   l = -r;   r = -temp;
				rot = (trans) ? ((rot+2700) % 3600) : ((rot+900) % 3600);
				trans = !trans;
				break;
			case MIRY:
				temp = t;   t = -b;   b = -temp;
				rot = (trans) ? ((rot+900) % 3600) : ((rot+2700) % 3600);
				trans = !trans;
				break;
			case TRANS:
				l += ctrans.x;   r += ctrans.x;
				b += ctrans.y;   t += ctrans.y;
				break;
			case ROT:
				int deg = GenMath.figureAngle(new Point2D.Double(0, 0), new Point2D.Double(ctrans.x, ctrans.y));
				if (deg != 0)
				{
					int hlen = Math.abs(((l-r)/2));   int hwid = Math.abs(((b-t)/2));
					int cenX = (l+r)/2;   int cenY = (b+t)/2;
					Point pt = new Point(cenX, cenY);
					rotateLayer(pt, deg);
					cenX = pt.x;   cenY = pt.y;
					l = cenX - hlen;   r = cenX + hlen;
					b = cenY - hwid;   t = cenY + hwid;
					rot += ((trans) ? -deg : deg);
				}
		}
		while (rot >= 3600) rot -= 3600;
		while (rot < 0) rot += 3600;
		double lX = convertFromCentimicrons(l);
		double hX = convertFromCentimicrons(r);
		double lY = convertFromCentimicrons(b);
		double hY = convertFromCentimicrons(t);
		double x = (lX + hX) / 2;
		double y = (lY + hY) / 2;
		double sX = hX - lX;
		double sY = hY - lY;
		Rectangle2D bounds = cell.addr.getBounds();
		sX = bounds.getWidth();
		sY = bounds.getHeight();

		// transform is rotation and transpose: convert to rotation/MX/MY
		Orientation or = Orientation.fromC(rot, trans);

		// special code to account for rotation of cell centers
		FixpTransform ctrTrans = or.rotateAbout(x, y);
		Point2D spin = new Point2D.Double(x - bounds.getCenterX(), y - bounds.getCenterY());
		ctrTrans.transform(spin, spin);
		x = spin.getX();   y = spin.getY();

		// create the node
		NodeInst ni = NodeInst.makeInstance(cell.addr, ep, new Point2D.Double(x, y), sX, sY, currentBackCell.addr, or, null);
		if (ni == null)
		{
			System.out.println("Problems creating an instance of " + cell.addr + " in " + currentBackCell.addr);
			return true;
		}
		return false;
	}

	private boolean nodesPoly()
	{
		BackCIFPoly cp = (BackCIFPoly)currentFrontElement.member;
		if (cp.lim == 0) return false;
		NodeProto np = findPrototype(cp.lay);
		int lx = cp.x[0];
		int hx = cp.x[0];
		int ly = cp.y[0];
		int hy = cp.y[0];
		for(int i=1; i<cp.lim; i++)
		{
			if (cp.x[i] < lx) lx = cp.x[i];
			if (cp.x[i] > hx) hx = cp.x[i];
			if (cp.y[i] < ly) ly = cp.y[i];
			if (cp.y[i] > hy) hy = cp.y[i];
		}

		// convert from centimicrons
		double lowX = convertFromCentimicrons(lx);
		double highX = convertFromCentimicrons(hx);
		double lowY = convertFromCentimicrons(ly);
		double highY = convertFromCentimicrons(hy);
		double x = (lowX + highX) / 2;
		double y = (lowY + highY) / 2;
		double sX = highX - lowX;
		double sY = highY - lowY;
		NodeInst newni = NodeInst.makeInstance(np, ep, new Point2D.Double(x, y), sX, sY, currentBackCell.addr);
		if (newni == null)
		{
			System.out.println("Problems creating a polygon on layer " + cp.lay + " in " + currentBackCell.addr);
			return true;
		}

		// store the trace information
		EPoint [] points = new EPoint[cp.lim];
		for(int i=0; i<cp.lim; i++)
		{
			points[i] = EPoint.fromLambda(convertFromCentimicrons(cp.x[i]), convertFromCentimicrons(cp.y[i]));
		}

		// store the trace information
		newni.setTrace(points);

		return false;
	}

    private boolean nodesGeoName()
    {
		BackCIFGeomName gn = (BackCIFGeomName)currentFrontElement.member;
        Layer lay = gn.lay;
		NodeProto np = findPrototype(lay);
        String name = gn.name;
        double x = convertFromCentimicrons(gn.cx);
        double y = convertFromCentimicrons(gn.cy);
		NodeInst newni = NodeInst.makeInstance(np, ep, new Point2D.Double(x, y), 0, 0, currentBackCell.addr);
        Export.newInst(currentBackCell.addr, newni.getOnlyPortInst(), name, ep);
        return false;
    }

	private void rotateLayer(Point pt, int deg)
	{
		// trivial test to prevent atan2 domain errors
		if (pt.x == 0 && pt.y == 0) return;
		switch (deg)	// do the Manhattan cases directly
		{
			case 0:
			case 3600:	// just in case
				break;
			case 900:
				int temp = pt.x;   pt.x = -pt.y;   pt.y = temp;
				break;
			case 1800:
				pt.x = -pt.x;   pt.y = -pt.y;
				break;
			case 2700:
				temp = pt.x;   pt.x = pt.y;   pt.y = -temp;
				break;
			default: // this old code only permits rotation by integer angles
				double factx = 1, facty = 1;
				while(Math.abs(pt.x/factx) > 1000) factx *= 10.0;
				while(Math.abs(pt.y/facty) > 1000) facty *= 10.0;
				double fact = (factx > facty) ? facty : factx;
				double fx = pt.x / fact;		  double fy = pt.y / fact;
				double vlen = fact * Math.hypot(fx, fy);
				double vang = (deg + GenMath.figureAngle(new Point2D.Double(0, 0), new Point2D.Double(pt.x, pt.y))) / 10.0 / (45.0 / Math.atan(1.0));
				pt.x = (int)(vlen * Math.cos(vang));
				pt.y = (int)(vlen * Math.sin(vang));
				break;
		}
	}

	private void outputPolygon(Layer lay, FrontPath pPath)
	{
		int lim = pPath.pLength;
		if (lim < 3) return;

		placeCIFList(CPOLY);
		BackCIFPoly cp = (BackCIFPoly)currentFrontElement.member;
		cp.lay = lay;
		cp.x = new int[lim];
		cp.y = new int[lim];

		cp.lim = lim;
		for (int i = 0; i < lim; i++)
		{
			Point temp = removePoint(pPath);
			cp.x[i] = temp.x;
			cp.y[i] = temp.y;
		}
	}

	private double convertFromCentimicrons(double v)
	{
		return TextUtils.convertFromDistance(v/100, curTech, TextUtils.UnitScale.MICRO);
	}

	private boolean nodesBox()
	{
		BackCIFBox cb = (BackCIFBox)currentFrontElement.member;
		NodeProto node = findPrototype(cb.lay);
		if (node == null)
		{
			String layname = cb.lay.getName();
			System.out.println("Cannot find primitive to use for layer '" + layname + "' (number " + cb.lay + ")");
			return true;
		}
		int r = GenMath.figureAngle(new Point2D.Double(0, 0), new Point2D.Double(cb.xRot, cb.yRot));
		double x = convertFromCentimicrons(cb.cenX);
		double y = convertFromCentimicrons(cb.cenY);
		double len = convertFromCentimicrons(cb.length);
		double wid = convertFromCentimicrons(cb.width);
        Orientation orient = Orientation.fromAngle(r);
		NodeInst ni = NodeInst.makeInstance(node, ep, new Point2D.Double(x, y), len, wid, currentBackCell.addr, orient, null);
		if (ni == null)
		{
			String layname = cb.lay.getName();
			System.out.println("Problems creating a box on layer " + layname + " in " + currentBackCell.addr);
			return true;
		}
		return false;
	}

	private boolean interpret()
	{
		initParser();
		initInterpreter();
		inFromFile();
		parseFile();		// read in the CIF
		doneParser();

		if (numFatalErrors > 0) return true;
		if (unknownLayerNames.size() > 0)
		{
			System.out.println("Error: these layers appear in the CIF file but are not assigned to Electric layers:");
			for(String str : unknownLayerNames)
			{
				System.out.println("    " + str);
			}
		}

		getInterpreterBounds();

		// construct a list: first step in the conversion
		createList();
		return false;
	}

	private BackCIFCell findBackCIFCell(int cIndex)
	{
		return cifCellMap.get(Integer.valueOf(cIndex));
	}

	private BackCIFCell makeBackCIFCell(int cIndex)
	{
		BackCIFCell newCC = new BackCIFCell();
		newCC.addr = null;
		cifCellMap.put(Integer.valueOf(cIndex), newCC);

		currentBackCell = newCC;
		return newCC;
	}

	private NodeProto findPrototype(Layer lay)
	{
		return lay.getPureLayerNode();
	}

	private boolean initFind()
	{
		// get the array of CIF names
		cifLayerNames = new HashMap<String,Layer>();
		unknownLayerNames = new HashSet<String>();
		boolean valid = false;
		for(Iterator<Layer> it = curTech.getLayers(); it.hasNext(); )
		{
			Layer layer = it.next();
			String cifName = layer.getCIFLayer();
			if (cifName != null && cifName.length() > 0)
			{
				cifLayerNames.put(cifName, layer);
				valid = true;
			}
		}
		if (!valid)
		{
			System.out.println("There are no CIF layer names assigned in the " + curTech.getTechName() + " technology");
			return true;
		}

		return false;
	}

	private void initInterpreter()
	{
		numNullLayerErrors = false;
		isInCellDefinition = false;
		ignoreStatements = false;
		namePending = false;
		endCommandFound = false;
		currentLayer = null;
		currentItemList = new ArrayList<FrontItem>();
		initUtilities();
		initMatrices();
	}

	private void initParser()
	{
		errorFound = false;
		errorType = NOERROR;
		isInCellDefinition = false;
		endIsSeen = false;
		initInput();
		initErrors();
	}

	private void doneParser()
	{
		if (!endIsSeen) errorReport("missing End command", FATALSYNTAX);
	}

	private int parseFile()
	{
		int comCount = 1;
		for(;;)
		{
			int com = parseStatement();
			if (com == END || com == ENDFILE) break;
			comCount++;
		}
		return comCount;
	}

	private void doneInterpreter()
	{
		if (numNullLayerErrors)
		{
			System.out.println("Warning: some CIF objects were not read");
		}
	}

	private Rectangle getInterpreterBounds()
	{
		boolean first = true;

		pushTransform();
		for(FrontItem h : currentItemList)
		{
			FrontObjBase obj = h.what;
			Point temp = new Point();
			temp.x = obj.bb.l;
			temp.y = obj.bb.b;
			Point comperror = transformPoint(temp);
			initMinMax(comperror);
			temp.x = obj.bb.r;
			comperror = transformPoint(temp);
			minMax(comperror);
			temp.y = obj.bb.t;
			comperror = transformPoint(temp);
			minMax(comperror);
			temp.x = obj.bb.l;
			comperror = transformPoint(temp);
			minMax(comperror);

			int left = getMinMaxMinX();
			int right = getMinMaxMaxX();
			int bottom = getMinMaxMinY();
			int top = getMinMaxMaxY();
			doneMinMax();
			temp.x = left;   temp.y = bottom;
			if (first) {first = false; initMinMax(temp);} else minMax(temp);
			temp.x = right;   temp.y = top;
			minMax(temp);
		}
		Rectangle ret = new Rectangle(getMinMaxMinX(), getMinMaxMinY(),
			getMinMaxMaxX()-getMinMaxMinX(), getMinMaxMaxY()-getMinMaxMinY());
		doneMinMax();
		popTransform();
		return ret;
	}

	private void createList()
	{
		if (!isEndSeen()) System.out.println("missing End command, assumed");
		if (numFatalErrors > 0) return;
		sendList(currentItemList);		// sendlist deletes nodes
		currentItemList = null;
	}

	private void sendList(List<FrontItem> list)
	{
		for(FrontItem h : list)
			outItem(h);
	}

	/**
	 * spit out an item
	 */
	private void outItem(FrontItem thing)
	{
		if (thing.what instanceof FrontPoly)
		{
			FrontPoly po = (FrontPoly)thing.what;
			FrontPath pPath = new FrontPath();
			for (int i = 0; i < po.points.length; i++)
				appendPoint(pPath, po.points[i]);
			outputPolygon(po.layer, pPath);
			return;
		}
		if (thing.what instanceof FrontWire)
		{
			FrontWire wi = (FrontWire)thing.what;
			FrontPath pPath = new FrontPath();
			for (int i = 0; i < wi.points.length; i++)
				appendPoint(pPath, wi.points[i]);
			outputWire(wi.layer, wi.width, pPath);
			return;
		}
		if (thing.what instanceof FrontFlash)
		{
			FrontFlash fl = (FrontFlash)thing.what;
			outputFlash(fl.layer, fl.diameter, fl.center);
			return;
		}
		if (thing.what instanceof FrontBox)
		{
			FrontBox bo = (FrontBox)thing.what;
			outputBox(bo.layer, bo.length, bo.width, bo.center, bo.xRot, bo.yRot);
			return;
		}
		if (thing.what instanceof FrontManBox)
		{
			Point temp = new Point();
			FrontManBox mb = (FrontManBox)thing.what;
			temp.x = (mb.bb.r + mb.bb.l)/2;
			temp.y = (mb.bb.t + mb.bb.b)/2;
			outputBox(mb.layer, mb.bb.r-mb.bb.l, mb.bb.t-mb.bb.b, temp, 1, 0);
			return;
		}
		if (thing.what instanceof FrontCall)
		{
			pushTransform();
			FrontCall sc = (FrontCall)thing.what;
			applyLocal(sc.matrix);
			FrontTransformList tList = new FrontTransformList();
			dupTransformList(sc.transList, tList);
			dumpDefinition(sc.unID);
			outputCall(sc.symNumber, sc.unID.name, tList, sc.lineNumber);
			popTransform();
			return;
		}
		if (thing.what instanceof FrontGeomName)
		{
			FrontGeomName gn = (FrontGeomName)thing.what;
			outputGeomName(gn.layer, gn.name, (gn.bb.l + gn.bb.r)/2, (gn.bb.b + gn.bb.t)/2);
			return;
		}
		if (thing.what instanceof FrontLabel)
		{
			FrontLabel la = (FrontLabel)thing.what;
			outputLabel(la.name, la.pos);
			return;
		}
	}

	private void outputLabel(String name, Point pt)
	{
		placeCIFList(CLABEL);
		BackCIFLabel cl = (BackCIFLabel)currentFrontElement.member;
		cl.label = name;
		cl.x = pt.x;   cl.y = pt.y;
	}

	private void outputGeomName(Layer lay, String name, int cX, int cY)
	{
		placeCIFList(CGNAME);
		BackCIFGeomName cg = (BackCIFGeomName)currentFrontElement.member;
		cg.lay = lay;
        cg.name = name;
        cg.cx = cX;
        cg.cy = cY;
	}

	private void outputCall(int number, String name, FrontTransformList list, int lineNumber)
	{
		placeCIFList(CCALL);
		currentFrontElement.lineNumber = lineNumber;
		BackCIFCall cc = (BackCIFCall)currentFrontElement.member;
		cc.cIndex = number;
		cc.name = name;
		cc.list = currentCTrans = null;
		for(int i = getFrontTransformListLength(list); i>0; i--)
		{
			if (newBackCIFTransform() == null) return;
			FrontTransformEntry temp = removeFrontTransformEntry(list);
			if (temp.kind == MIRROR)
			{
				if (temp.xCoord) currentCTrans.type = MIRX; else
					currentCTrans.type = MIRY;
			} else if (temp.kind == TRANSLATE)
			{
				currentCTrans.type = TRANS;
				currentCTrans.x = temp.xt;
				currentCTrans.y = temp.yt;
			} else if (temp.kind == ROTATE)
			{
				currentCTrans.type = ROT;
				currentCTrans.x = temp.xRot;
				currentCTrans.y = temp.yRot;
			}
		}
	}

	private BackCIFTransform newBackCIFTransform()
	{
		BackCIFTransform newCT = new BackCIFTransform();
		newCT.next = null;
		BackCIFCall cc = (BackCIFCall)currentFrontElement.member;
		if (cc.list == null) cc.list = newCT; else
			currentCTrans.next = newCT;
		currentCTrans = newCT;
		return newCT;
	}

	private void dumpDefinition(FrontSymbol sym)
	{
		if (sym.dumped) return;		// already done
		if (sym.numCalls > 0)			// dump all children
		{
			int count = sym.numCalls;

			FrontObjBase ro = sym.guts;
			while (ro != null && count > 0)
			{
				if (ro instanceof FrontCall)
				{
					dumpDefinition(((FrontCall)ro).unID);
					count--;
				}
				ro = ro.next;
			}
		}
		shipContents(sym);
		sym.dumped = true;
	}

	private void shipContents(FrontSymbol sym)
	{
		FrontObjBase ro = sym.guts;
		outputDefinitionStart(sym.symNumber, sym.name, sym.bounds.l, sym.bounds.r, sym.bounds.b, sym.bounds.t);
		while (ro != null)
		{
			if (ro instanceof FrontPoly)
			{
				FrontPoly po = (FrontPoly)ro;
				FrontPath pPath = new FrontPath();
				for (int i = 0; i < po.points.length; i++)
					appendPoint(pPath, po.points[i]);
				outputPolygon(po.layer, pPath);
			} else if (ro instanceof FrontWire)
			{
				FrontWire wi = (FrontWire)ro;
				FrontPath pPath = new FrontPath();
				for (int i = 0; i < wi.points.length; i++)
					appendPoint(pPath, wi.points[i]);
				outputWire(wi.layer, wi.width, pPath);
			} else if (ro instanceof FrontFlash)
			{
				FrontFlash fl = (FrontFlash)ro;
				outputFlash(fl.layer, fl.diameter, fl.center);
			} else if (ro instanceof FrontBox)
			{
				FrontBox bo = (FrontBox)ro;
				outputBox(bo.layer, bo.length, bo.width, bo.center, bo.xRot, bo.yRot);
			} else if (ro instanceof FrontManBox)
			{
				FrontManBox mb = (FrontManBox)ro;
				Point temp = new Point();
				temp.x = (((FrontManBox)ro).bb.r + ((FrontManBox) ro).bb.l)/2;
				temp.y = (((FrontManBox)ro).bb.t + ((FrontManBox) ro).bb.b)/2;
				outputBox(mb.layer, mb.bb.r-mb.bb.l, mb.bb.t-mb.bb.b, temp, 1, 0);
			} else if (ro instanceof FrontCall)
			{
				FrontCall sc = (FrontCall)ro;
				FrontTransformList tList = new FrontTransformList();
				dupTransformList(sc.transList, tList);
				outputCall(sc.symNumber, sc.unID.name, tList, sc.lineNumber);
			} else if (ro instanceof FrontGeomName)
			{
				FrontGeomName gn = (FrontGeomName)ro;
				outputGeomName(gn.layer, gn.name, (gn.bb.l + gn.bb.r)/2, (gn.bb.b + gn.bb.t)/2);
			} else if (ro instanceof FrontLabel)
			{
				FrontLabel la = (FrontLabel)ro;
				outputLabel(la.name, la.pos);
			}
			ro = ro.next;
		}
		outputDefinitionEnd();
	}

	private void outputDefinitionEnd()
	{
		placeCIFList(CEND);
	}

	private void outputDefinitionStart(int number, String name, int l, int r, int b, int t)
	{
		placeCIFList(CSTART);
		BackCIFStart cs = (BackCIFStart)currentFrontElement.member;
		cs.cIndex = number;
		cs.name = name;
		cs.l = l;   cs.r = r;
		cs.b = b;   cs.t = t;
	}

	private void dupTransformList(FrontTransformList src, FrontTransformList dest)
	{
		if (src == null || dest == null) return;
		FrontLinkedTransform node = src.tFirst;
		while (node != null)
		{
			appendTransformEntry(dest, node.tValue);
			node = node.tNext;
		}
	}

	private void outputBox(Layer lay, int length, int width, Point center, int xRotation, int yRotation)
	{
		if (length == 0 && width == 0) return;	// ignore null boxes
		placeCIFList(CBOX);
		BackCIFBox cb = (BackCIFBox)currentFrontElement.member;
		cb.lay = lay;
		cb.length = length;	cb.width = width;
		cb.cenX = center.x;	cb.cenY = center.y;
		cb.xRot = xRotation;	cb.yRot = yRotation;
	}

	private void placeCIFList(int id)
	{
		BackCIFList cl = newBackCIFList(id);
		if (cl == null) return;

		if (currentFrontList == null) currentFrontList = new ArrayList<BackCIFList>();
		currentFrontList.add(cl);
		currentFrontElement = cl;
	}

	private BackCIFList newBackCIFList(int id)
	{
		BackCIFList newCL = new BackCIFList();
		newCL.identity = id;
		switch (id)
		{
			case CSTART:
				BackCIFStart cs = new BackCIFStart();
				newCL.member = cs;
				cs.name = null;
				break;
			case CBOX:
				newCL.member = new BackCIFBox();
				break;
			case CPOLY:
				newCL.member = new BackCIFPoly();
				break;
			case CGNAME:
				newCL.member = new BackCIFGeomName();
				break;
			case CLABEL:
				newCL.member = new BackCIFLabel();
				break;
			case CCALL:
				BackCIFCall cc = new BackCIFCall();
				newCL.member = cc;
				cc.name = null;
				break;
		}
		return newCL;
	}

	private void outputFlash(Layer lay, int diameter, Point center)
	{
		// flash approximated by an octagon
		int radius = diameter/2;
		double fCX = center.x;
		double fCY = center.y;
		double offset = ((diameter)/2.0f)*0.414213f;
		FrontPath fpath = new FrontPath();

		Point temp = new Point(center.x-radius, (int)(fCY+offset));
		appendPoint(fpath, temp);
		temp = new Point(center.x-radius, (int)(fCY-offset));
		appendPoint(fpath, temp);
		temp = new Point((int)(fCX-offset), center.y-radius);
		appendPoint(fpath, temp);
		temp = new Point((int)(fCX+offset), center.y-radius);
		appendPoint(fpath, temp);
		temp = new Point(center.x+radius, (int)(fCY-offset));
		appendPoint(fpath, temp);
		temp = new Point(center.x+radius, (int)(fCY+offset));
		appendPoint(fpath, temp);
		temp = new Point((int)(fCX+offset), center.y+radius);
		appendPoint(fpath, temp);
		temp = new Point((int)(fCX-offset), center.y+radius);
		appendPoint(fpath, temp);

		outputPolygon(lay, fpath);
	}

	/**
	 * convert wires to boxes and flashes
	 */
	private void outputWire(Layer lay, int width, FrontPath wpath)
	{
		int lim = wpath.pLength;
		Point prev = removePoint(wpath);

		// do not use round-flashes with zero-width wires
		if (width != 0 && !localPrefs.squareWires)
		{
			boundsFlash(width, prev);
			outputFlash(lay, width, prev);
		}
		for (int i = 1; i < lim; i++)
		{
			Point curr = removePoint(wpath);

			// do not use round-flashes with zero-width wires
			if (width != 0 && !localPrefs.squareWires)
			{
				boundsFlash(width, curr);
				outputFlash(lay, width, curr);
			}
			int xr = curr.x-prev.x;
			int yr = curr.y-prev.y;
			int len = (int)new Point2D.Double(0, 0).distance(new Point2D.Double(xr, yr));
			if (localPrefs.squareWires) len += width;
			Point center = new Point((curr.x+prev.x)/2, (curr.y+prev.y)/2);
			boundsBox(len, width, center, xr, yr);
			outputBox(lay, len, width, center, xr, yr);
			prev = curr;
		}
	}

	private boolean isEndSeen()
	{
		return endCommandFound;
	}

	private void initInput()
	{
		charactersRead = 0;
		resetInputBuffer = true;
	}

	private void initErrors()
	{
		numFatalErrors = 0;
	}

	private void initUtilities()
	{
		minMaxStackLeft = new int[MAXMMSTACK];
		minMaxStackRight = new int[MAXMMSTACK];
		minMaxStackBottom = new int[MAXMMSTACK];
		minMaxStackTop = new int[MAXMMSTACK];
		symbolTable = new HashMap<Integer,FrontSymbol>();
		minMaxStackPtr = -1;			// min/max stack pointer
	}

	private void initMatrices()
	{
		matrixStackTop = new FrontMatrix();
		clearMatrix(matrixStackTop);
		matrixStackTop.next = null;
		matrixStackTop.prev = null;
		matrixStackTop.multiplied = true;
	}

	private void clearMatrix(FrontMatrix mat)
	{
		mat.a11 = 1.0;   mat.a12 = 0.0;
		mat.a21 = 0.0;   mat.a22 = 1.0;
		mat.a31 = 0.0;   mat.a32 = 0.0;   mat.a33 = 1.0;
		mat.type = TIDENT;   mat.multiplied = false;
	}

	private void inFromFile()
	{
		try
		{
			nextInputCharacter = lineReader.read();
			updateProgressDialog(1);
		} catch (IOException e)
		{
			nextInputCharacter = -1;
		}
	}

	private char getNextCharacter()
	{
		if (resetInputBuffer)
		{
			resetInputBuffer = false;
			inputBuffer = new StringBuilder();
			charactersRead = 0;
		}

		int c = nextInputCharacter;
		if (c >= 0)
		{
			if (c != '\n')
			{
				charactersRead++;
				inputBuffer.append((char)c);
			} else resetInputBuffer = true;
			try
			{
				nextInputCharacter = lineReader.read();
				updateProgressDialog(1);
			} catch (IOException e)
			{
				nextInputCharacter = -1;
			}
		}
		return (char)c;
	}

	private char peekNextCharacter()
	{
		return (char)nextInputCharacter;
	}

	private boolean atEndOfFile()
	{
		return nextInputCharacter < 0;
	}

	private int flushInput(char breakchar)
	{
		int c;
		while ((c = peekNextCharacter()) >= 0 && c != breakchar) getNextCharacter();
		return c;
	}

	private void skipBlanks()
	{
		for(;;)
		{
			if (atEndOfFile()) break;
			int c = peekNextCharacter();
			if (TextUtils.isDigit((char)c) || Character.isUpperCase((char)c)) break;
			if (c == '(' || c == ')' || c == ';' || c == '-') break;
			getNextCharacter();
		}
	}

	private int parseStatement()
	{
		if (atEndOfFile()) return ENDFILE;

		skipBlanks();		// flush initial junk

		int curChar = getNextCharacter();
		int command = 0;
		int xRotate=0, yRotate=0, length=0, width=0, diameter=0, symbolNumber=0, multiplier=0, divisor=0, userCommand=0;
		Point center = null, namePoint = null;
		FrontTransformList curTList = null;
		FrontPath curPath = null;
		String lName = null, nameText = null, userText = null;
		switch (curChar)
		{
			case 'P':
				command = POLYCOM;
				curPath = new FrontPath();
				getPath(curPath); if (errorFound) return reportError();
				break;

			case 'B':
				command = BOXCOM;
				xRotate = 1; yRotate = 0;
				length = getNumber(); if (errorFound) return reportError();
				width = getNumber(); if (errorFound) return reportError();
				center = getPoint(); if (errorFound) return reportError();
				skipSeparators();
				if (((curChar = peekNextCharacter()) >= '0' && curChar <= '9') || curChar == '-')
				{
					xRotate = getSignedInteger(); if (errorFound) return reportError();
					yRotate = getSignedInteger(); if (errorFound) return reportError();
				}
				break;

			case 'R':
				command = FLASHCOM;
				diameter = getNumber(); if (errorFound) return reportError();
				center = getPoint(); if (errorFound) return reportError();
				break;

			case 'W':
				command = WIRECOM;
				width = getNumber(); if (errorFound) return reportError();
				curPath = new FrontPath();
				getPath(curPath); if (errorFound) return reportError();
				break;

			case 'L':
				command = LAYER;
				skipBlanks();
				StringBuilder layerName = new StringBuilder();
				for (;;)
//				for (int i = 0; i<4; i++)
				{
					int chr = peekNextCharacter();
					if (!Character.isUpperCase((char)chr) && !TextUtils.isDigit((char)chr)) break;
					layerName.append(getNextCharacter());
				}
				if (layerName.length() == 0) {errorFound = true; errorType = NOLAYER; return reportError();}
				lName = layerName.toString();
				break;

			case 'D':
				skipBlanks();
				switch (getNextCharacter())
				{
					case 'S':
						command = DEFSTART;
						symbolNumber = getNumber(); if (errorFound) return reportError();
						skipSeparators(); multiplier = divisor = 1;
						if (TextUtils.isDigit(peekNextCharacter()))
						{
							multiplier = getNumber(); if (errorFound) return reportError();
							divisor = getNumber(); if (errorFound) return reportError();
						}
						if (isInCellDefinition)
						{
							errorFound = true;
							errorType = NESTDEF;
							return reportError();
						}
						isInCellDefinition = true;
						break;
					case 'F':
						command = DEFEND;
						if (!isInCellDefinition)
						{
							errorFound = true;
							errorType = NODEFSTART;
							return reportError();
						}
						isInCellDefinition = false;
						break;
					case 'D':
						command = DELETEDEF;
						symbolNumber = getNumber(); if (errorFound) return reportError();
						if (isInCellDefinition)
						{
							errorFound = true;
							errorType = NESTDD;
							return reportError();
						}
						break;
					default:
						errorFound = true;
						errorType = BADDEF;
						return reportError();
				}
				break;

			case 'C':
				command = CALLCOM;
				symbolNumber = getNumber(); if (errorFound) return reportError();
				skipBlanks();
				curTList = new FrontTransformList();
				for(;;)
				{
					FrontTransformEntry trans = new FrontTransformEntry();
					int val = peekNextCharacter();
					if (val == ';') break;
					switch (peekNextCharacter())
					{
						case 'T':
							getNextCharacter();
							trans.kind = TRANSLATE;
							trans.xt = getSignedInteger(); if (errorFound) return reportError();
							trans.yt = getSignedInteger(); if (errorFound) return reportError();
							appendTransformEntry(curTList, trans);
							break;

						case 'M':
							trans.kind = MIRROR;
							getNextCharacter(); skipBlanks();
							switch (getNextCharacter())
							{
								case 'X': trans.xCoord = true; break;
								case 'Y': trans.xCoord = false; break;
								default:  errorFound = true; errorType = BADAXIS; return reportError();
							}
							appendTransformEntry(curTList, trans);
							break;

						case 'R':
							trans.kind = ROTATE;
							getNextCharacter();
							trans.xRot = getSignedInteger(); if (errorFound) return reportError();
							trans.yRot = getSignedInteger(); if (errorFound) return reportError();
							appendTransformEntry(curTList, trans);
							break;

						default:
							errorFound = true; errorType = BADTRANS; return reportError();
					}
					skipBlanks();		// between transformation commands
				}	// end of while (1) loop
				break;

			case '(':
				{
					int level = 1;
					command = COMMENT;
					StringBuffer comment = new StringBuffer();
					while (level != 0)
					{
						curChar = getNextCharacter();
						switch (curChar)
						{
							case '(':
								level++;
								comment.append('(');
								break;
							case ')':
								level--;
								if (level != 0) comment.append(')');
								break;
							case -1:
								errorFound = true; errorType = BADCOMMENT; return reportError();
							default:
								comment.append(curChar);
						}
					}
				}
				break;

			case 'E':
				skipBlanks();
				if (isInCellDefinition)
				{
					errorFound = true;
					errorType = NESTEND;
					return reportError();
				}
				if (!atEndOfFile()) errorReport("more text follows end command", ADVISORY);
				endIsSeen = true;
				processEnd();
				return END;

			case ';':
				return NULLCOMMAND;

			default:
				if (TextUtils.isDigit((char)curChar))
				{
					userCommand = curChar - '0';
					if (userCommand == 9)
					{
						curChar = peekNextCharacter();
						if (curChar == ' ' || curChar == '\t' || curChar == '1' || curChar == '2' || curChar == '3')
						{
							switch (getNextCharacter())
							{
								case ' ':
								case '\t':
									skipSpaces(); nameText = parseName(); if (errorFound) return reportError();
									command = SYMNAME;
									break;
								case '1':
								case '2':
								case '3':
									if (!skipSpaces())
									{
										errorFound = true; errorType = NOSPACE;
										return reportError();
									}
									nameText = parseName(); if (errorFound) return reportError();
									switch (curChar)
									{
										case '1':
											command = INSTNAME;
											break;
										case '2':
										{
											command = GEONAME;
											namePoint = getPoint(); if (errorFound) return reportError();
											skipBlanks();
											StringBuffer layName = new StringBuffer();
											for (int i = 0; i<4; i++)
											{
												int chr = peekNextCharacter();
												if (!Character.isUpperCase((char)chr) && !TextUtils.isDigit((char)chr)) break;
												layName.append(getNextCharacter());
											}
											lName = layName.toString();
											break;
										}
										case '3':
											command = LABELCOM;
											namePoint = getPoint(); if (errorFound) return reportError();
											break;
									}
									break;
							}
						} else
						{
							command = USERS;
							userText = getUserText();
							if (atEndOfFile())
							{
								errorFound = true; errorType = BADUSER; return reportError();
							}
						}
					} else if (userCommand == 4)
                    {
						curChar = peekNextCharacter();
						if (curChar == 'I' || curChar == 'X' || curChar == 'N')
						{
							switch (getNextCharacter())
							{
								case 'I':
								case 'X':
								case 'N':
									if (!skipSpaces())
									{
										errorFound = true; errorType = NOSPACE;
										return reportError();
									}
									nameText = parseName(); if (errorFound) return reportError();
									switch (curChar)
									{
										case 'I':
											command = INSTNAME;
											break;
										case 'X':
										{
											command = GEONAME;
                                            getSignedInteger(); // skip Index
											namePoint = getPoint(); if (errorFound) return reportError();
                                            /* int w = */ getSignedInteger();
                                            getUserText();
											lName = currentLayer != null ? currentLayer.getCIFLayer() : null;
											break;
										}
										case 'N':
											command = LABELCOM;
											namePoint = getPoint(); if (errorFound) return reportError();
											break;
									}
									break;
							}
						} else
						{
							command = USERS;
							userText = getUserText();
							if (atEndOfFile())
							{
								errorFound = true; errorType = BADUSER; return reportError();
							}
						}
                    } else {
                        command = USERS;
                        userText = getUserText();
                        if (atEndOfFile())
                        {
                            errorFound = true; errorType = BADUSER; return reportError();
                        }
                    }
                } else {
					errorFound = true;
					errorType = BADCOMMAND;
					return reportError();
				}
		}

		// by now we have a syntactically valid command although it might be missing a semicolon
		switch (command)
		{
			case WIRECOM:
				makeWire(width, curPath);
				break;
			case DEFSTART:
				makeStartDefinition(symbolNumber, multiplier, divisor);
				break;
			case DEFEND:
				makeEndDefinition();
				break;
			case DELETEDEF:
				makeDeleteDefinition(symbolNumber);
				break;
			case CALLCOM:
				makeCall(symbolNumber, curTList);
				break;
			case LAYER:
				makeLayer(lName);
				break;
			case FLASHCOM:
				makeFlash(diameter, center);
				break;
			case POLYCOM:
				makePolygon(curPath);
				break;
			case BOXCOM:
				makeBox(length, width, center, xRotate, yRotate);
				break;
			case COMMENT:
				break;
			case USERS:
				makeUserComment(userCommand, userText);
				break;
			case SYMNAME:
				makeSymbolName(nameText);
				break;
			case INSTNAME:
				makeInstanceName(nameText);
				break;
			case GEONAME:
				makeGeomName(nameText, namePoint, cifLayerNames.get(lName));
				break;
			case LABELCOM:
				makeLabel(nameText, namePoint);
				break;
			default:
				errorFound = true;
				errorType = INTERNAL;
				return reportError();
		}
		if (!skipSemicolon()) {errorFound = true; errorType = NOSEMI; return reportError();}

		return command;
	}

	private void makeLabel(String name, Point pt)
	{
		statementsSince91 = true;
		if (ignoreStatements) return;
		if (name.length() == 0)
		{
			errorReport("null label ignored", ADVISORY);
			return;
		}
		FrontLabel obj = new FrontLabel();
		if (isInCellDefinition && cellScaleFactor != 1.0)
		{
			pt.x = (int)Math.round(cellScaleFactor * pt.x);
			pt.y = (int)Math.round(cellScaleFactor * pt.y);
		}
		obj.pos = pt;
		obj.name = name;

		pushTransform();
		Point temp = transformPoint(pt);
		popTransform();
		obj.bb.l = temp.x;
		obj.bb.r = temp.x;
		obj.bb.b = temp.y;
		obj.bb.t = temp.y;

		if (isInCellDefinition)
		{
			// insert into symbol's guts
			obj.next = currentFrontSymbol.guts;
			currentFrontSymbol.guts = obj;
		} else topLevelItem(obj);		// stick into item list
	}

	private void makeGeomName(String name, Point pt, Layer lay)
	{
		statementsSince91 = true;
		if (ignoreStatements) return;
		if (name.length() == 0)
		{
			errorReport("null geometry name ignored", ADVISORY);
			return;
		}
		FrontGeomName obj = new FrontGeomName();
		obj.layer = lay;
        obj.name = name;
		if (isInCellDefinition && cellScaleFactor != 1.0)
		{
			pt.x = (int)Math.round(cellScaleFactor * pt.x);
			pt.y = (int)Math.round(cellScaleFactor * pt.y);
		}

		pushTransform();
		Point temp = transformPoint(pt);
		popTransform();
		obj.bb.l = temp.x;
		obj.bb.r = temp.x;
		obj.bb.b = temp.y;
		obj.bb.t = temp.y;

		if (isInCellDefinition)
		{
			// insert into symbol's guts
			obj.next = currentFrontSymbol.guts;
			currentFrontSymbol.guts = obj;
		} else topLevelItem(obj);		// stick into item list
	}

	private void makeSymbolName(String name)
	{
		statementsSince91 = true;
		if (ignoreStatements) return;
		if (!isInCellDefinition)
		{
			errorReport("no symbol to name", FATALSEMANTIC);
			return;
		}
		if (name.length() == 0)
		{
			errorReport("null symbol name ignored", ADVISORY);
			return;
		}
		if (symbolNamed)
		{
			errorReport("symbol is already named, new name ignored", FATALSEMANTIC);
			return;
		}
		symbolNamed = true;
		currentFrontSymbol.name = name;
	}

	private void makeUserComment(int command, String text)
	{
		statementsSince91 = true;
		if (ignoreStatements) return;
	}

	private void makeBox(int length, int width, Point center, int xr, int yr)
	{
		statementsSince91 = true;
		if (ignoreStatements) return;
		if (currentLayer == null)
		{
			numNullLayerErrors = true;
			return;
		}
		if (length == 0 || width == 0)
		{
			errorReport("box with null length or width specified, ignored", ADVISORY);
			return;
		}

		if (isInCellDefinition && cellScaleFactor != 1.0)
		{
			length = (int)Math.round(cellScaleFactor * length);
			width = (int)Math.round(cellScaleFactor * width);
			center.x = (int)Math.round(cellScaleFactor * center.x);
			center.y = (int)Math.round(cellScaleFactor * center.y);
		}

		Rectangle box = boundsBox(length, width, center, xr, yr);
		int tl = box.x;   int tr = box.x + box.width;
		int tb = box.y;   int tt = box.y + box.height;

		// check for Manhattan box
		int halfW = width/2;
		int halfL = length/2;
		if (
			(yr == 0 && (length%2) == 0 && (width%2) == 0 &&
			(center.x-halfL) == tl && (center.x+halfL) == tr &&
			(center.y-halfW) == tb && (center.y+halfW) == tt)
			||
			(xr == 0 && (length%2) == 0 && (width%2) == 0 &&
			(center.x-halfW) == tl && (center.x+halfW) == tr &&
			(center.y-halfL) == tb && (center.y+halfL) == tt)
		)
		{
			// a Manhattan box
			FrontManBox obj = new FrontManBox();
			obj.layer = currentLayer;
			if (yr == 0)
			{
				obj.bb.l = tl;
				obj.bb.r = tr;
				obj.bb.b = tb;
				obj.bb.t = tt;
			} else
			{
				// this assumes that bounding box is unaffected by rotation
				obj.bb.l = center.x-halfW;
				obj.bb.r = center.x+halfW;
				obj.bb.b = center.y-halfL;
				obj.bb.t = center.y+halfL;
			}
			if (isInCellDefinition)
			{
				// insert into symbol's guts
				obj.next = currentFrontSymbol.guts;
				currentFrontSymbol.guts = obj;
			} else topLevelItem(obj);		// stick into item list
		} else
		{
			FrontBox obj = new FrontBox();
			obj.layer = currentLayer;
			obj.length = length;
			obj.width = width;
			obj.center = center;
			obj.xRot = xr;
			obj.yRot = yr;

			obj.bb.l = tl;
			obj.bb.r = tr;
			obj.bb.b = tb;
			obj.bb.t = tt;
			if (isInCellDefinition)
			{
				// insert into symbol's guts
				obj.next = currentFrontSymbol.guts;
				currentFrontSymbol.guts = obj;
			} else topLevelItem(obj);		// stick into item list
		}
	}

	private void makeFlash(int diameter, Point center)
	{
		statementsSince91 = true;
		if (ignoreStatements) return;
		if (currentLayer == null)
		{
			numNullLayerErrors = true;
			return;
		}
		if (diameter == 0)
		{
			errorReport("flash with null diamter, ignored", ADVISORY);
			return;
		}

		FrontFlash obj = new FrontFlash();
		obj.layer = currentLayer;
		if (isInCellDefinition && cellScaleFactor != 1.0)
		{
			diameter = (int)Math.round(cellScaleFactor * diameter);
			center.x = (int)Math.round(cellScaleFactor * center.x);
			center.y = (int)Math.round(cellScaleFactor * center.y);
		}
		obj.diameter = diameter;
		obj.center = center;

		Rectangle box = boundsFlash(diameter, center);
		obj.bb.l = box.x;
		obj.bb.r = box.x + box.width;
		obj.bb.b = box.y;
		obj.bb.t = box.y + box.height;

		if (isInCellDefinition)
		{
			// insert into symbol's guts
			obj.next = currentFrontSymbol.guts;
			currentFrontSymbol.guts = obj;
		} else topLevelItem(obj);		// stick into item list
	}

	private Rectangle boundsFlash(int diameter, Point center)
	{
		return boundsBox(diameter, diameter, center, 1, 0);
	}

	private Rectangle boundsBox(int length, int width, Point center, int xr, int yr)
	{
		int dx = length/2;
		int dy = width/2;

		pushTransform();	// new transformation
		rotateMatrix(xr, yr);
		translateMatrix(center.x, center.y);
		Point temp = new Point(dx, dy);
		initMinMax(transformPoint(temp));
		temp.y = -dy;
		minMax(transformPoint(temp));
		temp.x = -dx;
		minMax(transformPoint(temp));
		temp.y = dy;
		minMax(transformPoint(temp));
		popTransform();
		Rectangle ret = new Rectangle(getMinMaxMinX(), getMinMaxMinY(),
			getMinMaxMaxX()-getMinMaxMinX(), getMinMaxMaxY()-getMinMaxMinY());
		doneMinMax();
		return ret;
	}

	private void makeLayer(String lName)
	{
		statementsSince91 = true;
		if (ignoreStatements) return;
		currentLayer = cifLayerNames.get(lName);
		if (currentLayer == null)
		{
			unknownLayerNames.add(lName);
		}
	}

	private void makeCall(int symbol, FrontTransformList list)
	{
		if (ignoreStatements) return;
		int j = getFrontTransformListLength(list);
		FrontTransformList newtlist = null;
		if (j != 0) newtlist = new FrontTransformList();

		pushTransform();		// get new frame of reference
		for (int i = 1; i <=j; i++)
		{
			// build up incremental transformations
			FrontTransformEntry temp = removeFrontTransformEntry(list);
			if (temp.kind == MIRROR)
			{
				mirrorMatrix(temp.xCoord);
			} else if (temp.kind == TRANSLATE)
			{
				if (isInCellDefinition && cellScaleFactor != 1.0)
				{
					temp.xt = (int)Math.round(cellScaleFactor * temp.xt);
					temp.yt = (int)Math.round(cellScaleFactor * temp.yt);
				}
				translateMatrix(temp.xt, temp.yt);
			} else if (temp.kind == ROTATE)
			{
				rotateMatrix(temp.xRot, temp.yRot);
			} else
			{
				errorReport("interpreter: no such transformation", FATALINTERNAL);
			}
			appendTransformEntry(newtlist, temp);	// copy the list
		}

		FrontCall obj = new FrontCall();
		obj.lineNumber = lineReader.getLineNumber();

		// must make a copy of the matrix
		obj.matrix = new FrontMatrix();
		obj.matrix.a11 = matrixStackTop.a11;
		obj.matrix.a12 = matrixStackTop.a12;
		obj.matrix.a21 = matrixStackTop.a21;
		obj.matrix.a22 = matrixStackTop.a22;
		obj.matrix.a31 = matrixStackTop.a31;
		obj.matrix.a32 = matrixStackTop.a32;
		obj.matrix.a33 = matrixStackTop.a33;
		obj.matrix.type = matrixStackTop.type;
		obj.matrix.multiplied = matrixStackTop.multiplied;

		popTransform();		// return to previous state

		obj.symNumber = symbol;
		obj.unID = null;
		obj.transList = newtlist;

		if (namePending)
		{
			if (statementsSince91)
				errorReport("statements between name and instance", ADVISORY);
			namePending = false;
		}
		if (isInCellDefinition)
		{
			// insert into guts of symbol
			obj.next = currentFrontSymbol.guts;
			currentFrontSymbol.guts = obj;
			currentFrontSymbol.numCalls++;
		} else
		{
			topLevelItem(obj);
		}
	}

	private void rotateMatrix(int xRot, int yRot)
	{
		double si = yRot;
		double co = xRot;
		if (yRot == 0 && xRot >= 0) return;

		matrixStackTop.type |= TROTATE;
		if (xRot == 0)
		{
			double temp = matrixStackTop.a11;
			matrixStackTop.a11 = -matrixStackTop.a12;
			matrixStackTop.a12 = temp;

			temp = matrixStackTop.a21;
			matrixStackTop.a21 = -matrixStackTop.a22;
			matrixStackTop.a22 = temp;

			temp = matrixStackTop.a31;
			matrixStackTop.a31 = -matrixStackTop.a32;
			matrixStackTop.a32 = temp;
			if (yRot < 0) matrixStackTop.a33 = -matrixStackTop.a33;
		} else
			if (yRot == 0) matrixStackTop.a33 = -matrixStackTop.a33;	// xRot < 0
		else
		{
			double temp = matrixStackTop.a11*co - matrixStackTop.a12*si;
			matrixStackTop.a12 = matrixStackTop.a11*si + matrixStackTop.a12*co;
			matrixStackTop.a11 = temp;
			temp = matrixStackTop.a21*co - matrixStackTop.a22*si;
			matrixStackTop.a22 = matrixStackTop.a21*si + matrixStackTop.a22*co;
			matrixStackTop.a21 = temp;
			temp = matrixStackTop.a31*co - matrixStackTop.a32*si;
			matrixStackTop.a32 = matrixStackTop.a31*si + matrixStackTop.a32*co;
			matrixStackTop.a31 = temp;
			matrixStackTop.a33 = new Point2D.Double(0, 0).distance(new Point2D.Double(co, si));
		}
	}

	private void translateMatrix(int xtrans, int ytrans)
	{
		if (xtrans != 0 || ytrans != 0)
		{
			matrixStackTop.a31 += matrixStackTop.a33*xtrans;
			matrixStackTop.a32 += matrixStackTop.a33*ytrans;
			matrixStackTop.type |= TTRANSLATE;
		}
	}

	private void mirrorMatrix(boolean xCoord)
	{
		if (xCoord)
		{
			matrixStackTop.a11 = -matrixStackTop.a11;
			matrixStackTop.a21 = -matrixStackTop.a21;
			matrixStackTop.a31 = -matrixStackTop.a31;
		} else
		{
			matrixStackTop.a12 = -matrixStackTop.a12;
			matrixStackTop.a22 = -matrixStackTop.a22;
			matrixStackTop.a32 = -matrixStackTop.a32;
		}
		matrixStackTop.type |= TMIRROR;
	}

	private int getFrontTransformListLength(FrontTransformList a)
	{
		if (a == null) return 0;
		return a.tLength;
	}

	private FrontTransformEntry removeFrontTransformEntry(FrontTransformList a)
	{
		if (a.tFirst == null)
		{
			// added extra code to initialize "ans" to a dummy value
			FrontTransformEntry ans = new FrontTransformEntry();
			ans.kind = TRANSLATE;
			ans.xt = ans.yt = 0;
			return ans;
		}
		FrontLinkedTransform temp = a.tFirst.tNext;
		FrontTransformEntry ans = a.tFirst.tValue;
		a.tFirst = temp;
		if (a.tFirst == null) a.tLast = null;
		a.tLength -= 1;
		return ans;
	}

	private void makeDeleteDefinition(int n)
	{
		statementsSince91 = true;
		errorReport("DD not supported (ignored)", ADVISORY);
	}

	private void makeEndDefinition()
	{
		statementsSince91 = true;
		if (ignoreStatements)
		{
			ignoreStatements = false;
			return;
		}
		isInCellDefinition = false;
		currentLayer = backupLayer;		// restore old layer
		if (!symbolNamed)
		{
			String s = "SYM" + currentFrontSymbol.symNumber;
			currentFrontSymbol.name = s;
		}
		currentFrontSymbol.defined = true;
	}

	private void makeStartDefinition(int symbol, int mtl, int div)
	{
		statementsSince91 = true;
		currentFrontSymbol = lookupSymbol(symbol);
		if (currentFrontSymbol.defined)
		{
			// redefining this symbol
			String mess = "attempt to redefine symbol " + symbol + " (ignored)";
			errorReport(mess, ADVISORY);
			ignoreStatements = true;
			return;
		}

		isInCellDefinition = true;
		if (mtl != 0 && div != 0) cellScaleFactor = ((float) mtl)/((float) div); else
		{
			errorReport("illegal scale factor, ignored", ADVISORY);
			cellScaleFactor = 1.0;
		}
		backupLayer = currentLayer;	// save current layer
		currentLayer = null;
		symbolNamed = false;					// symbol not named
	}

	private void makeWire(int width, FrontPath a)
	{
		int length = a.pLength;
		statementsSince91 = true;
		if (ignoreStatements) return;
		if (currentLayer == null)
		{
			numNullLayerErrors = true;
			return;
		}
		FrontWire obj = new FrontWire();
		FrontPath tPath = a;
		FrontPath sPath = null;			// path in case of scaling
		obj.layer = currentLayer;
		if (isInCellDefinition && cellScaleFactor != 1.0)
		{
			sPath = new FrontPath();
			scalePath(a, sPath);		// scale all points
			width = (int)Math.round(cellScaleFactor * width);
			tPath = sPath;
		}
		obj.width = width;

		FrontPath bbpath = new FrontPath();		// get a new path for bounding box use
		copyPath(tPath, bbpath);
		boundsWire(width, bbpath);
		obj.points = new Point[length];
		for (int i = 0; i < length; i++) obj.points[i] = removePoint(tPath);

		if (isInCellDefinition)
		{
			// insert into symbol's guts
			obj.next = currentFrontSymbol.guts;
			currentFrontSymbol.guts = obj;
		} else topLevelItem(obj);		// stick into item list
	}

	private Rectangle boundsWire(int width, FrontPath pPath)
	{
		int half = (width+1)/2;
		int limit = pPath.pLength;

		pushTransform();	// new transformation
		initMinMax(transformPoint(removePoint(pPath)));
		for (int i = 1; i < limit; i++)
		{
			minMax(transformPoint(removePoint(pPath)));
		}
		popTransform();
		Rectangle rect = new Rectangle(getMinMaxMinX()-half, getMinMaxMinY()-half,
			getMinMaxMaxX()-getMinMaxMinX()+half*2, getMinMaxMaxY()-getMinMaxMinY()+half*2);
		doneMinMax();
		return rect;
	}

	private String getUserText()
	{
		StringBuffer user = new StringBuffer();
		for(;;)
		{
			if (atEndOfFile()) break;
			if (peekNextCharacter() == ';') break;
			user.append(getNextCharacter());
		}
		return user.toString();
	}

	private String parseName()
	{
		StringBuffer nText = new StringBuffer();
		boolean noChar = true;
		for(;;)
		{
			if (atEndOfFile()) break;
			int c = peekNextCharacter();
			if (c == ';' || c == ' ' || c == '\t' || c == '{' || c == '}') break;
			noChar = false;
			getNextCharacter();
			nText.append((char)c);
		}
		if (noChar) logIt(NONAME);
		return nText.toString();
	}

	private void appendTransformEntry(FrontTransformList a, FrontTransformEntry p)
	{
		FrontLinkedTransform newT = new FrontLinkedTransform();
		if (newT == null) return;

		FrontLinkedTransform temp = a.tLast;
		a.tLast = newT;
		if (temp != null) temp.tNext = a.tLast;
		a.tLast.tValue = p;
		a.tLast.tNext = null;
		if (a.tFirst == null) a.tFirst = a.tLast;
		a.tLength += 1;
	}

	private int getNumber()
	{
		boolean somedigit = false;
		int ans = 0;
		skipSpaces();

		while (ans < BIGSIGNED && TextUtils.isDigit(peekNextCharacter()))
		{
			ans *= 10; ans += getNextCharacter() - '0';
			somedigit = true;
		}

		if (!somedigit)
		{
			logIt(NOUNSIGNED);
			return 0;
		}
		if (TextUtils.isDigit(peekNextCharacter()))
		{
			logIt(NUMTOOBIG);
			return 0XFFFFFFFF;
		}
		return ans;
	}

	private boolean skipSemicolon()
	{
		boolean ans = false;
		skipBlanks();
		if (peekNextCharacter() == ';') { getNextCharacter(); ans = true; skipBlanks(); }
		return ans;
	}

	private boolean skipSpaces()
	{
		boolean ans = false;
		for(;;)
		{
			int c = peekNextCharacter();
			if (c != ' ' && c != '\t') break;
			getNextCharacter();
			ans = true;
		}
		return ans;
	}

	private void makePolygon(FrontPath a)
	{
		int length = a.pLength;
		statementsSince91 = true;
		if (ignoreStatements) return;
		if (currentLayer == null)
		{
			numNullLayerErrors = true;
			return;
		}
		if (length < 3)
		{
			errorReport("polygon with < 3 pts in path, ignored", ADVISORY);
			return;
		}

		FrontPoly obj = new FrontPoly();
		FrontPath tPath = a;
		obj.layer = currentLayer;
		if (isInCellDefinition && cellScaleFactor != 1.0)
		{
			FrontPath sPath = new FrontPath();
			scalePath(a, sPath);		// scale all points
			tPath = sPath;
		}

		FrontPath bbpath = new FrontPath();		// get a new path for bounding box use
		copyPath(tPath, bbpath);
		Rectangle box = getPolyBounds(bbpath);
		obj.bb.l = box.x;
		obj.bb.r = box.x + box.width;
		obj.bb.b = box.y;
		obj.bb.t = box.y + box.height;
		obj.points = new Point[length];
		for (int i = 0; i < length; i++) obj.points[i] = removePoint(tPath);

		if (isInCellDefinition)
		{
			// insert into symbol's guts
			obj.next = currentFrontSymbol.guts;
			currentFrontSymbol.guts = obj;
		} else topLevelItem(obj);		// stick into item list
	}

	/**
	 * a bare item has been found
	 */
	private void topLevelItem(FrontObjBase object)
	{
		if (object == null)
		{
			errorReport("item: null object", FATALINTERNAL);
			return;
		}

		FrontItem newItem = new FrontItem();
		currentItemList.add(newItem);
		newItem.what = object;

		// symbol calls only
		if (object instanceof FrontCall) findCallBounds((FrontCall)object);
	}

	/**
	 * find the bounding box for this particular call
	 */
	private void findCallBounds(FrontCall object)
	{
		FrontSymbol thisST = lookupSymbol(object.symNumber);
		if (!thisST.defined)
		{
			String mess = "call to undefined symbol " + thisST.symNumber;
			errorReport(mess, FATALSEMANTIC);
			return;
		}
		if (thisST.expanded)
		{
			String mess = "recursive call on symbol " + thisST.symNumber;
			errorReport(mess, FATALSEMANTIC);
			return;
		}
		thisST.expanded = true;		// mark as under expansion

		findBounds(thisST);		// get the bounding box of the symbol in its FrontSymbol
		object.unID = thisST;	// get this symbol's id

		pushTransform();			// set up a new frame of reference
		applyLocal(object.matrix);
		Point temp = new Point();
		temp.x = thisST.bounds.l;   temp.y = thisST.bounds.b;	// lower-left
		Point comperror = transformPoint(temp);
		initMinMax(comperror);
		temp.x = thisST.bounds.r;
		comperror = transformPoint(temp);
		minMax(comperror);
		temp.y = thisST.bounds.t;	// upper-right
		comperror = transformPoint(temp);
		minMax(comperror);
		temp.x = thisST.bounds.l;
		comperror = transformPoint(temp);
		minMax(comperror);

		object.bb.l = getMinMaxMinX();   object.bb.r = getMinMaxMaxX();
		object.bb.b = getMinMaxMinY();   object.bb.t = getMinMaxMaxY();
		doneMinMax();		// object now has transformed bounding box of the symbol
		popTransform();

		thisST.expanded = false;
	}

	/**
	 * find bounding box for sym
	 */
	private void findBounds(FrontSymbol sym)
	{
		boolean first = true;
		FrontObjBase ob = sym.guts;
		if (sym.boundsValid) return;			// already done
		if (ob == null)			// empty symbol
		{
			String name = sym.name;
			if (name == null) name = "#" + sym.symNumber;
			System.out.println("Warning: cell " + name + " has no geometry in it");
			sym.bounds.l = 0;   sym.bounds.r = 0;
			sym.bounds.b = 0;   sym.bounds.t = 0;
			sym.boundsValid = true;
			return;
		}

		while (ob != null)
		{
			// find bounding box for symbol calls, all primitive are done already
			if (ob instanceof FrontCall) findCallBounds((FrontCall)ob);
			Point temp = new Point();
			temp.x = ob.bb.l;   temp.y = ob.bb.b;
			if (first) {first = false; initMinMax(temp);}
				else minMax(temp);
			temp.x = ob.bb.r;   temp.y = ob.bb.t;
			minMax(temp);
			ob = ob.next;
		}
		sym.bounds.l = getMinMaxMinX();   sym.bounds.r = getMinMaxMaxX();
		sym.bounds.b = getMinMaxMinY();   sym.bounds.t = getMinMaxMaxY();
		sym.boundsValid = true;
		doneMinMax();
	}

	/**
	 * Method to find a given symbol.
	 * If none, make a blank entry.
	 * @return a pointer to whichever.
	 */
	private FrontSymbol lookupSymbol(int sym)
	{
		FrontSymbol val = symbolTable.get(Integer.valueOf(sym));
		if (val == null)
		{
			// create a new entry
			val = new FrontSymbol(sym);
			symbolTable.put(Integer.valueOf(sym), val);
		}
		return val;
	}

	private void applyLocal(FrontMatrix tm)
	{
		assignMatrix(tm, matrixStackTop);
	}

	private void scalePath(FrontPath src, FrontPath dest)
	{
		int limit = src.pLength;
		for (int i = 0; i < limit; i++)
		{
			Point temp = removePoint(src);

			temp.x = (int)Math.round(cellScaleFactor * temp.x);
			temp.y = (int)Math.round(cellScaleFactor * temp.y);
			appendPoint(dest, temp);
		}
	}

	private void copyPath(FrontPath src, FrontPath dest)
	{
		FrontLinkedPoint temp = src.pFirst;
		if (src == dest) return;
		while (temp != null)
		{
			appendPoint(dest, temp.pValue);
			temp = temp.pNext;
		}
	}

	private Rectangle getPolyBounds(FrontPath pPath)
	{
		int limit = pPath.pLength;

		pushTransform();	// new transformation
		initMinMax(transformPoint(removePoint(pPath)));
		for (int i = 1; i < limit; i++)
		{
			minMax(transformPoint(removePoint(pPath)));
		}
		popTransform();
		Rectangle ret = new Rectangle(getMinMaxMinX(), getMinMaxMinY(), getMinMaxMaxX()-getMinMaxMinX(), getMinMaxMaxY()-getMinMaxMinY());
		doneMinMax();
		return ret;
	}

	private void minMax(Point foo)
	{
		if (foo.x > minMaxStackRight[minMaxStackPtr]) minMaxStackRight[minMaxStackPtr] = foo.x;
			else {if (foo.x < minMaxStackLeft[minMaxStackPtr]) minMaxStackLeft[minMaxStackPtr] = foo.x;}
		if (foo.y > minMaxStackTop[minMaxStackPtr]) minMaxStackTop[minMaxStackPtr] = foo.y;
			else {if (foo.y < minMaxStackBottom[minMaxStackPtr]) minMaxStackBottom[minMaxStackPtr] = foo.y;}
	}

	private void initMinMax(Point foo)
	{
		if (++minMaxStackPtr >= MAXMMSTACK)
		{
			errorReport("initMinMax: out of stack", FATALINTERNAL);
			return;
		}
		minMaxStackLeft[minMaxStackPtr] = foo.x;   minMaxStackRight[minMaxStackPtr] = foo.x;
		minMaxStackBottom[minMaxStackPtr] = foo.y; minMaxStackTop[minMaxStackPtr] = foo.y;
	}

	private void doneMinMax()
	{
		if (minMaxStackPtr < 0) errorReport("doneMinMax: pop from empty stack", FATALINTERNAL);
			else minMaxStackPtr--;
	}

	private int getMinMaxMinX()
	{
		return minMaxStackLeft[minMaxStackPtr];
	}

	private int getMinMaxMinY()
	{
		return minMaxStackBottom[minMaxStackPtr];
	}

	private int getMinMaxMaxX()
	{
		return minMaxStackRight[minMaxStackPtr];
	}

	private int getMinMaxMaxY()
	{
		return minMaxStackTop[minMaxStackPtr];
	}

	private void pushTransform()
	{
		if (matrixStackTop.next == null)
		{
			matrixStackTop.next = new FrontMatrix();
			clearMatrix(matrixStackTop.next);
			matrixStackTop.next.prev = matrixStackTop;
			matrixStackTop = matrixStackTop.next;
			matrixStackTop.next = null;
		} else
		{
			matrixStackTop = matrixStackTop.next;
			clearMatrix(matrixStackTop);
		}
	}

	private void popTransform()
	{
		if (matrixStackTop.prev != null) matrixStackTop = matrixStackTop.prev;
			else errorReport("pop, empty trans stack", FATALINTERNAL);
	}

	private Point transformPoint(Point foo)
	{
		Point ans = new Point();

		if (!matrixStackTop.multiplied)
		{
			matrixMult(matrixStackTop, matrixStackTop.prev, matrixStackTop);
		}
		switch (matrixStackTop.type)
		{
			case TIDENT:
				return foo;
			case TTRANSLATE:
				ans.x = (int)matrixStackTop.a31;
				ans.y = (int)matrixStackTop.a32;
				ans.x += foo.x;   ans.y += foo.y;
				return ans;
			case TMIRROR:
				ans.x = (matrixStackTop.a11 < 0) ? -foo.x : foo.x;
				ans.y = (matrixStackTop.a22 < 0) ? -foo.y : foo.y;
				return ans;
			case TROTATE:
				ans.x = (int)(foo.x*matrixStackTop.a11 + foo.y*matrixStackTop.a21);
				ans.y = (int)(foo.x*matrixStackTop.a12 + foo.y*matrixStackTop.a22);
				return ans;
			default:
				ans.x = (int)(matrixStackTop.a31 + foo.x*matrixStackTop.a11+
					foo.y*matrixStackTop.a21);
				ans.y = (int)(matrixStackTop.a32 + foo.x*matrixStackTop.a12+
					foo.y*matrixStackTop.a22);
		}
		return ans;
	}

	private void matrixMult(FrontMatrix l, FrontMatrix r, FrontMatrix result)
	{
		if (l == null || r == null || result == null)
			errorReport("null arg to matrixMult", FATALINTERNAL);
		if (result.multiplied)
		{
			errorReport("can't re-mult matrix", FATALINTERNAL);
			return;
		}
		if (!r.multiplied)
		{
			FrontMatrix temp = new FrontMatrix();
			temp.multiplied = false;
			matrixMult(r, r.prev, temp);
			matrixMultCore(l, temp, result);
		} else matrixMultCore(l, r, result);
	}

	private void matrixMultCore(FrontMatrix l, FrontMatrix r, FrontMatrix result)
	{
		if (l == null || r == null || result == null)
		{
			errorReport("null arg to matrixMultCore", FATALINTERNAL);
			return;
		}
		if (l.type == TIDENT) assignMatrix(r, result); else
			if (r.type == TIDENT) assignMatrix(l, result); else
		{
			FrontMatrix temp = new FrontMatrix();
			temp.a11 = l.a11 * r.a11 + l.a12 * r.a21;
			temp.a12 = l.a11 * r.a12 + l.a12 * r.a22;
			temp.a21 = l.a21 * r.a11 + l.a22 * r.a21;
			temp.a22 = l.a21 * r.a12 + l.a22 * r.a22;
			temp.a31 = l.a31 * r.a11 + l.a32 * r.a21 + l.a33 * r.a31;
			temp.a32 = l.a31 * r.a12 + l.a32 * r.a22 + l.a33 * r.a32;
			temp.a33 = l.a33*r.a33;
			temp.type = l.type | r.type;
			assignMatrix(temp, result);
		}
		if (result.a33 != 1.0)
		{
			// divide by a33
			result.a11 /= result.a33;
			result.a12 /= result.a33;
			result.a21 /= result.a33;
			result.a22 /= result.a33;
			result.a31 /= result.a33;
			result.a32 /= result.a33;
			result.a33 = 1.0;
		}
		result.multiplied = true;
	}

	private void assignMatrix(FrontMatrix src, FrontMatrix dest)
	{
		dest.a11 = src.a11;
		dest.a12 = src.a12;
		dest.a21 = src.a21;
		dest.a22 = src.a22;
		dest.a31 = src.a31;
		dest.a32 = src.a32;
		dest.a33 = src.a33;
		dest.type = src.type;
		dest.multiplied = src.multiplied;
	}

	private Point removePoint(FrontPath a)
	{
		if (a.pFirst == null)
		{
			// added code to initialize return value with dummy numbers
			return new Point(0, 0);
		}
		FrontLinkedPoint temp = a.pFirst.pNext;
		Point ans = a.pFirst.pValue;
		a.pFirst = temp;
		if (a.pFirst == null) a.pLast = null;
		a.pLength -= 1;
		return ans;
	}

	private void makeInstanceName(String name)
	{
		if (ignoreStatements) return;
		if (name.length() == 0)
		{
			errorReport("null instance name ignored", ADVISORY);
			return;
		}
		if (namePending)
		{
			errorReport("there is already a name pending, new name replaces it", ADVISORY);
		}
		namePending = true;
		statementsSince91 = false;
	}

	private void processEnd()
	{
		statementsSince91 = true;
		endCommandFound = true;
		if (namePending)
		{
			errorReport("no instance to match name command", ADVISORY);
			namePending = false;
		}
	}

	private int getSignedInteger()
	{
		boolean sign = false;
		int ans = 0;
		skipSeparators();

		if (peekNextCharacter() == '-') { sign = true;   getNextCharacter(); }

		boolean someDigit = false;
		while (ans < BIGSIGNED && TextUtils.isDigit(peekNextCharacter()))
		{
			ans *= 10; ans += getNextCharacter() - '0';
			someDigit = true;
		}

		if (!someDigit) { logIt(NOSIGNED);   return 0; }
		if (TextUtils.isDigit(peekNextCharacter()))
		{
			logIt(NUMTOOBIG);
			return sign ? -0X7FFFFFFF : 0X7FFFFFFF;
		}
		return sign ? -ans : ans;
	}

	private void logIt(int thing)
	{
		errorFound = true;
		errorType = thing;
	}

	private Point getPoint()
	{
		int x = getSignedInteger();
		int y = getSignedInteger();
		return new Point(x, y);
	}

	private void getPath(FrontPath a)
	{
		skipSeparators();
		for(;;)
		{
			int c = peekNextCharacter();
			if (!TextUtils.isDigit((char)c) && c != '-') break;
			Point temp = getPoint();	  if (errorFound) break;
			appendPoint(a, temp);
			skipSeparators();
		}
		if (a.pLength == 0) logIt(NOPATH);
	}

	private void appendPoint(FrontPath a, Point p)
	{
		FrontLinkedPoint temp;

		temp = a.pLast;
		a.pLast = new FrontLinkedPoint();
		if (temp != null) temp.pNext = a.pLast;
		a.pLast.pValue = p;
		a.pLast.pNext = null;
		if (a.pFirst == null) a.pFirst = a.pLast;
		a.pLength += 1;
	}

	private void skipSeparators()
	{
		for(;;)
		{
			int c = peekNextCharacter();
			switch (c)
			{
				case '(':
				case ')':
				case ';':
				case '-':
				case -1:
					return;
				default:
					if (TextUtils.isDigit((char)c)) return;
					getNextCharacter();
			}
		}
	}

	private int reportError()
	{
		switch (errorType)
		{
			case NUMTOOBIG:  errorReport("number too large", FATALSYNTAX); break;
			case NOUNSIGNED: errorReport("unsigned integer expected", FATALSYNTAX); break;
			case NOSIGNED:   errorReport("signed integer expected", FATALSYNTAX); break;
			case NOSEMI:     errorReport("missing ';' inserted", FATALSYNTAX); break;
			case NOPATH:     errorReport("no points in path", FATALSYNTAX); break;
			case BADTRANS:   errorReport("no such transformation command", FATALSYNTAX); break;
			case BADUSER:    errorReport("end of file inside user command", FATALSYNTAX); break;
			case BADCOMMAND: errorReport("unknown command encountered", FATALSYNTAX); break;
			case INTERNAL:   errorReport("parser can't find i routine", FATALINTERNAL); break;
			case BADDEF:     errorReport("no such define command", FATALSYNTAX); break;
			case NOLAYER:    errorReport("layer name expected", FATALSYNTAX); break;
			case BADCOMMENT: errorReport("end of file inside a comment", FATALSYNTAX); break;
			case BADAXIS:    errorReport("no such axis in mirror command", FATALSYNTAX); break;
			case NESTDEF:    errorReport("symbol definitions can't nest", FATALSYNTAX); break;
			case NODEFSTART: errorReport("DF without DS", FATALSYNTAX); break;
			case NESTDD:     errorReport("DD can't appear inside symbol definition", FATALSYNTAX); break;
			case NOSPACE:    errorReport("missing space in name command", FATALSYNTAX); break;
			case NONAME:     errorReport("no name in name command", FATALSYNTAX); break;
			case NESTEND:    errorReport("End command inside symbol definition", FATALSYNTAX); break;
			case NOERROR:    errorReport("error signaled but not reported", FATALINTERNAL); break;
			default:         errorReport("uncaught error", FATALSYNTAX);
		}
		if (errorType != INTERNAL && errorType != NOSEMI && flushInput(';') < 0)
			errorReport("unexpected end of input file", FATALSYNTAX);
				else skipBlanks();
		errorFound = false;
		errorType = NOERROR;
		return SYNTAXERROR;
	}

	private void errorReport(String mess, int kind)
	{
		if (charactersRead > 0)
		{
			System.out.println("line " + (lineReader.getLineNumber()+(resetInputBuffer?0:1)) + ": " + inputBuffer.toString());
		}
		if (kind == FATALINTERNAL || kind == FATALINTERNAL ||
			kind == FATALSEMANTIC || kind == FATALOUTPUT) numFatalErrors++;
		switch (kind)
		{
			case FATALINTERNAL: System.out.println("Fatal internal error: " + mess);  break;
			case FATALSYNTAX:   System.out.println("Syntax error: " + mess);          break;
			case FATALSEMANTIC: System.out.println("Error: " + mess);                 break;
			case FATALOUTPUT:   System.out.println("Output error: " + mess);          break;
			case ADVISORY:      System.out.println("Warning: " + mess);               break;
			default:            System.out.println(mess);                             break;
		}
	}
}
