Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.sun.electric.technology.technologies.FPGA Maven / Gradle / Ivy
/* -*- tab-width: 4 -*-
*
* Electric(tm) VLSI Design System
*
* File: FPGA.java
* FPGA, a customizable technology.
* Written by Steven M. Rubin
*
* Copyright (c) 2005, Oracle and/or its affiliates. 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 Electric(tm); see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, Mass 02111-1307, USA.
*/
package com.sun.electric.technology.technologies;
import com.sun.electric.database.EditingPreferences;
import com.sun.electric.database.ImmutableArcInst;
import com.sun.electric.database.geometry.EGraphics;
import com.sun.electric.database.geometry.EPoint;
import com.sun.electric.database.geometry.ERectangle;
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.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.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.EditWindow_;
import com.sun.electric.database.variable.ElectricObject;
import com.sun.electric.database.variable.UserInterface;
import com.sun.electric.database.variable.VarContext;
import com.sun.electric.database.variable.Variable;
import com.sun.electric.technology.ArcProto;
import com.sun.electric.technology.EdgeH;
import com.sun.electric.technology.EdgeV;
import com.sun.electric.technology.Foundry;
import com.sun.electric.technology.Layer;
import com.sun.electric.technology.PrimitiveNode;
import com.sun.electric.technology.PrimitivePort;
import com.sun.electric.technology.TechFactory;
import com.sun.electric.technology.Technology;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.JobException;
import com.sun.electric.tool.io.FileType;
import com.sun.electric.tool.user.User;
import com.sun.electric.tool.user.dialogs.OpenFile;
import com.sun.electric.tool.user.dialogs.PromptAt;
import com.sun.electric.util.TextUtils;
import com.sun.electric.util.math.GenMath;
import com.sun.electric.util.math.Orientation;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* This is the FPGA Technology.
*/
public class FPGA extends Technology
{
/** the FPGA Technology object. */ public static FPGA tech() { return (FPGA)findTechnology("fpga"); }
private final Layer wireLayer, componentLayer, pipLayer, repeaterLayer;
private final ArcProto wireArc;
private final PrimitiveNode wirePinNode, pipNode, repeaterNode;
public FPGA(Generic generic, TechFactory techFactory)
{
super(generic, techFactory, Foundry.Type.NONE, 1);
setTechShortName("FPGA");
setTechDesc("FPGA Building-Blocks");
setFactoryScale(2000, true); // in nanometers: really 2 microns
setStaticTechnology();
setNonStandard();
setNoPrimitiveNodes();
//**************************************** LAYERS ****************************************
/** Wire layer */
wireLayer = Layer.newInstance(this, "Wire",
new EGraphics(false, false, null, 0, 255,0,0,1,true,
new int[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}));
/** Component layer */
componentLayer = Layer.newInstance(this, "Component",
new EGraphics(false, false, null, 0, 0,0,0,1,true,
new int[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}));
/** Pip layer */
pipLayer = Layer.newInstance(this, "Pip",
new EGraphics(false, false, null, 0, 0,255,0,1,true,
new int[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}));
/** Repeater layer */
repeaterLayer = Layer.newInstance(this, "Repeater",
new EGraphics(false, false, null, 0, 0,0,255,1,true,
new int[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}));
// The layer functions
wireLayer.setFunction(Layer.Function.METAL1); // wire
componentLayer.setFunction(Layer.Function.ART); // component
pipLayer.setFunction(Layer.Function.ART); // pip
repeaterLayer.setFunction(Layer.Function.ART); // repeater
//**************************************** ARC ****************************************
/** wire arc */
wireArc = newArcProto("wire", 0, 0.0, ArcProto.Function.METAL1,
new Technology.ArcLayer(wireLayer, 0, Poly.Type.FILLED)
);
wireArc.setFactoryFixedAngle(true);
wireArc.setFactorySlidable(false);
wireArc.setFactoryAngleIncrement(45);
//**************************************** NODES ****************************************
/** wire pin */
wirePinNode = PrimitiveNode.newInstance("Wire_Pin", this, 1, 1,
new Technology.NodeLayer []
{
new Technology.NodeLayer(wireLayer, 0, Poly.Type.DISC, Technology.NodeLayer.POINTS, new Technology.TechPoint [] {
new Technology.TechPoint(EdgeH.c(0), EdgeV.c(0)),
new Technology.TechPoint(EdgeH.r(0.5), EdgeV.c(0))})
});
wirePinNode.addPrimitivePorts(
PrimitivePort.single(wirePinNode, new ArcProto[] {wireArc}, "wire", 0,180, 0, PortCharacteristic.UNKNOWN,
EdgeH.c(0), EdgeV.c(0), EdgeH.c(0), EdgeV.c(0))
);
wirePinNode.setFunction(PrimitiveNode.Function.PIN);
wirePinNode.setSquare();
wirePinNode.setWipeOn1or2();
/** pip */
pipNode = PrimitiveNode.newInstance("Pip", this, 2, 2,
new Technology.NodeLayer []
{
new Technology.NodeLayer(pipLayer, 0, Poly.Type.FILLED, Technology.NodeLayer.BOX, new Technology.TechPoint [] {
new Technology.TechPoint(EdgeH.l(-1), EdgeV.b(-1)),
new Technology.TechPoint(EdgeH.r(1), EdgeV.t(1))})
});
pipNode.addPrimitivePorts(
PrimitivePort.single(pipNode, new ArcProto[] {wireArc}, "pip", 0,180, 0, PortCharacteristic.UNKNOWN,
EdgeH.c(0), EdgeV.c(0), EdgeH.c(0), EdgeV.c(0))
);
pipNode.setFunction(PrimitiveNode.Function.CONNECT);
pipNode.setSquare();
/** repeater */
repeaterNode = PrimitiveNode.newInstance("Repeater", this, 10, 3,
new Technology.NodeLayer []
{
new Technology.NodeLayer(repeaterLayer, 0, Poly.Type.FILLED, Technology.NodeLayer.BOX, new Technology.TechPoint [] {
new Technology.TechPoint(EdgeH.l(-5), EdgeV.b(-1.5)),
new Technology.TechPoint(EdgeH.r(5), EdgeV.t(1.5))})
});
repeaterNode.addPrimitivePorts(
PrimitivePort.newInstance(repeaterNode, new ArcProto[] {wireArc}, "a", 180,45, 0, PortCharacteristic.UNKNOWN,
EdgeH.l(-5), EdgeV.c(0), EdgeH.l(-5), EdgeV.c(0)),
PrimitivePort.newInstance(repeaterNode, new ArcProto[] {wireArc}, "b", 0,45, 1, PortCharacteristic.UNKNOWN,
EdgeH.r(5), EdgeV.c(0), EdgeH.r(5), EdgeV.c(0))
);
repeaterNode.setFunction(PrimitiveNode.Function.CONNECT);
// Building information for palette
loadFactoryMenuPalette(FPGA.class.getResource("fpgaMenu.xml"));
// Foundry
newFoundry(Foundry.Type.NONE, null);
}
/******************** TREE STRUCTURE FOR ARCHITECTURE FILE ********************/
/** max depth of FPGA nesting */ private static final int MAXDEPTH = 50;
private static class LispTree
{
private String keyword;
private int lineNumber;
private List values;
LispTree()
{
values = new ArrayList();
}
void add(Object obj) { values.add(obj); }
int size() { return values.size(); }
boolean isLeaf(int i) { return !(values.get(i) instanceof LispTree); }
boolean isBranch(int i) { return values.get(i) instanceof LispTree; }
String getLeaf(int i) { return (String)values.get(i); }
LispTree getBranch(int i) { return (LispTree)values.get(i); }
};
private static LispTree [] treeStack = new LispTree[MAXDEPTH];
private static int treeDepth;
private static LispTree treePosition;
/******************** ADDITIONAL INFORMATION ABOUT PRIMITIVES ********************/
/** level of display */ private static final int DISPLAYLEVEL = 07;
/** display no internals */ private static final int NOPRIMDISPLAY = 0;
/** display all internals */ private static final int FULLPRIMDISPLAY = 01;
/** display only active internals */ private static final int ACTIVEPRIMDISPLAY = 02;
/** set to display text */ private static final int TEXTDISPLAY = 010;
/** set if segment or pip is active */ private static final int ACTIVEPART = 1;
/** saved area for segment/pip activity */ private static final int ACTIVESAVE = 2;
private static class FPGAPort
{
String name;
double posX, posY;
int con;
PortCharacteristic characteristic;
PrimitivePort pp;
};
private static class FPGANet
{
String name;
int segActive;
Point2D [] segFrom;
Point2D [] segTo;
};
private static class FPGAPip
{
String name;
int pipActive;
int con1, con2;
double posX, posY;
};
private static class FPGANode extends PrimitiveNode
{
FPGAPort [] portList;
FPGANet [] netList;
FPGAPip [] pipList;
protected FPGANode(String protoName, Technology tech, double defWidth, double defHeight,
Technology.NodeLayer [] layers)
{
super(protoName, tech, EPoint.ORIGIN, defWidth, defHeight, ERectangle.ORIGIN, layers);
}
int numPorts()
{
if (portList == null) return 0;
return portList.length;
}
int numNets()
{
if (netList == null) return 0;
return netList.length;
}
int numPips()
{
if (pipList == null) return 0;
return pipList.length;
}
};
/** key of Variable holding active pips. */ private static final Variable.Key ACTIVEPIPS_KEY = Variable.newKey("FPGA_activepips");
/** key of Variable holding active repeaters. */ private static final Variable.Key ACTIVEREPEATERS_KEY = Variable.newKey("FPGA_activerepeaters");
// /** key of Variable holding cache of pips on node. */ private static final Variable.Key NODEPIPCACHE_KEY = Variable.newKey("FPGA_nodepipcache");
// /** key of Variable holding cache of active arcs. */ private static final Variable.Key ARCACTIVECACHE_KEY = Variable.newKey("FPGA_arcactivecache");
/** name of current repeater for activity examining */ private String repeaterName;
/** nonzero if current repeater is found to be active */private boolean repeaterActive;
/** what is being displayed */ private int internalDisplay = FULLPRIMDISPLAY | TEXTDISPLAY;
/** whether the technology has been read */ private boolean defined = false;
private static final Technology.NodeLayer[] NULLNODELAYER = new Technology.NodeLayer[0];
// /**
// * Method to return a list of Polys that describe a given NodeInst.
// * This method overrides the general one in the Technology object
// * because of the unusual primitives in this Technology.
// * @param ni the NodeInst to describe.
// * @param electrical true to get the "electrical" layers.
// * This makes no sense for Schematics primitives.
// * @param reasonable true to get only a minimal set of contact cuts in large contacts.
// * This makes no sense for Schematics primitives.
// * @param primLayers an array of NodeLayer objects to convert to Poly objects.
// * @return an array of Poly objects.
// */
// @Override
// protected Poly [] getShapeOfNode(NodeInst ni, boolean electrical, boolean reasonable, Technology.NodeLayer [] primLayers) {
// return getShapeOfNode(ni, null, null, electrical, reasonable, primLayers);
// }
//
// /**
// * Method to return a list of Polys that describe a given NodeInst.
// * This method overrides the general one in the Technology object
// * because of the unusual primitives in this Technology.
// * @param ni the NodeInst to describe.
// * @param wnd the window in which this node will be drawn.
// * @param context the VarContext to this node in the hierarchy.
// * @param electrical true to get the "electrical" layers.
// * This makes no sense for Schematics primitives.
// * @param reasonable true to get only a minimal set of contact cuts in large contacts.
// * This makes no sense for Schematics primitives.
// * @param primLayers an array of NodeLayer objects to convert to Poly objects.
// * @return an array of Poly objects.
// */
// private Poly [] getShapeOfNode(NodeInst ni, EditWindow0 wnd, VarContext context, boolean electrical, boolean reasonable, Technology.NodeLayer [] primLayers)
// {
// if (ni.isCellInstance()) return null;
//
// PrimitiveNode np = (PrimitiveNode)ni.getProto();
// if (np == wirePinNode)
// {
// if (ni.pinUseCount()) primLayers = NULLNODELAYER;
// } else if (np == repeaterNode)
// {
// if ((internalDisplay&DISPLAYLEVEL) == ACTIVEPRIMDISPLAY)
// {
// if (!repeaterActive(ni)) primLayers = NULLNODELAYER;
// }
// } else if (np instanceof FPGANode)
// {
// // dynamic primitive
// FPGANode fn = (FPGANode)np;
//
// // hard reset of all segment and pip activity
// int numPips = 0, numSegs = 0;
// for(int i=0; i it = ni.getConnections(); it.hasNext(); )
// {
// Connection con = it.next();
// if (con.getPortInst().getPortProto() != fn.portList[j].pp) continue;
// ArcInst ai = con.getArc();
// int otherEnd = 1 - con.getEndIndex();
// if (arcEndActive(ai, otherEnd, higher)) { found = true; break; }
// }
// if (found) break;
// }
// if (found) fn.netList[i].segActive |= ACTIVESAVE;
// }
// }
//
// // add up the active segments
// for(int i=0; i it = subni.getConnections(); it.hasNext(); )
{
Connection nextCon = it.next();
ArcInst oAi = nextCon.getArc();
int newEnd = 0;
if (oAi.getPortInst(0).getNodeInst() == subni) newEnd = 1;
if (arcEndActive(oAi, newEnd, down)) return true;
}
return false;
}
// primitive: see if it is one of ours
if (np instanceof FPGANode)
{
FPGANode fn = (FPGANode)np;
reEvaluatePips(ni, fn, curContext);
for(int i = 0; i < fn.numPorts(); i++)
{
if (fn.portList[i].pp != pp) continue;
int index = fn.portList[i].con;
if (index >= 0 && fn.netList != null)
{
if ((fn.netList[index].segActive&ACTIVEPART) != 0) return true;
}
break;
}
}
// propagate
Cell parent = ai.getParent();
if (parent != null)
{
Netlist nl = parent.getNetlist();
Network net = nl.getNetwork(ni, pp, 0);
if (net != null)
{
for(Iterator it = ni.getConnections(); it.hasNext(); )
{
Connection nextCon = it.next();
ArcInst oAi = nextCon.getArc();
if (oAi == ai) continue;
Network oNet = nl.getNetwork(oAi, 0);
if (oNet != net) continue;
int newEnd = 1 - nextCon.getEndIndex();
if (arcEndActive(oAi, newEnd, curContext)) return true;
}
VarContext higher = curContext.pop();
if (higher != null && higher.getNodable() != null)
{
NodeInst oNi = (NodeInst)higher.getNodable();
for (Iterator it = ni.getExports(); it.hasNext(); )
{
Export opp = it.next();
Network oNet = nl.getNetwork(opp, 0);
if (oNet != net) continue;
for(Iterator uIt = oNi.getConnections(); uIt.hasNext(); )
{
Connection nextCon = uIt.next();
ArcInst oAi = nextCon.getArc();
if (nextCon.getPortInst().getPortProto() != opp) continue;
int newEnd = 1 - nextCon.getEndIndex();
if (arcEndActive(oAi, newEnd, higher)) return true;
}
}
}
}
}
return false;
}
/**
* Method to reevaluate primitive node "ni" (which is associated with internal
* structure "fn"). Finds programming of pips and sets pip and net activity.
*/
private void reEvaluatePips(NodeInst ni, FPGANode fn, VarContext context)
{
// primitives with no pips or nets need no evaluation
if (fn.numNets() == 0 && fn.numPips() == 0) return;
// reevaluate: presume all nets and pips are inactive
for(int i=0; i 0) fn.netList[fPip.con1].segActive |= ACTIVEPART;
if (fPip.con2 > 0) fn.netList[fPip.con2].segActive |= ACTIVEPART;
}
}
/**
* Method to examine primitive node "ni" and return true if the repeater is active.
*/
private boolean repeaterActive(NodeInst ni)
{
repeaterName = ni.getName();
repeaterActive = false;
findVariableObjects(null, ni, ACTIVEREPEATERS_KEY, false, null);
return repeaterActive;
}
Nodable [] path = new Nodable[100];
private void findVariableObjects(FPGANode fn, NodeInst ni, Variable.Key varKey, boolean setPips, VarContext context)
{
// search hierarchical path
int depth = 0;
path[depth++] = ni;
while (context != null)
{
Nodable niClimb = context.getNodable();
if (niClimb == null) break;
path[depth++] = niClimb;
context = context.pop();
}
// look for programming variables on the nodes
for(int c=0; c depth) continue;
boolean pathGood = true;
for(int j=0; j= line.length()) break;
// check for special characters
char chr = line.charAt(pt);
if (chr == ')')
{
if (pushKeyword(line.substring(pt, pt+1), lnr)) return null;
pt++;
continue;
}
// gather a keyword
int ptEnd = pt;
for(;;)
{
if (ptEnd >= line.length()) break;
char chEnd = line.charAt(ptEnd);
if (chEnd == ')' || Character.isWhitespace(chEnd)) break;
if (chEnd == '"')
{
ptEnd++;
for(;;)
{
if (ptEnd >= line.length() || line.charAt(ptEnd) == '"') break;
ptEnd++;
}
if (ptEnd < line.length()) ptEnd++;
break;
}
ptEnd++;
}
if (pushKeyword(line.substring(pt, ptEnd), lnr)) return null;
pt = ptEnd;
}
}
lnr.close();
System.out.println(fileName + " read");
} catch (IOException e)
{
System.out.println("Error reading " + fileName);
return null;
}
if (treeDepth != 0)
{
System.out.println("Not enough close parenthesis in file");
return null;
}
return treeTop;
}
/**
* Method to add the next keyword "keyword" to the lisp tree in the globals.
* Returns true on error.
*/
private boolean pushKeyword(String keyword, LineNumberReader lnr)
{
if (keyword.startsWith("("))
{
if (treeDepth >= MAXDEPTH)
{
System.out.println("Nesting too deep (more than " + MAXDEPTH + ")");
return true;
}
// create a new tree branch
LispTree newTree = new LispTree();
newTree.lineNumber = lnr.getLineNumber();
// add branch to previous branch
treePosition.add(newTree);
// add keyword
int pt = 1;
while (pt < keyword.length() && Character.isWhitespace(keyword.charAt(pt))) pt++;
newTree.keyword = keyword.substring(pt);
// push tree onto stack
treeStack[treeDepth] = treePosition;
treeDepth++;
treePosition = newTree;
return false;
}
if (keyword.equals(")"))
{
// pop tree stack
if (treeDepth <= 0)
{
System.out.println("Too many close parenthesis");
return true;
}
treeDepth--;
treePosition = treeStack[treeDepth];
return false;
}
// just add the atomic keyword
if (keyword.startsWith("\"") && keyword.endsWith("\""))
keyword = keyword.substring(1, keyword.length()-1);
treePosition.add(keyword);
return false;
}
/******************** ARCHITECTURE PARSING: PRIMITIVES ********************/
/**
* Method to parse the entire tree and create primitives.
* Returns the number of primitives made.
*/
private int makePrimitives(LispTree lt, EditingPreferences ep)
{
// look through top level for the "primdef"s
int total = 0;
for(int i=0; i it = cell.getNodes(); it.hasNext(); )
{
NodeInst ni = it.next();
if (ni.getName().equalsIgnoreCase(name))
{
niFound = ni;
break;
}
}
if (niFound == null)
{
System.out.println("Cannot find component '" + scanLT.getLeaf(pos+1) +
"' in block net segment (line " + scanLT.lineNumber + ")");
return true;
}
nis[i] = niFound;
pps[i] = niFound.getProto().findPortProto(scanLT.getLeaf(pos+2));
if (pps[i] == null)
{
System.out.println("Cannot find port '" + scanLT.getLeaf(pos+2) +
"' on component '" + scanLT.getLeaf(pos+1) +
"' in block net segment (line " + scanLT.lineNumber + ")");
return true;
}
pos += 3;
} else if (scanLT.getLeaf(pos).equalsIgnoreCase("coord"))
{
if (scanLT.size() < pos+3)
{
System.out.println("Incomplete block net segment (line " + scanLT.lineNumber + ")");
return true;
}
if (scanLT.isBranch(pos+1) || scanLT.isBranch(pos+2))
{
System.out.println("Must have atoms in block net segment (line " + scanLT.lineNumber + ")");
return true;
}
double x = TextUtils.atof(scanLT.getLeaf(pos+1));
double y = TextUtils.atof(scanLT.getLeaf(pos+2));
Rectangle2D search = new Rectangle2D.Double(x, y, 0, 0);
// find pin at this point
NodeInst niFound = null;
for(Iterator it = cell.searchIterator(search); it.hasNext(); )
{
Geometric geom = it.next();
if (!(geom instanceof NodeInst)) continue;
NodeInst ni = (NodeInst)geom;
if (ni.getProto() != wirePinNode) continue;
if (ni.getTrueCenterX() == x && ni.getTrueCenterY() == y)
{
niFound = ni;
break;
}
}
if (niFound == null)
{
niFound = NodeInst.makeInstance(wirePinNode, ep, new Point2D.Double(x, y), 0, 0, cell);
if (niFound == null)
{
System.out.println("Cannot create pin for block net segment (line " + scanLT.lineNumber + ")");
return true;
}
}
nis[i] = niFound;
pps[i] = niFound.getProto().getPort(0);
pos += 3;
} else if (scanLT.getLeaf(pos).equalsIgnoreCase("port"))
{
if (scanLT.size() < pos+2)
{
System.out.println("Incomplete block net segment (line " + scanLT.lineNumber + ")");
return true;
}
if (scanLT.isBranch(pos+1))
{
System.out.println("Must have atoms in block net segment (line " + scanLT.lineNumber + ")");
return true;
}
// find port
Export pp = cell.findExport(scanLT.getLeaf(pos+1));
if (pp == null)
{
System.out.println("Cannot find port '" + scanLT.getLeaf(pos+1) +
"' in block net segment (line " + scanLT.lineNumber + ")");
return true;
}
pps[i] = pp.getOriginalPort().getPortProto();
nis[i] = pp.getOriginalPort().getNodeInst();
pos += 2;
} else
{
System.out.println("Unknown keyword '" + scanLT.getLeaf(pos) +
"' in block net segment (line " + scanLT.lineNumber + ")");
return true;
}
}
// now create the arc
PortInst pi0 = nis[0].findPortInstFromProto(pps[0]);
PortInst pi1 = nis[1].findPortInstFromProto(pps[1]);
ArcInst ai = ArcInst.makeInstanceBase(wireArc, ep, 0, pi0, pi1);
if (ai == null)
{
System.out.println("Cannot run segment (line " + scanLT.lineNumber + ")");
return true;
}
}
}
return false;
}
}