package org.yinwang.pysonar; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.yinwang.pysonar.ast.Node; import org.yinwang.pysonar.types.Type; import org.yinwang.pysonar.types.UnionType; import java.util.*; import java.util.Map.Entry; public class Scope { public enum ScopeType { CLASS, INSTANCE, FUNCTION, MODULE, GLOBAL, SCOPE } @Nullable private Map<String, Binding> table; // stays null for most scopes (mem opt) @Nullable public Scope parent; private Scope forwarding; // link to the closest non-class scope, for lifting functions out @Nullable private List<Scope> supers; @Nullable private Set<String> globalNames; private ScopeType scopeType; private Type type; @NotNull private String path = ""; public Scope(@Nullable Scope parent, ScopeType type) { this.parent = parent; this.scopeType = type; if (type == ScopeType.CLASS) { this.forwarding = parent.getForwarding(); } else { this.forwarding = this; } } public Scope(@NotNull Scope s) { if (s.table != null) { this.table = new HashMap<>(s.table); } this.parent = s.parent; this.scopeType = s.scopeType; this.forwarding = s.forwarding; this.supers = s.supers; this.globalNames = s.globalNames; this.type = s.type; this.path = s.path; } public void setParent(@Nullable Scope parent) { this.parent = parent; } @Nullable public Scope getParent() { return parent; } public Scope getForwarding() { if (forwarding != null) { return forwarding; } else { return this; } } public void addSuper(Scope sup) { if (supers == null) { supers = new ArrayList<Scope>(); } supers.add(sup); } public void setScopeType(ScopeType type) { this.scopeType = type; } public ScopeType getScopeType() { return scopeType; } /** * 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(@Nullable String name) { if (name == null) { throw new IllegalArgumentException("name shouldn't be null"); } 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); } else if (parent != null) { return parent.isGlobalName(name); } else { return false; } } @Nullable public Binding put(String id, Node loc, @NotNull Type type, Binding.Kind kind, int tag) { Binding b = lookupScope(id); return insertOrUpdate(b, id, loc, type, kind, tag); } @Nullable public Binding putAttr(String id, Node loc, @NotNull Type type, Binding.Kind kind, int tag) { Binding b = lookupAttr(id); return insertOrUpdate(b, id, loc, type, kind, tag); } public void remove(String id) { if (table != null) { table.remove(id); } } // helper for put and putAttr @NotNull private Binding insertOrUpdate(@Nullable Binding binding, String id, @NotNull Node loc, @NotNull Type type, Binding.Kind kind, int tag) { if (binding == null) { binding = update(id, new Binding(id, loc, type, kind, tag)); } else if (tag == binding.tag) { binding = update(id, new Binding(id, loc, type, kind, tag)); } else { binding.addDef(loc); binding.setType(UnionType.union(type, binding.getType())); } return binding; } // direct update and replace the same name with a new binding @NotNull public Binding update(String id, Node node, Type type, Binding.Kind kind) { return update(id, new Binding(id, node, type, kind)); } // direct update and replace the name with a binding @NotNull public Binding update(String id, @NotNull Binding b) { getInternalTable().put(id, b); return b; } @NotNull public Scope copy(ScopeType tableType) { Scope ret = new Scope(null, tableType); if (table != null) { ret.getInternalTable().putAll(table); } return ret; } public void setPath(@NotNull String path) { this.path = path; } @NotNull public String getPath() { return path; } public Type getType() { return type; } public void setType(Type type) { this.type = type; } /** * Look up a name in the current symbol table only. Don't recurse on the * parent table. */ @Nullable public Binding lookupLocal(String name) { if (table == null) { return null; } else { return table.get(name); } } /** * Look up a name (String) in the current symbol table. If not found, * recurse on the parent table. */ @Nullable public Binding lookup(String name) { Binding b = getModuleBindingIfGlobal(name); if (b != null) { return b; } else { Binding ent = lookupLocal(name); if (ent != null) { return ent; } else if (getParent() != null) { return getParent().lookup(name); } else { return null; } } } /** * Look up a name in the module if it is declared as global, otherwise look * it up locally. */ @Nullable public Binding lookupScope(String name) { Binding b = getModuleBindingIfGlobal(name); if (b != null) { return b; } else { return lookupLocal(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. */ @NotNull private static Set<Scope> looked = new HashSet<>(); // circularity prevention @Nullable public Binding lookupAttr(String attr) { if (looked.contains(this)) { return null; } else { Binding b = lookupLocal(attr); if (b != null) { return b; } else { if (supers != null && !supers.isEmpty()) { looked.add(this); for (Scope p : supers) { b = p.lookupAttr(attr); if (b != null) { looked.remove(this); return b; } } looked.remove(this); return null; } else { return null; } } } } /** * Look for a binding named {@code name} and if found, return its type. */ @Nullable public Type lookupType(String name) { Binding b = lookup(name); if (b == null) { return null; } else { return b.getType(); } } /** * Look for a attribute named {@code attr} and if found, return its type. */ @Nullable public Type lookupAttrType(String attr) { Binding b = lookupAttr(attr); if (b == null) { return null; } else { return b.getType(); } } /** * Find a symbol table of a certain type in the enclosing scopes. */ @Nullable private Scope getSymtabOfType(ScopeType type) { if (scopeType == type) { return this; } else if (parent == null) { return null; } else { return parent.getSymtabOfType(type); } } /** * Returns the global scope (i.e. the module scope for the current module). */ @NotNull public Scope getGlobalTable() { Scope result = getSymtabOfType(ScopeType.MODULE); if (result != null) { return result; } else { Util.die("Couldn't find global table. Shouldn't happen"); return this; } } /** * If {@code name} is declared as a global, return the module binding. */ @Nullable private Binding getModuleBindingIfGlobal(String name) { if (isGlobalName(name)) { Scope module = getGlobalTable(); if (module != this) { return module.lookupLocal(name); } } return null; } public void putAll(@NotNull Scope other) { getInternalTable().putAll(other.getInternalTable()); } @NotNull public Set<String> keySet() { if (table != null) { return table.keySet(); } else { return Collections.emptySet(); } } @NotNull public Collection<Binding> values() { if (table != null) { return table.values(); } return Collections.emptySet(); } @NotNull public Set<Entry<String, Binding>> entrySet() { if (table != null) { return table.entrySet(); } return Collections.emptySet(); } public boolean isEmpty() { return table == null || table.isEmpty(); } @Nullable public String extendPath(@NotNull String name) { if (name.endsWith(".py")) { name = Util.moduleNameFor(name); } if (path.equals("")) { return name; } String sep; switch (scopeType) { case MODULE: case CLASS: case INSTANCE: case SCOPE: sep = "."; break; case FUNCTION: sep = "."; break; default: Util.msg("unsupported context for extendPath: " + scopeType); return path; } return path + sep + name; } @NotNull private Map<String, Binding> getInternalTable() { if (this.table == null) { this.table = new HashMap<>(); } return this.table; } @NotNull @Override public String toString() { return "<Scope:" + getScopeType() + ":" + path + ":" + (table == null ? "{}" : table.keySet()) + ">"; } }