/* * xtc - The eXTensible Compiler * Copyright (C) 2005-2010 Robert Grimm * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA. */ package xtc.util; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import xtc.Constants; import xtc.tree.Node; import xtc.tree.Printer; /** * A symbol table. This class implements a symbol table, which maps * symbols represented as strings to values of any type. The mapping * is organized into hierarchical {@link Scope scopes}, which allows * for multiple definitions of the same symbol across different * scopes. Additionally, a symbol may have multiple definitions * within the same scope: if the corresponding value is a Java * collections framework list, it is recognized as a multiply defined * symbol. Scopes are named, with names being represented as strings. * Both scope names and symbols can be unqualified — that is, * they need to be resolved relative to the {@link #current() current * scope} — or qualified by the {@link Constants#QUALIFIER * qualification character} '<code>.</code>' — that is, they are * resolved relative to the symbol table's {@link #root() root}. Once * {@link #enter(String) created}, a scope remains in the symbol table * and the corresponding AST node should be associated with that scope * by setting the corresponding {@link Constants#SCOPE property} to * the scope's qualified name. Subsequent traversals over that node * can then automatically {@link #enter(Node) enter} and {@link * #exit(Node) exit} that scope. Alternatively, if traversing out of * tree order, the current scope can be set {@link * #setScope(SymbolTable.Scope) explicitly}. * * <p />To support different name spaces within the same scope, this * class can optionally {@link #toNameSpace mangle} and {@link * #fromNameSpace unmangle} unqualified symbols. By convention, a * name in any name space besides the default name space is prefixed * by the name of the name space and an opening parenthesis * '<code>(</code>' and suffixed by a closing parenthesis * '<code>)</code>'. * * @author Robert Grimm * @version $Revision: 1.35 $ */ public class SymbolTable { /** * A symbol table scope. A scope has a name and may have a parent * (unless it is the root scope), one or more nested scopes, and one * or more definitions. */ public static class Scope { /** The name. */ String name; /** The fully qualified name. */ String qName; /** The parent scope. */ Scope parent; /** The nested scopes, if any. */ Map<String, Scope> scopes; /** The map from symbols to values, if any. */ Map<String, Object> symbols; /** * Create a new root scope with the specified name, which may be * the empty string. * * @param name The name. */ Scope(String name) { this.name = name; this.qName = name; } /** * Create a new nested scope with the specified unqualified name * and parent. * * @param name The unqualified name. * @param parent The parent. * @throws IllegalArgumentException * Signals that the specified parent already has a nested scope * with the specified name. */ Scope(String name, Scope parent) { if ((null != parent.scopes) && parent.scopes.containsKey(name)) { throw new IllegalArgumentException("Scope " + parent.qName + " already contains scope " + name); } this.name = name; this.qName = Utilities.qualify(parent.qName, name); this.parent = parent; if (null == parent.scopes) { parent.scopes = new HashMap<String, Scope>(); } parent.scopes.put(name, this); } /** * Get this scope's unqualfied name. * * @return This scope's unqualified name. */ public String getName() { return name; } /** * Get this scope's qualified name. * * @return This scope's qualified name. */ public String getQualifiedName() { return qName; } /** * Update this scope's qualified name relative to the parent * scope's qualified name. This method also requalifies any * nested scopes' qualified names. It must not be called on the * root scope. */ void requalify() { qName = Utilities.qualify(parent.qName, name); if (null != scopes) { for (Scope scope : scopes.values()) { scope.requalify(); } } } /** * Determine whether this scope is the root scope. * * @return <code>true</code> if this scope is the root scope. */ public boolean isRoot() { return (null == parent); } /** * Get this scope's parent. * * @return This scope's parent scope or <code>null</code> if this * scope does not have a parent (i.e., is the root scope). */ public Scope getParent() { return parent; } /** * Determine whether this scope has any nested scopes. * * @return <code>true</code> if this scope has any nested scopes. */ public boolean hasNested() { return ((null != scopes) && (0 < scopes.size())); } /** * Get an iterator over the names of all nested scopes. * * @return An iterator over the nested scopes. */ public Iterator<String> nested() { if (null == scopes) { return EmptyIterator.value(); } else { return scopes.keySet().iterator(); } } /** * Determine whether this scope has the specified unqualified * nested scope. * * @param name The nested scope's unqualified name. * @return <code>true</code> if the corresponding scope exists. */ public boolean hasNested(String name) { return (null != getNested(name)); } /** * Get the nested scope with the specified unqualified name. * * @param name The nested scope's unqualified name. * @return The corresponding scope or <code>null</code> if there is * no such scope. */ public Scope getNested(String name) { return (null == scopes)? null : scopes.get(name); } /** * Determine whether the scope with the specified unqualified name * can be merged into this scope. A nested scope can be merged if * it (1) does not contain any bindings with the same names as * this scope's bindings and (2) does not have any children with * the same names as this scope's children. * * @param name The nested scope's unqualified name. * @return <code>true</code> if the scope can be merged. * @throws IllegalArgumentException Signals that this scope does * not have a nested scope with the specified name. */ public boolean isMergeable(String name) { Scope nested = getNested(name); if (null == nested) { throw new IllegalArgumentException("Scope " + qName + " does not " + " contain scope " + name); } if (null != nested.scopes) { // Note that this scope must have nested scopes, since we just // looked one up. for (String s : nested.scopes.keySet()) { if ((! s.equals(name)) && this.scopes.containsKey(s)) { return false; } } } if ((null != this.symbols) && (null != nested.symbols)) { for (String s : nested.symbols.keySet()) { if (this.symbols.containsKey(s)) { return false; } } } return true; } /** * Merge the nested scope with the specified unqualified name into * this scope. * * @param name The nested scope's unqualified name. * @throws IllegalArgumentException Signals that (1) this scope * does not have a nested scope with the specified name, (2) any * of the nested scope's children has the same name as one of * this scope's children, or (3) any of the nested scope's * bindings has the same name as one of this scope's bindings. */ public void merge(String name) { final Scope nested = getNested(name); // Make sure the nested scope is mergeable. Note that the // nested scope must exist in the consequence of the // if-statement, since isMergeable signals an exception for // non-existent scopes. if (! isMergeable(name)) { throw new IllegalArgumentException("Scope " + nested.qName + " cannot be merged into the parent"); } // Remove the nested scope. this.scopes.remove(name); // Add the nested scope's children. if (null != nested.scopes) { this.scopes.putAll(nested.scopes); for (Scope s : nested.scopes.values()) { s.parent = this; s.requalify(); } } // Add the nested scope's bindings. if (null != nested.symbols) { if (null == this.symbols) { this.symbols = nested.symbols; } else { this.symbols.putAll(nested.symbols); } } // Invalidate the nested scope. nested.parent = null; nested.name = null; nested.qName = null; nested.scopes = null; nested.symbols = null; } /** * Determine whether this scope has any local definitions. * * @return <code>true</code> if this scope has any local * definitions. */ public boolean hasSymbols() { return ((null != symbols) && (0 < symbols.size())); } /** * Get an iterator over the all locally defined symbols. * * @return An iterator over the locally defined symbols. */ public Iterator<String> symbols() { if (null == symbols) { return EmptyIterator.value(); } else { return symbols.keySet().iterator(); } } /** * Determine whether the specified symbol is defined in this * scope. * * @param symbol The unqualified symbol. * @return <code>true</code> if the symbol is defined in this * scope. */ public boolean isDefinedLocally(String symbol) { return (null == symbols)? false : symbols.containsKey(symbol); } /** * Determine whether the specified unqualified symbol is defined * in this scope or any of its ancestors. * * @param symbol The unqualified symbol. * @return <code>true</code> if the symbol is defined in this scope * or any of its ancestors. */ public boolean isDefined(String symbol) { return (null != lookupScope(symbol)); } /** * Determine whether the specified symbol is defined multiple * times in this scope or any of its ancestors. * * @param symbol The unqualified symbol. * @return <code>true</code> if the symbol is defined multiple * times. */ public boolean isDefinedMultiply(String symbol) { Scope scope = lookupScope(symbol); return (null == scope)? false : scope.symbols.get(symbol) instanceof List; } /** * Get the scope defining the specified unqualified symbol. This * method searches this scope and all its ancestors, returning the * first defining scope. * * @param symbol The unqualified symbol. * @return The definining scope or <code>null</code> if there is * no such scope. */ public Scope lookupScope(String symbol) { Scope scope = this; do { if ((null != scope.symbols) && (scope.symbols.containsKey(symbol))) { return scope; } scope = scope.parent; } while (null != scope); return null; } /** * Get the value for the specified unqualified symbol. This * method searches this scope and all its ancestors, returning the * value of the first definition. * * @param symbol The unqualified symbol. * @return The corresponding value or <code>null</code> if there is * no definition. */ public Object lookup(String symbol) { Scope scope = lookupScope(symbol); return (null == scope)? null : scope.symbols.get(symbol); } /** * Get the scope named by the specified unqualified symbol, which * is nested in the scope defining the symbol. This method * searches this scope and all its ancestors, up to the first * defining scope. It then looks for the nested scope with the * same name. * * @param symbol The unqualified symbol. * @return The bound scope or <code>null</code> if there is no * definition or nested scope with the same name. */ public Scope lookupBoundScope(String symbol) { Scope scope = lookupScope(symbol); return (null == scope)? null : scope.getNested(symbol); } /** * Get the local value for the specified unqualified symbol. * * @param symbol The unqualified symbol. * @return The corresponding value or <code>null</code> if there is * no local definition. */ public Object lookupLocally(String symbol) { return (null == symbols)? null : symbols.get(symbol); } /** * Set the specified symbol's value to the specified value in this * scope. * * @param symbol The unqualified symbol. * @param value The value. */ public void define(String symbol, Object value) { if (null == symbols) { symbols = new HashMap<String, Object>(); } symbols.put(symbol, value); } /** * Add the specified value to the specified symbol's values in * this scope. * * @param symbol The unqualified symbol. * @param value The value. */ @SuppressWarnings("unchecked") public void addDefinition(String symbol, Object value) { if (null == symbols) { symbols = new HashMap<String, Object>(); } if (symbols.containsKey(symbol)) { Object o = symbols.get(symbol); if (o instanceof List) { ((List<Object>)o).add(value); } else { List<Object> l = new ArrayList<Object>(); l.add(o); l.add(value); symbols.put(symbol, l); } } else { symbols.put(symbol, value); } } /** * Undefine the specified unqualified symbol. If the symbol is * defined in this scope, this method removes all its values. * * @param symbol The unqualified symbol. */ public void undefine(String symbol) { if (null != symbols) { symbols.remove(symbol); } } /** * Qualify the specified unqualified symbol with this scope's * name. * * @param symbol The unqualified symbol. * @return The qualified symbol. */ public String qualify(String symbol) { return Utilities.qualify(qName, symbol); } /** * Dump the contents of this scope. This method pretty prints the * contents of this scope and all nested scopes with the specified * printer. If the printer is registered with a visitor, that * visitor is used for formatting any node values. * * @param printer The printer, which need not be registered with a * visitor. */ public void dump(Printer printer) { boolean hasVisitor = (null != printer.visitor()); printer.indent().p('.').p(name).pln(" = {").incr(); if (null != symbols) { List<String> keys = new ArrayList<String>(symbols.keySet()); Collections.sort(keys); for (String symbol : keys) { Object value = symbols.get(symbol); printer.indent().p(symbol).p(" = "); if (null == value) { printer.p("null"); } else if (hasVisitor && (value instanceof Node)) { printer.p((Node)value); } else if (value instanceof String) { printer.p('"').escape((String)value, Utilities.JAVA_ESCAPES).p('"'); } else { try { printer.p(value.toString()); } catch (final Exception e) { printer.p(value.getClass().getName() + "@?"); } } printer.pln(';'); Scope nested = getNested(symbol); if (null != nested) { nested.dump(printer); } } } if (null != scopes) { List<String> keys = new ArrayList<String>(scopes.keySet()); Collections.sort(keys); for (String name : keys) { if ((null == symbols) || (! symbols.containsKey(name))) { scopes.get(name).dump(printer); } } } printer.decr().indent().pln("};"); } } // ========================================================================= /** The root scope. */ protected Scope root; /** The current scope. */ protected Scope current; /** The fresh name count. */ protected int freshNameCount; /** The fresh identifier count. */ protected int freshIdCount; // ========================================================================= /** * Create a new symbol table with the empty string as the root * scope's name. */ public SymbolTable() { this(""); } /** * Create a new symbol table. * * @param root The name of the root scope. */ public SymbolTable(String root) { this.root = new Scope(root); current = this.root; freshNameCount = 0; freshIdCount = 0; } // ========================================================================= /** * Clear this symbol table. This method deletes all scopes and * their definitions from this symbol table. */ public void reset() { root.scopes = null; root.symbols = null; current = root; freshNameCount = 0; freshIdCount = 0; } /** * Get the root scope. * * @return The root scope. */ public Scope root() { return root; } /** * Get the current scope. * * @return The current scope. */ public Scope current() { return current; } /** * Get the scope with the specified qualified name. * * @param name The qualified name. * @return The corresponding scope or <code>null</code> if no such * scope exits. */ public Scope getScope(String name) { // Optimize for the common case where the specified name denotes a // scope directly nested in the current scope. Scope scope = current; if (name.startsWith(scope.qName) && (name.lastIndexOf(Constants.QUALIFIER) == scope.qName.length())) { return scope.getNested(Utilities.getName(name)); } String[] components = Utilities.toComponents(name); scope = root.name.equals(components[0])? root : null; int index = 1; while ((null != scope) && (index < components.length)) { scope = scope.getNested(components[index]); index++; } return scope; } /** * Set the current scope to the specified scope. * * @param scope The new current scope. * @throws IllegalArgumentException Signals that this symbol table's * root is not the specified scope's root. */ public void setScope(Scope scope) { // Check the specified scope. Scope s = scope; while (null != s.parent) s = s.parent; if (s != root) { throw new IllegalArgumentException("Scope " + scope.qName + " not " + "in this symbol table " + this); } // Make the scope the current scope. current = scope; } /** * Determine whether the specified symbol is defined. If the symbol * is qualified, this method checks whether the symbol is defined in * the named scope. Otherwise, it checks whether the symbol is * defined in the current scope or one of its ancestors. * * @param symbol The symbol. * @return <code>true</code> if the specified symbol is defined. */ public boolean isDefined(String symbol) { Scope scope = lookupScope(symbol); if ((null == scope) || (null == scope.symbols)) { return false; } else { return scope.symbols.containsKey(Utilities.unqualify(symbol)); } } /** * Determine whether the specified symbol is define multiple times. * If the symbol is qualified, this method checks whether the symbol * has multiple definitions in the named scope. Otherwise, it * checks whether the symbol has multiple definitions in the current * scope or one of its ancestors. * * @param symbol The symbol. * @return <code>true</code> if the specified symbol is multiply * defined. */ public boolean isDefinedMultiply(String symbol) { Scope scope = lookupScope(symbol); if ((null == scope) || (null == scope.symbols)) { return false; } else { return scope.symbols.get(Utilities.unqualify(symbol)) instanceof List; } } /** * Get the scope for the specified symbol. If the symbol is * qualified, this method returns the named scope (without checking * whether the symbol is defined in that scope). Otherwise, it * searches the current scope and all its ancestors, returning the * first defining scope. * * @param symbol The symbol. * @return The corresponding scope or <code>null</code> if no such * scope exits. */ public Scope lookupScope(String symbol) { if (Utilities.isQualified(symbol)) { return getScope(Utilities.getQualifier(symbol)); } else { return current.lookupScope(symbol); } } /** * Get the value for the specified symbol. If the symbol is * qualified, this method returns the definition within the named * scope. Otherwise, it searches the current scope and all its * ancestors, returning the value of the first definition. * * @param symbol The symbol. * @return The corresponding value or <code>null</code> if no such * definition exists. */ public Object lookup(String symbol) { Scope scope = lookupScope(symbol); if ((null == scope) || (null == scope.symbols)) { return null; } else { return scope.symbols.get(Utilities.unqualify(symbol)); } } /** * Enter the scope with the specified unqualified name. If the * current scope does not have a scope with the specified name, a * new scope with the specified name is created. In either case, * the scope with that name becomes the current scope. * * @param name The unqualified name. */ public void enter(String name) { Scope parent = current; Scope child = parent.getNested(name); if (null == child) { child = new Scope(name, parent); } current = child; } /** * Exit the current scope. * * @throws IllegalStateException * Signals that the current scope is the root scope. */ public void exit() { if (null == current.parent) { throw new IllegalStateException("Unable to exit root scope"); } current = current.parent; } /** * Delete the scope with the specified unqualified name. If the * current scope contains a nested scope with the specified name, * this method deletes that scope and <em>all its contents</em>, * including nested scopes. * * @param name The unqualified name. */ public void delete(String name) { if (null != current.scopes) { current.scopes.remove(name); } } /** * Determine whether the specified node has an associated {@link * Constants#SCOPE scope}. * * @param n The node. * @return <code>true</code> if the node has an associated scope. */ public static boolean hasScope(Node n) { return n.hasProperty(Constants.SCOPE); } /** * Mark the specified node. If the node does not have an associated * {@link Constants#SCOPE scope}, this method set the property with * the current scope's qualified name. * * @param n The node. */ public void mark(Node n) { if (! n.hasProperty(Constants.SCOPE)) { n.setProperty(Constants.SCOPE, current.getQualifiedName()); } } /** * Enter the specified node. If the node has an associated {@link * Constants#SCOPE scope}, this method tries to enter the scope. * Otherwise, it does not change the scope. * * @param n The node. * @throws IllegalStateException Signals that the node's scope is * invalid or not nested within the current scope. */ public void enter(Node n) { if (n.hasProperty(Constants.SCOPE)) { String name = n.getStringProperty(Constants.SCOPE); Scope scope = getScope(name); if (null == scope) { throw new IllegalStateException("Invalid scope " + name); } else if (scope.getParent() != current) { throw new IllegalStateException("Scope " + name + " not nested in " + current.getQualifiedName()); } current = scope; } } /** * Exit the specified node. If the node has an associated {@link * Constants#SCOPE scope}, the current scope is exited. * * @param n The node. */ public void exit(Node n) { if (n.hasProperty(Constants.SCOPE)) { exit(); } } /** * Create a fresh name. The returned name has * "<code>anonymous</code>" as it base name. * * @see #freshName(String) * * @return A fresh name. */ public String freshName() { return freshName("anonymous"); } /** * Create a fresh name incorporating the specified base name. The * returned name is of the form * <code><i>name</i>(<i>count</i>)</code>. * * @param base The base name. * @return The corresponding fresh name. */ public String freshName(String base) { StringBuilder buf = new StringBuilder(); buf.append(base); buf.append(Constants.START_OPAQUE); buf.append(freshNameCount++); buf.append(Constants.END_OPAQUE); return buf.toString(); } /** * Create a fresh C identifier. The returned identifier has * "<code>tmp</code>" as its base name. * * @see #freshCId(String) * * @return A fresh C identifier. */ public String freshCId() { return freshCId("tmp"); } /** * Create a fresh C identifier incorporating the specified base * name. The returned name is of the form * <code>__<i>name</i>_<i>count</i></code>. * * @param base The base name. * @return The corresponding fresh C identifier. */ public String freshCId(String base) { StringBuilder buf = new StringBuilder(); buf.append("__"); buf.append(base); buf.append('_'); buf.append(freshIdCount++); return buf.toString(); } /** * Create a fresh Java identifier. The returned identifier has * "<code>tmp</code>" as its base name. * * @see #freshJavaId(String) * * @return A fresh Java identifier. */ public String freshJavaId() { return freshJavaId("tmp"); } /** * Create a fresh Java identifier incorporating the specified base * name. The returned name is of the form * <code><i>name</i>$<i>count</i></code>. * * @param base The base name. * @return The corresponding fresh Java identifier. */ public String freshJavaId(String base) { StringBuilder buf = new StringBuilder(); buf.append(base); buf.append('$'); buf.append(freshIdCount++); return buf.toString(); } /** * Convert the specified unqualified symbol to a symbol in the * specified name space. * * @param symbol The symbol * @param space The name space. * @return The mangled symbol. */ public static String toNameSpace(String symbol, String space) { return space + Constants.START_OPAQUE + symbol + Constants.END_OPAQUE; } /** * Determine whether the specified symbol is in the specified name * space. * * @param symbol The symbol. * @param space The name space. * @return <code>true</code> if the symbol is mangled symbol in the * name space. */ public static boolean isInNameSpace(String symbol, String space) { try { return (symbol.startsWith(space) && (Constants.START_OPAQUE == symbol.charAt(space.length())) && symbol.endsWith(END_OPAQUE)); } catch (IndexOutOfBoundsException x) { return false; } } /** The end of opaqueness marker as a string. */ private static final String END_OPAQUE = Character.toString(Constants.END_OPAQUE); /** * Convert the specified unqualified symbol within a name space to a * symbol without a name space. * * @param symbol The mangled symbol within a name space. * @return The corresponding symbol without a name space. */ public static String fromNameSpace(String symbol) { int start = symbol.indexOf(Constants.START_OPAQUE); int end = symbol.length() - 1; if ((0 < start) && (Constants.END_OPAQUE == symbol.charAt(end))) { return symbol.substring(start + 1, end); } else { throw new IllegalArgumentException("Not a mangled symbol '"+symbol+"'"); } } /** * Conver the specified C macro identifier into a symbol table scope * name. * * @param id The macro identifier. * @return The corresponding symbol table scope name. */ public static String toMacroScopeName(String id) { return toNameSpace(id, "macro"); } /** * Determine whether the specified scope name represents a macro's * scope. * * @param name The name. * @return <code>true</code> if the name denotes a macro scope. */ public static boolean isMacroScopeName(String name) { return isInNameSpace(name, "macro"); } /** * Convert the specified C function identifier into a symbol table * scope name. * * @param id The function identifier. * @return The corresponding symbol table scope name. */ public static String toFunctionScopeName(String id) { return toNameSpace(id, "function"); } /** * Determine whether the specified scope name represents a * function's scope. * * @param name The name. * @return <code>true</code> if the name denotes a function scope. */ public static boolean isFunctionScopeName(String name) { return isInNameSpace(name, "function"); } /** * Convert the specified C struct, union, or enum tag into a symbol * table name. * * @param tag The tag. * @return The corresponding symbol table name. */ public static String toTagName(String tag) { return toNameSpace(tag, "tag"); } /** * Convert the specified label identifier into a symbol table name. * * @param id The identifier. * @return The corresponding symbol table name. */ public static String toLabelName(String id) { return toNameSpace(id, "label"); } /** * Convert the specified method identifier into a symbol table name. * * @param id The method identifier. * @return The corresponding symbol table name. */ public static String toMethodName(String id) { return toNameSpace(id, "method"); } }