All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.org.python.indexer.Scope Maven / Gradle / Ivy

Go to download

Jython is an implementation of the high-level, dynamic, object-oriented language Python written in 100% Pure Java, and seamlessly integrated with the Java platform. It thus allows you to run Python on any Java platform.

There is a newer version: 2.7.4
Show newest version
/**
 * Copyright 2009, Google Inc.  All rights reserved.
 * Licensed to PSF under a Contributor Agreement.
 */
package org.python.indexer;

import org.python.indexer.ast.NName;
import org.python.indexer.ast.NNode;
import org.python.indexer.ast.NUrl;
import org.python.indexer.types.NType;
import org.python.indexer.types.NUnionType;
import org.python.indexer.types.NUnknownType;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.Map;
import java.util.Set;

/**
 * Symbol table.
 */
public class Scope {

    /**
     * For preventing circular inheritance from recursing.
     */
    private static Set looked = new HashSet();

    public enum Type {
        CLASS,
        INSTANCE,
        FUNCTION,
        MODULE,
        GLOBAL,
        SCOPE
    }

    /**
     * XXX: This table is incorrectly overloaded to contain both object
     * attributes and lexical-ish scope names, when they are in some cases
     * separate namespaces.  (In particular, they're effectively the same
     * namespace for module scope and class scope, and they're different for
     * function scope, which uses the {@code func_dict} namespace for storing
     * attributes.)
     */
    private Map table;  // stays null for most scopes (mem opt)

    private Scope parent;
    private List supers;
    private Set globalNames;
    private Type scopeType;
    private String path = "";
    private int lambdaCounter = 0;
    private boolean isBindingPhase = false;

    public Scope(Scope parent, Type type) {
        if (type == null) {
            throw new IllegalArgumentException("'type' param cannot be null");
        }
        setParent(parent);
        setScopeType(type);
    }

    public void setTable(Map table) {
        this.table = table;
    }

    /**
     * Returns an immutable view of the table.
     */
    public Map getTable() {
        if (table != null) {
            return Collections.unmodifiableMap(table);
        }
        Map map = Collections.emptyMap();
        return map;
    }

    public void setParent(Scope parent) {
        this.parent = parent;
    }

    public Scope getParent() {
        return parent;
    }

    public void addSuper(Scope sup) {
        if (supers == null) {
            supers = new ArrayList();
        }
        supers.add(sup);
    }

    public void setSupers(List supers) {
        this.supers = supers;
    }

    public List getSupers() {
        if (supers != null) {
            return Collections.unmodifiableList(supers);
        }
        List list = Collections.emptyList();
        return list;
    }

    public void setScopeType(Type type) {
        this.scopeType = type;
    }

    public Type getScopeType() {
        return scopeType;
    }

    public boolean isFunctionScope() {
        return scopeType == Type.FUNCTION;
    }

    /**
     * Mark a name as being global (i.e. module scoped) for name-binding and
     * name-lookup operations in this code block and any nested scopes.
     */
    public void addGlobalName(String name) {
        if (name == null) {
            return;
        }
        if (globalNames == null) {
            globalNames = new HashSet();
        }
        globalNames.add(name);
    }

    /**
     * Returns {@code true} if {@code name} appears in a {@code global}
     * statement in this scope or any enclosing scope.
     */
    public boolean isGlobalName(String name) {
        if (globalNames != null) {
            return globalNames.contains(name);
        }
        return parent == null ? false : parent.isGlobalName(name);
    }

    /**
     * Directly assigns a binding to a name in this table.  Does not add a new
     * definition or reference to the binding.  This form of {@code put} is
     * often followed by a call to {@link Indexer#putLocation} to create a reference to
     * the binding.  When there is no code location associated with {@code id},
     * or it is otherwise undesirable to create a reference, the
     * {@link Indexer#putLocation} call is omitted.
     */
    public void put(String id, NBinding b) {
        putBinding(id, b);
    }

    /**
     * Adds a definition and/or reference to the table.
     * If there is no binding for {@code id}, creates one and gives it
     * {@code type} and {@code kind}.  

* * If a binding already exists, then add either a definition or a reference * at {@code loc} to the binding. By convention we consider it a definition * if the type changes. If the passed type is different from the binding's * current type, set the binding's type to the union of the old and new * types, and add a definition. If the new type is the same, just add a * reference.

* * If the binding already exists, {@code kind} is only updated if a * definition was added and the binding's type was previously the * unknown type. */ public NBinding put(String id, NNode loc, NType type, NBinding.Kind kind) { if (type == null) { throw new IllegalArgumentException("Null type: id=" + id + ", loc=" + loc); } NBinding b = lookupScope(id); return insertOrUpdate(b, id, loc, type, kind); } /** * Same as {@link #put}, but adds the name as an attribute of this scope. * Looks up the superclass chain to see if the attribute exists, rather * than looking in the lexical scope chain. * * @return the new binding, or {@code null} if the current scope does * not have a properly initialized path. */ public NBinding putAttr(String id, NNode loc, NType type, NBinding.Kind kind) { if (type == null) { throw new IllegalArgumentException("Null type: id=" + id + ", loc=" + loc); } // Attributes are always part of a qualified name. If there is no qname // on the target type, it's a bug (we forgot to set the path somewhere.) if ("".equals(path)) { Indexer.idx.reportFailedAssertion( "Attempting to set attr '" + id + "' at location " + loc + (loc != null ? loc.getFile() : "") + " in scope with no path (qname) set: " + this.toShortString()); return null; } NBinding b = lookupAttr(id); return insertOrUpdate(b, id, loc, type, kind); } private NBinding insertOrUpdate(NBinding b, String id, NNode loc, NType t, NBinding.Kind k) { if (b == null) { b = insertBinding(new NBinding(id, loc, t, k)); } else { updateType(b, loc, t, k); } return b; } /** * Adds a new binding for {@code id}. If a binding already existed, * replaces its previous definitions, if any, with {@code loc}. Sets the * binding's type to {@code type} (not a union with the previous type). */ public NBinding update(String id, NNode loc, NType type, NBinding.Kind kind) { if (type == null) { throw new IllegalArgumentException("Null type: id=" + id + ", loc=" + loc); } return update(id, new Def(loc), type, kind); } /** * Adds a new binding for {@code id}. If a binding already existed, * replaces its previous definitions, if any, with {@code loc}. Sets the * binding's type to {@code type} (not a union with the previous type). */ public NBinding update(String id, Def loc, NType type, NBinding.Kind kind) { if (type == null) { throw new IllegalArgumentException("Null type: id=" + id + ", loc=" + loc); } NBinding b = lookupScope(id); if (b == null) { return insertBinding(new NBinding(id, loc, type, kind)); } b.getDefs().clear(); // XXX: what about updating refs & idx.locations? b.addDef(loc); b.setType(type); // XXX: is this a bug? I think he meant to do this check before the // line above that sets b.type, if it's supposed to be like put(). if (b.getType().isUnknownType()) { b.setKind(kind); } return b; } private NBinding insertBinding(NBinding b) { switch (b.getKind()) { case MODULE: b.setQname(b.getType().getTable().path); break; case PARAMETER: b.setQname(extendPathForParam(b.getName())); break; default: b.setQname(extendPath(b.getName())); break; } b = Indexer.idx.putBinding(b); putBinding(b.getName(), b); return b; } private void putBinding(String id, NBinding b) { ensureTable(); table.put(id, b); } private void updateType(NBinding b, NNode loc, NType type, NBinding.Kind kind) { NType curType = b.followType(); if (!isNewType(curType, type)) { if (loc != null && !(loc instanceof NUrl) && !b.getDefs().contains(loc)) { Indexer.idx.putLocation(loc, b); } return; } if (loc != null && !b.getRefs().contains(loc)) { b.addDef(loc); b.setProvisional(false); } // The union ordering matters here. If they're two different unknown // types, union() points the first one to the second one. We want to // keep the binding's existing type iff its table contains provisional // attribute bindings that we need to look up later. NType btype = b.getType(); NType t1, t2; if (btype.isUnknownType() && !btype.getTable().isEmpty()) { t1 = type; t2 = btype; } else { t1 = btype; t2 = type; } NType newType = NUnionType.union(t1, t2); b.setType(newType); if (curType.isUnknownType()) { b.setKind(kind); } retargetReferences(b, curType); } /** * If the current type had a provisional binding, retarget its refs to the * new type. It probably only works one level deep: need dataflow analysis * in the general case. However, it does pick up some extra references, * so it's reasonable for now. */ private void retargetReferences(NBinding b, NType curType) { Scope newScope = b.followType().getTable(); for (Map.Entry e : curType.getTable().entrySet()) { String attr = e.getKey(); NBinding oldBinding = e.getValue(); if (!oldBinding.isProvisional()) { continue; } Indexer.idx.removeBinding(oldBinding); NBinding newBinding = newScope.lookupAttr(attr); if (newBinding != null) { List refs = new ArrayList(); // avoid ConcurrentModificationException refs.addAll(oldBinding.getRefs()); for (Ref ref : refs) { Indexer.idx.updateLocation(ref, newBinding); } } } } /** * Returns {@code true} if the binding is being assigned a new type. */ private boolean isNewType(NType curType, NType type) { // In the bindNames() phase we want all places where a given name // is bound in the same scope to share the same binding, because // we haven't resolved the types yet. This takes care of that case. if (isBindingPhase) { return false; } if (curType.isUnionType()) { return !curType.asUnionType().contains(type); } return curType != type; } public void remove(String id) { if (table != null) { table.remove(id); } } /** * Create a copy of the symbol table but without the links to parent, supers * and children. Useful for creating instances. * * @return the symbol table for use by the instance. */ public Scope copy(Type tableType) { Scope ret = new Scope(null, tableType); if (table != null) { ret.ensureTable(); ret.table.putAll(table); } return ret; } public void setPath(String path) { if (path == null) { throw new IllegalArgumentException("'path' param cannot be null"); } this.path = path; } public String getPath() { return path; } public void setPath(String a, String b) { NBinding b1 = lookup(a); NBinding b2 = lookup(b); if (b1 != null && b2 != null) { b1.setQname(b2.getQname()); } } /** * Look up a name (String) in the current symbol table. If not found, * recurse on the parent table. */ public NBinding lookup(String name) { NBinding b = getModuleBindingIfGlobal(name); if (b != null) { return b; } if (table != null) { NBinding ent = table.get(name); if (ent != null) { return ent; } } if (getParent() == null) { return null; } return getParent().lookup(name); } /** * Specialized version for the convenience of looking up {@code Name}s. * For all other types return {@code null}. */ public NBinding lookup(NNode n) { if (n instanceof NName) { return lookup(((NName)n).id); } return null; } /** * Look up a name, but only in the current scope. * @return the local binding for {@code name}, or {@code null}. */ public NBinding lookupLocal(String name) { NBinding b = getModuleBindingIfGlobal(name); if (b != null) { return b; } return table == null ? null : table.get(name); } /** * Look up an attribute in the type hierarchy. Don't look at parent link, * because the enclosing scope may not be a super class. The search is * "depth first, left to right" as in Python's (old) multiple inheritance * rule. The new MRO can be implemented, but will probably not introduce * much difference. * @param supersOnly search only in the supers' scopes, not in local table. */ public NBinding lookupAttr(String name, boolean supersOnly) { if (looked.contains(this)) { return null; } if (table != null && !supersOnly) { NBinding b = table.get(name); if (b != null) { return b; } } if (supers == null || supers.isEmpty()) { return null; } looked.add(this); try { for (Scope p : supers) { NBinding b = p.lookupAttr(name); if (b != null) { return b; } } return null; } finally { looked.remove(this); } } /** * Look up an attribute in the local scope and superclass scopes. * @see #lookupAttr(String,boolean) */ public NBinding lookupAttr(String name) { return lookupAttr(name, false); } /** * Look up the scope chain for a binding named {@code name} * and if found, return its type. */ public NType lookupType(String name) { return lookupType(name, false); } /** * Look for a binding named {@code name} and if found, return its type. * @param localOnly {@code true} to look only in the current scope; * if {@code false}, follows the scope chain. */ public NType lookupType(String name, boolean localOnly) { NBinding b = localOnly ? lookupLocal(name) : lookup(name); if (b == null) { return null; } NType ret = b.followType(); // XXX: really need to make ModuleTable polymorphic... if (this == Indexer.idx.moduleTable) { if (ret.isModuleType()) { return ret; } if (ret.isUnionType()) { for (NType t : ret.asUnionType().getTypes()) { NType realType = t.follow(); if (realType.isModuleType()) { return realType; } } } Indexer.idx.warn("Found non-module type in module table: " + b); return null; } return ret; } public NType lookupTypeAttr(String name) { NBinding b = lookupAttr(name); if (b != null) { return b.followType(); } return null; } /** * Look up a name, but the search is bounded by a type and will not proceed * to an outer scope when reaching a certain type of symbol table. * * @param name the name to be looked up * @param typebound the type we wish the search to be bounded at * @return a binding, or {@code null} if not found */ public NBinding lookupBounded(String name, Type typebound) { if (scopeType == typebound) { return table == null ? null : table.get(name); } if (getParent() == null) { return null; } return getParent().lookupBounded(name, typebound); } /** * Returns {@code true} if this is a scope in which names may be bound. */ public boolean isScope() { switch (scopeType) { case CLASS: case INSTANCE: case FUNCTION: case MODULE: case GLOBAL: return true; default: return false; } } /** * Find the enclosing scope-defining symbol table.

* * More precisely, if a form introduces a new name in the "current scope", * resolving the form needs to search up the symbol-table chain until it * finds the table representing the scope to which the name should be added. * Used by {@link org.python.indexer.ast.NameBinder} to create new name * bindings in the appropriate enclosing table with the appropriate binding * type. */ public Scope getScopeSymtab() { if (this.isScope()) { return this; } if (getParent() == null) { Indexer.idx.reportFailedAssertion("No binding scope found for " + this.toShortString()); return this; } return getParent().getScopeSymtab(); } /** * Look up a name, but bounded by a scope defining construct. Those scopes * are of type module, class, instance or function. This is used in * determining the locations of a variable's definition. */ public NBinding lookupScope(String name) { NBinding b = getModuleBindingIfGlobal(name); if (b != null) { return b; } Scope st = getScopeSymtab(); if (st != null) { return st.lookupLocal(name); } return null; } /** * Find a symbol table of a certain type in the enclosing scopes. */ public Scope getSymtabOfType(Type type) { if (scopeType == type) { return this; } if (parent == null) { return null; } return parent.getSymtabOfType(type); } /** * Returns the global scope (i.e. the module scope for the current module). */ public Scope getGlobalTable() { Scope result = getSymtabOfType(Type.MODULE); if (result == null) { Indexer.idx.reportFailedAssertion("No module table found for " + this); result = this; } return result; } /** * Returns the containing lexical scope (which may be this scope) * for lexical name lookups. In particular, it skips class scopes. */ public Scope getEnclosingLexicalScope() { if (scopeType == Scope.Type.FUNCTION || scopeType == Scope.Type.MODULE) { return this; } if (parent == null) { Indexer.idx.reportFailedAssertion("No lexical scope found for " + this); return this; } return parent.getEnclosingLexicalScope(); } /** * If {@code name} is declared as a global, return the module binding. */ private NBinding getModuleBindingIfGlobal(String name) { if (isGlobalName(name)) { Scope module = getGlobalTable(); if (module != null && module != this) { return module.lookupLocal(name); } } return null; } /** * Name binding occurs in a separate pass before the name resolution pass, * building out the scope tree and binding names in the correct scopes. * In this pass, the name binding and lookup rules are slightly different. * This condition is transient: no scopes will be in the name-binding phase * in a completed index (or module). */ public boolean isNameBindingPhase() { return isBindingPhase; } public void setNameBindingPhase(boolean isBindingPhase) { this.isBindingPhase = isBindingPhase; } /** * Merge all records from another symbol table. Used by {@code import from *}. */ public void merge(Scope other) { ensureTable(); this.table.putAll(other.table); } public Set keySet() { if (table != null) { return table.keySet(); } Set result = Collections.emptySet(); return result; } public Collection values() { if (table != null) { return table.values(); } Collection result = Collections.emptySet(); return result; } public Set> entrySet() { if (table != null) { return table.entrySet(); } Set> result = Collections.emptySet(); return result; } public boolean isEmpty() { return table == null ? true : table.isEmpty(); } /** * Dismantles all resources allocated by this scope. */ public void clear() { if (table != null) { table.clear(); table = null; } parent = null; if (supers != null) { supers.clear(); supers = null; } if (globalNames != null) { globalNames.clear(); globalNames = null; } } public String newLambdaName() { return "lambda%" + (++lambdaCounter); } /** * Generates a qname for a parameter of a function or method. * There is not enough context for {@link #extendPath} to differentiate * params from locals, so callers must use this method when the name is * known to be a parameter name. */ public String extendPathForParam(String name) { if (path.equals("")) { throw new IllegalStateException("Not inside a function"); } return path + "@" + name; } /** * Constructs a qualified name by appending {@code name} to this scope's qname.

* * The indexer uses globally unique fully qualified names to address * identifier definition sites. Many Python identifiers are already * globally addressable using dot-separated package, class and attribute * names.

* * Function variables and parameters are not globally addressable in the * language, so the indexer uses a special path syntax for creating globally * unique qualified names for them. By convention the syntax is "@" for * parameters and "&" for local variables. * * @param name a name to append to the current qname * @return the qname for {@code name}. Does not change this scope's path. */ public String extendPath(String name) { if (name.endsWith(".py")) { name = Util.moduleNameFor(name); } if (path.equals("")) { return name; } String sep = null; switch (scopeType) { case MODULE: case CLASS: case INSTANCE: case SCOPE: sep = "."; break; case FUNCTION: sep = "&"; break; default: System.err.println("unsupported context for extendPath: " + scopeType); return path; } return path + sep + name; } private void ensureTable() { if (table == null) { table = new LinkedHashMap(); } } @Override public String toString() { return ""; } public String toShortString() { return ""; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy