# -*- coding: utf-8 -*-
"""
equip.bytecode.decl
~~~~~~~~~~~~~~~~~~~
Structured representation of Module, Types, Method, Imports.
:copyright: (c) 2014 by Romain Gaucher (@rgaucher)
:license: Apache 2, see LICENSE for more details.
"""
import dis
from operator import attrgetter, \
methodcaller
from ..utils.log import logger
from ..visitors.bytecode import BytecodeVisitor
from .utils import update_nested_code_object
[docs]class Declaration(object):
"""
Base class for the declaration types of object.
"""
MODULE = 1
TYPE = 2
METHOD = 3
FIELD = 4
IMPORT = 5
def __init__(self, kind, _code_object):
self._kind = kind
self._code_object = _code_object
self._parent = None
self._children = []
self._lines = None
self._bytecode = []
self._bytecode_object = None
self._has_changes = False
@property
def lines(self):
"""
A tuple of start/end line numbers that encapsulates this declaration.
"""
return self._lines
@lines.setter
def lines(self, value):
self._lines = value
@property
def start_lineno(self):
"""
Returns the start line number of the declaration.
"""
return self._lines[0] if self._lines else -1
[docs] def get_start_lineno(self):
return self.start_lineno
@property
def end_lineno(self):
"""
Returns the end line number of the declaration.
"""
return self._lines[1] if self._lines else -1
@property
def parent(self):
"""
Returns the parent of this declaration or ``None`` if there is
no parent (e.g., for a ``ModuleDeclaration``).
"""
return self._parent
@parent.setter
def parent(self, value):
# logger.debug("Set parent. %s", value)
self._parent = value
self._parent.add_child(self)
@property
def children(self):
"""
Returns the children of this declaration.
"""
return self._children
[docs] def add_child(self, child):
"""
Adds a child to this declaration.
:param child: A ``Declaration`` that is a child of the current declaration.
"""
self._children.append(child)
# logger.debug("add_child:: Children: %s", self.children)
# Keep sorting by line number
self._children = sorted(self._children, key=methodcaller('get_start_lineno'))
@property
def parent_module(self):
"""
Returns the parent module (a ``ModuleDeclaration``) for this declaration.
"""
return self.__get_parent_kind(ModuleDeclaration)
@property
def parent_class(self):
"""
Returns the parent class (a ``TypeDeclaration``) for this declaration.
"""
return self.__get_parent_kind(TypeDeclaration)
@property
def parent_method(self):
"""
Returns the parent method (a ``MethodDeclaration``) for this declaration.
"""
return self.__get_parent_kind(MethodDeclaration)
def __get_parent_kind(self, kind):
p = self.parent
while p is not None:
if isinstance(p, kind):
return p
p = p.parent
return None
@property
def bytecode(self):
"""
Returns the bytecode associated with this declaration.
"""
return self._bytecode
@bytecode.setter
def bytecode(self, value):
self._bytecode = value
@property
def code_object(self):
return self._code_object
@code_object.setter
def code_object(self, value):
self._code_object = value
[docs] def update_nested_code_object(self, original_co, new_co):
self._code_object = update_nested_code_object(self._code_object,
original_co,
new_co)
self._has_changes = True
@property
def has_changes(self):
return self._has_changes
@has_changes.setter
def has_changes(self, value):
self._has_changes = value
# Mostly reserved
@property
def bytecode_object(self):
return self._bytecode_object
@bytecode_object.setter
def bytecode_object(self, value):
self._bytecode_object = value
[docs] def accept(self, visitor):
if isinstance(visitor, BytecodeVisitor):
for i in xrange(len(self._bytecode)):
index, lineno, op, arg, cflow_in, _ = self._bytecode[i]
visitor.visit(index, op, arg=arg, lineno=lineno, cflow_in=cflow_in)
is_module = lambda self: self.kind == Declaration.MODULE
is_type = lambda self: self.kind == Declaration.TYPE
is_method = lambda self: self.kind == Declaration.METHOD
is_field = lambda self: self.kind == Declaration.FIELD
is_import = lambda self: self.kind == Declaration.IMPORT
@property
def kind(self):
return self._kind
[docs]class ImportDeclaration(Declaration):
"""
Models an import statement. It handles relatives/absolute
imports, as well as aliases.
"""
def __init__(self, code_object):
Declaration.__init__(self, Declaration.IMPORT, code_object)
self._root = None
self._aliases = None
self._live_names = None
self._dots = -1
self._star = False
@property
def star(self):
return self._star
@star.setter
def star(self, value):
self._star = value
@property
def aliases(self):
return self._aliases
@aliases.setter
def aliases(self, value):
self._aliases = value
@property
def live_names(self):
if self._live_names is None:
self._live_names = set()
for (name, alias) in self.aliases:
if alias is None:
if '.' not in name:
self._live_names.add(name)
else:
live_name = name[:name.rfind('.')]
self._live_names.add(live_name)
else:
self._live_names.add(alias)
return self._live_names
@property
def dots(self):
return self._dots
@dots.setter
def dots(self, value):
self._dots = value
@property
def root(self):
return self._root
@root.setter
def root(self, value):
self._root = value
def __eq__(self, obj):
return self.root == obj.root and self.aliases == obj.aliases and self.dots == obj.dots
def __repr__(self):
skip_import_root = False
import_buffer = ''
if self.dots > 0:
import_buffer += 'from ' + '.' * self.dots
if self.root:
import_buffer += self.root
skip_import_root = True
import_buffer += ' import '
elif self.root:
import_buffer += 'from '
else:
import_buffer += 'import '
if self.root and not skip_import_root:
import_buffer += self.root + ' import '
if self.star:
import_buffer += '*'
import_list = []
for aliased_name in self.aliases:
local_import = aliased_name[0]
if aliased_name[1]:
local_import += ' as ' + aliased_name[1]
import_list.append(local_import)
if import_list:
import_buffer += ', '.join(import_list)
return 'Import(%s)' % import_buffer
[docs]class ModuleDeclaration(Declaration):
"""
The module is the object that captures everything under one pyc file.
It contains nested classes and functions, as well as import statements.
"""
def __init__(self, module_path, code_object):
Declaration.__init__(self, Declaration.MODULE, code_object)
self._module_path = module_path
self._imports = []
self._classes = None
self._functions = None
[docs] def add_import(self, importDecl):
if importDecl not in self._imports:
self._imports.append(importDecl)
@property
def imports(self):
return self._imports
@property
def module_path(self):
return self._module_path
@property
def classes(self):
if self._classes is None:
self._classes = [ c for c in self.children if c.is_type() ]
return self._classes
@property
def functions(self):
if self._functions is None:
self._functions = [ f for f in self.children if f.is_method() ]
return self._functions
def __repr__(self):
return 'ModuleDeclaration(path=%s, co=%s)' % (self.module_path, self.code_object)
[docs]class TypeDeclaration(Declaration):
"""
Represent a class declaration. It has a name, as well as a hierarchy
(superclass). The type contains several methods and fields, and can
have nested types.
"""
def __init__(self, type_name, code_object):
Declaration.__init__(self, Declaration.TYPE, code_object)
self._type_name = type_name
self._superclasses = []
self._methods = None
self._fields = None
self._nested_types = None
@property
def type_name(self):
"""
Returns the name of the type.
"""
return self._type_name
@property
def superclasses(self):
return self._superclasses
@property
def methods(self):
"""
Returns a list of ``MethodDeclaration`` that belong to this type.
"""
if self._methods is None:
self._methods = [ f for f in self.children if f.is_method() ]
return self._methods
@property
def fields(self):
return self.fields
@property
def nested_types(self):
"""
Returns a list of ``TypeDeclaration`` that belong to this type.
"""
if self._nested_types is None:
self._nested_types = [ c for c in self.children if c.is_type() ]
return self._nested_types
def __repr__(self):
return 'TypeDeclaration#%d(name=%s)' % (self.start_lineno, self.type_name)
[docs]class MethodDeclaration(Declaration):
"""
The declaration of a method or a function.
"""
def __init__(self, method_name, code_object):
Declaration.__init__(self, Declaration.METHOD, code_object)
self._method_name = method_name
self._formal_parameters = []
self._body = None
self._labels = dis.findlabels(code_object.co_code)
self._nested_types = []
@property
def body(self):
return self._body
@body.setter
def body(self, value):
self._body = value
@property
def labels(self):
return self._labels
@property
def is_lambda(self):
return self.method_name == '<lambda>'
@property
def formal_parameters(self):
return self._formal_parameters
@formal_parameters.setter
def formal_parameters(self, value):
self._formal_parameters = value
@property
def method_name(self):
return self._method_name
@property
def nested_types(self):
return self._nested_types
def __repr__(self):
return 'MethodDeclaration#%d(name=%s, args=%s)' \
% (self.start_lineno, self.method_name, self.formal_params)
[docs]class FieldDeclaration(Declaration):
def __init__(self, field_name, code_object):
Declaration.__init__(self, Declaration.FIELD, code_object)
self._field_name = field_name
@property
def field_name(self):
return self._field_name