/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: EDIF.java
 * Input/output tool: EDIF 2.0.0 input
 * Original EDIF reader by Glen Lawson.
 * Translated into Java and debugged 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.geometry.Poly;
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.hierarchy.Nodable;
import com.sun.electric.database.hierarchy.View;
import com.sun.electric.database.id.CellId;
import com.sun.electric.database.network.Netlist;
import com.sun.electric.database.network.Network;
import com.sun.electric.database.prototype.NodeProto;
import com.sun.electric.database.prototype.PortCharacteristic;
import com.sun.electric.database.prototype.PortProto;
import com.sun.electric.database.text.Name;
import com.sun.electric.database.topology.ArcInst;
import com.sun.electric.database.topology.Connection;
import com.sun.electric.database.topology.Geometric;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.topology.PortInst;
import com.sun.electric.database.variable.CodeExpression;
import com.sun.electric.database.variable.TextDescriptor;
import com.sun.electric.database.variable.Variable;
import com.sun.electric.technology.ArcProto;
import com.sun.electric.technology.Layer;
import com.sun.electric.technology.PrimitiveNode;
import com.sun.electric.technology.Technology;
import com.sun.electric.technology.technologies.Artwork;
import com.sun.electric.technology.technologies.Generic;
import com.sun.electric.technology.technologies.Schematics;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.JobException;
import com.sun.electric.tool.io.IOTool;
import com.sun.electric.tool.io.output.EDIFEquiv;
import com.sun.electric.tool.routing.AutoStitch;
import com.sun.electric.tool.user.IconParameters;
import com.sun.electric.util.TextUtils;
import com.sun.electric.util.math.GenMath;
import com.sun.electric.util.math.MutableDouble;
import com.sun.electric.util.math.Orientation;

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.Collections;
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 EDIF files.
 * <BR>
 * Notes:
 *	Does not fully support portbundles
 *	Multiple ports of the same name are named port_x (x is 1 to n duplicate)
 *	Keywords such as ARRAY have unnamed parameters, ie (array (name..) 5 6)
 *	this is handled in the processInteger function called by getKeyword,
 *	this is a hack to fix this problem, a real table driven parser should be used.
 *	Use circle arcs instead of splines.
 *	Support text justifications and text height
 * 	Better NAME/RENAME/STRINGDISPLAY/ANNOTATE text handling.
 *  ANSI prototypes
 *	Changed arcs to simple polygons plus ARC attribute
 *<BR>
 * Some examples that can be handled:
 *
 * ------------------------- LAYOUT -------------------------
 * (cell XXXXX (cellType GENERIC)
 *  (view layout (viewType MASKLAYOUT)
 *   (interface
 *    (protectionFrame)
 *   )
 *   (contents
 *    ========== (LAYOUT DESCRIPTION)
 *   )
 *  )
 * )
 * ------------------------- AN ICON -------------------------
 * (cell XXXXX (cellType GENERIC)
 *  (view INTERFACE (viewType NETLIST)
 *   (interface
 *    (port A)
 *   )
 *  )
 * )
 * ------------------------- AN ICON -------------------------
 * (cell XXXXX (cellType GENERIC)
 *  (view symbol (viewType SCHEMATIC)
 *   (interface
 *    (port A)
 *    (symbol
 *     ========== (ICON DESCRIPTION)
 *    )
 *   )
 *  )
 * )
 * ------------------------- AN ICON -------------------------
 * (cell XXXXX (cellType GENERIC)
 *  (view VLSI_Symbol (viewType SCHEMATIC)
 *   (interface
 *    (port A)
 *    (symbol
 *     ========== (ICON DESCRIPTION)
 *    )
 *   )
 *  )
 * )
 * ------------------------- AN ICON -------------------------
 * (cell XXXXX (cellType GENERIC)
 *  (view symbol (viewType GRAPHIC)
 *   (interface)
 *   (contents
 *    ========== (ICON DESCRIPTION)
 *   )
 *  )
 * )
 * ------------------------- AN ICON AND SCHEMATIC -------------------------
 * (cell XXXXX (cellType GENERIC)
 *  (view symbol (viewType SCHEMATIC)
 *   (interface
 *    (port A)
 *    (symbol
 *     ========== (ICON DESCRIPTION)
 *    )
 *   )
 *   (contents
 *    (page SH1
 *     ========== (SCHEMATIC DESCRIPTION)
 *    )
 *   )
 *  )
 * )
 * ------------------------- AN ICON AND SCHEMATIC -------------------------
 * (cell XXXXX (cellType GENERIC)
 *  (view NETLIST (viewType NETLIST)
 *   (interface
 *    (port A)
 *   )
 *   (contents
 *    ========== (SCHEMATIC DESCRIPTION)
 *   )
 *  )
 * )
 * ------------------------- AN ICON, SCHEMATIC, AND LAYOUT -------------------------
 * (cell XXXXX (cellType GENERIC)
 *  (view layout (viewType MASKLAYOUT)
 *   (interface
 *    (port A)
 *    (protectionFrame)
 *   (contents
 *    ========== (LAYOUT DESCRIPTION)
 *   )
 *  )
 *  (view schematic (viewType SCHEMATIC)
 *   (interface
 *    (port A)
 *   (contents
 *    (page SH1
 *     ========== (SCHEMATIC DESCRIPTION)
 *    )
 *   )
 *  )
 *  (view symbol (viewType SCHEMATIC)
 *   (interface
 *    (port A)
 *    (symbol
 *     ========== (ICON DESCRIPTION)
 *    )
 *   )
 *   (contents
 *    (page SH1
 *     ========== (SCHEMATIC DESCRIPTION AGAIN!!!)
 *    )
 *   )
 *  )
 * )
 */
public class EDIF extends Input<Object>
{
	private static final double INCH = 10;
	private static final int SHEETWIDTH = 44;		// was 32
	private static final int SHEETHEIGHT = 20;

	// name table for layers
	private static class NameEntry
	{
		/** the original MASK layer */	private String original;
		/** the replacement layer */	private String replace;
		/** the basic electric node */	private NodeProto node;
		/** default text height */		private double textHeight;
		/** default justification */	private TextDescriptor.Position justification;
	}

	// Edif viewtypes ...
	private static class ViewType
	{
		private String name;
		ViewType(String name) { this.name = name; }
		public String toString() { return "VIEWTYPE "+name; }
	}
	private static final ViewType VNULL       = new ViewType("Null");
	private static final ViewType VBEHAVIOR   = new ViewType("Behavior");
	private static final ViewType VDOCUMENT   = new ViewType("Document");
	private static final ViewType VGRAPHIC    = new ViewType("Graphic");
	private static final ViewType VLOGICMODEL = new ViewType("LogicModel");
	private static final ViewType VMASKLAYOUT = new ViewType("MaskLayout");
	private static final ViewType VNETLIST    = new ViewType("Netlist");
	private static final ViewType VPCBLAYOUT  = new ViewType("PCBLayout");
	private static final ViewType VSCHEMATIC  = new ViewType("Schematic");
	private static final ViewType VSTRANGER   = new ViewType("Stranger");
	private static final ViewType VSYMBOLIC   = new ViewType("Symbolic");

	// Edif geometry types ...
	private static class GeometryType {}
	private static final GeometryType GUNKNOWN            = new GeometryType();
	private static final GeometryType GINSTANCE           = new GeometryType();
	private static final GeometryType GPORTIMPLEMENTATION = new GeometryType();
	private static final GeometryType GPROTECTIONFRAME    = new GeometryType();
	private static final GeometryType GNET                = new GeometryType();
	private static final GeometryType GBUS                = new GeometryType();

	// 8 standard orientations
	private static final Orientation OR0      = Orientation.fromC(0, false);
	private static final Orientation OR90     = Orientation.fromC(900, false);
	private static final Orientation OR180    = Orientation.fromC(1800, false);
	private static final Orientation OR270    = Orientation.fromC(2700, false);
	private static final Orientation OMX      = Orientation.fromC(2700, true);
	private static final Orientation OMY      = Orientation.fromC(900, true);
	private static final Orientation OMYMX	  = Orientation.fromC(1800, false);
	private static final Orientation OMYR90   = Orientation.fromC(0, true);
	private static final Orientation OMXR90   = Orientation.fromC(1800, true);

	private static class VendorType {}
	private static final VendorType EVUNKNOWN    = new VendorType();
	private static final VendorType EVCADENCE    = new VendorType();
	private static final VendorType EVVIEWLOGIC  = new VendorType();

	private static class EDIFProperty
	{
		private String name;
		private Object val;
	}

	private static class PlannedPort
	{
		List<EDIFProperty> properties;
		PortCharacteristic direction;
		boolean knownLocation;
		Export createdPort;
		double x, y;
		double sX, sY;
		String name, alternateName;
		List<PlannedPort> exportsToConnect;
		NodeInst alreadyThere;
		NodeProto pinType;
		Orientation orientation;

		PlannedPort()
		{
			sX = sY = 0;
		}
	}

	// view information ...
	/** indicates we are in a NETLIST view */	private ViewType activeView;
	/** the current vendor type */				private VendorType curVendor;

	// parser variables ...
	/** the current parser state */				private EDIFKEY curKeyword;
	/** the read buffer */						private String inputBuffer;
	/** the position within the buffer */		private int inputBufferPos;
	/** no update flag */						private boolean ignoreBlock;
	/** no update flag */						private boolean ignoreHigherBlock;
	/** load status */							private int errorCount, warningCount;

	// electric context data ...
	/** the new library */						private Library curLibrary;
	/** the current technology */				private Technology curTech;
	/** the current active cell */				private Cell curCell;
	/** current Cells in Libraries */			private Map<Library,Cell> currentCells;
	/** the current page in cell */				private int curCellPage;
	/** the current active instance */			private NodeInst curNode;
	/** the current active arc */				private ArcInst curArc;
	/** the current figure group node */		private NodeProto curFigureGroup;
	/** the cellRef type */						private NodeProto cellRefProto;
	/** the cellRef tech bits if primitive */	private PrimitiveNode.Function cellRefProtoFunction;
	/** the cellRef addt'l rotation (in degrees) */ private int cellRefProtoRotation;
	/** the cellRef offset when mapped */		private double cellRefOffsetX, cellRefOffsetY;
	/** the current port proto */				private PortProto curPort;
	/** the current portList */					private List<Network> curPortlist;
	/** the last portList */					private List<Network> lastPortlist;
	/** the current input job */				private Job job;

	// general geometry information ...
	/** the current geometry type */			private GeometryType curGeometryType;
	/** the list of points */					private List<Point2D> curPoints;
	/** the orientation of the structure */		private Orientation curOrientation;
	/** port direction */						private PortCharacteristic curDirection;

	// geometric path constructors ...
	/** the width of the path */				private double pathWidth;
//	/** extend path end flag */					private boolean extendEnd;

	// array variables ...
	/** set if truely an array */				private boolean isArray;
	/** the bounds of the array */				private int arrayXVal, arrayYVal;
	/** offsets in x and y for X increment */	private double deltaPointXX, deltaPointXY;
	/** offsets in x and y for Y increment */	private double deltaPointYX, deltaPointYY;
	/** which offset flag */					private boolean deltaPointsSet;
	/** the element of the array */				private int memberXVal, memberYVal;

	// text variables ...
	/** Text string */							private String textString;
	/** the current default text height */		private double textHeight;
	/** the current text justificaton */		private TextDescriptor.Position textJustification;
	/** origin x and y */						private List<Point2D> saveTextPoints;
	/** the height of text */					private double saveTextHeight;
	/** justification of the text */			private TextDescriptor.Position saveTextJustification;
	/** orientation of the text */				private Orientation saveOrientation;

	// current name and rename of EDIF objects ...
	/** the current cell name */				private String cellReference;
	/** the current cell name (original) */		private String cellName;
	/** the current port name */				private String portReference;
	/** the current port name (original) */		private String portName;
	/** the current instance name */			private String instanceReference;
	/** the current instance name (original) */	private String instanceName;
	/** the current bundle name */				private String bundleReference;
	/** the current bundle name (original) */	private String bundleName;
	/** the current net name */					private String netReference;
	/** the current net name (original) */		private String netName;
	/** the current property name */			private String propertyReference;
	/** the current viewRef name */				private String viewRef;
	/** the current cellRef name */				private String cellRef;
	/** the current libraryRef name */			private String libraryRef;
	/** the current commentGraphics name */		private String commentGraphicsText;

	/** property value */						private Object propertyValue;

	/** the name of the object */				private String objectName;
	/** the original name of the object */		private String originalName;

	/** cells that have been built so far */	private Set<Cell> builtCells;
	/** whether a symbol was found in an interface */	private boolean symbolDefined;

	// layer or figure information ...
	/** the current name mapping table */		private List<NameEntry> activeFigures;
	/** the current name table entry */			private NameEntry curActiveFigure;

	/** cell name lookup (from rename) */ 		private Map<String,NameEntry> cellTable;

	// port data for port exporting
	/** list of exports in the cell */			private List<PlannedPort> plannedPorts;
	/** place for unconnected ports */			private double inputLocY, outputLocY;
	/** Map of duplicated exports */			private Map<String,Integer> uniqueExportNames;

	// property data for all objects
	/** active property list */					private List<EDIFProperty> propertiesList;

	// view NETLIST layout
	/** current sheet position */				private int sheetXPos, sheetYPos, sheetOffset;

	/** stack of keywords at this point */		private EDIFKEY [] keyStack = new EDIFKEY[1000];
	/** depth of keyword stack */				private int keyStackDepth;
	/** place where ignoring starts */			private int saveStack = -1;

	/** Edif equivs for primitives */			private EDIFEquiv equivs;
	/** List of ports in net -> joined list */  private List<PortInst> netPortRefs;
	/** mapping of renamed objects */			private Map<String,String> renamedObjects;
	/** list of nodes that were equived */		private Map<String,EDIFEquiv.NodeEquivalence> mappedNodes;
	/** mapping of named arcs in each cell */	private Map<Cell,Map<String,List<ArcInst>>> namedArcs = new HashMap<Cell,Map<String,List<ArcInst>>>();
	/** export names needed on current net */	private Set<String> exportsOnNet;
	/** arcs placed on current net */			private List<ArcInst> arcsOnNet;
	/** nodes placed on current net */			private List<NodeInst> nodesOnNet;

    // some standard artwork primitives
	private PortProto defaultPort;
	private PortProto defaultBusPort;

	/** all keywords for parsing */				private static Map<String,EDIFKEY> edifKeys = new HashMap<String,EDIFKEY>();

	/** key of Variable with true port size. */	private static final Variable.Key PORT_SIZE = Variable.newKey("ATTR_EDIF_port_size");

	/**************************************** MAIN CONTROL ****************************************/

	private EDIFPreferences localPrefs;

	public static class EDIFPreferences extends InputPreferences
	{
		public double inputScale;
		public boolean importAllParameters;
		public boolean importStitchesCells;
		public String acceptedParameters;
		public String configurationFile;
		public boolean cadenceCompatibility;
		public boolean showArcNames, showNodeNames;
		public IconParameters iconParameters;
		public AutoStitch.AutoOptions autoParameters;

		public EDIFPreferences(boolean factory)
		{
			super(factory);
            iconParameters = IconParameters.makeInstance(!factory);
            autoParameters = new AutoStitch.AutoOptions(factory);
			if (factory)
			{
				inputScale = IOTool.getFactoryEDIFInputScale();
				acceptedParameters = IOTool.getFactoryEDIFAcceptedParameters();
                importAllParameters = IOTool.isFactoryEDIFImportAllParameters();
                importStitchesCells = IOTool.isFactoryEDIFImportStitchesCells();
				configurationFile = IOTool.getFactoryEDIFConfigurationFile();
				cadenceCompatibility = IOTool.isFactoryEDIFCadenceCompatibility();
				showArcNames = IOTool.isFactoryEDIFShowArcNames();
				showNodeNames = IOTool.isFactoryEDIFShowNodeNames();
				autoParameters.createExports = false;
			} else
            {
                inputScale = IOTool.getEDIFInputScale();
                acceptedParameters = IOTool.getEDIFAcceptedParameters();
                importAllParameters = IOTool.isEDIFImportAllParameters();
                importStitchesCells = IOTool.isEDIFImportStitchesCells();
                configurationFile = IOTool.getEDIFConfigurationFile();
                cadenceCompatibility = IOTool.isEDIFCadenceCompatibility();
                showArcNames = IOTool.isEDIFShowArcNames();
                showNodeNames = IOTool.isEDIFShowNodeNames();
            }
		}

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

	/**
	 * Creates a new instance of EDIF.
	 */
	EDIF(EditingPreferences ep, EDIFPreferences 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)
	{
		// setup keyword prerequisites: XX.stateArray = new EDIFKEY [] {KYY};	means YYs can be found inside of XX: (xx (yy))
		KARRAY.stateArray = new EDIFKEY [] {KINSTANCE, KPORT, KNET};
		KAUTHOR.stateArray = new EDIFKEY [] {KWRITTEN};
		KBOUNDINGBOX.stateArray = new EDIFKEY [] {KSYMBOL, KCONTENTS, KPROTECTIONFRAME};
		KCELL.stateArray = new EDIFKEY [] {KEXTERNAL, KLIBRARY};
		KCELLREF.stateArray = new EDIFKEY [] {KDESIGN, KVIEWREF, KINSTANCE};
		KCELLTYPE.stateArray = new EDIFKEY [] {KCELL};
		KCONTENTS.stateArray = new EDIFKEY [] {KVIEW};
		KDESIGN.stateArray = new EDIFKEY [] {KEDIF};
		KDIRECTION.stateArray = new EDIFKEY [] {KPORT};
		KUNUSED.stateArray = new EDIFKEY [] {KPORT};
		KEDIF.stateArray = new EDIFKEY [] {KINIT};
		KEDIFLEVEL.stateArray = new EDIFKEY [] {KEDIF, KEXTERNAL, KLIBRARY};
		KEXTERNAL.stateArray = new EDIFKEY [] {KEDIF};
		KINSTANCE.stateArray = new EDIFKEY [] {KCONTENTS, KPAGE, KPORTIMPLEMENTATION, KCOMMENTGRAPHICS};
		KINSTANCEREF.stateArray = new EDIFKEY [] {KINSTANCEREF, KPORTREF};
		KINTERFACE.stateArray = new EDIFKEY [] {KVIEW};
		KJOINED.stateArray = new EDIFKEY [] {KINTERFACE, KNET};
		KEDIFKEYMAP.stateArray = new EDIFKEY [] {KEDIF};
		KLIBRARYREF.stateArray = new EDIFKEY [] {KCELLREF};
		KLISTOFNETS.stateArray = new EDIFKEY [] {KNETBUNDLE};
		KNET.stateArray = new EDIFKEY [] {KCONTENTS, KPAGE, KLISTOFNETS};
		KNETBUNDLE.stateArray = new EDIFKEY [] {KCONTENTS, KPAGE};
		KNUMBERDEFINITION.stateArray = new EDIFKEY [] {KTECHNOLOGY};
		KPORT.stateArray = new EDIFKEY [] {KINTERFACE, KLISTOFPORTS};
		KPROTECTIONFRAME.stateArray = new EDIFKEY [] {KINTERFACE, KLISTOFPORTS};
		KSCALE.stateArray = new EDIFKEY [] {KNUMBERDEFINITION};
		KSTATUS.stateArray = new EDIFKEY [] {KCELL, KDESIGN, KEDIF, KEXTERNAL, KLIBRARY, KVIEW};
		KSYMBOL.stateArray = new EDIFKEY [] {KINTERFACE};
		KTECHNOLOGY.stateArray = new EDIFKEY [] {KEXTERNAL, KLIBRARY};
		KTIMESTAMP.stateArray = new EDIFKEY [] {KWRITTEN};
		KUNIT.stateArray = new EDIFKEY [] {KPROPERTY, KSCALE};
		KVIEW.stateArray = new EDIFKEY [] {KPROPERTY, KCELL};
		KVIEWREF.stateArray = new EDIFKEY [] {KINSTANCE, KINSTANCEREF, KPORTREF};
		KVIEWTYPE.stateArray = new EDIFKEY [] {KVIEW};
		KWRITTEN.stateArray = new EDIFKEY [] {KSTATUS};

		// inits
		propertyValue = null;
		curPoints = new ArrayList<Point2D>();
		saveTextPoints = new ArrayList<Point2D>();

		// parser inits
		curKeyword = KINIT;
		inputBuffer = "";
		inputBufferPos = 0;
		errorCount = warningCount = 0;
		ignoreBlock = ignoreHigherBlock = false;
		curVendor = EVUNKNOWN;
		builtCells = new HashSet<Cell>();

		// general inits
		curLibrary = lib;
		curTech = tech;
		cellTable = new HashMap<String,NameEntry>();
		propertiesList = new ArrayList<EDIFProperty>();
		
		// active database inits
		curCell = null;
		this.currentCells = currentCells;
		curCellPage = 0;
		curNode = null;
		curArc = null;
		curPort = null;
		curFigureGroup = null;
		cellRefProto = null;

		// name inits
		cellReference = "";
		portReference = "";
		instanceReference = "";
		bundleReference = "";
		netReference = "";
		propertyReference = "";

		// geometry inits
		activeFigures = new ArrayList<NameEntry>();
		curActiveFigure = null;
		freePointList();

		// text inits
		textHeight = 0;
		textJustification = TextDescriptor.Position.DOWNRIGHT;
		freeSavedPointList();

		sheetXPos = sheetYPos = -1;

		keyStackDepth = 0;

		defaultPort = Schematics.tech().wirePinNode.findPortProto("wire");
		defaultBusPort = Schematics.tech().busPinNode.findPortProto("bus");

		equivs = new EDIFEquiv(localPrefs.configurationFile);
		netPortRefs = new ArrayList<PortInst>();
		renamedObjects = new HashMap<String,String>();

		// parse the file
		try
		{
			loadEDIF();
		} catch (IOException e)
		{
			System.out.println("line " + lineReader.getLineNumber() + ": " + e.getMessage());
			return null;
		}

		if (errorCount != 0 || warningCount != 0)
			System.out.println("A total of " + errorCount + " errors, and " + warningCount + " warnings encountered during load");

		if (curLibrary != null && !currentCells.containsKey(curLibrary) && curLibrary.getCells().hasNext())
			currentCells.put(curLibrary, curLibrary.getCells().next());
		return curLibrary;
	}

	/**
	 * Method to load the edif netlist into memory
	 * Does a simple keyword lookup to load the lisp structured
	 * EDIF language into Electric's database
	 */
	private void loadEDIF()
		throws IOException
	{

		// now read and parse the edif netlist
		for(;;)
		{
			String token = getKeyword();
			if (token == null) break;

			// locate the keyword
			EDIFKEY key = edifKeys.get(TextUtils.canonicString(token));
			if (key == null)
			{
				System.out.println("Warning, line " + lineReader.getLineNumber() + ": unknown keyword <" + token + ">");
				warningCount++;
				keyStack[keyStackDepth++] = curKeyword;
				curKeyword = KUNKNOWN;
				continue;
			}

			// found the keyword, check state
			if (key.stateArray != null && curKeyword != KUNKNOWN)
			{
				boolean found = false;
				for(int i=0; i<key.stateArray.length; i++)
					if (key.stateArray[i] == curKeyword) { found = true;   break; }
				if (!found)
				{
					System.out.println("Error, line " + lineReader.getLineNumber() + ": illegal state (" + curKeyword.name + ") for keyword <" + token + ">");
					errorCount++;
				}
			}

			// call the function
			keyStack[keyStackDepth++] = curKeyword;
			curKeyword = key;
			if (saveStack >= keyStackDepth)
			{
				saveStack = -1;
				ignoreBlock = false;
			}
			if (!ignoreBlock)
			{
				key.push();
				if (ignoreHigherBlock)
				{
					ignoreHigherBlock = false;
					ignoreBlock = true;
					saveStack = keyStackDepth-1;
				}
			}
			if (ignoreBlock)
			{
				if (saveStack == -1) saveStack = keyStackDepth;
			}
		}
		if (curKeyword != KINIT)
		{
			System.out.println("Error, line " + lineReader.getLineNumber() + ": unexpected end-of-file encountered");
			errorCount++;
		}
		cleanupAtEnd();
	}

	/**
	 * Method to get a keyword.
	 */
	private String getKeyword()
		throws IOException
	{
		// look for a '(' before the edif keyword
		for(;;)
		{
			String p = getToken((char)0);
			if (p == null) break;
			if (p.equals("(")) break;
			if (p.equals(")"))
			{
				// pop the keyword state stack, called when ')' is encountered.
				if (keyStackDepth != 0)
				{
					if (!ignoreBlock)
					{
						curKeyword.pop();
					}
					if (keyStackDepth != 0) curKeyword = keyStack[--keyStackDepth]; else
						curKeyword = KINIT;
					if (saveStack > keyStackDepth)
					{
						saveStack = -1;
						ignoreBlock = false;
					}
				}
			} else
			{
				if (TextUtils.isANumber(p)) processInteger(TextUtils.atoi(p));
			}
		}
		return getToken((char)0);
	}

	private void cleanupAtEnd()
	{
		// clean-up schematic cells with isolated pins
		List<Cell> cellsToStitch = new ArrayList<Cell>();
		for(Cell cell : builtCells)
		{			
			if (cell.isSchematic())
			{
				List<NodeInst> deletePins = new ArrayList<NodeInst>();
				for(Iterator<NodeInst> it = cell.getNodes(); it.hasNext(); )
				{
					NodeInst ni = it.next();
					if (ni.getProto() == Schematics.tech().wirePinNode)
					{
						if (!ni.hasConnections() && !ni.hasExports())
							deletePins.add(ni);
					}
				}
				for(NodeInst ni : deletePins)
					ni.kill();

				// now auto-route the schematic to connect everything
				cellsToStitch.add(cell);
			}
		}
		if (cellsToStitch.size() > 0 && localPrefs.importStitchesCells)
		{
			for(int i=0; i<cellsToStitch.size();  i++)
			{
				Cell cell = cellsToStitch.get(i);
				Job.getUserInterface().setProgressNote("Stitching " + cell.noLibDescribe() + "(" + (i+1) + " of " + cellsToStitch.size() + ")");
				Job.getUserInterface().setProgressValue(i * 100 / cellsToStitch.size());
				if (cell.isLinked())
					AutoStitch.runAutoStitch(cell, null, null, job, null, null, false, false, ep, localPrefs.autoParameters, false, null);
			}
			Job.getUserInterface().setProgressValue(0);
			System.out.println("Stitched " + cellsToStitch.size() + " cells");
		}
		
		// put names on named arcs
		for(Cell cell : namedArcs.keySet())
		{
			Netlist nl = cell.getNetlist();
			Map<String,List<ArcInst>> arcsInCell = namedArcs.get(cell);
			Map<ArcInst,String> arcsToName = new HashMap<ArcInst,String>();
			for(String name : arcsInCell.keySet())
			{
				List<ArcInst> arcsWithName = arcsInCell.get(name);
				Collections.sort(arcsWithName, new ArcInst.ArcsByLength());
				Set<Network> netsNamed = new HashSet<Network>();
				for(int i=0; i<arcsWithName.size(); i++)
				{
					ArcInst ai = arcsWithName.get(i);
					if (!ai.isLinked()) continue;
					Network net = nl.getNetwork(ai, 0);
					if (netsNamed.contains(net)) continue;
					netsNamed.add(net);
					arcsToName.put(ai, name);
				}
			}
			for(ArcInst ai : arcsToName.keySet())
			{
				String arcName = arcsToName.get(ai);
				if (ai.getName().equals(arcName)) continue;
				if (ai.getNameKey().isTempname())
				{
					ai.setName(arcName, ep);
					if (!localPrefs.showArcNames)
					{
						TextDescriptor td = ai.getTextDescriptor(ArcInst.ARC_NAME);
						td = td.withDisplay(TextDescriptor.Display.NONE);
						ai.setTextDescriptor(ArcInst.ARC_NAME, td);
					}
				} else
				{
					// two names on one arc: duplicate the arc
					ArcInst second = ArcInst.newInstanceBase(ai.getProto(), ep, ai.getLambdaBaseWidth(),
						ai.getHeadPortInst(), ai.getTailPortInst(), ai.getHeadLocation(), ai.getTailLocation(),
						arcName, ai.getAngle(), ai.getD().flags);
					TextDescriptor td = ep.getArcTextDescriptor().withOff(0, -1);
					if (!localPrefs.showArcNames)
						td = td.withDisplay(TextDescriptor.Display.NONE);
					second.setTextDescriptor(ArcInst.ARC_NAME, td);
				}
			}
		}
	}

	/**
	 * Method to do cleanup processing of an integer argument such as in (array (...) 1 2)
	 */
	private void processInteger(int value)
	{
		if (keyStackDepth > 0)
		{
			if (!ignoreBlock)
			{
				if (curKeyword == KARRAY)
				{
					if (arrayXVal == 0) arrayXVal = value;
						else if (arrayYVal == 0) arrayYVal = value;
				} else if (curKeyword == KMEMBER)
				{
					if (memberXVal == -1) memberXVal = value;
						else if (memberYVal == -1) memberYVal = value;
				}
			}
		}
	}

	/**************************************** SUPPORT ****************************************/

	/**
	 * Method to position to the next token
	 * @return true on EOF.
	 */
	private boolean positionToNextToken()
		throws IOException
	{
		for(;;)
		{
			if (inputBuffer == null) return true;
			if (inputBufferPos >= inputBuffer.length())
			{
				inputBuffer = readWholeLine();
				if (inputBuffer == null) return true;
				inputBufferPos = 0;
				continue;
			}
			char chr = inputBuffer.charAt(inputBufferPos);
			if (chr == ' ' || chr == '\t')
			{
				inputBufferPos++;
				continue;
			}
			return false;
		}
	}

	/**
	 * Method to get a delimeter routine
	 */
	private void getDelimeter(char delim)
		throws IOException
	{
		if (positionToNextToken())
		{
			throw new IOException("Unexpected end-of-file");
		}
		char chr = inputBuffer.charAt(inputBufferPos);
		if (chr != delim)
		{
			throw new IOException("Illegal delimeter");
		}
		inputBufferPos++;
	}

	/**
	 * Method to get a token
	 */
	private String getToken(char iDelim)
		throws IOException
	{
		// locate the first non-white space character for non-delimited searches
		char delim = iDelim;
		if (delim == 0)
		{
			if (positionToNextToken()) return null;
		}

		// set up the string copy
		StringBuffer sBuf = new StringBuffer();
		char chr = inputBuffer.charAt(inputBufferPos);
		if (delim == 0 && chr == '"')
		{
			// string search
			delim = '"';
			sBuf.append(chr);
			inputBufferPos++;
		}

		// now locate the next white space or the delimiter
		for(;;)
		{
			if (inputBufferPos >= inputBuffer.length())
			{
				// end of string, get the next line
				inputBuffer = readWholeLine();
				if (inputBuffer == null)
				{
					// end-of-file, if no delimiter return NULL, otherwise the string
					if (delim != 0) break;
					return sBuf.toString();
				}
				inputBufferPos = 0;
			} else if (delim == 0)
			{
				chr = inputBuffer.charAt(inputBufferPos);
				switch (chr)
				{
					case '#':
						// comment -> skip next line
						inputBuffer = readWholeLine();
						inputBufferPos = 0;
						chr = inputBuffer.charAt(inputBufferPos);
					break;
					case ' ':
					case '\t':
						// skip to the next character
						inputBufferPos++;
						return sBuf.toString();
						// special EDIF delimiters
					case '(':
					case ')':
						if (sBuf.length() == 0)
						{
							sBuf.append(chr);
							inputBufferPos++;
						}
						return sBuf.toString();
					default:
						sBuf.append(chr);
						inputBufferPos++;
						break;
				}
			} else
			{
				// check for user specified delimiter
				chr = inputBuffer.charAt(inputBufferPos);
				if (chr == delim)
				{
					// skip the delimiter, unless string
					if (delim != iDelim) sBuf.append(chr);
					inputBufferPos++;
					return sBuf.toString();
				}
				sBuf.append(chr);
				inputBufferPos++;
			}
		}
		return null;
	}

	private void makeFigure()
		throws IOException
	{
		// get the layer name; check for figuregroup override
		if (positionToNextToken()) return;
		if (inputBuffer.charAt(inputBufferPos) == '(') return;
		String layer = getToken((char)0);

		// now look for this layer in the list of layers
		for(NameEntry nt : activeFigures)
		{
			if (nt.original.equalsIgnoreCase(layer))
			{
				// found the layer
				curFigureGroup = nt.node;
				textHeight = nt.textHeight;
				textJustification = nt.justification;

				// allow new definitions
				curActiveFigure = nt;
				return;
			}
		}

		NameEntry nt = new NameEntry();
		activeFigures.add(nt);
		nt.original = layer;
		nt.replace = nt.original;
		nt.node = Artwork.tech().boxNode;
		curFigureGroup = nt.node;
		textHeight = nt.textHeight = 0;
		textJustification = nt.justification = TextDescriptor.Position.DOWNRIGHT;

		// allow new definitions
		curActiveFigure = nt;
	}

	/**
	 * Method to determine whether a name has multiple signals in it.
	 * @param name the name to check.
	 * @return true if it is a bus name.
	 */
	private boolean isBusName(String name)
	{
		if (name == null || name.length() <= 0) return false;
		Name nn = Name.findName(name);
		if (nn == null) return false;
		return nn.isBus();
	}

	private boolean checkName()
		throws IOException
	{
		positionToNextToken();
		char chr = inputBuffer.charAt(inputBufferPos);
		if (chr != '(' && chr != ')')
		{
			String aName = getToken((char)0);
			objectName = fixLeadingAmpersand(aName);
			return true;
		}
		return false;
	}

	private void freePointList()
	{
		curPoints = new ArrayList<Point2D>();
	}

	private void freeSavedPointList()
	{
		saveTextPoints = new ArrayList<Point2D>();
	}

	private double getNumber()
		throws IOException
	{
		String value = getToken((char)0);
		if (value == null) throw new IOException("No integer value");

		if (value.startsWith("("))
		{
			// must be in e notation
			value = getToken((char)0);
			if (value == null) throw new IOException("Illegal number value");
			if (!value.equalsIgnoreCase("e")) throw new IOException("Illegal number value");

			// now the matissa
			value = getToken((char)0);
			if (value == null) throw new IOException("No matissa value");
			double matissa = TextUtils.atof(value);

			// now the exponent
			value = getToken((char)0);
			if (value == null) throw new IOException("No exponent value");
			double exponent = TextUtils.atof(value);
			getDelimeter(')');
			return matissa * Math.pow(10.0, exponent);
		}
		return TextUtils.atof(value);
	}

	private NodeInst placePin(NodeProto np, double cX, double cY, double sX, double sY, Orientation orient, Cell parent)
	{
		for(Iterator<NodeInst> it = parent.getNodes(); it.hasNext(); )
		{
			NodeInst ni = it.next();
			if (ni.getProto() != np) continue;
			if (!ni.getOrient().equals(orient)) continue;
			if (ni.getAnchorCenterX() != cX) continue;
			if (ni.getAnchorCenterY() != cY) continue;
			return ni;
		}
		NodeInst ni = NodeInst.makeInstance(np, ep, new Point2D.Double(cX, cY), sX, sY, parent, orient, null);
		return ni;
	}

	/**
	 * Method to return the text height to use when it says "textheight" units in the EDIF file.
	 */
	private double convertTextSize(double textHeight)
	{
		double i = textHeight / 4;
		if (i < TextDescriptor.Size.TXTMINQGRID) i = TextDescriptor.Size.TXTMINQGRID;
		if (i > TextDescriptor.Size.TXTMAXQGRID) i = TextDescriptor.Size.TXTMAXQGRID;
		return i;
	}

	private void makeArc(double [] x, double [] y)
	{
		if (curCell == null) return;

		MutableDouble [] A = new MutableDouble[2];
		MutableDouble [] B = new MutableDouble[2];
		MutableDouble [] C = new MutableDouble[2];
		for(int i=0; i<2; i++)
		{
			A[i] = new MutableDouble(0);
			B[i] = new MutableDouble(0);
			C[i] = new MutableDouble(0);
		}

		// get line equations of perpendicular bi-sectors of p1 to p2
		double px1 = (x[0] + x[1]) / 2.0;
		double py1 = (y[0] + y[1]) / 2.0;

		// now rotate end point 90 degrees
		double px0 = px1 - (y[0] - py1);
		double py0 = py1 + (x[0] - px1);
		equationOfALine(px0, py0, px1, py1, A[0], B[0], C[0]);

		// get line equations of perpendicular bi-sectors of p2 to p3
		px1 = (x[2] + x[1]) / 2.0;
		py1 = (y[2] + y[1]) / 2.0;

		// now rotate end point 90 degrees
		double px2 = px1 - (y[2] - py1);
		double py2 = py1 + (x[2] - px1);
		equationOfALine(px1, py1, px2, py2, A[1], B[1], C[1]);

		// determine the point of intersection
		Point2D ctr = determineIntersection(A, B, C);

		double ixc = ctr.getX();
		double iyc = ctr.getY();

		double dx = ixc - x[0];
		double dy = iyc - y[0];
		double r = Math.hypot(dx, dy);

		// now calculate the angle to the start and endpoint
		dx = x[0] - ixc;  dy = y[0] - iyc;
		if (dx == 0.0 && dy == 0.0)
		{
			System.out.println("Domain error doing arc computation");
			return;
		}
		double a0 = Math.atan2(dy, dx) * 1800.0 / Math.PI;
		if (a0 < 0.0) a0 += 3600.0;

		dx = x[2] - ixc;  dy = y[2] - iyc;
		if (dx == 0.0 && dy == 0.0)
		{
			System.out.println("Domain error doing arc computation");
			return;
		}
		double a2 = Math.atan2(dy, dx) * 1800.0 / Math.PI;
		if (a2 < 0.0) a2 += 3600.0;

		// determine the angle of rotation, object orientation, and direction
		// calculate x1*y2 + x2*y3 + x3*y1 - y1*x2 - y2*x3 - y3*x1
		double area = x[0]*y[1] + x[1]*y[2] + x[2]*y[0] - y[0]*x[1] - y[1]*x[2] - y[2]*x[0];
		double ar = 0, so = 0;
		int rot = 0;
		boolean trans = false;
		if (area > 0.0)
		{
			// counter clockwise
			if (a2 < a0) a2 += 3600.0;
			ar = a2 - a0;
			rot = (int)a0;
			so = (a0 - rot) * Math.PI / 1800.0;
		} else
		{
			// clockwise
			if (a0 > 2700)
			{
				rot = 3600 - (int)a0 + 2700;
				so = ((3600.0 - a0 + 2700.0) - rot) * Math.PI / 1800.0;
			} else
			{
				rot = 2700 - (int)a0;
				so = ((2700.0 - a0) - rot) * Math.PI / 1800.0;
			}
			if (a0 < a2) a0 += 3600;
			ar = a0 - a2;
			trans = true;
		}

		// get the bounds of the circle
		double sX = r*2;
		double sY = r*2;
		Orientation or = Orientation.fromC(rot, trans);
		if (curCellPage > 0) iyc += (curCellPage-1) * Cell.FrameDescription.MULTIPAGESEPARATION;
		NodeInst ni = NodeInst.makeInstance(curFigureGroup != null && curFigureGroup != Artwork.tech().boxNode ?
			curFigureGroup : Artwork.tech().circleNode, ep, new Point2D.Double(ixc, iyc), sX, sY, curCell, or, null);
		if (ni == null)
		{
			System.out.println("Error, line " + lineReader.getLineNumber() + ": could not create arc");
			errorCount++;
		} else
		{
			// store the angle of the arc
			ni.setArcDegrees(so, ar*Math.PI/1800.0, ep);
		}
	}

	private String convertParens(String name)
	{
		return name.replace('(', '[').replace(')', ']');
	}

	/**
	 * Method to determine the intersection of two lines
	 * Inputs: Ax + By + C = 0 form
	 * Outputs: x, y - the point of intersection
	 * returns 1 if found, 0 if non-intersecting
	 */
	private Point2D determineIntersection(MutableDouble [] A, MutableDouble [] B, MutableDouble [] C)
	{
		// check for parallel lines
		double A1B2 = A[0].doubleValue() * B[1].doubleValue();
		double A2B1 = A[1].doubleValue() * B[0].doubleValue();
		if (A1B2 == A2B1)
		{
			// check for coincident lines
			if (C[0].doubleValue() == C[1].doubleValue()) return null;
			return null;
		}
		double A1C2 = A[0].doubleValue() * C[1].doubleValue();
		double A2C1 = A[1].doubleValue() * C[0].doubleValue();

		if (A[0].doubleValue() != 0)
		{
			double Y = (A2C1 - A1C2) / (A1B2 - A2B1);
			double X = -(B[0].doubleValue() * Y + C[0].doubleValue()) / A[0].doubleValue();
			return new Point2D.Double(X, Y);
		}
		double Y = (A1C2 - A2C1) / (A2B1 - A1B2);
		double X = -(B[1].doubleValue() * Y + C[1].doubleValue()) / A[1].doubleValue();
		return new Point2D.Double(X, Y);
	}

	/**
	 * Method to calculate the equation of a line (Ax + By + C = 0)
	 * Inputs:  sx, sy  - Start point
	 *          ex, ey  - End point
	 *          px, py  - Point line should pass through
	 * Outputs: A, B, C - constants for the line
	 */
	private void equationOfALine(double sx, double sy, double ex, double ey, MutableDouble A, MutableDouble B, MutableDouble C)
	{
		if (sx == ex)
		{
			A.setValue(1.0);
			B.setValue(0.0);
			C.setValue(-ex);
		} else if (sy == ey)
		{
			A.setValue(0.0);
			B.setValue(1.0);
			C.setValue(-ey);
		} else
		{
			// let B = 1 then
			B.setValue(1.0);
			if (sx != 0.0)
			{
				// Ax1 + y1 + C = 0    =>    A = -(C + y1) / x1
				// C = -Ax2 - y2       =>    C = (Cx2 + y1x2) / x1 - y2   =>   Cx1 - Cx2 = y1x2 - y2x1
				// C = (y2x1 - y1x2) / (x2 - x1)
				C.setValue((ey * sx - sy * ex) / (ex - sx));
				A.setValue(-(C.doubleValue() + sy) / sx);
			} else
			{
				C.setValue((sy * ex - ey * sx) / (sx - ex));
				A.setValue(-(C.doubleValue() + ey) / ex);
			}
		}
	}

	private void nameEDIFArc(ArcInst ai, boolean forceBus)
	{
		if (isArray)
		{
			// name the bus
			if (netReference.length() > 0)
			{
				ai.newVar("EDIF_name", stripPercentEscapes(netReference), ep);
			}

			StringBuffer aName = new StringBuffer();
			if (netName.indexOf('[') >= 0)
			{
				// the name is already indexed: break it apart and reindex it
				int xMost = arrayXVal, yMost = arrayYVal;
				if (yMost > xMost) { xMost = arrayYVal; yMost = arrayXVal; }
				for (int iX = 0; iX < xMost; iX++)
				{
					for (int iY = 0; iY < yMost; iY++)
					{
						if (aName.length() > 0) aName.append(',');
						aName.append(getMemberName(netName, iX, iY));
					}
				}
			} else
			{
				if (forceBus)
				{
					// a typical foreign array bus will have some range value appended
					// to the name. This will cause some odd names with duplicate values
					for (int iX = 0; iX < arrayXVal; iX++)
					{
						for (int iY = 0; iY < arrayYVal; iY++)
						{
							if (aName.length() > 0) aName.append(',');
							if (arrayXVal > 1)
							{
								if (arrayYVal > 1)
									aName.append(netName + "[" + iX + "," + iY + "]");
								else
									aName.append(netName + "[" + iX + "]");
							}
							else aName.append(netName + "[" + iY + "]");
						}
					}
				} else
				{
					aName.append(netName);
				}
			}
			putNameOnArcOnce(ai, convertParens(aName.toString()));
		} else
		{
			if (netReference.length() > 0)
				ai.newVar("EDIF_name", stripPercentEscapes(netName), ep);
			if (netName.length() > 0)
			{
				// set name of arc but don't display name
				String arcName = convertParens(netName).replaceAll("@", "_");
				putNameOnArcOnce(ai, arcName);
			}
		}
	}

	private void putNameOnArcOnce(ArcInst ai, String name)
	{
		if (namedArcs != null && curCell != null)
		{
			Map<String,List<ArcInst>> cellArcNames = namedArcs.get(curCell);
			if (cellArcNames == null)
			{
				cellArcNames = new HashMap<String,List<ArcInst>>();
				namedArcs.put(curCell, cellArcNames);
			}
			List<ArcInst> arcsWithName = cellArcNames.get(name);
			if (arcsWithName == null)
			{
				arcsWithName = new ArrayList<ArcInst>();
				cellArcNames.put(name, arcsWithName);
			}
			arcsWithName.add(ai);
		}
	}

	private void putNameOnNode(NodeInst ni, String name)
	{
		ni.setName(convertParens(name));
		if (!localPrefs.showNodeNames)
		{
			TextDescriptor td = ni.getTextDescriptor(NodeInst.NODE_NAME);
			td = td.withDisplay(TextDescriptor.Display.NONE);
			ni.setTextDescriptor(NodeInst.NODE_NAME, td);
		}
	}

	/**
	 * Method to locate the nodeinstance and portproto corresponding to
	 * to a direct intersection with the given point.
	 * inputs:
	 * cell - cell to search
	 * x, y  - the point to exam
	 * ap    - the arc used to connect port (must match pp)
	 */
	private List<PortInst> findEDIFPort(Cell cell, double x, double y, ArcProto ap)
	{
		List<PortInst> ports = new ArrayList<PortInst>();
		List<PortInst> closePorts = new ArrayList<PortInst>();
		PortInst bestPi = null;
		Point2D pt = new Point2D.Double(x, y);
		double bestDist = Double.MAX_VALUE;
		ArcInst ai = null;
		for(Iterator<Geometric> sea = cell.searchIterator(new Rectangle2D.Double(x, y, 0, 0)); sea.hasNext(); )
		{
			Geometric geom = sea.next();

			if (geom instanceof NodeInst)
			{
				// now locate a portproto
				NodeInst ni = (NodeInst)geom;
				for(Iterator<PortInst> it = ni.getPortInsts(); it.hasNext(); )
				{
					PortInst pi = it.next();
					Poly poly = pi.getPoly();
					if (poly.isInside(pt))
					{
						// check if port connects to arc ...*/
						if (pi.getPortProto().getBasePort().connectsTo(ap))
							ports.add(pi);
					} else
					{
						// see if there is a port size specified
						if (ni.isCellInstance())
						{
							Export pp = (Export)pi.getPortProto();
							NodeInst subNode = pp.getOriginalPort().getNodeInst();
							Variable var = subNode.getVar(PORT_SIZE);
							if (var != null)
							{
								String sizeStr = (String)var.getObject();
								int xPos = sizeStr.indexOf('x');
								if (xPos >= 0)
								{
									double wid = TextUtils.atof(sizeStr.substring(0, xPos));
									double hei = TextUtils.atof(sizeStr.substring(xPos+1));
									EPoint ctr = pi.getCenter();
									Rectangle2D rect = new Rectangle2D.Double(ctr.getX()-wid/2, ctr.getY()-hei/2, wid, hei);
									if (GenMath.containedInRectangle(rect, pt)) closePorts.add(pi);
								}
							}
						}
						double dist = pt.distance(new Point2D.Double(poly.getCenterX(), poly.getCenterY()));
						if (bestPi == null || dist < bestDist) { bestDist = dist;   bestPi = pi; }
					}
				}
			} else
			{
				// only accept valid wires
				ArcInst oAi = (ArcInst)geom;
				if (oAi.getProto().getTechnology() == Schematics.tech())
				{
					// make sure the point is really on the arc
					if (GenMath.distToLine(oAi.getHeadLocation(), oAi.getTailLocation(), pt) == 0)
						ai = oAi;
				}
			}
		}

		if (ports.size() == 0 && closePorts.size() > 0) return closePorts;
		if (ports.size() == 0 && ai != null)
		{
			// direct hit on an arc, verify connection
			ArcProto nAp = ai.getProto();
			PrimitiveNode np = nAp.findPinProto();
			if (np == null) return ports;
			PortProto pp = np.getPort(0);
			if (!pp.getBasePort().connectsTo(ap)) return ports;

			// try to split arc (from us_getnodeonarcinst)*/
			// break is at (prefx, prefy): save information about the arcinst
			PortInst fPi = ai.getHeadPortInst();
			Point2D fPt = ai.getHeadLocation();
			PortInst tPi = ai.getTailPortInst();
			Point2D tPt = ai.getTailLocation();

			// create the splitting pin
			NodeInst ni = placePin(np, x, y, np.getDefWidth(ep), np.getDefHeight(ep), Orientation.IDENT, cell);
			if (ni == null)
			{
				System.out.println("Cannot create splitting pin");
				return ports;
			}

			// set the node, and port
			PortInst pi = ni.findPortInstFromProto(pp);
			ports.add(pi);

			// create the two new arcinsts
			ArcInst ar1 = ArcInst.makeInstance(nAp, ep, fPi, pi, fPt, pt, null);
			ArcInst ar2 = ArcInst.makeInstance(nAp, ep, pi, tPi, pt, tPt, null);
			if (ar1 == null || ar2 == null)
			{
				System.out.println("Error creating the split arc parts");
				return ports;
			}
			ar1.copyPropertiesFrom(ai);
			if (ai.isHeadNegated()) ar1.setHeadNegated(true);
			if (ai.isTailNegated()) ar2.setTailNegated(true);

			// delete the old arcinst
			ai.kill();
		}
		return ports;
	}

	private void checkBusNames(ArcInst ai, String base, HashSet<ArcInst> seenArcs)
	{
		if (ai.getProto() != Schematics.tech().bus_arc)
		{
			// verify the name
			String aName = ai.getName();
			{
				if (aName.startsWith(base))
				{
					if (aName.length() > base.length() && TextUtils.isDigit(aName.charAt(base.length())))
					{
						String newName = base + "[" + aName.substring(base.length()) + "]";
						putNameOnArcOnce(ai, newName);
					}
				}
			}
		}
		seenArcs.add(ai);
		for (int i = 0; i < 2; i++)
		{
			NodeInst ni = ai.getPortInst(i).getNodeInst();
			if (ni.getFunction().isPin())
			{
				// scan through this nodes portarcinst's
				for(Iterator<Connection> it = ni.getConnections(); it.hasNext(); )
				{
					Connection oCon = it.next();
					ArcInst pAi = oCon.getArc();
					if (!seenArcs.contains(pAi))
						checkBusNames(pAi, base, seenArcs);
				}
			}
		}
	}

	private void doPoly()
	{
		if (curPoints.size() == 0) return;
		if (curCell == null) return;

		// get the bounds of the poly
		Point2D p0 = curPoints.get(0);
		double lX = p0.getX();
		double lY = p0.getY();
		double hX = lX;
		double hY = lY;
		for(int i=1; i<curPoints.size(); i++)
		{
			Point2D point = curPoints.get(i);
			if (lX > point.getX()) lX = point.getX();
			if (hX < point.getX()) hX = point.getX();
			if (lY > point.getY()) lY = point.getY();
			if (hY < point.getY()) hY = point.getY();
		}

		NodeProto np = curFigureGroup;
		if (curFigureGroup == null || curFigureGroup == Artwork.tech().boxNode)
		{
			if (curKeyword == KPOLYGON) np = Artwork.tech().closedPolygonNode; else
				np = Artwork.tech().openedPolygonNode;
		}
		double sX = hX - lX;
		double sY = hY - lY;
		double cX = (hX + lX) / 2;
		double cY = (hY + lY) / 2;
		double yPos = cY;
		if (curCellPage > 0) yPos += (curCellPage-1) * Cell.FrameDescription.MULTIPAGESEPARATION;
		NodeInst ni = NodeInst.makeInstance(np, ep, new Point2D.Double(cX, yPos), sX, sY,
			curCell, curOrientation, null);
		if (ni == null)
		{
			System.out.println("Error, line " + lineReader.getLineNumber() + ": could not create " + sX + "x" + sY +
				" node " + np.describe(false) + " at (" + cX + "," + yPos + ")");
			errorCount++;
		} else
		{
			EPoint [] trace = new EPoint[curPoints.size()];
			for(int i=0; i<curPoints.size(); i++)
			{
				Point2D point = curPoints.get(i);
				trace[i] = EPoint.fromLambda(point.getX(), point.getY());
			}

			// store the trace information
			ni.setTrace(trace);
		}
		freePointList();
	}

	/**
	 * Method to fix symbols that start with &<number> by removing the ampersand.
	 * @param inStr the original symbol.
	 * @return the fixed symbol.
	 */
	private String fixLeadingAmpersand(String inStr)
	{
		if (inStr.startsWith("&"))
		{
			if (inStr.length() > 1 && Character.isDigit(inStr.charAt(1)))
				return inStr.substring(1);
		}
		return inStr;
	}

	private String fixAngleBracketBusses(String busName)
	{
		for(;;)
		{
			int openAngle = busName.indexOf('<');
			if (openAngle < 0) break;

			int closeAngle = busName.indexOf('>', openAngle);
			if (closeAngle < 0) break;

			String busPart = busName.substring(openAngle+1, closeAngle);
			if (busPart.startsWith("*")) busPart = ""; else
				busPart = "[" + busPart + "]";
			busName = busName.substring(0, openAngle) + busPart + busName.substring(closeAngle+1);
		}
		return busName;
	}

	/**
	 * Method to convert a bus name to an individual member name.
	 * @param name the bus name.
	 * @param ind1 the index of the first entry.
	 * @param ind2 the index of the second entry.
	 * @return the specified member name.
	 */
	private String getMemberName(String name, int ind1, int ind2)
	{
		// get true name of array specification
		String baseName = renamedObjects.get(name);
		if (baseName == null) baseName = name;

		// find first index entry
		int openSq = baseName.indexOf('[');
		if (openSq < 0) return baseName;
		int closeSq = baseName.indexOf(']', openSq);
		if (closeSq < 0) closeSq = baseName.length();
		int comma = baseName.indexOf(',', openSq);
		if (comma < 0) comma = baseName.length();
		int endInd1 = Math.min(closeSq, comma);
		if (endInd1 < 0) return baseName;
		String index1 = baseName.substring(openSq+1, endInd1);
		String index2 = null;
		String restOfLine = baseName.substring(endInd1);

		if (comma >= 0 && comma < baseName.length())
		{
			closeSq = baseName.indexOf(']', comma);
			if (closeSq >= 0)
			{
				index2 = baseName.substring(comma+1, closeSq);
				restOfLine = baseName.substring(closeSq);
			}
		}

		int [] indices = getIndices(index1);
		if (ind1 >= 0 && ind1 < indices.length)
			baseName = baseName.substring(0, openSq+1) + indices[ind1];
		if (index2 != null)
		{
			int [] indices2 = getIndices(index2);
			if (ind2 >= 0 && ind2 < indices2.length)
				baseName += "," + indices2[ind2];
		}
		baseName += restOfLine;
		return baseName;
	}

	/**
	 * Method to examine an array specification and return all of the indices.
	 * @param index the array specification, for example "1:5" or "4:2".
	 * @return an array of indices, for example [1,2,3,4,5] or [4,3,2].
	 */
	private int [] getIndices(String index)
	{
		List<Integer> indices = new ArrayList<Integer>();
		for(int pos=0; pos<index.length(); )
		{
			if (Character.isWhitespace(index.charAt(pos))) { pos++;   continue; }
			if (Character.isDigit(index.charAt(pos)))
			{
				int val = TextUtils.atoi(index.substring(pos));
				int valEnd = val;
				while (pos < index.length() && Character.isDigit(index.charAt(pos))) pos++;
				while (pos < index.length() && Character.isWhitespace(index.charAt(pos))) pos++;
				if (pos < index.length() && index.charAt(pos) == ':')
				{
					pos++;
					while (pos < index.length() && Character.isWhitespace(index.charAt(pos))) pos++;
					if (Character.isDigit(index.charAt(pos)))
					{
						valEnd = TextUtils.atoi(index.substring(pos));
						while (pos < index.length() && Character.isDigit(index.charAt(pos))) pos++;
						while (pos < index.length() && Character.isWhitespace(index.charAt(pos))) pos++;
					}
				}
				if (valEnd < val)
				{
					for(int i=val; i>=valEnd; i--) indices.add(Integer.valueOf(i));
				} else
				{
					for(int i=val; i<=valEnd; i++) indices.add(Integer.valueOf(i));
				}
				continue;
			}
			break;
		}
		int [] retInd = new int[indices.size()];
		for(int i=0; i<indices.size(); i++) retInd[i] = indices.get(i).intValue();
		return retInd;
	}

	/**
	 * Method to find an existing cell.
	 * @param libName the name of the library in which the cell resides
	 * (null to use the current library).
	 * @param name the name of the cell.
	 * @param view the view name of the cell.
	 * @return the cell (null if it does not exist).
	 */
	private Cell findCell(String libName, String name, String view)
	{
		Library lib = curLibrary;
		if (libName != null)
		{
			Library namedLib = Library.findLibrary(libName);
			if (namedLib != null) lib = namedLib;
		}
		for(Iterator<Cell> it = lib.getCells(); it.hasNext(); )
		{
			Cell cell = it.next();
			if (cell.getName().equalsIgnoreCase(name) &&
				cell.getView().getAbbreviation().equalsIgnoreCase(view)) return cell;
		}
		return null;
	}

	/**
	 * Method to create a new cell.
	 * @param libName the name of the library in which the cell will be created
	 * (null to use the current library).
	 * @param name the name of the cell.
	 * @param view the view name to create.
	 * @return the newly created cell (or existing one if allowed).
	 */
	private Cell createCell(String libName, String name, String view)
	{
		Cell proto = findCell(libName, name, view);
		if (proto != null) return proto;

		Library lib = curLibrary;
		if (libName != null)
		{
			Library namedLib = Library.findLibrary(libName);
			if (namedLib != null) lib = namedLib;
		}

		String cName = name;
		if (view.length() > 0) cName += "{" + view + "}";
		proto = Cell.makeInstance(ep, lib, cName);
		if (proto != null)
		{
			if (view.equals("ic")) proto.setWantExpanded();
			builtCells.add(proto);
		}
		return proto;
	}

	private void instantiatePlannedPort(PlannedPort plp, Rectangle2D cellBounds)
	{
		if (plp.createdPort != null) return;
		if (curCell == null) return;
		PortInst pi = null;
		if (plp.alreadyThere == null)
		{
			// now create the off-page reference
			if (!plp.knownLocation)
			{
				if (plp.direction == PortCharacteristic.IN || plp.direction == PortCharacteristic.BIDIR)
				{
					plp.x = cellBounds.getMinX() - INCH/2;
					plp.y = cellBounds.getMinY() + inputLocY * INCH/2;
					inputLocY++;
				} else
				{
					plp.x = cellBounds.getMaxX() + INCH/2;
					plp.y = cellBounds.getMinY() + outputLocY * INCH/2;
					outputLocY++;
				}
				plp.knownLocation = true;
			}

			NodeProto type = plp.pinType;
			if (type == Schematics.tech().offpageNode)
			{
				if (curCell.getView() == View.LAYOUT)
					type = curTech.getNodes().next();
			}
			if (type == Artwork.tech().boxNode)
			{
				if (curCell.getView() == View.SCHEMATIC) type = Schematics.tech().offpageNode; else
				if (curCell.getView() == View.ICON) type = Schematics.tech().busPinNode;
			}
			List<PortInst> tList = findEDIFPort(curCell, plp.x, plp.y, type.getPort(0).getBasePort().getConnections()[0]);
			if (tList.size() != 0)
			{
				pi = tList.get(0);
				plp.alreadyThere = pi.getNodeInst();
			} else
			{
				double psX = type.getDefWidth(ep);
				double psY = type.getDefHeight(ep);
				plp.alreadyThere = NodeInst.makeInstance(type, ep, new Point2D.Double(plp.x, plp.y), psX, psY, curCell);
				if (plp.alreadyThere == null)
				{
					System.out.println("Error, line " + lineReader.getLineNumber() + ": could not create external port");
					errorCount++;
					return;
				}
				if (plp.sX != 0 || plp.sY != 0)
				{
					String sizeString = TextUtils.formatDouble(plp.sX) + "x" + TextUtils.formatDouble(plp.sY);
					plp.alreadyThere.newVar(PORT_SIZE, sizeString, ep);
				}
				pi = plp.alreadyThere.getPortInst(0);
			}
		}

		if (plp.alreadyThere.getProto() == Schematics.tech().offpageNode)
		{
			PortProto fPp = Schematics.tech().offpageNode.findPortProto("y");
			if (plp.direction == PortCharacteristic.OUT) fPp = Schematics.tech().offpageNode.findPortProto("a");
			pi = plp.alreadyThere.findPortInstFromProto(fPp);
		}
		String name = plp.name;
		String exportName = renamedObjects.get(name);

		if (exportName != null) name = exportName;

		plp.createdPort = Export.newInstanceNoIcon(curCell, pi, convertParens(name), ep, plp.direction);
		if (plp.createdPort == null)
		{
			System.out.println("Error, line " + lineReader.getLineNumber() + ": could not create port <" + name + ">");
			errorCount++;
			return;
		}
		TextDescriptor td = plp.createdPort.getTextDescriptor(Export.EXPORT_NAME);
		td = td.withPos(TextDescriptor.Position.UP);
		if (plp.orientation != null)
			td = td.withRotation(TextDescriptor.Rotation.getRotation(plp.orientation.getAngle()/10));
		plp.createdPort.setTextDescriptor(Export.EXPORT_NAME, td);

		// when creating an export on a schematic, copy characteristics from the icon
		if (curCell.getView() == View.SCHEMATIC)
		{
			Cell iconCell = curCell.iconView();
			if (iconCell != null)
			{
				Export e = plp.createdPort.findEquivalent(iconCell);
				if (e != null)
				{
					if (plp.direction == PortCharacteristic.UNKNOWN && e.getCharacteristic() != PortCharacteristic.UNKNOWN)
						plp.createdPort.setCharacteristic(e.getCharacteristic());
				}
			}
		}
			
		// create the properties on the port
		for (EDIFProperty property : plp.properties)
		{
			plp.createdPort.newVar(stripPercentEscapes(property.name), stripPercentEscapes(property.val.toString()), ep);
		}
		
		// creating generic arcs to connect exports that had same names needed to be split
		List<PlannedPort> toRemove = new ArrayList<PlannedPort>();
		for (PlannedPort p : plp.exportsToConnect)
		{
			// time to build arc
			if (p.alreadyThere != null)
			{
				PortInst onlyPi = p.alreadyThere.getOnlyPortInst();
				PortInst onlyOtherPi = plp.alreadyThere.getOnlyPortInst();
				if (onlyPi != null && onlyOtherPi != null)
				{
					ArcInst.makeInstance(Generic.tech().universal_arc, ep, onlyPi, onlyOtherPi);
					toRemove.add(p); p.exportsToConnect.remove(plp);
				}
			}
		}
		plp.exportsToConnect.removeAll(toRemove);
	}

	private boolean isSpiceParameter(String x)
	{
		if (localPrefs.cadenceCompatibility)
		{
			int parPos = x.indexOf("pPar(");
			if (parPos >= 0)
			{
				int closePos = x.indexOf(")", parPos);
				if (closePos >= 0) return true;
			}
		}
		return false;
	}

	private String stripPercentEscapes(String x)
	{
		if (localPrefs.cadenceCompatibility)
		{
			boolean checkAgain = true;
			while (checkAgain)
			{
				checkAgain = false;
				int parPos = x.indexOf("pPar(");
				if (parPos >= 0)
				{
					int closePos = x.indexOf(")", parPos);
					if (closePos >= 0)
					{
						String par = x.substring(parPos+5, closePos);
						if (par.startsWith("%34%") && par.endsWith("%34%"))
							par = par.substring(4, par.length()-4);
						x = x.substring(0, parPos) + "@" + par + x.substring(closePos+1);
						checkAgain = true;
						continue;
					}
				}
				if (x.indexOf("%34%") >= 0)
				{
					x = x.replaceAll("%34%", "\"");
					checkAgain = true;
				}
			}
		}
		return x;
	}
	 
	private PlannedPort addPlannedPort(String name, boolean checkSameName)
	{
		PlannedPort found = null;
		for(PlannedPort plp : plannedPorts)
			if (plp.name.equalsIgnoreCase(name) || plp.alternateName.equalsIgnoreCase(name))
			{
				if (!checkSameName || !plp.knownLocation)
					return plp;
				Integer val = uniqueExportNames.get(name);
	            int number = 0; // zero means first time
	            if (val != null)
	            {
	                number = val;
	                number += 1;
	                name = name + "_" + number;
	            }
	            uniqueExportNames.put(name, Integer.valueOf(number));
	            found = plp;
				break;
			}
		
		PlannedPort plp = new PlannedPort();
		plp.name = name;
		plp.alternateName = name;
		plp.direction = PortCharacteristic.UNKNOWN;
		plp.properties = new ArrayList<EDIFProperty>();
		plp.exportsToConnect = new ArrayList<PlannedPort>(1);
		if (found != null) 
		{
			plp.exportsToConnect.add(found);
			found.exportsToConnect.add(plp);
		}
		plp.pinType = Schematics.tech().offpageNode;
		plp.alreadyThere = null;
		plannedPorts.add(plp);
		return plp;
	}

	/**
	 * Method to see if a parameter name is acceptable for placement in the circuit.
	 * @param param the parameter name.
	 * @return true if the name should be placed in the circuit.
	 */	
	private boolean isAcceptedParameter(String param, Object value)
	{
		if (localPrefs.importAllParameters) return true; // import all variables
		
		if (localPrefs.acceptedParameters.length() == 0) return false;
		String [] params = localPrefs.acceptedParameters.split("/");
		for(int i=0; i<params.length; i++)
			if (param.equalsIgnoreCase(params[i])) return true;

		return false;
	}

	/**************************************** PARSING TABLE ****************************************/

	private class EDIFKEY
	{
		/** the name of the keyword */	private String name;
		/** edif state */				private EDIFKEY [] stateArray;

		private EDIFKEY(String name)
		{
			this.name = name;
			edifKeys.put(TextUtils.canonicString(name), this);
		}

		protected void push() throws IOException {}

		protected void pop() throws IOException {}
	}

							    private EDIFKEY KUNKNOWN = new EDIFKEY("");
							    private EDIFKEY KINIT = new EDIFKEY("");
								private EDIFKEY KANNOTATE = new EDIFKEY("annotate");
	@SuppressWarnings("unused")	private EDIFKEY KARC = new KeyArc();
								private EDIFKEY KARRAY = new KeyArray();
								private EDIFKEY KAUTHOR = new EDIFKEY ("author");
	@SuppressWarnings("unused")	private EDIFKEY KBOOLEAN = new EDIFKEY ("boolean");
	@SuppressWarnings("unused")	private EDIFKEY KBOOLEANMAP = new EDIFKEY("booleanMap");
	@SuppressWarnings("unused")	private EDIFKEY KBORDERPATTERN = new EDIFKEY ("borderpattern");
								private EDIFKEY KBOUNDINGBOX = new KeyBoundingBox();
								private EDIFKEY KCELL = new KeyCell();
								private EDIFKEY KCELLREF = new KeyCellRef();
								private EDIFKEY KCELLTYPE = new EDIFKEY("cellType");
	@SuppressWarnings("unused")	private EDIFKEY KCIRCLE = new KeyCircle();
	@SuppressWarnings("unused")	private EDIFKEY KCOLOR = new EDIFKEY("color");
	@SuppressWarnings("unused")	private EDIFKEY KCOMMENT = new EDIFKEY("comment");
								private EDIFKEY KCOMMENTGRAPHICS = new KeyCommentGraphics(); //new EDIFKEY("commentGraphics");
	@SuppressWarnings("unused")	private EDIFKEY KCONNECTLOCATION = new EDIFKEY("connectLocation");
								private EDIFKEY KCONTENTS = new KeyContents();
	@SuppressWarnings("unused")	private EDIFKEY KCORNERTYPE = new KeyCornerType();
	@SuppressWarnings("unused")	private EDIFKEY KCRITICALITY = new EDIFKEY("criticality");
	@SuppressWarnings("unused")	private EDIFKEY KCURVE = new EDIFKEY("curve");
	@SuppressWarnings("unused")	private EDIFKEY KDATAORIGIN = new EDIFKEY("dataOrigin");
	@SuppressWarnings("unused")	private EDIFKEY KDCFANOUTLOAD = new EDIFKEY("dcFanoutLoad");
	@SuppressWarnings("unused")	private EDIFKEY KDCMAXFANOUT = new EDIFKEY("dcMaxFanout");
								private EDIFKEY KDELTA = new KeyDelta();
								private EDIFKEY KDESIGN = new KeyDesign();
	@SuppressWarnings("unused")	private EDIFKEY KDESIGNATOR = new EDIFKEY("designator");
								private EDIFKEY KDIRECTION = new KeyDirection();
	@SuppressWarnings("unused")	private EDIFKEY KDISPLAY = new EDIFKEY("display");
	@SuppressWarnings("unused")	private EDIFKEY KDOT = new KeyDot();
	@SuppressWarnings("unused")	private EDIFKEY KSCALEDINTEGER = new EDIFKEY("e");
								private EDIFKEY KEDIF = new EDIFKEY("edif");
								private EDIFKEY KEDIFLEVEL = new EDIFKEY("edifLevel");
	@SuppressWarnings("unused")	private EDIFKEY KEDIFVERSION = new EDIFKEY("edifVersion");
	@SuppressWarnings("unused")	private EDIFKEY KENDTYPE = new KeyEndType();
								private EDIFKEY KEXTERNAL = new KeyExternal();
	@SuppressWarnings("unused")	private EDIFKEY KFABRICATE = new KeyFabricate();
	@SuppressWarnings("unused")	private EDIFKEY KFALSE = new KeyFalse();
	@SuppressWarnings("unused")	private EDIFKEY KFIGURE = new KeyFigure();
	@SuppressWarnings("unused")	private EDIFKEY KFIGUREGROUP = new KeyFigureGroup();
	@SuppressWarnings("unused")	private EDIFKEY KFIGUREGROUPOVERRIDE = new KeyFigureGroupOverride();
	@SuppressWarnings("unused")	private EDIFKEY KFILLPATTERN = new EDIFKEY("fillpattern");
	@SuppressWarnings("unused")	private EDIFKEY KGRIDMAP = new EDIFKEY("gridMap");
								private EDIFKEY KINSTANCE = new KeyInstance();
								private EDIFKEY KINSTANCEREF = new KeyInstanceRef();
	@SuppressWarnings("unused")	private EDIFKEY KINTEGER = new KeyInteger();
								private EDIFKEY KINTERFACE = new KeyInterface();
								private EDIFKEY KJOINED = new KeyJoined();
	@SuppressWarnings("unused")	private EDIFKEY KJUSTIFY = new KeyJustify();
	@SuppressWarnings("unused")	private EDIFKEY KKEYWORDDISPLAY = new EDIFKEY("keywordDisplay");
	@SuppressWarnings("unused")	private EDIFKEY KEDIFKEYLEVEL = new EDIFKEY("keywordLevel");
								private EDIFKEY KEDIFKEYMAP = new EDIFKEY("keywordMap");
								private EDIFKEY KLIBRARY = new KeyLibrary();
								private EDIFKEY KLIBRARYREF = new LibraryRef();
								private EDIFKEY KLISTOFNETS = new EDIFKEY("listOfNets");
								private EDIFKEY KLISTOFPORTS = new EDIFKEY("listOfPorts");
	@SuppressWarnings("unused")	private EDIFKEY KLOGICVALUE = new EDIFKEY("logicValue");
								private EDIFKEY KMEMBER = new KeyMember();
								private EDIFKEY KNAME = new KeyName();
								private EDIFKEY KNET = new KeyNet();
								private EDIFKEY KNETBUNDLE = new KeyNetBundle();
	@SuppressWarnings("unused")	private EDIFKEY KNUMBER = new KeyNumber();
								private EDIFKEY KNUMBERDEFINITION = new EDIFKEY("numberDefinition");
	@SuppressWarnings("unused")	private EDIFKEY KOPENSHAPE = new KeyOpenShape();
	@SuppressWarnings("unused")	private EDIFKEY KORIENTATION = new KeyOrientation();
	@SuppressWarnings("unused")	private EDIFKEY KORIGIN = new EDIFKEY("origin");
	@SuppressWarnings("unused")	private EDIFKEY KOWNER = new EDIFKEY("owner");
								private EDIFKEY KPAGE = new EDIFKEY("page");
								private EDIFKEY KPAGESIZE = new EDIFKEY("pageSize");
	@SuppressWarnings("unused")	private EDIFKEY KPATH = new KeyPath();
	@SuppressWarnings("unused")	private EDIFKEY KPATHWIDTH = new KeyPathWidth();
	@SuppressWarnings("unused")	private EDIFKEY KPOINT = new EDIFKEY("point");
	@SuppressWarnings("unused")	private EDIFKEY KPOINTLIST = new EDIFKEY("pointList");
								private EDIFKEY KPOLYGON = new KeyPolygon();
								private EDIFKEY KPORT = new KeyPort();
	@SuppressWarnings("unused")	private EDIFKEY KPORTBUNDLE = new EDIFKEY("portBundle");
								private EDIFKEY KPORTIMPLEMENTATION = new KeyPortImplementation();
	@SuppressWarnings("unused")	private EDIFKEY KPORTINSTANCE = new EDIFKEY("portInstance");
	@SuppressWarnings("unused")	private EDIFKEY KPORTLIST = new KeyPortList();
								private EDIFKEY KPORTREF = new KeyPortRef();
	@SuppressWarnings("unused")	private EDIFKEY KPROGRAM = new KeyProgram();
								private EDIFKEY KPROPERTY = new KeyProperty();
	@SuppressWarnings("unused")	private EDIFKEY KPROPERTYDISPLAY = new EDIFKEY("propertyDisplay");
								private EDIFKEY KPROTECTIONFRAME = new KeyProtectionFrame();
	@SuppressWarnings("unused")	private EDIFKEY KPT = new KeyPt();
	@SuppressWarnings("unused")	private EDIFKEY KRECTANGLE = new KeyRectangle();
								private EDIFKEY KRENAME = new KeyRename();
								private EDIFKEY KSCALE = new EDIFKEY("scale");
	@SuppressWarnings("unused")	private EDIFKEY KSCALEX = new EDIFKEY("scaleX");
	@SuppressWarnings("unused")	private EDIFKEY KSCALEY = new EDIFKEY("scaleY");
	@SuppressWarnings("unused")	private EDIFKEY KSHAPE = new EDIFKEY("shape");
	@SuppressWarnings("unused")	private EDIFKEY KSIMULATIONINFO = new EDIFKEY("simulationInfo");
								private EDIFKEY KSTATUS = new EDIFKEY("status");
	@SuppressWarnings("unused")	private EDIFKEY KSTRING = new KeyString();
	@SuppressWarnings("unused")	private EDIFKEY KSTRINGDISPLAY = new KeyStringDisplay();
								private EDIFKEY KSYMBOL = new KeySymbol();
								private EDIFKEY KTECHNOLOGY = new EDIFKEY("technology");
	@SuppressWarnings("unused")	private EDIFKEY KTEXTHEIGHT = new KeyTextHeight();
								private EDIFKEY KTIMESTAMP = new EDIFKEY("timestamp");
	@SuppressWarnings("unused")	private EDIFKEY KTRANSFORM = new EDIFKEY("transform");
	@SuppressWarnings("unused")	private EDIFKEY KTRUE = new KeyTrue();
								private EDIFKEY KUNIT = new KeyUnit();
								private EDIFKEY KUNUSED = new KeyUnused();		// TODO: fix
	@SuppressWarnings("unused")	private EDIFKEY KUSERDATA = new EDIFKEY("userData");
	@SuppressWarnings("unused")	private EDIFKEY KVERSION = new EDIFKEY("version");
								private EDIFKEY KVIEW = new KeyView();
								private EDIFKEY KVIEWREF = new ViewRef();
								private EDIFKEY KVIEWTYPE = new KeyViewType();
	@SuppressWarnings("unused")	private EDIFKEY KVISIBLE = new EDIFKEY("visible");
								private EDIFKEY KWRITTEN = new EDIFKEY("written");

	private class KeyArc extends EDIFKEY
	{
		private KeyArc() { super("arc"); }
		protected void pop()
		{
			// set array values
			if (curPoints.size() >= 3)
			{
				double [] x = new double[3];
				double [] y = new double[3];
				Point2D p0 = curPoints.get(0);
				Point2D p1 = curPoints.get(1);
				Point2D p2 = curPoints.get(2);
				x[0] = p0.getX();   y[0] = p0.getY();
				x[1] = p1.getX();   y[1] = p1.getY();
				x[2] = p2.getX();   y[2] = p2.getY();
				makeArc(x, y);
			}
			freePointList();
		}
	}

	private class KeyArray extends EDIFKEY
	{
		private KeyArray() { super("array"); }
		protected void push()
			throws IOException
		{
			// note array is a special process integer value function
			isArray = true;
			arrayXVal = arrayYVal = 0;
			deltaPointXX = deltaPointXY = 0;
			deltaPointYX = deltaPointYY = 0;
			deltaPointsSet = false;
			if (checkName())
			{
				if (keyStack[keyStackDepth-1] == KCELL)
				{
					cellName = cellReference = fixLeadingAmpersand(objectName);
				} else if (keyStack[keyStackDepth-1] == KPORT)
				{
					portName = portReference = fixLeadingAmpersand(objectName);
				} else if (keyStack[keyStackDepth-1] == KINSTANCE)
				{
					instanceName = instanceReference = objectName;
				} else if (keyStack[keyStackDepth-1] == KNET)
				{
					netReference = netName = fixLeadingAmpersand(objectName);
				} else if (keyStack[keyStackDepth-1] == KPROPERTY)
				{
					propertyReference = objectName;
				}
			}
		}

		protected void pop()
		{
			if (arrayXVal == 0) arrayXVal = 1;
			if (arrayYVal == 0) arrayYVal = 1;
		}
	}

	private class KeyBoundingBox extends EDIFKEY
	{
		private KeyBoundingBox() { super("boundingBox"); }
		protected void push()
		{
			curFigureGroup = Artwork.tech().openedDottedPolygonNode;
		}

		protected void pop()
		{
			curFigureGroup = null;
		}
	}

	private class KeyCell extends EDIFKEY
	{
		private KeyCell() { super("cell"); }
		protected void push()
			throws IOException
		{
			activeView = VNULL;
			objectName = "";   originalName = "";
			sheetXPos = sheetYPos = -1;
			if (checkName())
			{
				cellName = cellReference = fixLeadingAmpersand(objectName);
			}
		}
	}

	private class KeyCellRef extends EDIFKEY
	{
		private KeyCellRef() { super("cellRef"); }
		protected void push()
			throws IOException
		{
			cellRef = fixLeadingAmpersand(getToken((char)0));
			libraryRef = null;
		}

		protected void pop()
		{
			// get the name of the cell
			String aName = cellRef;
			cellRefProtoFunction = PrimitiveNode.Function.UNKNOWN;
			cellRefProtoRotation = 0;
			cellRefOffsetX = cellRefOffsetY = 0;
			String view = "lay";
			if (activeView != VMASKLAYOUT)
			{
				if (keyStack[keyStackDepth - 1] == KDESIGN) view = "sch"; else
					view = "ic";
			}
			if (curVendor == EVVIEWLOGIC && aName.equalsIgnoreCase("SPLITTER"))
			{
				cellRefProto = null;
				return;
			}

			// look for an equivalent primitive
			String libName = libraryRef;
			if (libName == null) libName = curLibrary.getName();
			EDIFEquiv.NodeEquivalence ne = equivs.getNodeEquivalence(libName, cellRef, viewRef);
			if (ne != null && ne.np != null)
			{
				mappedNodes.put(instanceName, ne);
				cellRefProto = ne.np;
				cellRefProtoFunction = PrimitiveNode.Function.UNKNOWN;
				cellRefProtoRotation = ne.rotation;

				// determine the offset
				cellRefOffsetX = ne.xOffset;
				cellRefOffsetY = ne.yOffset;
				if (cellRefProto instanceof PrimitiveNode)
				{
					Technology tech = cellRefProto.getTechnology();
					if (tech instanceof Schematics)
						cellRefProtoFunction = ne.function;
				}
				return;
			}

			// look for this cell name in the cell list
			NameEntry nt = cellTable.get(aName);
			if (nt == null)
			{
				String alternateName = renamedObjects.get(aName);
				if (alternateName != null)
					nt = cellTable.get(alternateName);
			}
			if (nt != null)
			{
				aName = nt.replace;
			} else
			{
				// Looking if the cell is already available in a library already loaded.
				Cell c = Library.findCellInLibraries(aName, View.findView(view), null);
				if (c == null)
				{
					System.out.println("Error, line " + lineReader.getLineNumber() + ": could not find cellRef '" + aName + "'");
				}
				else
				{
					cellRefProto = c;
					return;
				}
			}

			// create cell if not already there
			Cell proto = createCell(libraryRef, aName, view);
			if (proto == null)
				System.out.println("Error, line " + lineReader.getLineNumber() + ": cannot create cell "+aName+" in library "+libraryRef);

			// set the parent
			cellRefProto = proto;
		}
	}

	private class KeyCircle extends EDIFKEY
	{
		private KeyCircle() { super("circle"); }
		protected void push()
		{
			freePointList();
			curOrientation = OR0;
		}

		protected void pop()
		{
			if (curPoints.size() == 2 && curCell != null)
			{
				Point2D p0 = curPoints.get(0);
				Point2D p1 = curPoints.get(1);
				double lX = Math.min(p0.getX(), p1.getX());
				double hX = Math.max(p0.getX(), p1.getX());
				double lY = Math.min(p0.getY(), p1.getY());
				double hY = Math.max(p0.getY(), p1.getY());
				if (lX == hX)
				{
					lX -= (hY - lY) / 2;
					hX += (hY - lY) / 2;
				} else
				{
					lY -= (hX - lX) / 2;
					hY += (hX - lX) / 2;
				}
				double sX = hX - lX;
				double sY = hY - lY;

				// create the node instance
				double cX = (hX + lX) / 2;
				double cY = (hY + lY) / 2;
				if (curCellPage > 0) cY += (curCellPage-1) * Cell.FrameDescription.MULTIPAGESEPARATION;
				NodeInst ni = NodeInst.makeInstance(Artwork.tech().circleNode, ep, new Point2D.Double(cX, cY),
					sX, sY, curCell, curOrientation, null);
				if (ni == null)
				{
					System.out.println("Error, line " + lineReader.getLineNumber() + ": could not create circle");
					errorCount++;
				}
			}
			freePointList();
		}
	}

	private class KeyContents extends EDIFKEY
	{
		private KeyContents() { super("contents"); }
		protected void push()
			throws IOException
		{
			if (activeView != VNETLIST)
			{
				View view = View.SCHEMATIC;
				if (activeView == VMASKLAYOUT) view = View.LAYOUT; else
					if (activeView == VGRAPHIC) view = View.ICON;

				// check for the name
				checkName();

				// locate this in the list of cells
				Cell proto = findCell(null, cellName, view.getAbbreviation());
				if (proto == null)
				{
					// allocate the cell
					proto = createCell(null, cellName, view.getAbbreviation());
					if (proto == null) throw new IOException("Error creating cell");
				} else
				{
					if (activeView == VSCHEMATIC) ignoreBlock = true;
				}

				curCell = proto;
				curCellPage = 0;
				inputLocY = outputLocY = 0;
			}
			plannedPorts = new ArrayList<PlannedPort>();
			uniqueExportNames = new HashMap<String,Integer>();
		}

		protected void pop()
		{
			if (curCell != null)
			{
				Rectangle2D cellBounds = curCell.getBounds();
				for(PlannedPort plp : plannedPorts)
					instantiatePlannedPort(plp, cellBounds);
			}
			plannedPorts = null;
			uniqueExportNames = null;
		}
	}

	private class KeyCornerType extends EDIFKEY
	{
		private KeyCornerType() { super("cornerType"); }
		protected void push()
			throws IOException
		{
			// ignore the endtype
			getToken((char)0);
		}
	}

	private class KeyDelta extends EDIFKEY
	{
		private KeyDelta() { super("delta"); }
		protected void push()
		{
			deltaPointXX = deltaPointXY = 0;
			deltaPointYX = deltaPointYY = 0;
			deltaPointsSet = false;
		}
	}

	private class KeyDesign extends EDIFKEY
	{
		private KeyDesign() { super("design"); }
		protected void push()
			throws IOException
		{
			// ignore the name of the cell
			getToken((char)0);
		}

		protected void pop()
		{
			if (cellRefProto != null)
			{
				assert(cellRefProto instanceof Cell);
				Cell c = (Cell)cellRefProto;
				currentCells.put(c.getLibrary(), (Cell)c);
			}
		}
	}

	private class KeyDirection extends EDIFKEY
	{
		private KeyDirection() { super("direction"); }
		protected void push()
			throws IOException
		{
			// get the direction
			String aName = getToken((char)0);
			if (aName.equalsIgnoreCase("INPUT")) curDirection = PortCharacteristic.IN; else
				if (aName.equalsIgnoreCase("INOUT")) curDirection = PortCharacteristic.BIDIR; else
					if (aName.equalsIgnoreCase("OUTPUT")) curDirection = PortCharacteristic.OUT;
		}
	}

	private class KeyDot extends EDIFKEY
	{
		private KeyDot() { super("dot"); }
		protected void push()
		{
			freePointList();
			curOrientation = OR0;
		}

		protected void pop()
		{
			if (curGeometryType == GPORTIMPLEMENTATION)
			{
				// inside a PortImplementation
				String exportName = renamedObjects.get(objectName);
				if (exportName == null) exportName = objectName;
				PlannedPort plp = addPlannedPort(exportName, true);
				
				if (plp != null) // if port is not skipped
				{
					plp.pinType = cellRefProto;
					plp.knownLocation = true;
					Point2D p0 = curPoints.get(0);
					plp.x = p0.getX();
					plp.y = p0.getY();
					if (curCellPage > 0) plp.y += (curCellPage-1) * Cell.FrameDescription.MULTIPAGESEPARATION;
				}
			} else if (curCell != null)
			{
				// create the node instance
				Point2D p0 = curPoints.get(0);
				double xPos = p0.getX();
				double yPos = p0.getY();
				if (curCellPage > 0) yPos += (curCellPage-1) * Cell.FrameDescription.MULTIPAGESEPARATION;
				NodeInst ni = NodeInst.makeInstance(curFigureGroup != null ? curFigureGroup : Artwork.tech().boxNode, ep,
					new Point2D.Double(xPos, yPos), 0, 0, curCell);
				if (ni == null)
				{
					System.out.println("Error, line " + lineReader.getLineNumber() + ": could not create rectangle");
					errorCount++;
				}
			}
			freePointList();
		}
	}

	private class KeyEndType extends EDIFKEY
	{
		private KeyEndType() { super("endType"); }
		protected void push()
			throws IOException
		{
			// get the endtype
			/* String type = */ getToken((char)0);
//			if (type.equalsIgnoreCase("EXTEND")) extendEnd = true;
		}
	}

	private class KeyExternal extends EDIFKEY
	{
		private KeyExternal() { super("external"); }
		protected void push()
			throws IOException
		{
			// ignore the name of the library
			String libName = getToken((char)0);
			curLibrary = Library.findLibrary(libName);
			if (curLibrary == null)
				curLibrary = Library.newInst(libName, null);
		}
	}

	private class KeyFabricate extends EDIFKEY
	{
		private KeyFabricate() { super("fabricate"); }
		protected void push()
			throws IOException
		{
			NameEntry nt = new NameEntry();
			activeFigures.add(nt);

			// first get the original and replacement layers
			nt.original = getToken((char)0);
			nt.replace = getToken((char)0);
			nt.textHeight = 0;
			nt.justification = TextDescriptor.Position.DOWNRIGHT;
		}
	}

	private class KeyFalse extends EDIFKEY
	{
		private KeyFalse() { super("false"); }
		protected void push()
		{
            propertyValue = Boolean.valueOf(false);
        }
	}

	private class KeyFigure extends EDIFKEY
	{
		private KeyFigure() { super("figure"); }
		protected void push()
			throws IOException
		{
			makeFigure();
//			extendEnd = false;
			pathWidth = 0;
		}

		protected void pop()
		{
			curActiveFigure = null;
			textJustification = TextDescriptor.Position.DOWNRIGHT;
			textHeight = 0;
			if (curGeometryType == GPORTIMPLEMENTATION)
			{
				String exportName = renamedObjects.get(objectName);
				if (exportName == null) exportName = objectName;
//				boolean figurePinCase = inputBuffer.contains("figure pin");
				PlannedPort plp = addPlannedPort(exportName, false);
				if (plp != null)
					plp.pinType = curFigureGroup;
			}
		}
	}

	private class KeyFigureGroup extends EDIFKEY
	{
		private KeyFigureGroup() { super("figureGroup"); }
		protected void push()
			throws IOException
		{
			makeFigure();
		}

		protected void pop()
		{
			curActiveFigure = null;
		}
	}

	private class KeyFigureGroupOverride extends EDIFKEY
	{
		private KeyFigureGroupOverride() { super("figureGroupOverride"); }
		protected void push()
			throws IOException
		{
			makeFigure();
		}
	}

	private class KeyInstance extends EDIFKEY
	{
		private KeyInstance() { super("instance"); }
		protected void push()
			throws IOException
		{
			// set the current geometry type
			freePointList();
			cellRefProto = null;
			curGeometryType = GINSTANCE;
			curOrientation = OR0;
			isArray = false;
			arrayXVal = arrayYVal = 1;
			objectName = "";   originalName = "";
			instanceReference = "";
			curNode = null;
			if (checkName())
			{
				instanceReference = objectName;
				instanceName = objectName;
			}
		}

		protected void pop()
			throws IOException
		{
			if (activeView == VNETLIST)
			{
				for (int iX = 0; iX < arrayXVal; iX++)
				{
					for (int iY = 0; iY < arrayYVal; iY++)
					{
						// create this instance in the current sheet
						double width = cellRefProto.getDefWidth(ep);
						double height = cellRefProto.getDefHeight(ep);
						width = (width + INCH - 1) / INCH;
						height = (height + INCH - 1) / INCH;

						if (sheetXPos == -1)
						{
							curCell = createCell(null, cellName, "sch");
							if (curCell == null) throw new IOException("Error creating cell");
							sheetXPos = sheetYPos = 1;
							sheetOffset = 2;
						}

						// create this instance
						// find out where true center moves
						double cX = ((sheetXPos - (SHEETWIDTH / 2)) * INCH);
						double cY = ((sheetYPos - (SHEETHEIGHT / 2)) * INCH);
						if (curCellPage > 0) cY += (curCellPage-1) * Cell.FrameDescription.MULTIPAGESEPARATION;
						Orientation orient = curOrientation.concatenate(Orientation.fromAngle(cellRefProtoRotation*10));
						NodeInst ni = NodeInst.makeInstance(cellRefProto, ep, new Point2D.Double(cX+cellRefOffsetX, cY+cellRefOffsetY),
							cellRefProto.getDefWidth(ep), cellRefProto.getDefHeight(ep), curCell, orient, null, cellRefProtoFunction);
						curNode = ni;
						if (ni == null)
						{
							System.out.println("Error, line " + lineReader.getLineNumber() + ": could not create instance");
							errorCount++;
							break;
						}

						// update the current position
						if ((width + 2) > sheetOffset)
							sheetOffset = (int)width + 2;
						if ((sheetYPos += (height + 1)) >= SHEETHEIGHT)
						{
							sheetYPos = 1;
							sheetXPos += sheetOffset;
							sheetOffset = 2;
						}

						// name the instance
						if (instanceReference.length() > 0)
						{
							// if single element or array with no offset
							// construct the representative extended EDIF name (includes [...])
							String nodeName = instanceReference;
							if ((arrayXVal > 1 || arrayYVal > 1) &&
								(deltaPointXX != 0 || deltaPointXY != 0 || deltaPointYX != 0 || deltaPointYY != 0))
							{
								// if array in the x dimension
								if (arrayXVal > 1)
								{
									if (arrayYVal > 1)
										nodeName = instanceReference + "[" + iX + "," + iY + "]";
									else
										nodeName = instanceReference + "[" + iX + "]";
								} else if (arrayYVal > 1)
									nodeName = instanceReference + "[" + iY + "]";
							}

							// check for array element descriptor
							if (arrayXVal > 1 || arrayYVal > 1)
							{
								// array descriptor is of the form index:index:range index:index:range
								String baseName = iX + ":" + ((deltaPointXX == 0 && deltaPointYX == 0) ? arrayXVal-1:iX) + ":" + arrayXVal +
									" " + iY + ":" + ((deltaPointXY == 0 && deltaPointYY == 0) ? arrayYVal-1:iY) + ":" + arrayYVal;
								ni.newVar("EDIF_array", stripPercentEscapes(baseName), ep);
							}

							/* now set the name of the component (note that Electric allows any string
							 * of characters as a name, this name is open to the user, for ECO and other
							 * consistencies, the EDIF_name is saved on a variable)
							 */
							if (instanceReference.equalsIgnoreCase(instanceName))
							{
								putNameOnNode(ni, nodeName);
							} else
							{
								// now add the original name as the displayed name (only to the first element)
								if (iX == 0 && iY == 0)
									putNameOnNode(ni, instanceName);

								// now save the EDIF name (not displayed)
								ni.newVar("EDIF_name", stripPercentEscapes(nodeName), ep);
							}
						}
					}
				}
			} else
			{
				// get the corner offset
				double instPtX = 0, instPtY = 0;
				int instCount = curPoints.size();
				if (instCount > 0)
				{
					Point2D point = curPoints.get(0);
					instPtX = point.getX();
					instPtY = point.getY();
				}

				// if no points are specified, presume the origin
				if (instCount == 0) instCount = 1;

				if (instCount == 1 && cellRefProto != null)
				{
					for (int iX = 0; iX < arrayXVal; iX++)
					{
//						// ignore placement of schematic frames when using Cadence EDIF
//						if (localPrefs.cadenceCompatibility)
//						{
//							if (cellRefProto instanceof Cell)
//							{
//								Cell cellRefCell = (Cell)cellRefProto;
//								if (cellRefCell.getView() == View.ICON &&
//									cellRefCell.getLibrary().getName().equals("primitive"))
//								{
//									System.out.println("Use this code?");
//									if (cellRefProto.getName().equals("Asize_c")) continue;
//									if (cellRefProto.getName().equals("Bsize_c")) continue;
//									if (cellRefProto.getName().equals("CsizeB_c")) continue;
//								}
//							}
//						}
						double lX = instPtX + iX * deltaPointXX;
						double lY = instPtY + iX * deltaPointXY;
						for (int iY = 0; iY < arrayYVal; iY++)
						{
							double yPos = lY;
							if (curCellPage > 0) yPos += (curCellPage-1) * Cell.FrameDescription.MULTIPAGESEPARATION;
							Orientation orient = curOrientation.concatenate(Orientation.fromAngle(cellRefProtoRotation*10));
				// TODO Oh Dima!
				if (cellRefProto instanceof Cell && Cell.isInstantiationRecursive((Cell)cellRefProto, curCell) && curCell.getView() == View.ICON)
				{
					System.out.println("Cannot create instance of " + cellRefProto + " in icon " + curCell);
					if (deltaPointYX == 0 && deltaPointYY == 0) break;
					lX += deltaPointYX;
					lY += deltaPointYY;
					continue;
				}
							NodeInst ni = NodeInst.makeInstance(cellRefProto, ep, new Point2D.Double(lX+cellRefOffsetX, yPos+cellRefOffsetY),
								cellRefProto.getDefWidth(ep), cellRefProto.getDefHeight(ep), curCell, orient, null, cellRefProtoFunction);
							curNode = ni;
							if (ni == null)
							{
								System.out.println("Error, line " + lineReader.getLineNumber() + ": could not create instance");
								assert(false); // check this
								NodeInst.makeInstance(cellRefProto, ep, new Point2D.Double(lX+cellRefOffsetX, yPos+cellRefOffsetY),
										cellRefProto.getDefWidth(ep), cellRefProto.getDefHeight(ep), curCell, orient, null, cellRefProtoFunction);
								errorCount++;

								// and exit for loop
								iX = arrayXVal;
								iY = arrayYVal;

								freePointList();
								return;
							}
							if (curGeometryType == GPORTIMPLEMENTATION && lX == 0 && lY == 0)
							{
								// inside a PortImplementation: determine an appropriate port name
								String pName = portName;
								for (int dup = 0; ; dup++)
								{
									PortProto ppt = curCell.findPortProto(pName);
									if (ppt == null) break;
									pName = portName + "_" + (dup+1);
								}

								// only once
								iX = arrayXVal;
								iY = arrayYVal;

								// locate the direction of the port
								PlannedPort plp = addPlannedPort(pName, false);
								plp.alreadyThere = ni;
							} else
							{
								// name the instance
								if (instanceReference.length() > 0)
								{
									String nodeName = instanceReference;
									if ((arrayXVal > 1 || arrayYVal > 1) &&
										(deltaPointXX != 0 || deltaPointXY != 0 || deltaPointYX != 0 || deltaPointYY != 0))
									{
										// if array in the x dimension
										if (arrayXVal > 1)
										{
											if (arrayYVal > 1) nodeName = instanceReference + "[" + iX + "," + iY + "]"; else
												nodeName = instanceReference + "[" + iX + "]";
										} else if (arrayYVal > 1)
											nodeName = instanceReference + "[" + iY + "]";
									}

									// check for array element descriptor
									if (arrayXVal > 1 || arrayYVal > 1)
									{
										// array descriptor is of the form index:index:range index:index:range
										String baseName = iX + ":" + ((deltaPointXX == 0 && deltaPointYX == 0) ? arrayXVal-1:iX) + ":" + arrayXVal +
											" " + iY + ":" + ((deltaPointXY == 0 && deltaPointYY == 0) ? arrayYVal-1:iY) + ":" + arrayYVal;
										ni.newVar("EDIF_array", stripPercentEscapes(baseName), ep);
									}

									// now set the name of the component (note that Electric allows any string
			   						// of characters as a name, this name is open to the user, for ECO and other
									// consistencies, the EDIF_name is saved on a variable)
									Variable.Key varKey;
									if (instanceReference.equalsIgnoreCase(instanceName))
									{
										putNameOnNode(ni, nodeName);
										varKey = NodeInst.NODE_NAME;
									} else
									{
										// now add the original name as the displayed name (only to the first element)
										if (iX == 0 && iY == 0)
											putNameOnNode(ni, instanceName);

										// now save the EDIF name (not displayed)
										Variable var = ni.newVar("EDIF_name", stripPercentEscapes(nodeName), ep);
										varKey = var.getKey();
									}

									// now check for saved name attributes
									if (saveTextPoints.size() != 0)
									{
										// now set the position, relative to the center of the current object
										Point2D sP0 = saveTextPoints.get(0);
										double xOff = sP0.getX() - ni.getTrueCenterX();
										double yOff = sP0.getY() - ni.getTrueCenterY();

										// determine the size of text, 0.0278 in == 2 points or 36 (2xpixels) == 1 in fonts range from 4 to 20 points
										if (varKey != NodeInst.NODE_NAME)
										{
											TextDescriptor td = ni.getTextDescriptor(varKey);
											td = td.withRelSize(convertTextSize(saveTextHeight)).withPos(saveTextJustification).withOff(xOff, yOff);
											ni.setTextDescriptor(varKey, td);
										}
									}
								}
							}
							if (deltaPointYX == 0 && deltaPointYY == 0) break;

							// bump the y delta and x deltas
							lX += deltaPointYX;
							lY += deltaPointYY;
						}
						if (deltaPointXX == 0 && deltaPointXY == 0) break;
					}
				}
				freePointList();
			}

			// move the property list to the free list
			for (EDIFProperty property : propertiesList)
			{
				if (curNode != null)
				{
					String varName = property.name;
					Object varValue = property.val;
					String varNameLookup = varName;
					if (varNameLookup.startsWith("ATTR_")) 
					{
						varNameLookup = varNameLookup.substring(5);
					}
					if (isAcceptedParameter(varNameLookup, varValue))
					{
						EDIFEquiv.VariableEquivalence ve = equivs.getExternalVariableEquivalence(varNameLookup);
						if (ve != null)
						{
							varName = "ATTR_" + ve.elecVarName;
							if (ve.appendElecOutput.length() > 0)
							{
								String varValueString = varValue.toString();
								if (varValueString.endsWith(ve.appendElecOutput))
								{
									varValue = varValueString.substring(0, varValueString.length() - ve.appendElecOutput.length());
								}
							}
							if (ve.scale != 1)
							{
								String varValueString = varValue.toString();
								double newValue = TextUtils.atof(varValueString) / ve.scale;
								varValue = Double.toString(newValue);
							}
						}
						TextDescriptor td = ep.getNodeTextDescriptor().withDisplay(true).
							withDispPart(TextDescriptor.DispPos.NAMEVALUE).withPos(TextDescriptor.Position.DOWN);
						Cell parent = (Cell)curNode.getProto();
						for(Iterator<Variable> it = parent.getParameters(); it.hasNext(); )
						{
							Variable var = it.next();
							if (var.getKey().getName().equals(varName))
							{
								td = td.withOff(var.getXOff(), var.getYOff());
								break;
							}
						}
						String objStr = varValue.toString();
						Object newV = stripPercentEscapes(objStr);
						if (isSpiceParameter(objStr))
			                newV = Variable.withCode(newV, CodeExpression.Code.SPICE);
						curNode.newVar(Variable.newKey(varName), newV, td);
					}
				}
			}
			propertiesList = new ArrayList<EDIFProperty>();
			instanceReference = "";
			curNode = null;
			freeSavedPointList();
		}
	}

	private class KeyInstanceRef extends EDIFKEY
	{
		private KeyInstanceRef() { super("instanceRef"); }
		protected void push()
			throws IOException
		{
			instanceReference = "";
			if (checkName())
			{
				instanceReference = objectName;
			}
		}
	}

	private class KeyInteger extends EDIFKEY
	{
		private KeyInteger() { super("integer"); }
		protected void push()
			throws IOException
		{
			String value = getToken((char)0);
			
			if (value.isEmpty() || value.startsWith(")"))
				throw new IOException("Error reading integer '" + value + "' in line " + lineReader.getLineNumber());

			propertyValue = Integer.valueOf(TextUtils.atoi(value));
		}
	}

	private class KeyInterface extends EDIFKEY
	{
		private KeyInterface() { super("interface"); }
		protected void push()
			throws IOException
		{
//			if (activeView != VNETLIST) // TODO: latest change
			{
				String viewName = "ic";
				if (activeView == VMASKLAYOUT) viewName = "lay";
				curCell = findCell(null, cellName, viewName);
				if (curCell == null)
				{
					curCell = createCell(null, cellName, viewName);
					if (curCell == null) throw new IOException("Error creating cell");
				} else
				{
					ignoreBlock = true;
				}
				curCellPage = 0;
				inputLocY = outputLocY = 0;
			}
			plannedPorts = new ArrayList<PlannedPort>();
			uniqueExportNames = new HashMap<String,Integer>();
			symbolDefined = false;
		}

		protected void pop()
		{
			if (symbolDefined || activeView == VGRAPHIC)
			{
				Rectangle2D cellBounds = curCell.getBounds();
				for(PlannedPort plp : plannedPorts)
					instantiatePlannedPort(plp, cellBounds);

				// add cell name in Cadence mode
				if (localPrefs.cadenceCompatibility && curCell.getView() == View.ICON)
				{
					// add icon name in Cadence compatibility mode
					TextDescriptor td = ep.getAnnotationTextDescriptor();
					double xPos = curCell.getBounds().getCenterX();
					double yPos = curCell.getBounds().getMaxY() - td.getSize().getSize()/2;
					NodeInst ni = NodeInst.makeInstance(Generic.tech().invisiblePinNode,ep,  new Point2D.Double(xPos, yPos), 0, 0, curCell);
					if (ni != null)
					{
						td = td.withDisplay(true);
						ni.newVar(Artwork.ART_MESSAGE, curCell.getName(), td);
					}
				}
			} else if (activeView != VMASKLAYOUT)
			{
				// create a black-box symbol
				Rectangle2D cellBounds = curCell.getBounds();
				for(PlannedPort plp : plannedPorts)
					instantiatePlannedPort(plp, cellBounds);
				Cell nnp = null;
				try
				{
					nnp = localPrefs.iconParameters.makeIconForCell(curCell, ep);
				} catch (JobException e) {}
				if (nnp == null)
				{
					System.out.println("Error, line " + lineReader.getLineNumber() +
						", could not create icon <" + curCell.describe(true) + ">");
					errorCount++;
				} else
				{
					curCell.kill();
					curCell = nnp;
				}
			}
			plannedPorts = null;
			uniqueExportNames = null;
		}
	}

	private class KeyJoined extends EDIFKEY
	{
		private KeyJoined() { super("joined"); }
		protected void push()
		{
			lastPortlist = null;
		}
	}

	private class KeyJustify extends EDIFKEY
	{
		private KeyJustify() { super("justify"); }
		protected void push()
			throws IOException
		{
			// get the textheight value of the point
			String val = getToken((char)0);
			if (val.equalsIgnoreCase("UPPERLEFT")) textJustification = TextDescriptor.Position.DOWNRIGHT;
			else if (val.equalsIgnoreCase("UPPERCENTER")) textJustification = TextDescriptor.Position.DOWN;
			else if (val.equalsIgnoreCase("UPPERRIGHT")) textJustification = TextDescriptor.Position.DOWNLEFT;
			else if (val.equalsIgnoreCase("CENTERLEFT")) textJustification = TextDescriptor.Position.RIGHT;
			else if (val.equalsIgnoreCase("CENTERCENTER")) textJustification = TextDescriptor.Position.CENT;
			else if (val.equalsIgnoreCase("CENTERRIGHT")) textJustification = TextDescriptor.Position.LEFT;
			else if (val.equalsIgnoreCase("LOWERLEFT")) textJustification = TextDescriptor.Position.UPRIGHT;
			else if (val.equalsIgnoreCase("LOWERCENTER")) textJustification = TextDescriptor.Position.UP;
			else if (val.equalsIgnoreCase("LOWERRIGHT")) textJustification = TextDescriptor.Position.UPLEFT;
			else
			{
				System.out.println("Warning, line " + lineReader.getLineNumber() + ": unknown justify keyword <" + val + ">");
				warningCount++;
				return;
			}

			if (curActiveFigure != null)
				curActiveFigure.justification = textJustification;
		}
	}

	private class KeyLibrary extends EDIFKEY
	{
		private KeyLibrary() { super("library"); }
		protected void push()
			throws IOException
		{
			// get the name of the library
			String libName = getToken((char)0);
			curLibrary = Library.findLibrary(libName);
			if (curLibrary == null)
				curLibrary = Library.newInst(libName, null);
		}
	}

	private class LibraryRef extends EDIFKEY
	{
		private LibraryRef() { super ("libraryRef"); }
		protected void push()
			throws IOException
		{
			// get the name of the library
			libraryRef = getToken((char)0);
		}
	}

	private class KeyMember extends EDIFKEY
	{
		private KeyMember() { super("member"); }
		protected void push()
			throws IOException
		{
			memberXVal = -1; // no member
			memberYVal = -1; // no member
			if (checkName())
			{
				if (keyStack[keyStackDepth-1] == KPORTREF) portReference = objectName; else
					if (keyStack[keyStackDepth-1] == KINSTANCEREF) instanceReference = objectName;
			}
		}

		protected void pop()
		{
			if (memberXVal != -1)
			{
				// adjust the name of the current INSTANCE/NET/PORT(/VALUE)
				if (keyStack[keyStackDepth-1] == KINSTANCEREF)
				{
					instanceReference = getMemberName(instanceReference, memberXVal, memberYVal);
				} else if (keyStack[keyStackDepth-1] == KPORTREF)
				{
					portReference = getMemberName(portReference, memberXVal, memberYVal);
				}
			}
		}
	}

	private class KeyName extends EDIFKEY
	{
		private KeyName() { super("name"); }
		protected void push()
			throws IOException
		{
			int kPtr = keyStackDepth - 1;
			if (keyStack[keyStackDepth-1] == KARRAY || keyStack[keyStackDepth-1] == KMEMBER) kPtr = keyStackDepth-2;
			if (checkName())
			{
				if (keyStack[kPtr] == KCELL)
				{
					cellName = cellReference = fixLeadingAmpersand(objectName);
				} else if (keyStack[kPtr] == KPORTIMPLEMENTATION || keyStack[kPtr] == KPORT)
				{
					portName = portReference = fixLeadingAmpersand(objectName);
				} else if (keyStack[kPtr] == KPORTREF)
				{
					portReference = fixLeadingAmpersand(objectName);
				} else if (keyStack[kPtr] == KINSTANCE)
				{
					instanceName = instanceReference = objectName;
				} else if (keyStack[kPtr] == KINSTANCEREF)
				{
					instanceReference = objectName;
				} else if (keyStack[kPtr] == KNET)
				{
					netReference = netName = fixLeadingAmpersand(objectName);
				} else if (keyStack[kPtr] == KPROPERTY)
				{
					propertyReference = objectName;
				}

				// init the point lists
				freePointList();
				curOrientation = OR0;
				textJustification = TextDescriptor.Position.DOWNRIGHT;
				textHeight = 0;
				textString = objectName;
			}
		}

		protected void pop()
		{
			// save the data and break
			freeSavedPointList();
			textString = "";
			saveTextPoints = curPoints;
			curPoints = new ArrayList<Point2D>();
			saveTextHeight = textHeight;
			textHeight = 0;
			saveTextJustification = textJustification;
			textJustification = TextDescriptor.Position.DOWNRIGHT;
			saveOrientation = curOrientation;
			curOrientation = OR0;
		}
	}

	/**
	 * net:  Define a net name followed by edifgbl and local pin list
	 *		(net NAME
	 *			(joined
	 *			(portRef NAME (instanceRef NAME))
	 *			(portRef NAME)))
	 */
	private class KeyNet extends EDIFKEY
	{
		private KeyNet() { super("net"); }
		protected void push()
			throws IOException
		{
			netReference = "";
			objectName = "";   originalName = "";
			curArc = null;
			curNode = null;
			curPort = null;
			isArray = false;
			arrayXVal = arrayYVal = 1;
			if (keyStack[keyStackDepth-2] != KNETBUNDLE) curGeometryType = GNET;
			if (checkName())
			{
				netReference = objectName;
				netName = objectName;
			}
			exportsOnNet = new HashSet<String>();
			arcsOnNet = new ArrayList<ArcInst>();
			nodesOnNet = new ArrayList<NodeInst>();
		}

		protected void pop()
		{
			// move the property list to the free list
			for (EDIFProperty property : propertiesList)
			{
				if (curArc != null)
				{
					String objStr = property.val.toString();
					Object newV = stripPercentEscapes(objStr);
					if (isSpiceParameter(objStr))
		                newV = Variable.withCode(newV, CodeExpression.Code.SPICE);
					curArc.newVar(stripPercentEscapes(property.name), newV, ep);
				}
			}
			propertiesList = new ArrayList<EDIFProperty>();
			netReference = "";
			curArc = null;
			if (curGeometryType != GBUS) curGeometryType = GUNKNOWN;
			freeSavedPointList();
			netPortRefs.clear();

			// make sure all exports are on some arc
			for(String exportName : exportsOnNet)
			{
				ArcInst unNamedArc = null;
				boolean nameFound = false;
				for(ArcInst ai : arcsOnNet)
				{
					String arcName = ai.getName();
					if (arcName.equals(exportName)) nameFound = true;
					if (ai.getNameKey().isTempname()) unNamedArc = ai;
				}
				if (nameFound) continue;
				if (unNamedArc != null)
				{
					unNamedArc.setName(exportName, ep);
					if (!localPrefs.showArcNames)
					{
						TextDescriptor td = unNamedArc.getTextDescriptor(ArcInst.ARC_NAME);
						td = td.withDisplay(TextDescriptor.Display.NONE);
						unNamedArc.setTextDescriptor(ArcInst.ARC_NAME, td);
					}
				} else
				{
					if (arcsOnNet.size() > 0)
					{
						// two names on one arc: duplicate the arc
						ArcInst ai = arcsOnNet.get(0);
						ArcInst second = ArcInst.newInstanceBase(ai.getProto(), ep, ai.getLambdaBaseWidth(),
							ai.getHeadPortInst(), ai.getTailPortInst(), ai.getHeadLocation(), ai.getTailLocation(),
							exportName, ai.getAngle(), ai.getD().flags);
						TextDescriptor td = ep.getArcTextDescriptor().withOff(0, -1);
						if (!localPrefs.showArcNames)
							td = td.withDisplay(TextDescriptor.Display.NONE);
						second.setTextDescriptor(ArcInst.ARC_NAME, td);
					} else if (nodesOnNet.size() > 0)
					{
						// find port
						for(PlannedPort plp : plannedPorts)
						{
							if (plp.name.equalsIgnoreCase(exportName) || plp.alternateName.equalsIgnoreCase(exportName))
							{
								if (plp.alreadyThere == null)
									plp.alreadyThere = nodesOnNet.get(0);
								break;
							}
						}
					}
				}
			}
			exportsOnNet = null;
			arcsOnNet = null;
			nodesOnNet = null;
		}
	}

	private class KeyNetBundle extends EDIFKEY
	{
		private KeyNetBundle() { super("netBundle"); }
		protected void push()
			throws IOException
		{
			curGeometryType = GBUS;
			bundleReference = "";
			objectName = "";   originalName = "";
			isArray = false;
			if (checkName())
			{
				bundleReference = objectName;
				bundleName = objectName;
			}
		}

		protected void pop()
		{
			bundleReference = "";
			curArc = null;
			curGeometryType = GUNKNOWN;
			freeSavedPointList();
		}
	}

	private class KeyNumber extends EDIFKEY
	{
		private KeyNumber() { super("number"); }
		protected void push()
			throws IOException
		{
			propertyValue = Double.valueOf(getNumber());
		}
	}

	private class KeyOpenShape extends EDIFKEY
	{
		private KeyOpenShape() { super("openShape"); }
		protected void push()
		{
			freePointList();
			curOrientation = OR0;
		}

		protected void pop()
		{
			doPoly();
		}
	}

	private class KeyOrientation extends EDIFKEY
	{
		private KeyOrientation() { super("orientation"); }
		protected void push()
			throws IOException
		{
			// get the orientation keyword
			String orient = getToken((char)0);

			if (orient.equalsIgnoreCase("R0")) curOrientation = OR0;
			else if (orient.equalsIgnoreCase("R90")) curOrientation = OR90;
			else if (orient.equalsIgnoreCase("R180")) curOrientation = OR180;
			else if (orient.equalsIgnoreCase("R270")) curOrientation = OR270;
			else if (orient.equalsIgnoreCase("MY")) curOrientation = OMY;
			else if (orient.equalsIgnoreCase("MX")) curOrientation = OMX;
			else if (orient.equalsIgnoreCase("MYMX")) curOrientation = OMYMX;
			else if (orient.equalsIgnoreCase("MYR90")) curOrientation = OMYR90;
			else if (orient.equalsIgnoreCase("MXR90")) curOrientation = OMXR90;
			else
			{
				System.out.println("Warning, line " + lineReader.getLineNumber() + ": unknown orientation value <" + orient + ">");
				warningCount++;
			}
		}
	}

	private class KeyPath extends EDIFKEY
	{
		private KeyPath() { super("path"); }
		protected void push()
		{
			freePointList();
			curOrientation = OR0;
		}

		protected void pop()
		{
			if (curCell == null) return;

			// check for openShape type path
			if (pathWidth == 0 && curGeometryType != GNET && curGeometryType != GBUS)
			{
				doPoly();
				return;
			}
			List<PortInst> fList = new ArrayList<PortInst>();
			NodeProto np = Schematics.tech().wirePinNode;
			if (curGeometryType == GBUS || isArray) np = Schematics.tech().busPinNode;
			if (curGeometryType == GNET)
			{
				if (isBusName(netName))
					np = Schematics.tech().busPinNode;
			}

			if (curGeometryType == GNET || curGeometryType == GBUS)
			{
				for(int i=0; i<curPoints.size()-1; i++)
				{
					Point2D fPoint = curPoints.get(i);
					Point2D tPoint = curPoints.get(i+1);
					double fX = fPoint.getX();   double fY = fPoint.getY();
					double tX = tPoint.getX();   double tY = tPoint.getY();
					if (curCellPage > 0)
					{
						fY += (curCellPage-1) * Cell.FrameDescription.MULTIPAGESEPARATION;
						tY += (curCellPage-1) * Cell.FrameDescription.MULTIPAGESEPARATION;
					}
					// create a pin to pin connection
					if (fList.size() == 0)
					{
						// look for the first pin
						fList = findEDIFPort(curCell, fX, fY, Schematics.tech().wire_arc);
						if (fList.size() == 0)
						{
							// check for NodeEquivalences with translated ports
							for (PortInst pi : netPortRefs)
							{
								EDIFEquiv.NodeEquivalence ne = equivs.getNodeEquivalence(pi.getNodeInst());
								if (ne != null)
								{
									Point2D curPoint = new Point2D.Double(fX, fY);
									String orientation = com.sun.electric.tool.io.output.EDIF.getOrientation(pi.getNodeInst(), 0);
									String port = ne.getPortEquivElec(pi.getPortProto().getName()).getExtPort().name;
									curPoint = equivs.translatePortConnection(curPoint, ne.externalLib, ne.externalCell,
										ne.externalView, port, orientation);
									if ((curPoint.getX() != fX) || (curPoint.getY() != fY))
									{
										fList = findEDIFPort(curCell, curPoint.getX(), curPoint.getY(), Schematics.tech().wire_arc);
										if (fList.size() != 0)
										{
											// found exact match, move port
											fX = curPoint.getX();
											fY = curPoint.getY();
											break;
										}
									}
								}
							}
						}
						if (fList.size() == 0)
						{
							// create the "from" pin
							NodeInst ni = placePin(np, fX, fY, np.getDefWidth(ep), np.getDefHeight(ep), Orientation.IDENT, curCell);
							if (ni != null) fList.add(ni.getOnlyPortInst());
						}
					}
					// now the second ...
					List<PortInst> tList = findEDIFPort(curCell, tX, tY, Schematics.tech().wire_arc);
					if (tList.size() == 0)
					{
						// check for NodeEquivalences with translated ports
						for (PortInst pi : netPortRefs)
						{
							EDIFEquiv.NodeEquivalence ne = equivs.getNodeEquivalence(pi.getNodeInst());
							if (ne != null)
							{
								Point2D curPoint = new Point2D.Double(tX, tY);
								String orientation = com.sun.electric.tool.io.output.EDIF.getOrientation(pi.getNodeInst(), 0);
								String port = ne.getPortEquivElec(pi.getPortProto().getName()).getExtPort().name;
								curPoint = equivs.translatePortConnection(curPoint, ne.externalLib, ne.externalCell,
									ne.externalView, port, orientation);
								if (curPoint.getX() != tX || curPoint.getY() != tY)
								{
									tList = findEDIFPort(curCell, curPoint.getX(), curPoint.getY(), Schematics.tech().wire_arc);
									if (tList.size() != 0)
									{
										// found exact match, move port
										tX = curPoint.getX();
										tY = curPoint.getY();
										break;
									}
								}
							}
						}
					}
					if (tList.size() == 0)
					{
						// create the "to" pin
						if (curCellPage > 0) tY += (curCellPage-1) * Cell.FrameDescription.MULTIPAGESEPARATION;
						NodeInst ni = placePin(np, tX, tY, np.getDefWidth(ep), np.getDefHeight(ep), Orientation.IDENT, curCell);
						if (ni != null) tList.add(ni.getOnlyPortInst());
					}
					if (fList.size() == 0 || tList.size() == 0)
					{
						System.out.println("Error, line " + lineReader.getLineNumber() + ": could not create path");
						errorCount++;
					} else
					{
						// connect it
						ArcInst ai = null;
						PortInst fPi = null;
						PortInst tPi = null;
						for (int count = 0; count < fList.size() || count < tList.size(); count++)
						{
							boolean fbus = false;
							boolean tbus = false;
							if (count < fList.size())
							{
								fPi = fList.get(count);

								// check node for array variable
								NodeInst ni = fPi.getNodeInst();
								Variable var = ni.getVar("EDIF_array");
								if (var != null) fbus = true; else
								{
									if (isBusName(fPi.getPortProto().getName())) fbus = true;
								}
							}
							if (count < tList.size())
							{
								tPi = tList.get(count);

								// check node for array variable
								NodeInst ni = tPi.getNodeInst();
								Variable var = ni.getVar("EDIF_array");
								if (var != null) tbus = true; else
								{
									if (isBusName(tPi.getPortProto().getName())) tbus = true;
								}
							}

							// if bus to bus
							ArcProto ap = Schematics.tech().wire_arc;
							if ((fPi.getPortProto() == defaultBusPort || fbus) &&
								(tPi.getPortProto() == defaultBusPort || tbus))
							{
								ap = Schematics.tech().bus_arc;
							} else
							{
								if (curGeometryType == GNET)
								{
									if (isBusName(netName)) ap = Schematics.tech().bus_arc;
								} else if (curGeometryType == GBUS)
								{
									if (isBusName(bundleName)) ap = Schematics.tech().bus_arc;
								}
							}
							Point2D fromPoint = new Point2D.Double(fX, fY);
							if (!fPi.getPoly().contains(fromPoint)) fromPoint = fPi.getPoly().getCenter();
							Point2D toPoint = new Point2D.Double(tX, tY);
							if (!tPi.getPoly().contains(toPoint)) toPoint = tPi.getPoly().getCenter();

							// check for wire connecting to a bus port: insert a Join
							if (ap == Schematics.tech().wire_arc)
							{
								if (isBusName(fPi.getPortProto().getName()))
								{
									PrimitiveNode joinNode = Schematics.tech().wireConNode;
									Point2D ctrPoint = new Point2D.Double(fromPoint.getX() + (toPoint.getX()-fromPoint.getX())/3,
										fromPoint.getY() + (toPoint.getY()-fromPoint.getY())/3);
									NodeInst ni = NodeInst.makeInstance(joinNode, ep, ctrPoint,
										joinNode.getDefWidth(ep), joinNode.getDefHeight(ep), curCell);
									PortInst newFrom = ni.getOnlyPortInst();
									ArcInst.makeInstance(Schematics.tech().bus_arc, ep, fPi, newFrom);
									fPi = newFrom;
									fromPoint = ctrPoint;
								}
								if (isBusName(tPi.getPortProto().getName()))
								{
									PrimitiveNode joinNode = Schematics.tech().wireConNode;
									Point2D ctrPoint = new Point2D.Double(toPoint.getX() + (fromPoint.getX()-toPoint.getX())/3,
										toPoint.getY() + (fromPoint.getY()-toPoint.getY())/3);
									NodeInst ni = NodeInst.makeInstance(joinNode, ep, ctrPoint,
										joinNode.getDefWidth(ep), joinNode.getDefHeight(ep), curCell);
									PortInst newFrom = ni.getOnlyPortInst();
									ArcInst.makeInstance(Schematics.tech().bus_arc, ep, tPi, newFrom);
									tPi = newFrom;
									toPoint = ctrPoint;
								}
							} else if (ap == Schematics.tech().bus_arc)
							{
								if (fPi.getNodeInst().getProto() == Schematics.tech().wirePinNode)
								{
									NodeInst nowBus = fPi.getNodeInst().replace(Schematics.tech().busPinNode, ep, true, true, false);
									fPi = nowBus.getOnlyPortInst();
								}
								if (tPi.getNodeInst().getProto() == Schematics.tech().wirePinNode)
								{
									NodeInst nowBus = tPi.getNodeInst().replace(Schematics.tech().busPinNode, ep, true, true, false);
									tPi = nowBus.getOnlyPortInst();
								}
							}
							if (fPi != tPi)
							{
								ai = ArcInst.makeInstance(ap, ep, fPi, tPi, fromPoint, toPoint, null);
								if (ai == null)
								{
									System.out.println("Error, line " + lineReader.getLineNumber() + ": could not create path (arc)");
									errorCount++;
								} else
								{
									if (arcsOnNet != null) arcsOnNet.add(ai);
									if (curGeometryType == GNET && i == 0)
									{
										if (netReference.length() > 0) ai.newVar("EDIF_name", stripPercentEscapes(netReference), ep);
										if (netName.length() > 0) putNameOnArcOnce(ai, convertParens(netName));
									} else if (curGeometryType == GBUS && i == 0)
									{
										if (bundleReference.length() > 0) ai.newVar("EDIF_name", stripPercentEscapes(bundleReference), ep);
										if (bundleName.length() > 0) putNameOnArcOnce(ai, convertParens(bundleName));
									}
								}
							}
						}
						if (ai != null) curArc = ai;
						fList = tList;
					}
//				else
//				{
//					// rectilinear paths with some width
//					// create a path from here to there (orthogonal only now)
//					boolean verOrHor = false;
//					if (fY == tY || extendEnd)
//					{
//						fY -= pathWidth/2;
//						tY += pathWidth/2;
//						verOrHor = true;
//					}
//					if (fX == tX || extendEnd)
//					{
//						fX -= pathWidth/2;
//						tX += pathWidth/2;
//						verOrHor = true;
//					}
//					// Only when the line is horizontal or vertical -> create thin box
//					NodeInst ni = null;
//					if (verOrHor)
//					{
//						double cY = (fY+tY)/2;
//						double cX = (fX+tX)/2;
//						if (curCellPage > 0) cY += (curCellPage-1) * Cell.FrameDescription.MULTIPAGESEPARATION;
//						ni = placePin(curFigureGroup, cX, cY, Math.abs(fX-tX), Math.abs(fY-tY), curOrientation, curCell);
//					}
//					if (ni == null)
//					{
//						System.out.println("Error, line " + lineReader.getLineNumber() + ": could not create path");
//						errorCount++;
//					}
//				}
				}
			}
			else
			{
				// treating the information as for an open polygon
				NodeInst bbNi = NodeInst.newInst(Artwork.tech().openedThickerPolygonNode, ep, new Point2D.Double(0,5), 10, 10, curCell);
				EPoint[] boxOutline = new EPoint[curPoints.size()];
				for (int i=0; i<curPoints.size(); i++)
				{
					Point2D fPoint = curPoints.get(i);
					boxOutline[i] = EPoint.fromLambda(fPoint.getX(), fPoint.getY());
				}
	            bbNi.setTrace(boxOutline);
			}
			freePointList();
		}
	}

	private class KeyPathWidth extends EDIFKEY
	{
		private KeyPathWidth() { super("pathWidth"); }
		protected void push()
			throws IOException
		{
			// get the width string
			String width = getToken((char)0);
			pathWidth = TextUtils.atoi(width) * localPrefs.inputScale;
		}
	}

	private class KeyPolygon extends EDIFKEY
	{
		private KeyPolygon() { super("polygon"); }
		protected void push()
			throws IOException
		{
			freePointList();
			curOrientation = OR0;
		}

		protected void pop()
		{
			doPoly();
		}
	}

	private class KeyPort extends EDIFKEY
	{
		private KeyPort() { super("port"); }
		protected void push()
			throws IOException
		{
			objectName = "";   originalName = "";
			curDirection = PortCharacteristic.IN;
			portReference = "";
			isArray = false;
			arrayXVal = arrayYVal = 1;
			if (checkName())
			{
				portReference = objectName;
				portName = objectName;
			}
		}

		protected void pop()
		{
			PlannedPort plp = addPlannedPort(portName, false);
			if (plp == null) return; // skipping port
			
			plp.alternateName = objectName;
			plp.direction = curDirection;
			plp.knownLocation = false;
			plp.x = plp.y = 0;
			if (curCellPage > 0) plp.y += (curCellPage-1) * Cell.FrameDescription.MULTIPAGESEPARATION;
			for(EDIFProperty property : propertiesList) plp.properties.add(property);
			portReference = "";
			propertiesList = new ArrayList<EDIFProperty>();
		}
	}

	private class KeyPortImplementation extends EDIFKEY
	{
		private KeyPortImplementation() { super("portImplementation"); }
		protected void push()
			throws IOException
		{
			// set the current geometry type
			freePointList();
			cellRefProto = Schematics.tech().busPinNode;
			curGeometryType = GPORTIMPLEMENTATION;
			curOrientation = OR0;
			isArray = false;
			arrayXVal = arrayYVal = 1;
			objectName = "";   originalName = "";
			checkName();
		}

		protected void pop()
		{
			curGeometryType = GUNKNOWN;
		}
	}

	private class KeyPortList extends EDIFKEY
	{
		private KeyPortList() { super("portList"); }
		protected void push()
		{
			curPortlist = new ArrayList<Network>();
		}

		protected void pop()
		{
			if (keyStack[keyStackDepth-1] == KJOINED && curPortlist != null)
			{
				if (lastPortlist != null && curPortlist.size() > 0)
				{
					// connect two busses
					if (lastPortlist.size() != curPortlist.size())
					{
						String errorMsg = "Error, line " + lineReader.getLineNumber() +
							": net " + netName + " joins portlists with different length (" + lastPortlist.size() +
							" and "+curPortlist.size() + ") in cell " + curCell;
						System.out.println(errorMsg);
					}
				}
				lastPortlist = curPortlist;
			}
		}
	}

	private class KeyPortRef extends EDIFKEY
	{
		private KeyPortRef() { super("portRef"); }
		protected void push()
			throws IOException
		{
			portReference = "";
			instanceReference = "";
			if (checkName())
			{
				portReference = objectName;
			}
		}

		protected void pop()
		{
			// must have gathered a port reference
			if (portReference.length() <= 0) return;

			// check for the last pin
			NodeInst fNi = curNode;
			PortProto fPp = curPort;

			// For internal pins of an instance, determine the base port location and other pin assignments
			ArcProto ap = Generic.tech().unrouted_arc;
			NodeInst ni = null;
			Nodable no = null;
			PortProto pp = null;
			if (instanceReference.length() > 0)
			{
				String nodeName = instanceReference;
				String alternateInstName = renamedObjects.get(nodeName);
				if (alternateInstName != null) nodeName = alternateInstName;

				// locate the node and and port
				if (activeView == VNETLIST)
				{
					// scan all pages for this nodeinst
					for(Iterator<Cell> it = curLibrary.getCells(); it.hasNext(); )
					{
						Cell cell = it.next();
						if (!cell.getName().equalsIgnoreCase(cellName)) continue;
						for(Iterator<NodeInst> nIt = cell.getNodes(); nIt.hasNext(); )
						{
							NodeInst oNi = nIt.next();
							Variable var = oNi.getVar("EDIF_name");
							if (var != null && var.getPureValue(-1).equalsIgnoreCase(nodeName)) { ni = oNi;   break; }
							String name = oNi.getName();
							if (name.equalsIgnoreCase(nodeName)) { ni = oNi;   break; }
						}
						if (ni != null) break;
					}
					if (ni == null)
					{
						System.out.println("error, line " + lineReader.getLineNumber() + ": could not locate netlist node (" + nodeName + ")");
						return;
					}
				} else if (curCell != null)
				{
					// net always references the current page
					for(Iterator<NodeInst> nIt = curCell.getNodes(); nIt.hasNext(); )
					{
						NodeInst oNi = nIt.next();
						Variable var = oNi.getVar("EDIF_name");
						if (var != null && var.getPureValue(-1).equalsIgnoreCase(nodeName)) { ni = oNi;   break; }
						String name = oNi.getName();
						if (name.equalsIgnoreCase(nodeName)) { ni = oNi;   break; }
					}
					if (ni == null)
					{
						// might be an instance of an arrayed node
						for(Iterator<Nodable> nIt = curCell.getNetlist().getNodables(); nIt.hasNext(); )
						{
							Nodable oNo = nIt.next();
							if (oNo.getName().equalsIgnoreCase(nodeName))
							{
								no = oNo;
								ni = no.getNodeInst();
								break;
							}
						}
					}
					if (ni == null)
					{
						System.out.println("error, line " + lineReader.getLineNumber() + ": could not locate schematic node '" +
							nodeName + "' in " + curCell);
						return;
					}
					if (!isArray) ap = Schematics.tech().wire_arc; else
						ap = Schematics.tech().bus_arc;
				}

				// locate the port for this portref
				pp = ni.getProto().findPortProto(portReference);
				if (pp == null)
				{
					String alternateName = renamedObjects.get(portReference);
					if (alternateName != null)
					{
						alternateName = convertParens(alternateName);
						pp = ni.getProto().findPortProto(alternateName);
					}
					if (pp == null)
					{
						EDIFEquiv.NodeEquivalence ne = mappedNodes.get(nodeName);
						if (ne != null)
						{
							for(EDIFEquiv.PortEquivalence pe : ne.portEquivs)
							{
								if (pe.getExtPort().name.equals(portReference))
								{
									pp = ni.getProto().findPortProto(pe.getElecPort().name);
									break;
								}
								if (alternateName != null && pe.getExtPort().name.equals(alternateName))
								{
									pp = ni.getProto().findPortProto(pe.getElecPort().name);
									break;
								}
							}
						}
					}
					if (pp == null)
					{
						String errorMsg = "Error, line " + lineReader.getLineNumber() +
							": could not find port '" + portReference;
						if (alternateName != null) errorMsg += "' or '" + alternateName;
						errorMsg += "' on node '" + nodeName + "' in " + curCell;
						System.out.println(errorMsg);
						return;
					}
				}

//				// special case when connecting an arrayed node to a bus
//				if (no != null && netName.indexOf('[') >= 0)
//				{
//					// run a zero-length bus to a bus pin, then a named arc to a second pin, use the second pin
//					PrimitiveNode busPin = Schematics.tech().busPinNode;
//					PortInst pi = ni.findPortInstFromProto(pp);
//					Poly poly = pi.getPoly();
//					NodeInst ni1 = placePin(busPin, poly.getCenterX()+1, poly.getCenterY(),
//						busPin.getDefaultLambdaBaseWidth(), busPin.getDefaultLambdaBaseHeight(), Orientation.IDENT, curCell);
//					if (ni1 == null)
//					{
//						System.out.println("error, line " + lineReader.getLineNumber() + ": could not create bus pin");
//						return;
//					}
//					PortInst busPinPort = ni1.getOnlyPortInst();
//					boolean found = false;
//					for(Iterator<ArcInst> aIt = curCell.getArcs(); aIt.hasNext(); )
//					{
//						ArcInst ai = aIt.next();
//						if ((ai.getHeadPortInst() == busPinPort && ai.getTailPortInst() == pi) ||
//							(ai.getTailPortInst() == busPinPort && ai.getHeadPortInst() == pi))
//						{
//							found = true;
//							break;
//						}
//					}
//					if (!found) ArcInst.makeInstance(Schematics.tech().bus_arc, busPinPort, pi);
//
//					// now the named wire arc
//					PrimitiveNode wirePin = Schematics.tech().wirePinNode;
//					NodeInst ni2 = NodeInst.makeInstance(wirePin, new Point2D.Double(poly.getCenterX()+1, poly.getCenterY()+1),
//						wirePin.getDefaultLambdaBaseWidth(), wirePin.getDefaultLambdaBaseHeight(), curCell, Orientation.IDENT, null, 0);
//					if (ni2 == null)
//					{
//						System.out.println("error, line " + lineReader.getLineNumber() + ": could not create wire pin");
//						return;
//					}
//					PortInst wirePinPort = ni2.getOnlyPortInst();
//					ArcInst wireArc = ArcInst.makeInstance(Schematics.tech().wire_arc, wirePinPort, busPinPort);
//					int indPos = instanceReference.lastIndexOf('[');
//					int namePos = netName.lastIndexOf('[');
//					if (indPos > 0 && namePos > 0)
//					{
//						String newName = netName.substring(0, namePos) + instanceReference.substring(indPos);
//						wireArc.setName(newName);
//					}
//
//					// remember the end of the named wire arc as the true location of this portRef
//					ni = ni2;
//					pp = wirePinPort.getPortProto();
//					ap = Schematics.tech().wire_arc;
//				}

				// we have both, set global variable
				curNode = ni;
				curPort = pp;
				netPortRefs.add(ni.findPortInstFromProto(pp));

				// create extensions for net labels on single pin nets (externals), and placeholder for auto-routing later
				if (activeView == VNETLIST && curCell != null)
				{
					// route all pins with an extension
					if (ni != null)
					{
						Poly portPoly = ni.findPortInstFromProto(pp).getPoly();
						double hX = portPoly.getCenterX();
						double hY = portPoly.getCenterY();
						double lX = hX;
						double lY = hY;
						if (pp.getCharacteristic() == PortCharacteristic.IN) lX = hX - (INCH/10); else
							if (pp.getCharacteristic() == PortCharacteristic.BIDIR) lY = hY - (INCH/10); else
								if (pp.getCharacteristic() == PortCharacteristic.OUT)
						{
							lX = hX;
							hX = hX + (INCH/10);
						}

						// need to create a destination for the wire
						ArcProto lAp = Schematics.tech().bus_arc;
						PortProto lPp = defaultBusPort;
						NodeInst lNi = null;
						if (isArray)
						{
							lNi = placePin(Schematics.tech().busPinNode, (lX+hX)/2, (lY+hY)/2, hX-lX, hY-lY, Orientation.IDENT, curCell);
							if (lNi == null)
							{
								System.out.println("error, line " + lineReader.getLineNumber() + ": could not create bus pin");
								return;
							}
						} else
						{
							lNi = placePin(Schematics.tech().wirePinNode, (lX+hX)/2, (lY+hY)/2, hX-lX, hY-lY, Orientation.IDENT, curCell);
							if (lNi == null)
							{
								System.out.println("error, line " + lineReader.getLineNumber() + ": could not create wire pin");
								return;
							}
							lPp = defaultPort;
							lAp = Schematics.tech().wire_arc;
						}
						PortInst head = lNi.findPortInstFromProto(lPp);
						PortInst tail = ni.findPortInstFromProto(pp);
						curArc = ArcInst.makeInstance(lAp, ep, head, tail);
						if (curArc == null)
							System.out.println("error, line " + lineReader.getLineNumber() + ": could not create auto-path");
						else
							nameEDIFArc(curArc, true);
					}
				}
			} else
			{
				// external port reference, look for a off-page reference in schematic with this port name
				if (exportsOnNet != null)
				{
					String alternateName = renamedObjects.get(portReference);
					if (alternateName == null) alternateName = portReference;
					exportsOnNet.add(alternateName);
				}
				if (activeView == VNETLIST)
				{
					Cell np = null, npIcon = null;
					for(Iterator<Cell> it = curLibrary.getCells(); it.hasNext(); )
					{
						Cell cell = it.next();
						if (cell.getName().equalsIgnoreCase(cellName))
						{
							if (cell.isSchematic()) np = cell; else
								if (cell.getView() == View.ICON) npIcon = cell; 
						}
					}
					if (np == null && npIcon != null) np = npIcon;
					if (np == null)
					{
						System.out.println("error, line " + lineReader.getLineNumber() + ": could not locate top level schematic");
						return;
					}

					// now look for an instance with the correct port name
					pp = np.findPortProto(portReference);
					if (pp == null)
					{
						if (originalName.length() > 0)
							pp = np.findPortProto(originalName);
						String alternateName = portReference;
						if (pp == null)
						{
							String altName = renamedObjects.get(portReference);
							if (altName != null)
								pp = np.findPortProto(alternateName = altName);
						}
						if (pp == null && curCell != null)
						{
							PlannedPort plp = addPlannedPort(alternateName, false);
							Rectangle2D cellBounds = curCell.getBounds();
							instantiatePlannedPort(plp, cellBounds);
							pp = plp.createdPort;
						}
						if (pp == null)
						{
							System.out.println("Error, line " + lineReader.getLineNumber() + ": could not locate port '" +
								portReference + "' on cell " + np.describe(false));
							return;
						}
					}

					fNi = ((Export)pp).getOriginalPort().getNodeInst();
					fPp = ((Export)pp).getOriginalPort().getPortProto();
					Poly portPoly = fNi.findPortInstFromProto(fPp).getPoly();
					double lX = portPoly.getCenterX();
					double lY = portPoly.getCenterY();

					// determine x position by original placement
					if (pp.getCharacteristic() == PortCharacteristic.IN) lX = 1*INCH; else
						if (pp.getCharacteristic() == PortCharacteristic.BIDIR) lY = 4*INCH; else
							if (pp.getCharacteristic() == PortCharacteristic.OUT) lX = 5*INCH;

					// need to create a destination for the wire
					if (isArray)
					{
						ni = placePin(Schematics.tech().busPinNode, lX, lY,
							Schematics.tech().busPinNode.getDefWidth(ep), Schematics.tech().busPinNode.getDefHeight(ep), Orientation.IDENT, np);
						if (ni == null)
						{
							System.out.println("error, line " + lineReader.getLineNumber() + ": could not create bus pin");
							return;
						}
						pp = defaultBusPort;
					} else
					{
						ni = placePin(Schematics.tech().wirePinNode, lX, lY,
							Schematics.tech().wirePinNode.getDefWidth(ep), Schematics.tech().wirePinNode.getDefHeight(ep), Orientation.IDENT, np);
						if (ni == null)
						{
							System.out.println("error, line " + lineReader.getLineNumber() + ": could not create wire pin");
							return;
						}
						pp = defaultPort;
					}
					if (!isArray) ap = Schematics.tech().wire_arc; else
						ap = Schematics.tech().bus_arc;
				}
			}

			// now connect if we have from node and port
			if (fNi != null && fPp != null)
			{
				if (fNi.getParent() != ni.getParent())
				{
					System.out.println("error, line " + lineReader.getLineNumber() + ": could not create path (arc) between " +
						fNi.getParent() + " and " + ni.getParent());
				} else
				{
					// locate the position of the new ports
					Poly fPortPoly = fNi.findPortInstFromProto(fPp).getPoly();
					double lX = fPortPoly.getCenterX();
					double lY = fPortPoly.getCenterY();
					Poly portPoly = ni.findPortInstFromProto(pp).getPoly();
					double hX = portPoly.getCenterX();
					double hY = portPoly.getCenterY();

					// for nets with no physical representation ...
					curArc = null;
					PortInst head = fNi.findPortInstFromProto(fPp);
					PortInst tail = ni.findPortInstFromProto(pp);
					if (head != tail)
					{
						Point2D headPt = new Point2D.Double(lX, lY);
						Point2D tailPt = new Point2D.Double(hX, hY);
						double dist = 0;
						if (lX != hX || lY != hY) dist = headPt.distance(tailPt);
						if (dist <= 1)
						{
							if (!head.getPortProto().connectsTo(ap) || !tail.getPortProto().connectsTo(ap))
							{
								ArcProto [] headList = head.getPortProto().getBasePort().getConnections();
								for(int i=0; i<headList.length; i++)
								{
									ArcProto apAlt = headList[i];
									if (tail.getPortProto().connectsTo(apAlt))
									{
										ap = apAlt;
										break;
									}
								}
							}
							curArc = ArcInst.makeInstance(ap, ep, head, tail, headPt, tailPt, null);
							if (curArc == null)
							{
								System.out.println("error, line " + lineReader.getLineNumber() + ": could not create path (arc) among cells");
							}
						} else if (activeView == VNETLIST)
						{
							// use unrouted connection for NETLIST views
							curArc = ArcInst.makeInstance(ap, ep, head, tail, headPt, tailPt, null);
							if (curArc == null)
							{
								System.out.println("error, line " + lineReader.getLineNumber() + ": could not create auto-path in portRef");
							}
						}

						// add the net name
						if (curArc != null && no == null)
							nameEDIFArc(curArc, false);
					}
				}
			}
		}
	}

	private class KeyProgram extends EDIFKEY
	{
		private KeyProgram() { super("program"); }
		protected void push()
			throws IOException
		{
			String program = getToken((char)0);
			if (program.substring(1).startsWith("VIEWlogic"))
			{
				curVendor = EVVIEWLOGIC;
			} else if (program.substring(1).startsWith("edifout"))
			{
				curVendor = EVCADENCE;
			}
		}
	}

	private class KeyProperty extends EDIFKEY
	{
		private KeyProperty() { super("property"); }
		protected void push()
			throws IOException
		{
			propertyReference = "";
			objectName = "";   originalName = "";
			propertyValue = null;
			if (checkName())
			{
				propertyReference = objectName;
            }

		}

		private boolean checkValidArrayDescription(Object obj)
		{
			if (!(obj instanceof String)) return true;
			
			String val = (String)obj;
			if (!val.startsWith("[")) 
			{
				if ((!val.contains("@") && (!val.contains("$"))) && val.contains("=")) // contains = but not @ => invalid for Spice
					return false;
				return true; // nothing to check as array
			}
			return (val.charAt(val.length()-1) == ']'); // if starts with "[", must end with "]"
		}
		
		protected void pop()
			throws IOException
		{
			// add as a variable to the current object
			if (keyStack[keyStackDepth - 1] == KINTERFACE)
			{
				// add to the current property list, will be added latter
				EDIFProperty property = new EDIFProperty();
				propertiesList.add(property);
				property.name = "ATTR_" + propertyReference;
				property.val = propertyValue;
				if (!checkValidArrayDescription(propertyValue))
					System.out.println("Warning, line " + lineReader.getLineNumber() + ": badly formed variable <" + propertyValue + ">");

			} else if (keyStack[keyStackDepth - 1] == KINSTANCE || keyStack[keyStackDepth - 1] == KNET ||
				keyStack[keyStackDepth - 1] == KPORT)
			{
				// add to the current property list, will be added latter
				EDIFProperty property = new EDIFProperty();
				propertiesList.add(property);
				property.name = "ATTR_" + propertyReference;
				property.val = propertyValue;
				if (!checkValidArrayDescription(propertyValue))
					System.out.println("Warning, line " + lineReader.getLineNumber() + ": badly formed variable <" + propertyValue + ">");
			} else if (keyStack[keyStackDepth - 1] == KCELL)
			{
				if (isAcceptedParameter(propertyReference, null) && curCell != null && curCell.getCellGroup() != null)
				{
					TextDescriptor td = ep.getCellTextDescriptor().withDispPart(TextDescriptor.DispPos.NAMEVALUE).
						withInherit(true).withParam(true).withDispPart(TextDescriptor.DispPos.NAMEVALUE);
					Variable param = Variable.newInst(Variable.newKey("ATTR_" + propertyReference), propertyValue, td);
					curCell.getCellGroup().addParam(param);
				}
			}
			if (curActiveFigure != null)
			{
				if (propertyReference.equals("layerNumber"))
				{
					String value = propertyValue.toString();
					if (TextUtils.isANumber(value))
					{
						int layerNum = TextUtils.atoi(value);
						for (Map.Entry<Layer,String> e : curTech.getGDSLayers().entrySet())
						{
							Layer layer = e.getKey();
							String gdsLayer = e.getValue();
							if (layerNum != TextUtils.atoi(gdsLayer)) continue;
							PrimitiveNode pure = layer.getPureLayerNode();
							if (pure == null) continue;
							curActiveFigure.node = pure;
							break;
						}
					}
				}
			}
			propertyReference = "";
			freeSavedPointList();
		}
	}

	private class KeyProtectionFrame extends EDIFKEY
	{
		private KeyProtectionFrame() { super("protectionFrame"); }
		protected void push()
		{
			curGeometryType = GPROTECTIONFRAME;
		}

		protected void pop()
		{
			curGeometryType = GUNKNOWN;
		}
	}

	private class KeyPt extends EDIFKEY
	{
		private KeyPt() { super("pt"); }
		protected void push()
			throws IOException
		{
			// get the x and y values of the point
			String xStr = getToken((char)0);
			if (xStr == null) throw new IOException("Unexpected end-of-file");
			String yStr = getToken((char)0);
			if (yStr == null) throw new IOException("Unexpected end-of-file");

			if (keyStackDepth > 1 && keyStack[keyStackDepth-1] == KDELTA)
			{
				double x = TextUtils.atof(xStr) * localPrefs.inputScale;
				double y = TextUtils.atof(yStr) * localPrefs.inputScale;

				// transform the points per orientation
				if (curOrientation == OR90)   { double s = x;   x = -y;  y = s; } else
				if (curOrientation == OR180)  { x = -x;  y = -y; } else
				if (curOrientation == OR270)  { double s = x;   x = y;   y = -s; } else
				if (curOrientation == OMY)    { x = -x; } else
				if (curOrientation == OMX)    { y = -y; } else
				if (curOrientation == OMYR90) { double s = y;   y = -x;  x = -s; } else
				if (curOrientation == OMXR90) { double s = x;   x = y;   y = s; }

				// set the array deltas
				if (!deltaPointsSet)
				{
					deltaPointXX = x;
					deltaPointXY = y;
				} else
				{
					deltaPointYX = x;
					deltaPointYY = y;
				}
				deltaPointsSet = true;
			} else
			{
				// allocate a point to read
				Point2D point = new Point2D.Double(TextUtils.atof(xStr) * localPrefs.inputScale, TextUtils.atof(yStr) * localPrefs.inputScale);

				// add it to the list of points
				curPoints.add(point);
			}
		}
	}

	private class KeyRectangle extends EDIFKEY
	{
		private KeyRectangle() { super("rectangle"); }
		protected void push()
		{
			freePointList();
			curOrientation = OR0;
		}

		protected void pop()
		{
			if (curCell == null)
			{
				freePointList();
				return;
			}
			if (keyStackDepth > 1 && (keyStack[keyStackDepth-1] == KPAGESIZE || keyStack[keyStackDepth-1] == KBOUNDINGBOX))
			{
				freePointList();
				return;
			}
			if (curPoints.size() == 2)
			{
				// create the node instance
				Point2D p0 = curPoints.get(0);
				Point2D p1 = curPoints.get(1);
				double hX = p1.getX();
				double lX = p0.getX();
				if (p0.getX() > p1.getX())
				{
					lX = p1.getX();
					hX = p0.getX();
				}
				double hY = p1.getY();
				double lY = p0.getY();
				if (p0.getY() > p1.getY())
				{
					lY = p1.getY();
					hY = p0.getY();
				}
				double sX = hX - lX;
				double sY = hY - lY;
				double yPos = (lY+hY)/2;
				double xPos = (lX+hX)/2;
				if (curCellPage > 0) yPos += (curCellPage-1) * Cell.FrameDescription.MULTIPAGESEPARATION;
				if (curGeometryType == GPROTECTIONFRAME)
				{
					// inside a protection frame
					PrimitiveNode eb = Generic.tech().essentialBoundsNode;
					double wid = eb.getDefWidth(ep);
					double hei = eb.getDefHeight(ep);
					NodeInst ni1 = NodeInst.makeInstance(eb, ep, new Point2D.Double(hX, hY), wid, hei, curCell, Orientation.IDENT, null);
					NodeInst ni2 = NodeInst.makeInstance(eb, ep, new Point2D.Double(lX, hY), wid, hei, curCell, Orientation.R, null);
					NodeInst ni3 = NodeInst.makeInstance(eb, ep, new Point2D.Double(lX, lY), wid, hei, curCell, Orientation.RR, null);
					NodeInst ni4 = NodeInst.makeInstance(eb, ep, new Point2D.Double(hX, lY), wid, hei, curCell, Orientation.RRR, null);
					if (ni1 != null) ni1.setHardSelect();
					if (ni2 != null) ni2.setHardSelect();
					if (ni3 != null) ni3.setHardSelect();
					if (ni4 != null) ni4.setHardSelect();
				} else
				{
					NodeInst ni = NodeInst.makeInstance(curFigureGroup != null ? curFigureGroup : Artwork.tech().boxNode, ep,
						new Point2D.Double(xPos, yPos), sX, sY, curCell, curOrientation, null);
					if (ni == null)
					{
						System.out.println("Error, line " + lineReader.getLineNumber() + ": could not create rectangle");
						errorCount++;
					} else if (curFigureGroup == Artwork.tech().openedDottedPolygonNode)
					{
						EPoint [] pts = new EPoint[5];
						pts[0] = EPoint.fromLambda(p0.getX(), p0.getY());
						pts[1] = EPoint.fromLambda(p0.getX(), p1.getY());
						pts[2] = EPoint.fromLambda(p1.getX(), p1.getY());
						pts[3] = EPoint.fromLambda(p1.getX(), p0.getY());
						pts[4] = EPoint.fromLambda(p0.getX(), p0.getY());

						// store the trace information
						ni.setTrace(pts);
					} else if (curGeometryType == GPORTIMPLEMENTATION)
					{
						// inside a PortImplementation
						String exportName = renamedObjects.get(objectName);
						if (exportName == null) exportName = objectName;
						PlannedPort plp = addPlannedPort(exportName, false);
						if (plp != null)
						{
							plp.pinType = cellRefProto;
							if (!plp.knownLocation)
							{
								plp.knownLocation = true;
								plp.x = (lX+hX) / 2;
								plp.y = (lY+hY) / 2;
							}
							plp.sX = sX;
							plp.sY = sY;
							if (curCellPage > 0) plp.y += (curCellPage-1) * Cell.FrameDescription.MULTIPAGESEPARATION;
							if (Job.getDebug())
								if (plp.orientation != null && plp.orientation != saveOrientation)
									System.out.println("Overwritting previous definition");
							plp.orientation = saveOrientation;
						}
					} else if (nodesOnNet != null)
					{
						nodesOnNet.add(ni);
					}
				}
			}
			freePointList();
		}
	}

	private class KeyCommentGraphics extends EDIFKEY
	{
		private KeyCommentGraphics() { super("commentGraphics"); }

		protected void pop()
		{
			if (commentGraphicsText.startsWith("[") || saveTextPoints.size() != 1)
				return; // ignore it
			
			Point2D fPoint = saveTextPoints.get(0);
			double cX = fPoint.getX();   double cY = fPoint.getY();
			NodeInst ni = placePin(curFigureGroup, cX, cY, 0, 0, saveOrientation, curCell);
			if (ni == null)
			{
				System.out.println("Cannot create pin in commentGraphics");
				return;
			}
			Variable var = ni.newDisplayVar(Artwork.ART_MESSAGE, commentGraphicsText, ep);
			TextDescriptor td = ni.getTextDescriptor(var.getKey());
			TextDescriptor.Rotation r = TextDescriptor.Rotation.getRotation(saveOrientation.getAngle()/10);
			td = td.withRelSize(saveTextHeight).withRotation(r); //.withPos(saveTextJustification);
			ni.setTextDescriptor(var.getKey(), td);
		}
	}
	
	private class KeyRename extends EDIFKEY
	{
		private KeyRename() { super("rename"); }
		protected void push()
			throws IOException
		{
			// get the name of the object
			String aName = getToken((char)0);
			objectName = aName;

			// and the original name
			positionToNextToken();
			char chr = inputBuffer.charAt(inputBufferPos);
			if (chr == '(')
			{
				// must be stringDisplay, copy name to original
				originalName = objectName;
			} else
			{
				aName = getToken((char)0);

				// copy name without quotes
				originalName = aName.substring(1, aName.length()-1);
			}
			originalName = fixAngleBracketBusses(fixLeadingAmpersand(originalName));
			int kPtr = keyStackDepth;
			if (keyStack[keyStackDepth - 1] == KNAME) kPtr = keyStackDepth - 1;
			if (keyStack[kPtr - 1] == KARRAY) kPtr = kPtr - 2; else
				kPtr = kPtr -1;
			if (keyStack[kPtr] == KCELL)
			{
				cellName = cellReference = originalName;
			} else if (keyStack[kPtr] == KPORT)
			{
				// convert <> to [] in port names
				portReference = portName = originalName;
			} else if (keyStack[kPtr] == KINSTANCE)
			{
				instanceReference = instanceName = originalName;
			} else if (keyStack[kPtr] == KNETBUNDLE)
			{
				bundleReference = bundleName = originalName;
			} else if (keyStack[kPtr] == KNET)
			{
				netReference = netName = originalName;
			} else if (keyStack[kPtr] == KPROPERTY)
			{
				propertyReference = objectName;
			}
			if (!objectName.equals(originalName))
				renamedObjects.put(objectName, originalName);
		}
	}

	private class KeyString extends EDIFKEY
	{
		private KeyString() { super("string"); }
		protected void push()
			throws IOException
		{
			positionToNextToken();
			char chr = inputBuffer.charAt(inputBufferPos);

			if (chr != '(' && chr != ')')
			{
				String value = getToken((char)0);
				if (value == null) throw new IOException("Unexpected end-of-file");

				propertyValue = stripPercentEscapes(value.substring(1, value.length()-1));
			}
		}
	}

	private class KeyStringDisplay extends EDIFKEY
	{
		private KeyStringDisplay() { super("stringDisplay"); }
		protected void push()
			throws IOException
		{
			// init the point lists
			freePointList();
			saveOrientation = curOrientation = OR0;
			textJustification = TextDescriptor.Position.DOWNRIGHT;
			textHeight = 0;

			// get the string, remove the quote
			getDelimeter('\"');
			textString = getToken('\"');
			if (textString == null) throw new IOException("Unexpected end-of-file");

			// check for RENAME
			if (keyStack[keyStackDepth-1] != KRENAME && 
					keyStack[keyStackDepth-1] != KANNOTATE) return;
			originalName = fixAngleBracketBusses(textString);
			int kPtr = keyStackDepth - 2;
			if (keyStack[keyStackDepth - 2] == KARRAY) kPtr = keyStackDepth - 3;
			if (keyStack[kPtr] == KCELL)
			{
				cellName = fixLeadingAmpersand(originalName);
			} else if (keyStack[kPtr] == KPORT)
			{
				portName = fixLeadingAmpersand(originalName);
			} else if (keyStack[kPtr] == KINSTANCE)
			{
				instanceName = originalName;
			} else if (keyStack[kPtr] == KNET)
			{
				netName = fixLeadingAmpersand(originalName);
			} else if (keyStack[kPtr] == KNETBUNDLE)
			{
				bundleName = originalName;
			} else if (keyStack[kPtr] == KCOMMENTGRAPHICS)
			{
				commentGraphicsText = originalName;
			}
		}

		protected void pop()
		{
			if (keyStackDepth <= 1)
			{
				System.out.println("Error, line " + lineReader.getLineNumber() + ": bad location for \"stringDisplay\"");
				errorCount++;
			} else if (keyStack[keyStackDepth-1] == KRENAME || keyStack[keyStackDepth-1] == KANNOTATE)
			{
				// save the data and break
				freeSavedPointList();
				textString = "";
				saveTextPoints = curPoints;
				curPoints = new ArrayList<Point2D>();
				saveTextHeight = textHeight;
				textHeight = 0;
				saveTextJustification = textJustification;
				textJustification = TextDescriptor.Position.DOWNRIGHT;
				saveOrientation = curOrientation;
				curOrientation = OR0;
			}

//			// output the data for annotate (display graphics) and string (on properties)
//			else if (keyStack[keyStackDepth-1] == KANNOTATE || keyStack[keyStackDepth-1] == KSTRING)
//			{
//				// Suppress this if it starts with "["
//				if (!textString.startsWith("[") && curPoints.size() != 0)
//				{
//					// see if a pre-existing node or arc exists to add text
//					ArcInst ai = null;
//					NodeInst ni = null;
//					String key = propertyReference;
//					Point2D p0 = curPoints.get(0);
//					double xOff = 0, yOff = 0;
//					if (propertyReference.length() > 0 && curNode != null)
//					{
//						ni = curNode;
//						xOff = p0.getX() - ni.getAnchorCenterX();
//						yOff = p0.getY() - ni.getAnchorCenterY();
//					}
//					else if (propertyReference.length() > 0 && curArc != null)
//					{
//						ai = curArc;
//						xOff = p0.getX() - (ai.getHeadLocation().getX() + ai.getTailLocation().getX()) / 2;
//						yOff = p0.getY() - (ai.getHeadLocation().getY() + ai.getTailLocation().getY()) / 2;
//					} else
//					{
//						// create the node instance
//						double xPos = p0.getX();
//						double yPos = p0.getY();
//						if (curCellPage > 0) yPos += (curCellPage-1) * Cell.FrameDescription.MULTIPAGESEPARATION;
//						ni = NodeInst.makeInstance(Generic.tech().invisiblePinNode, new Point2D.Double(xPos, yPos), 0, 0, curCell);
//						key = Artwork.ART_MESSAGE.getName(); // "EDIF_annotate";
//					}
//					if (ni != null || ai != null)
//					{
//						Variable.Key varKey = Variable.newKey(key);
//						// determine the size of text, 0.0278 in == 2 points or 36 (2xpixels) == 1 in fonts range from 4 to 31
//						double relSize = convertTextSize(textHeight);
//						if (ni != null)
//						{
//							TextDescriptor td = TextDescriptor.getNodeTextDescriptor();
//							// now set the position, relative to the center of the current object
//							xOff = p0.getX() - ni.getAnchorCenterX();
//							yOff = p0.getY() - ni.getAnchorCenterY();
//							td = td.withDisplay(true).withRelSize(relSize).withPos(textJustification).withOff(xOff, yOff);
//							ni.newVar(varKey, textString, td);
//						} else
//						{
//							TextDescriptor td = TextDescriptor.getArcTextDescriptor();
//							// now set the position, relative to the center of the current object
//							xOff = p0.getX() - (ai.getHeadLocation().getX() + ai.getTailLocation().getX()) / 2;
//							yOff = p0.getY() - (ai.getHeadLocation().getY() + ai.getTailLocation().getY()) / 2;
//							td = td.withDisplay(true).withRelSize(relSize).withPos(textJustification).withOff(xOff, yOff);
//							ai.newVar(varKey, textString, td);
//						}
//					} else
//					{
//						System.out.println("Error, line " + lineReader.getLineNumber() + ": nothing to attach text to");
//						errorCount++;
//					}
//				}
//			}

			// clean up DISPLAY attributes
			freePointList();
			textJustification = TextDescriptor.Position.DOWNRIGHT;
			textHeight = 0;
		}
	}

	private class KeySymbol extends EDIFKEY
	{
		private KeySymbol() { super("symbol"); }
		protected void push()
		{
			symbolDefined = true;
		}
	}

	private class KeyTextHeight extends EDIFKEY
	{
		private KeyTextHeight() { super("textHeight"); }
		protected void push()
			throws IOException
		{
			// get the textheight value of the point
			String val = getToken((char)0);
			textHeight = TextUtils.atoi(val) * localPrefs.inputScale;
			if (!TextDescriptor.isValidRelSize(textHeight))
			{
				System.out.print("Invalid text height found in line " + lineReader.getLineNumber() + ": " + val + " (scaled value " + textHeight + "). ");
				textHeight = convertTextSize(textHeight);
				System.out.println("Rounding to closest valid scaled value: " + textHeight);
			}
			if (curActiveFigure != null)
				curActiveFigure.textHeight = textHeight;
		}
	}

	private class KeyTrue extends EDIFKEY
	{
		private KeyTrue() { super("true"); }
		protected void push()
		{
            propertyValue = Boolean.valueOf(true);
        }
	}

	private class KeyUnit extends EDIFKEY
	{
		private KeyUnit() { super("unit"); }
		protected void push()
			throws IOException
		{
			getToken((char)0);
		}
	}

	private class KeyUnused extends EDIFKEY
	{
		private KeyUnused() { super("unused"); }
		protected void push()
			throws IOException
		{
			curDirection = PortCharacteristic.UNKNOWN;
		}
	}

	private class KeyView extends EDIFKEY
	{
		private KeyView() { super("view"); }
		protected void push()
		{
			activeView = VNULL;
			mappedNodes = new HashMap<String,EDIFEquiv.NodeEquivalence>();
		}

		protected void pop()
		{
			if (curVendor == EVVIEWLOGIC && activeView != VNETLIST && curCell != null)
			{
				// now scan for BUS nets, and verify all wires connected to bus
				HashSet<ArcInst> seenArcs = new HashSet<ArcInst>();
				for(Iterator<ArcInst> it = curCell.getArcs(); it.hasNext(); )
				{
					ArcInst ai = it.next();
					if (!seenArcs.contains(ai) && ai.getProto() == Schematics.tech().bus_arc)
					{
						// get name of arc
						String baseName = ai.getName();
						int openPos = baseName.indexOf('[');
						if (openPos >= 0) baseName = baseName.substring(0, openPos);

						// expand this arc, and locate non-bracketed names
						checkBusNames(ai, baseName, seenArcs);
					}
				}
			}
			mappedNodes = null;
		}
	}

	private class ViewRef extends EDIFKEY
	{
		private ViewRef() { super("viewRef"); }
		protected void push()
			throws IOException
		{
			viewRef = getToken((char)0);
		}

		protected void pop()
		{
			viewRef = null;
		}
	}

	/**
	 * viewType:  Indicates the view style for this cell, ie
	 * BEHAVIOR, DOCUMENT, GRAPHIC, LOGICMODEL, MASKLAYOUT, NETLIST,
	 * PCBLAYOUT, SCHEMATIC, STRANGER, SYMBOLIC.
	 */
	private class KeyViewType extends EDIFKEY
	{
		private KeyViewType() { super("viewType"); }
		protected void push()
			throws IOException
		{
			// get the viewType
			String aName = getToken((char)0);
			if (aName.equalsIgnoreCase("BEHAVIOR"))   activeView = VBEHAVIOR;   else
			if (aName.equalsIgnoreCase("DOCUMENT"))   activeView = VDOCUMENT;   else
			if (aName.equalsIgnoreCase("GRAPHIC"))    activeView = VGRAPHIC;    else
			if (aName.equalsIgnoreCase("LOGICMODEL")) activeView = VLOGICMODEL; else
			if (aName.equalsIgnoreCase("MASKLAYOUT")) activeView = VMASKLAYOUT; else
			if (aName.equalsIgnoreCase("NETLIST"))    activeView = VNETLIST;    else
			if (aName.equalsIgnoreCase("PCBLAYOUT"))  activeView = VPCBLAYOUT;  else
			if (aName.equalsIgnoreCase("SCHEMATIC"))  activeView = VSCHEMATIC;  else
			if (aName.equalsIgnoreCase("STRANGER"))   activeView = VSTRANGER;   else
			if (aName.equalsIgnoreCase("SYMBOLIC"))   activeView = VSYMBOLIC;

			// add the name to the cellTable
			NameEntry nt = new NameEntry();
			nt.original = cellReference;
			nt.replace = cellName;
			cellTable.put(cellReference, nt);
		}
	}
}
