package LBJ2.IR; import java.io.File; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import LBJ2.classify.Classifier; import LBJ2.learn.Learner; import LBJ2.learn.Normalizer; import LBJ2.infer.Inference; import LBJ2.infer.ParameterizedConstraint; /** * A symbol table is simply a <code>HashMap</code> associating names with * their types. This class also assumes responsibility for determining type * information for externally defined names. * * <p> The global symbol table also keeps track of <code>package</code> and * <code>import</code> declarations so that when a name cannot be found * otherwise, it is searched for in the imported packages. * * @author Nick Rizzolo **/ public class SymbolTable { /** * Given the name of a class, which may or may not be fully qualified, this * method returns the absolute path to the file with the same name and the * given extension. This method was written for use by the LBJ compiler * and its generated code. The user should not need to call it. * * @param name The name of the class. * @param extension The extension of the file to search for. * @param path Paths in which to search for name.extension. * @return An object representing the file or <code>null</code> if it can't * be located. **/ protected static File findFile(String name, String extension, String path) { String fqName = name.replace('.', File.separatorChar) + "." + extension; String[] paths = path.split("\\" + File.pathSeparator); for (int i = 0; i < paths.length; ++i) { File file = new File(paths[i] + File.separator + fqName); if (file.exists()) return file; } return null; } /** * The parent of this symbol table, or <code>null</code> if there is none. **/ private SymbolTable parent; /** The children of this symbol table. */ private LinkedList children; /** Associates variable names with their types. */ private HashMap table; /** Associates externally defined names with their types. */ private HashMap external; /** The name representing the package of this source. */ private String sourcePackage; /** The list of names representing packages that have been imported. */ private HashSet imported; /** Initializes the member variables. */ public SymbolTable() { this(null); } /** * Initializes the member variables. * * @param p The parent of this table. **/ public SymbolTable(SymbolTable p) { parent = p; table = new HashMap(); children = new LinkedList(); sourcePackage = ""; if (parent == null) { external = new HashMap(); imported = new HashSet(); } else parent.addChild(this); } /** Retrieves the parent of this table. */ public SymbolTable getParent() { return parent; } /** * Adds a child to this table. * * @param s The child to add. **/ public void addChild(SymbolTable s) { children.add(s); } /** * Adds a new entry to the table. * * @param a An argument containing the name its associated type. * @return The type previously associated with the given name or * <code>null</code> if no type was previously associated with it. **/ public Type put(Argument a) { return put(a.getName(), a.getType()); } /** * Adds a new entry to the table. * * @param name The name to add to the table. * @param type The type to associate with the given name. * @return The type previously associated with the given name or * <code>null</code> if no type was previously associated with it. **/ public Type put(ClassifierName name, Type type) { return put(name.toString(), type); } /** * Adds a new entry to the table. * * @param name The name to add to the table. * @param type The type to associate with the given name. * @return The type previously associated with the given name or * <code>null</code> if no type was previously associated with it. **/ public Type put(Name name, Type type) { return put(name.toString(), type); } /** * Adds a new entry to the table. * * @param name The name to add to the table. * @param type The type to associate with the given name. * @return The type previously associated with the given name or * <code>null</code> if no type was previously associated with it. **/ public Type put(String name, Type type) { Type result = (Type) table.get(name); table.put(name, type); return result; } /** * Retrieves the type associated with the given name. If this table does * not contain the name, imported packages are searched. * * @param name The name to retrieve type information for. * @return The type associated with the given name or <code>null</code> if * no type information could be found. **/ public Type get(ClassifierName name) { return get(name.referent.toString()); } /** * Retrieves the type associated with the given name. If this table does * not contain the name, imported packages are searched. * * @param name The name to retrieve type information for. * @return The type associated with the given name or <code>null</code> if * no type information could be found. **/ public Type get(Name name) { return get(name.toString()); } /** * Retrieves the type associated with the given name. If this table does * not contain the name, imported packages are searched. * * @param name The name to retrieve type information for. * @return The type associated with the given name or <code>null</code> if * no type information could be found. **/ public Type get(String name) { if (localContainsKey(name)) return (Type) table.get(name); if (parent != null) return parent.get(name); if (external.containsKey(name)) return (Type) external.get(name); Type result = null; Class c = classForName(name); if (c != null) { if (ParameterizedConstraint.class.isAssignableFrom(c)) { ParameterizedConstraint constraint = null; try { constraint = (ParameterizedConstraint) c.newInstance(); } catch (Exception e) { System.err.println("Can't instantiate parameterized constraint '" + c + "'. Make sure there is a public, no argument " + "constructor defined:"); e.printStackTrace(); System.exit(1); } result = new ConstraintType(Type.parseType(constraint.getInputType())); } else if (Classifier.class.isAssignableFrom(c)) { Classifier classifier = null; try { classifier = (Classifier) c.newInstance(); } catch (Exception e) { System.err.println("Can't instantiate classifier '" + c + "'. Make sure there is a public, no argument constructor " + "defined:"); e.printStackTrace(); System.exit(1); } result = new ClassifierType( Type.parseType(classifier.getInputType()), new ClassifierReturnType( classifier.getOutputType(), new ConstantList(classifier.allowableValues())), Learner.class.isAssignableFrom(c)); } else if (Inference.class.isAssignableFrom(c)) { Inference inference = null; try { inference = (Inference) c.newInstance(); } catch (Exception e) { System.err.println("Can't instantiate inference '" + c + "'. Make sure there is a public, no argument constructor " + "defined:"); e.printStackTrace(); System.exit(1); } Type headType = new ReferenceType(new Name(inference.getHeadType())); String[] headFinderTypeStrings = inference.getHeadFinderTypes(); Type[] headFinderTypes = new Type[headFinderTypeStrings.length]; for (int i = 0; i < headFinderTypeStrings.length; ++i) headFinderTypes[i] = new ReferenceType(new Name(headFinderTypeStrings[i])); result = new InferenceType(headType, headFinderTypes); } else if (Normalizer.class.isAssignableFrom(c)) { try { c.newInstance(); } catch (Exception e) { System.err.println("Can't instantiate normalizer '" + c + "'. Make sure there is a public, no argument constructor " + "defined:"); e.printStackTrace(); System.exit(1); } result = new NormalizerType(); } } external.put(name, result); return result; } /** * Attempts to locate the named class in the current package and any * imported packages. If the corresponding Java source file is found and * either the class file does not exist or its time of last modification is * earlier than the java file's, it is recompiled. If no class with the * specified name is found, <code>null</code> is returned. * * @param name The name of the class to search for. * @return The <code>Class</code> object representing that class. **/ public Class classForName(ClassifierName name) { return classForName(name.referent); } /** * Attempts to locate the named class in the current package and any * imported packages. If the corresponding Java source file is found and * either the class file does not exist or its time of last modification is * earlier than the java file's, it is recompiled. If no class with the * specified name is found, <code>null</code> is returned. * * @param name The name of the class to search for. * @return The <code>Class</code> object representing that class. **/ public Class classForName(String name) { return classForName(new Name(name)); } /** * Attempts to locate the named class in the current package and any * imported packages. If the corresponding Java source file is found and * either the class file does not exist or its time of last modification is * earlier than the java file's, it is recompiled. If no class with the * specified name is found, <code>null</code> is returned. * * @param name The name of the class to search for. * @return The <code>Class</code> object representing that class. **/ public Class classForName(Name name) { if (parent != null) return parent.classForName(name); Class result = null; LinkedList prefixes = new LinkedList(); prefixes.add(""); if (sourcePackage.length() != 0) prefixes.add(sourcePackage + "."); prefixes.add("java.lang."); for (Iterator I = imported.iterator(); I.hasNext(); ) { String s = (String) I.next(); if (s.endsWith(".*")) prefixes.add(s.substring(0, s.length() - 1)); else if (s.endsWith("." + name.name[0])) prefixes.add(s.substring(0, s.length() - name.name[0].length())); } for (Iterator I = prefixes.iterator(); I.hasNext() && result == null; ) { String prefix = (String) I.next(); String fqName = prefix + name; File javaFile = findFile(fqName, "java", LBJ2.Main.sourcePath); if (javaFile != null) { File classFile = findFile(fqName, "class", LBJ2.Main.classPath); if ((classFile == null || javaFile.lastModified() > classFile.lastModified()) && LBJ2.Train.runJavac(javaFile.toString())) System.exit(1); } try { result = Class.forName(fqName); } catch (Exception e) { } catch (NoClassDefFoundError e) { } for (int i = 0; i < name.name.length - 1 && result == null; ++i) { fqName = prefix + name.name[0]; for (int j = 1; j <= i; ++j) fqName += "." + name.name[j]; javaFile = findFile(fqName, "java", LBJ2.Main.sourcePath); if (javaFile != null) { File classFile = findFile(fqName, "class", LBJ2.Main.classPath); if ((classFile == null || javaFile.lastModified() > classFile.lastModified()) && LBJ2.Train.runJavac(javaFile.toString())) System.exit(1); } for (int j = i + 1; j < name.name.length; ++j) fqName += "$" + name.name[j]; try { result = Class.forName(fqName); } catch (Exception e) { } catch (NoClassDefFoundError e) { } } } return result; } /** * Determines whether the specified name has been used as a key in this * table or any of its parents. * * @param key The name. * @return <code>true</code> iff <code>key</code> is already a key in this * table or any of its parents. **/ public boolean containsKey(ClassifierName key) { return containsKey(key.toString()); } /** * Determines whether the specified name has been used as a key in this * table or any of its parents. * * @param key The name. * @return <code>true</code> iff <code>key</code> is already a key in this * table or any of its parents. **/ public boolean containsKey(Name key) { return containsKey(key.toString()); } /** * Determines whether the specified name has been used as a key in this * table or any of its parents. * * @param key The name. * @return <code>true</code> iff <code>key</code> is already a key in this * table or any of its parents. **/ public boolean containsKey(String key) { if (!table.containsKey(key)) { if (parent != null) return parent.containsKey(key); return false; } return true; } /** * Determines whether the specified name has been used as a key in this * table. * * @param key The name. * @return <code>true</code> iff <code>key</code> is already a key in this * table. **/ public boolean localContainsKey(ClassifierName key) { return localContainsKey(key.toString()); } /** * Determines whether the specified name has been used as a key in this * table. * * @param key The name. * @return <code>true</code> iff <code>key</code> is already a key in this * table. **/ public boolean localContainsKey(Name key) { return localContainsKey(key.toString()); } /** * Determines whether the specified name has been used as a key in this * table. * * @param key The name. * @return <code>true</code> iff <code>key</code> is already a key in this * table. **/ public boolean localContainsKey(String key) { return table.containsKey(key); } /** * Adds a name to the list of imported names in the top level table. * * @param name The name of a new imported package. **/ public void addImported(String name) { if (parent == null) imported.add(name); else parent.addImported(name); } /** * Returns the size of the list of imported items. * * @return The size of the list of imported items. **/ public int importedSize() { if (parent == null) return imported.size(); return parent.importedSize(); } /** * Sets the package name in the top level table. * * @param name The package name. **/ public void setPackage(String name) { if (parent == null) sourcePackage = name; else parent.setPackage(name); } /** * Gets the package name. * * @return The package name. **/ public String getPackage() { if (parent == null) return sourcePackage; return parent.getPackage(); } /** * Generates <code>package</code> and <code>import</code> statements from * the names in the member variable <code>imported</code>. * * @param out The stream to write to. **/ public void generateHeader(java.io.PrintStream out) { if (parent != null) { parent.generateHeader(out); return; } if (sourcePackage.length() != 0) out.println("package " + sourcePackage + ";\n"); String[] names = (String[]) imported.toArray(new String[0]); Arrays.sort(names); for (int i = 0; i < names.length; ++i) out.println("import " + names[i] + ";"); } /** Returns the names of the symbols in this (local) table. */ public String[] getSymbols() { return (String[]) table.keySet().toArray(new String[0]); } /** * Prints this table and all its children recursively to * <code>STDOUT</code>. **/ public void print() { print(""); } /** * Prints this table and all its children recursively to * <code>STDOUT</code>. * * @param indent The level of indentation. **/ public void print(String indent) { if (parent == null) { if (sourcePackage.length() == 0) System.out.println("Package: " + sourcePackage); System.out.println("Imported:"); for (Iterator I = imported.iterator(); I.hasNext(); ) System.out.println(" " + I.next()); } System.out.println(indent + "Symbols:"); String[] symbols = (String[]) table.keySet().toArray(new String[0]); Arrays.sort(symbols); for (int i = 0; i < symbols.length; ++i) System.out.println(indent + " " + symbols[i] + " -> " + table.get(symbols[i])); if (children.size() > 0) { for (Iterator I = children.iterator(); I.hasNext(); ) { System.out.println(); ((SymbolTable) I.next()).print(indent + " "); } } } }