// (C) Copyright 2001 Samuele Pedroni package org.python.compiler; import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.Map; import java.util.Hashtable; import java.util.Vector; import org.python.antlr.ParseException; import org.python.antlr.PythonTree; import org.python.antlr.ast.Return; import org.python.antlr.base.expr; public class ScopeInfo extends Object implements ScopeConstants { public PythonTree scope_node; public String scope_name; public int level; public int func_level; public void dump() { // for debugging if (org.python.core.Options.verbose < org.python.core.Py.DEBUG) return; for(int i=0; i<level; i++) System.err.print(' '); System.err.print(((kind != CLASSSCOPE)?scope_name:"class "+ scope_name)+": "); for (Map.Entry<String, SymInfo> entry : tbl.entrySet()) { String name = entry.getKey(); SymInfo info = entry.getValue(); int flags = info.flags; System.err.print(name); if ((flags&BOUND) != 0) System.err.print('='); // func scope global (affect nested scopes) // vs. class scope global if ((flags&NGLOBAL) != 0) System.err.print('G'); else if ((flags&CLASS_GLOBAL) != 0) System.err.print('g'); if ((flags&PARAM) != 0) System.err.print('P'); else if ((flags&FROM_PARAM) != 0) System.err.print('p'); if ((flags&CELL) != 0) System.err.print('!'); if ((flags&FREE) != 0) System.err.print(",f"); System.err.print(" "); } System.err.println(); } public ScopeInfo(String name, PythonTree node, int level, int kind, int func_level, ArgListCompiler ac) { scope_name = name; scope_node = node; this.level = level; this.kind = kind; this.func_level = func_level; this.ac = ac; } public int kind; public boolean unqual_exec; public boolean exec; public boolean from_import_star; public boolean contains_ns_free_vars; public boolean generator; private boolean hasReturnWithValue; public int yield_count; public int max_with_count; public ArgListCompiler ac; public Map<String, SymInfo> tbl = new LinkedHashMap<String, SymInfo>(); public Vector<String> names = new Vector<String>(); public int addGlobal(String name) { // global kind = func vs. class int global = kind==CLASSSCOPE?CLASS_GLOBAL:NGLOBAL; SymInfo info = tbl.get(name); if (info == null) { tbl.put(name,new SymInfo(global|BOUND)); return -1; } int prev = info.flags; info.flags |= global|BOUND; return prev; } public int local = 0; public void addParam(String name) { //System.out.println("addParam " + name); tbl.put(name, new SymInfo(PARAM|BOUND,local++)); names.addElement(name); } public void markFromParam() { for (SymInfo info : tbl.values()) { info.flags |= FROM_PARAM; } } public void addBound(String name) { SymInfo info = tbl.get(name); if (info == null) { tbl.put(name, new SymInfo(BOUND)); return; } info.flags |= BOUND; } public void addUsed(String name) { if (tbl.get(name) == null) { tbl.put(name, new SymInfo(0)); return; } } private final static Object PRESENT = new Object(); public Hashtable<String,Object> inner_free = new Hashtable<String,Object>(); public Vector<String> cellvars = new Vector<String>(); public Vector<String> jy_paramcells = new Vector<String>(); public int jy_npurecell; public int cell, distance; public ScopeInfo up; //Resolve the names used in the given scope, and mark any freevars used in the up scope public void cook(ScopeInfo up, int distance, CompilationContext ctxt) throws Exception { if(up == null) return; // top level => nop this.up = up; this.distance = distance; boolean func = kind == FUNCSCOPE; Vector<String> purecells = new Vector<String>(); cell = 0; boolean some_inner_free = inner_free.size() > 0; for (Enumeration e = inner_free.keys(); e.hasMoreElements(); ) { String name = (String)e.nextElement(); SymInfo info = tbl.get(name); if (info == null) { tbl.put(name,new SymInfo(FREE)); continue; } int flags = info.flags; if (func) { // not func global and bound ? if ((flags&NGLOBAL) == 0 && (flags&BOUND) != 0) { info.flags |= CELL; if ((info.flags&PARAM) != 0) jy_paramcells.addElement(name); cellvars.addElement(name); info.env_index = cell++; if ((flags&PARAM) == 0) purecells.addElement(name); continue; } } else { info.flags |= FREE; } } boolean some_free = false; boolean nested = up.kind != TOPSCOPE; for (Map.Entry<String, SymInfo> entry : tbl.entrySet()) { String name = entry.getKey(); SymInfo info = entry.getValue(); int flags = info.flags; if (nested && (flags&FREE) != 0) up.inner_free.put(name,PRESENT); if ((flags&(GLOBAL|PARAM|CELL)) == 0) { if ((flags&BOUND) != 0) { // ?? only func // System.err.println("local: "+name); names.addElement(name); info.locals_index = local++; continue; } info.flags |= FREE; some_free = true; if (nested) up.inner_free.put(name,PRESENT); } } if ((jy_npurecell = purecells.size()) > 0) { int sz = purecells.size(); for (int i = 0; i < sz; i++) { names.addElement(purecells.elementAt(i)); } } if (some_free && nested) { up.contains_ns_free_vars = true; } // XXX - this doesn't catch all cases - may depend subtly // on how visiting NOW works with antlr compared to javacc if ((unqual_exec || from_import_star)) { if(some_inner_free) dynastuff_trouble(true, ctxt); else if(func_level > 1 && some_free) dynastuff_trouble(false, ctxt); } } private void dynastuff_trouble(boolean inner_free, CompilationContext ctxt) throws Exception { StringBuilder illegal = new StringBuilder(); if (unqual_exec && from_import_star) { illegal.append("function '") .append(scope_name) .append("' uses import * and bare exec, which are illegal"); } else if (unqual_exec) { illegal.append("unqualified exec is not allowed in function '") .append(scope_name) .append("'"); } else { illegal.append("import * is not allowed in function '").append(scope_name).append("'"); } if (inner_free) { illegal.append(" because it contains a function with free variables"); } else { illegal.append(" because it contains free variables"); } ctxt.error(illegal.toString(), true, scope_node); } public Vector<String> freevars = new Vector<String>(); /** * setup the closure on this scope using the scope passed into cook as up as * the containing scope */ public void setup_closure() { setup_closure(up); } /** * setup the closure on this scope using the passed in scope. This is used * by jythonc to setup its closures. */ public void setup_closure(ScopeInfo up){ int free = cell; // env = cell...,free... Map<String, SymInfo> up_tbl = up.tbl; boolean nested = up.kind != TOPSCOPE; for (Map.Entry<String, SymInfo> entry : tbl.entrySet()) { String name = entry.getKey(); SymInfo info = entry.getValue(); int flags = info.flags; if ((flags&FREE) != 0) { SymInfo up_info = up_tbl.get(name); // ?? differs from CPython -- what is the intended behaviour? if (up_info != null) { int up_flags = up_info.flags; if ((up_flags&(CELL|FREE)) != 0) { info.env_index = free++; freevars.addElement(name); continue; } // ! func global affect nested scopes if (nested && (up_flags&NGLOBAL) != 0) { info.flags = NGLOBAL|BOUND; continue; } } info.flags &= ~FREE; } } } @Override public String toString() { return "ScopeInfo[" + scope_name + " " + kind + "]@" + System.identityHashCode(this); } public void defineAsGenerator(expr node) { generator = true; if (hasReturnWithValue) { throw new ParseException("'return' with argument " + "inside generator", node); } } public void noteReturnValue(Return node) { if (generator) { throw new ParseException("'return' with argument " + "inside generator", node); } hasReturnWithValue = true; } }