summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--inference.py1
-rw-r--r--infutils.py66
-rw-r--r--lookup.py139
-rw-r--r--node_classes.py108
-rw-r--r--nodes.py39
-rw-r--r--patchcomptransformer.py3
-rw-r--r--protocols.py3
-rw-r--r--rebuilder.py15
-rw-r--r--scoped_nodes.py304
9 files changed, 416 insertions, 262 deletions
diff --git a/inference.py b/inference.py
index 9e60662f..1bd424b7 100644
--- a/inference.py
+++ b/inference.py
@@ -404,3 +404,4 @@ def infer_empty_node(self, context=None):
except ASTNGError:
yield YES
nodes.EmptyNode.infer = path_wrapper(infer_empty_node)
+
diff --git a/infutils.py b/infutils.py
index b4e5650c..a796eed9 100644
--- a/infutils.py
+++ b/infutils.py
@@ -26,7 +26,6 @@ from logilab.common.compat import chain, imap
from logilab.astng._exceptions import InferenceError, NotFoundError, UnresolvableName
from logilab.astng._nodes import BaseClass
-from logilab.astng.node_classes import List, Tuple, If, TryExcept
class Proxy(BaseClass):
@@ -77,71 +76,6 @@ class InferenceContext(object):
clone.boundnode = self.boundnode
return clone
-
-def are_exclusive(stmt1, stmt2, exceptions=None):
- """return true if the two given statements are mutually exclusive
-
- `exceptions` may be a list of exception names. If specified, discard If
- branches and check one of the statement is in an exception handler catching
- one of the given exceptions.
-
- algorithm :
- 1) index stmt1's parents
- 2) climb among stmt2's parents until we find a common parent
- 3) if the common parent is a If or TryExcept statement, look if nodes are
- in exclusive branches
- """
- # index stmt1's parents
- stmt1_parents = {}
- children = {}
- node = stmt1.parent
- previous = stmt1
- while node:
- stmt1_parents[node] = 1
- children[node] = previous
- previous = node
- node = node.parent
- # climb among stmt2's parents until we find a common parent
- node = stmt2.parent
- previous = stmt2
- while node:
- if stmt1_parents.has_key(node):
- # if the common parent is a If or TryExcept statement, look if
- # nodes are in exclusive branches
- if isinstance(node, If) and exceptions is None:
- if (node.locate_child(previous)[1]
- is not node.locate_child(children[node])[1]):
- return True
- elif isinstance(node, TryExcept):
- c2attr, c2node = node.locate_child(previous)
- c1attr, c1node = node.locate_child(children[node])
- if c1node is not c2node:
- if ((c2attr == 'body' and c1attr == 'handlers' and children[node].catch(exceptions)) or
- (c2attr == 'handlers' and c1attr == 'body' and previous.catch(exceptions)) or
- (c2attr == 'handlers' and c1attr == 'orelse') or
- (c2attr == 'orelse' and c1attr == 'handlers')):
- return True
- elif c2attr == 'handlers' and c1attr == 'handlers':
- return previous is not children[node]
- return False
- previous = node
- node = node.parent
- return False
-
-
-def unpack_infer(stmt, context=None):
- """return an iterator on nodes inferred by the given statement if the inferred
- value is a list or a tuple, recurse on it to get values inferred by its
- content
- """
- if isinstance(stmt, (List, Tuple)):
- # XXX loosing context
- return chain(*imap(unpack_infer, stmt.elts))
- infered = stmt.infer(context).next()
- if infered is stmt:
- return iter( (stmt,) )
- return chain(*imap(unpack_infer, stmt.infer(context)))
-
def copy_context(context):
if context is not None:
return context.clone()
diff --git a/lookup.py b/lookup.py
index fd731cda..0065d28c 100644
--- a/lookup.py
+++ b/lookup.py
@@ -33,8 +33,8 @@ import __builtin__
from logilab.astng import MANAGER, NotFoundError
from logilab.astng import nodes # XXX this will not work ?? circular import
from logilab.astng._nodes import BaseClass, NodeNG
-from logilab.astng.infutils import are_exclusive, copy_context, _infer_stmts
-
+from logilab.astng.infutils import copy_context, _infer_stmts
+from logilab.astng.node_classes import are_exclusive
class LookupMixIn(BaseClass):
"""Mixin looking up a name in the right scope
@@ -175,141 +175,6 @@ class LookupMixIn(BaseClass):
_stmt_parents.append(stmt.parent)
return _stmts
-
-def builtin_lookup(name):
- """lookup a name into the builtin module
- return the list of matching statements and the astng for the builtin
- module
- """
- builtinastng = MANAGER.astng_from_module(__builtin__)
- if name == '__dict__':
- return builtinastng, ()
- try:
- stmts = builtinastng.locals[name]
- except KeyError:
- stmts = ()
- return builtinastng, stmts
-
-class LocalsDictNodeNG(LookupMixIn, NodeNG):
- """ this class provides locals handling common to Module, Function
- and Class nodes, including a dict like interface for direct access
- to locals information
- """
-
- # attributes below are set by the builder module or by raw factories
-
- # dictionary of locals with name as key and node defining the local as
- # value
- locals = None
-
- def qname(self):
- """return the 'qualified' name of the node, eg module.name,
- module.class.name ...
- """
- if self.parent is None:
- return self.name
- return '%s.%s' % (self.parent.frame().qname(), self.name)
-
- def frame(self):
- """return the first parent frame node (i.e. Module, Function or Class)
- """
- return self
-
- def scope(self):
- """return the first node defining a new scope (i.e. Module,
- Function, Class, Lambda but also GenExpr)
- """
- return self
-
-
- def _scope_lookup(self, node, name, offset=0):
- """XXX method for interfacing the scope lookup"""
- try:
- stmts = node._filter_stmts(self.locals[name], self, offset)
- except KeyError:
- stmts = ()
- if stmts:
- return self, stmts
- if self.parent: # i.e. not Module
- # nested scope: if parent scope is a function, that's fine
- # else jump to the module
- pscope = self.parent.scope()
- if not isinstance(pscope, nodes.Function):
- pscope = pscope.root()
- return pscope.scope_lookup(node, name)
- return builtin_lookup(name) # Module
-
-
-
- def set_local(self, name, stmt):
- """define <name> in locals (<stmt> is the node defining the name)
- if the node is a Module node (i.e. has globals), add the name to
- globals
-
- if the name is already defined, ignore it
- """
- assert self.locals is not None, (self, id(self))
- #assert not stmt in self.locals.get(name, ()), (self, stmt)
- self.locals.setdefault(name, []).append(stmt)
-
- __setitem__ = set_local
-
- def _append_node(self, child):
- """append a child, linking it in the tree"""
- self.body.append(child)
- child.parent = self
-
- def add_local_node(self, child_node, name=None):
- """append a child which should alter locals to the given node"""
- if name != '__class__':
- # add __class__ node as a child will cause infinite recursion later!
- self._append_node(child_node)
- self.set_local(name or child_node.name, child_node)
-
-
- def __getitem__(self, item):
- """method from the `dict` interface returning the first node
- associated with the given name in the locals dictionary
-
- :type item: str
- :param item: the name of the locally defined object
- :raises KeyError: if the name is not defined
- """
- return self.locals[item][0]
-
- def __iter__(self):
- """method from the `dict` interface returning an iterator on
- `self.keys()`
- """
- return iter(self.keys())
-
- def keys(self):
- """method from the `dict` interface returning a tuple containing
- locally defined names
- """
- return self.locals.keys()
-
- def values(self):
- """method from the `dict` interface returning a tuple containing
- locally defined nodes which are instance of `Function` or `Class`
- """
- return [self[key] for key in self.keys()]
-
- def items(self):
- """method from the `dict` interface returning a list of tuple
- containing each locally defined name with its associated node,
- which is an instance of `Function` or `Class`
- """
- return zip(self.keys(), self.values())
-
- def has_key(self, name):
- """method from the `dict` interface returning True if the given
- name is defined in the locals dictionary
- """
- return self.locals.has_key(name)
-
- __contains__ = has_key
-
# maybe import at the end ?
from logilab.astng import nodes
diff --git a/node_classes.py b/node_classes.py
index 8598311c..d7deb76b 100644
--- a/node_classes.py
+++ b/node_classes.py
@@ -1,14 +1,79 @@
#
from logilab.astng import (ASTNGBuildingException, InferenceError,
NotFoundError, NoDefault)
-from logilab.astng._nodes import NodeNG, StmtMixIn, BlockRangeMixIn
-from logilab.astng.lookup import LookupMixIn
+from logilab.astng._nodes import NodeNG, StmtMixIn, BlockRangeMixIn, BaseClass, _const_factory
from logilab.astng.infutils import Instance
"""
Module for all nodes (except scoped nodes).
"""
+def unpack_infer(stmt, context=None):
+ """return an iterator on nodes inferred by the given statement if the inferred
+ value is a list or a tuple, recurse on it to get values inferred by its
+ content
+ """
+ if isinstance(stmt, (List, Tuple)):
+ # XXX loosing context
+ return chain(*imap(unpack_infer, stmt.elts))
+ infered = stmt.infer(context).next()
+ if infered is stmt:
+ return iter( (stmt,) )
+ return chain(*imap(unpack_infer, stmt.infer(context)))
+
+
+
+def are_exclusive(stmt1, stmt2, exceptions=None):
+ """return true if the two given statements are mutually exclusive
+
+ `exceptions` may be a list of exception names. If specified, discard If
+ branches and check one of the statement is in an exception handler catching
+ one of the given exceptions.
+
+ algorithm :
+ 1) index stmt1's parents
+ 2) climb among stmt2's parents until we find a common parent
+ 3) if the common parent is a If or TryExcept statement, look if nodes are
+ in exclusive branches
+ """
+ # index stmt1's parents
+ stmt1_parents = {}
+ children = {}
+ node = stmt1.parent
+ previous = stmt1
+ while node:
+ stmt1_parents[node] = 1
+ children[node] = previous
+ previous = node
+ node = node.parent
+ # climb among stmt2's parents until we find a common parent
+ node = stmt2.parent
+ previous = stmt2
+ while node:
+ if stmt1_parents.has_key(node):
+ # if the common parent is a If or TryExcept statement, look if
+ # nodes are in exclusive branches
+ if isinstance(node, If) and exceptions is None:
+ if (node.locate_child(previous)[1]
+ is not node.locate_child(children[node])[1]):
+ return True
+ elif isinstance(node, TryExcept):
+ c2attr, c2node = node.locate_child(previous)
+ c1attr, c1node = node.locate_child(children[node])
+ if c1node is not c2node:
+ if ((c2attr == 'body' and c1attr == 'handlers' and children[node].catch(exceptions)) or
+ (c2attr == 'handlers' and c1attr == 'body' and previous.catch(exceptions)) or
+ (c2attr == 'handlers' and c1attr == 'orelse') or
+ (c2attr == 'orelse' and c1attr == 'handlers')):
+ return True
+ elif c2attr == 'handlers' and c1attr == 'handlers':
+ return previous is not children[node]
+ return False
+ previous = node
+ node = node.parent
+ return False
+
+
class Arguments(NodeNG):
"""class representing an Arguments node"""
@@ -86,10 +151,6 @@ class AssAttr(NodeNG):
"""class representing an AssAttr node"""
-class AssName(LookupMixIn, NodeNG):
- """class representing an AssName node"""
-
-
class Assert(StmtMixIn, NodeNG):
"""class representing an Assert node"""
@@ -166,10 +227,6 @@ class DelAttr(NodeNG):
"""class representing a DelAttr node"""
-class DelName(LookupMixIn, NodeNG):
- """class representing a DelName node"""
-
-
class Delete(StmtMixIn, NodeNG):
"""class representing a Delete node"""
@@ -351,10 +408,6 @@ class ListComp(NodeNG):
"""class representing a ListComp node"""
-class Name(LookupMixIn, NodeNG):
- """class representing a Name node"""
-
-
class Pass(StmtMixIn, NodeNG):
"""class representing a Pass node"""
@@ -456,3 +509,30 @@ class With(BlockRangeMixIn, StmtMixIn, NodeNG):
class Yield(NodeNG):
"""class representing a Yield node"""
+# constants ##############################################################
+
+CONST_CLS = {
+ list: List,
+ tuple: Tuple,
+ dict: Dict,
+ }
+
+def const_factory(value):
+ """return an astng node for a python value"""
+ try:
+ # if value is of class list, tuple, dict use specific class, not Const
+ cls = CONST_CLS[value.__class__]
+ node = cls()
+ if isinstance(node, Dict):
+ node.items = ()
+ else:
+ node.elts = ()
+ except KeyError:
+ try:
+ node = Const(value)
+ except KeyError:
+ node = _const_factory(value)
+ return node
+
+
+
diff --git a/nodes.py b/nodes.py
index 244b43c8..b015820a 100644
--- a/nodes.py
+++ b/nodes.py
@@ -44,16 +44,16 @@ from itertools import imap
from logilab.astng._exceptions import UnresolvableName, NotFoundError, \
InferenceError, ASTNGError
from logilab.astng.utils import REDIRECT
-from logilab.astng._nodes import _const_factory
+from logilab.astng._nodes import class_factory, module_factory
-from logilab.astng.node_classes import (Arguments, AssAttr, AssName, Assert,
+from logilab.astng.node_classes import (Arguments, AssAttr, Assert,
Assign, AugAssign, Backquote, BinOp, BoolOp, Break, CallFunc, Compare,
- Comprehension, Const, Continue, Decorators, DelAttr, DelName, Delete,
+ Comprehension, Const, Continue, Decorators, DelAttr, Delete,
Dict, Discard, Ellipsis, EmptyNode, ExceptHandler, Exec, ExtSlice, For,
From, Getattr, Global, If, IfExp, Import, Index, Keyword,
- List, ListComp, Name, Pass, Print, Raise, Return, Slice, Subscript,
- TryExcept, TryFinally, Tuple, UnaryOp, While, With, Yield)
-from logilab.astng.scoped_nodes import Module, GenExpr, Lambda, Function, Class
+ List, ListComp, Pass, Print, Raise, Return, Slice, Subscript,
+ TryExcept, TryFinally, Tuple, UnaryOp, While, With, Yield, const_factory )
+from logilab.astng.scoped_nodes import AssName, DelName, Name, Module, GenExpr, Lambda, Function, Class
# astng fields definition ####################################################
@@ -116,32 +116,5 @@ With._astng_fields = ('expr', 'vars', 'body')
While._astng_fields = ('test', 'body', 'orelse',)
Yield._astng_fields = ('value',)
-# constants ... ##############################################################
-
-CONST_CLS = {
- list: List,
- tuple: Tuple,
- dict: Dict,
- }
-
-def const_factory(value):
- """return an astng node for a python value"""
- try:
- # if value is of class list, tuple, dict use specific class, not Const
- cls = CONST_CLS[value.__class__]
- node = cls()
- if isinstance(node, Dict):
- node.items = ()
- else:
- node.elts = ()
- except KeyError:
- try:
- node = Const(value)
- except KeyError:
- node = _const_factory(value)
- return node
-
-LOCALS_NODES = (Class, Function, GenExpr, Lambda, Module)
-
diff --git a/patchcomptransformer.py b/patchcomptransformer.py
index 586effe8..8744a781 100644
--- a/patchcomptransformer.py
+++ b/patchcomptransformer.py
@@ -18,12 +18,13 @@
:copyright: 2003-2009 Sylvain Thenault
:contact: mailto:thenault@gmail.com
"""
+# TODO : move this module to _nodes_compiler
from types import TupleType
from token import DEDENT
from compiler import transformer
-from logilab.astng import nodes
+import compiler.ast as nodes
def fromto_lineno(asttuple):
"""return the minimum and maximum line number of the given ast tuple"""
diff --git a/protocols.py b/protocols.py
index b7962fe2..57039929 100644
--- a/protocols.py
+++ b/protocols.py
@@ -25,7 +25,8 @@ from __future__ import generators
__doctype__ = "restructuredtext en"
from logilab.astng import InferenceError, NoDefault, _nodes as nodes
-from logilab.astng.infutils import copy_context, unpack_infer, \
+from logilab.astng.node_classes import unpack_infer
+from logilab.astng.infutils import copy_context, \
raise_if_nothing_infered, yes_if_nothing_infered, Instance, Generator, YES
from logilab.astng.nodes import Const, Class, Function, Tuple, List, \
const_factory
diff --git a/rebuilder.py b/rebuilder.py
index 90047215..b25f13c6 100644
--- a/rebuilder.py
+++ b/rebuilder.py
@@ -21,8 +21,9 @@ order to get a single ASTNG representation
"""
from logilab.astng import ASTNGBuildingException, InferenceError, NodeRemoved
+from logilab.astng import _nodes
from logilab.astng import nodes
-from logilab.astng.utils import ASTVisitor
+from logilab.astng.utils import ASTVisitor, REDIRECT
from logilab.astng.infutils import YES, Instance
@@ -38,8 +39,8 @@ class RebuildVisitor(ASTVisitor):
self._metaclass = None
self._global_names = None
self._delayed = []
- self.rebuilder = nodes.TreeRebuilder(self)
- self.set_line_info = nodes.AST_MODE == '_ast'
+ self.rebuilder = _nodes.TreeRebuilder(self)
+ self.set_line_info = _nodes.AST_MODE == '_ast'
def _push(self, node):
"""update the stack and init some parts of the Function or Class node
@@ -86,15 +87,19 @@ class RebuildVisitor(ASTVisitor):
self.__asscontext = None
def walk(self, node):
- self._walk(node)
+ node = self._walk(node)
delayed = self._delayed
while delayed:
dnode = delayed.pop(0)
node_name = dnode.__class__.__name__.lower()
self.delayed_visit_assattr(dnode)
+ return node
def _walk(self, node, parent=None):
"""default visit method, handle the parent attribute"""
+ cls_name = node.__class__.__name__
+ NGNode = getattr(nodes, REDIRECT.get(cls_name, cls_name))
+ node = NGNode()
node.parent = parent
try:
node.accept(self.rebuilder)
@@ -114,7 +119,7 @@ class RebuildVisitor(ASTVisitor):
if handle_leave:
leave = getattr(self, "leave_" + node.__class__.__name__.lower())
leave(node)
-
+ return node
# general visit_<node> methods ############################################
diff --git a/scoped_nodes.py b/scoped_nodes.py
index 8702f976..f0795758 100644
--- a/scoped_nodes.py
+++ b/scoped_nodes.py
@@ -36,13 +36,15 @@ from logilab.common.decorators import cached
from logilab.astng import MANAGER, NotFoundError, NoDefault, \
ASTNGBuildingException, InferenceError
-from logilab.astng.node_clases import (Const, Dict, From, List, Name, Pass,
- Raise, Return, Tuple, Yield, AssName, DelAttr, DelName,)
-from logilab.astng._nodes import const_factory as cf, NodeNG, StmtMixIn
+from logilab.astng.node_classes import (Const, Dict, From, List, Pass,
+ Raise, Return, Tuple, Yield, DelAttr, are_exclusive, const_factory as cf,
+ unpack_infer)
+from logilab.astng._nodes import NodeNG, StmtMixIn, BaseClass
from logilab.astng.infutils import YES, InferenceContext, Instance, Generator, \
- UnboundMethod, copy_context, unpack_infer, _infer_stmts
+ UnboundMethod, _infer_stmts, copy_context
+
from logilab.astng.nodes_as_string import as_string
-from logilab.astng.lookup import LocalsDictNodeNG
+
def remove_nodes(func, cls):
def wrapper(*args, **kwargs):
@@ -73,6 +75,298 @@ def std_special_attributes(self, name, add_locals=True):
raise NotFoundError(name)
+# MixIns -----------------------------------------------------
+
+
+
+class LookupMixIn(BaseClass):
+ """Mixin looking up a name in the right scope
+ """
+
+ def lookup(self, name):
+ """lookup a variable name
+
+ return the scope node and the list of assignments associated to the given
+ name according to the scope where it has been found (locals, globals or
+ builtin)
+
+ The lookup is starting from self's scope. If self is not a frame itself and
+ the name is found in the inner frame locals, statements will be filtered
+ to remove ignorable statements according to self's location
+ """
+ return self.scope().scope_lookup(self, name)
+
+ def ilookup(self, name, context=None):
+ """infered lookup
+
+ return an iterator on infered values of the statements returned by
+ the lookup method
+ """
+ frame, stmts = self.lookup(name)
+ context = copy_context(context)
+ context.lookupname = name
+ return _infer_stmts(stmts, context, frame)
+
+ def _filter_stmts(self, stmts, frame, offset):
+ """filter statements to remove ignorable statements.
+
+ If self is not a frame itself and the name is found in the inner
+ frame locals, statements will be filtered to remove ignorable
+ statements according to self's location
+ """
+ # if offset == -1, my actual frame is not the inner frame but its parent
+ #
+ # class A(B): pass
+ #
+ # we need this to resolve B correctly
+ if offset == -1:
+ myframe = self.frame().parent.frame()
+ else:
+ myframe = self.frame()
+ if not myframe is frame or self is frame:
+ return stmts
+ mystmt = self.statement()
+ # line filtering if we are in the same frame
+ #
+ # take care node may be missing lineno information (this is the case for
+ # nodes inserted for living objects)
+ if myframe is frame and mystmt.fromlineno is not None:
+ assert mystmt.fromlineno is not None, mystmt
+ mylineno = mystmt.fromlineno + offset
+ else:
+ # disabling lineno filtering
+ mylineno = 0
+ _stmts = []
+ _stmt_parents = []
+ for node in stmts:
+ stmt = node.statement()
+ # line filtering is on and we have reached our location, break
+ if mylineno > 0 and stmt.fromlineno > mylineno:
+ break
+ if isinstance(node, nodes.Class) and self in node.bases:
+ break
+ assert hasattr(node, 'ass_type'), (node, node.scope(),
+ node.scope().locals)
+ ass_type = node.ass_type()
+ if ass_type is mystmt and not isinstance(ass_type, (nodes.Class,
+ nodes.Function, nodes.Import, nodes.From, nodes.Lambda)):
+ if not isinstance(ass_type, nodes.Comprehension):
+ break
+ if isinstance(self, (nodes.Const, nodes.Name)):
+ _stmts = [self]
+ break
+ elif ass_type.statement() is mystmt:
+ # original node's statement is the assignment, only keeps
+ # current node (gen exp, list comp)
+ _stmts = [node]
+ break
+ optional_assign = isinstance(ass_type, (nodes.For, nodes.Comprehension))
+ if optional_assign and ass_type.parent_of(self):
+ # we are inside a loop, loop var assigment is hidding previous
+ # assigment
+ _stmts = [node]
+ _stmt_parents = [stmt.parent]
+ continue
+ # XXX comment various branches below!!!
+ try:
+ pindex = _stmt_parents.index(stmt.parent)
+ except ValueError:
+ pass
+ else:
+ # we got a parent index, this means the currently visited node
+ # is at the same block level as a previously visited node
+ if _stmts[pindex].ass_type().parent_of(ass_type):
+ # both statements are not at the same block level
+ continue
+ # if currently visited node is following previously considered
+ # assignement and both are not exclusive, we can drop the
+ # previous one. For instance in the following code ::
+ #
+ # if a:
+ # x = 1
+ # else:
+ # x = 2
+ # print x
+ #
+ # we can't remove neither x = 1 nor x = 2 when looking for 'x'
+ # of 'print x'; while in the following ::
+ #
+ # x = 1
+ # x = 2
+ # print x
+ #
+ # we can remove x = 1 when we see x = 2
+ #
+ # moreover, on loop assignment types, assignment won't
+ # necessarily be done if the loop has no iteration, so we don't
+ # want to clear previous assigments if any (hence the test on
+ # optional_assign)
+ if not (optional_assign or are_exclusive(_stmts[pindex], node)):
+ del _stmt_parents[pindex]
+ del _stmts[pindex]
+ if isinstance(node, nodes.AssName):
+ if not optional_assign and stmt.parent is mystmt.parent:
+ _stmts = []
+ _stmt_parents = []
+ elif isinstance(node, nodes.DelName):
+ _stmts = []
+ _stmt_parents = []
+ continue
+ if not are_exclusive(self, node):
+ _stmts.append(node)
+ _stmt_parents.append(stmt.parent)
+ return _stmts
+
+
+def builtin_lookup(name):
+ """lookup a name into the builtin module
+ return the list of matching statements and the astng for the builtin
+ module
+ """
+ builtinastng = MANAGER.astng_from_module(__builtin__)
+ if name == '__dict__':
+ return builtinastng, ()
+ try:
+ stmts = builtinastng.locals[name]
+ except KeyError:
+ stmts = ()
+ return builtinastng, stmts
+
+
+class LocalsDictNodeNG(LookupMixIn, NodeNG):
+ """ this class provides locals handling common to Module, Function
+ and Class nodes, including a dict like interface for direct access
+ to locals information
+ """
+
+ # attributes below are set by the builder module or by raw factories
+
+ # dictionary of locals with name as key and node defining the local as
+ # value
+ locals = None
+
+ def qname(self):
+ """return the 'qualified' name of the node, eg module.name,
+ module.class.name ...
+ """
+ if self.parent is None:
+ return self.name
+ return '%s.%s' % (self.parent.frame().qname(), self.name)
+
+ def frame(self):
+ """return the first parent frame node (i.e. Module, Function or Class)
+ """
+ return self
+
+ def scope(self):
+ """return the first node defining a new scope (i.e. Module,
+ Function, Class, Lambda but also GenExpr)
+ """
+ return self
+
+
+ def _scope_lookup(self, node, name, offset=0):
+ """XXX method for interfacing the scope lookup"""
+ try:
+ stmts = node._filter_stmts(self.locals[name], self, offset)
+ except KeyError:
+ stmts = ()
+ if stmts:
+ return self, stmts
+ if self.parent: # i.e. not Module
+ # nested scope: if parent scope is a function, that's fine
+ # else jump to the module
+ pscope = self.parent.scope()
+ if not isinstance(pscope, nodes.Function):
+ pscope = pscope.root()
+ return pscope.scope_lookup(node, name)
+ return builtin_lookup(name) # Module
+
+
+
+ def set_local(self, name, stmt):
+ """define <name> in locals (<stmt> is the node defining the name)
+ if the node is a Module node (i.e. has globals), add the name to
+ globals
+
+ if the name is already defined, ignore it
+ """
+ assert self.locals is not None, (self, id(self))
+ #assert not stmt in self.locals.get(name, ()), (self, stmt)
+ self.locals.setdefault(name, []).append(stmt)
+
+ __setitem__ = set_local
+
+ def _append_node(self, child):
+ """append a child, linking it in the tree"""
+ self.body.append(child)
+ child.parent = self
+
+ def add_local_node(self, child_node, name=None):
+ """append a child which should alter locals to the given node"""
+ if name != '__class__':
+ # add __class__ node as a child will cause infinite recursion later!
+ self._append_node(child_node)
+ self.set_local(name or child_node.name, child_node)
+
+
+ def __getitem__(self, item):
+ """method from the `dict` interface returning the first node
+ associated with the given name in the locals dictionary
+
+ :type item: str
+ :param item: the name of the locally defined object
+ :raises KeyError: if the name is not defined
+ """
+ return self.locals[item][0]
+
+ def __iter__(self):
+ """method from the `dict` interface returning an iterator on
+ `self.keys()`
+ """
+ return iter(self.keys())
+
+ def keys(self):
+ """method from the `dict` interface returning a tuple containing
+ locally defined names
+ """
+ return self.locals.keys()
+
+ def values(self):
+ """method from the `dict` interface returning a tuple containing
+ locally defined nodes which are instance of `Function` or `Class`
+ """
+ return [self[key] for key in self.keys()]
+
+ def items(self):
+ """method from the `dict` interface returning a list of tuple
+ containing each locally defined name with its associated node,
+ which is an instance of `Function` or `Class`
+ """
+ return zip(self.keys(), self.values())
+
+ def has_key(self, name):
+ """method from the `dict` interface returning True if the given
+ name is defined in the locals dictionary
+ """
+ return self.locals.has_key(name)
+
+ __contains__ = has_key
+
+# Name classses
+
+class AssName(LookupMixIn, NodeNG):
+ """class representing an AssName node"""
+
+
+class DelName(LookupMixIn, NodeNG):
+ """class representing a DelName node"""
+
+
+class Name(LookupMixIn, NodeNG):
+ """class representing a Name node"""
+
+
# Module #####################################################################
class Module(LocalsDictNodeNG):