mirror of
https://github.com/boostorg/python.git
synced 2026-01-22 05:22:45 +00:00
methods. - Applied a patch by Baptiste Lepilleur that allows the user to inject code inside the class definition. - Applied another patch by Baptiste Lepilleur that inserts two new command-line options that helps with writing makefiles. [SVN r23725]
915 lines
37 KiB
Python
915 lines
37 KiB
Python
# Copyright Bruno da Silva de Oliveira 2003. Use, modification and
|
|
# distribution is subject to the Boost Software License, Version 1.0.
|
|
# (See accompanying file LICENSE_1_0.txt or copy at
|
|
# http:#www.boost.org/LICENSE_1_0.txt)
|
|
|
|
import exporters
|
|
from Exporter import Exporter
|
|
from declarations import *
|
|
from settings import *
|
|
from policies import *
|
|
from SingleCodeUnit import SingleCodeUnit
|
|
from EnumExporter import EnumExporter
|
|
from utils import makeid, enumerate
|
|
import copy
|
|
import exporterutils
|
|
import re
|
|
|
|
#==============================================================================
|
|
# ClassExporter
|
|
#==============================================================================
|
|
class ClassExporter(Exporter):
|
|
'Generates boost.python code to export a class declaration'
|
|
|
|
def __init__(self, info, parser_tail=None):
|
|
Exporter.__init__(self, info, parser_tail)
|
|
# sections of code
|
|
self.sections = {}
|
|
# template: each item in the list is an item into the class_<...>
|
|
# section.
|
|
self.sections['template'] = []
|
|
# constructor: each item in the list is a parameter to the class_
|
|
# constructor, like class_<C>(...)
|
|
self.sections['constructor'] = []
|
|
# inside: everything within the class_<> statement
|
|
self.sections['inside'] = []
|
|
# scope: items outside the class statement but within its scope.
|
|
# scope* s = new scope(class<>());
|
|
# ...
|
|
# delete s;
|
|
self.sections['scope'] = []
|
|
# declarations: outside the BOOST_PYTHON_MODULE macro
|
|
self.sections['declaration'] = []
|
|
self.sections['declaration-outside'] = []
|
|
self.sections['include'] = []
|
|
# a list of Constructor instances
|
|
self.constructors = []
|
|
# a list of code units, generated by nested declarations
|
|
self.nested_codeunits = []
|
|
|
|
|
|
def ScopeName(self):
|
|
return makeid(self.class_.FullName()) + '_scope'
|
|
|
|
|
|
def Name(self):
|
|
return self.info.name
|
|
|
|
|
|
def SetDeclarations(self, declarations):
|
|
Exporter.SetDeclarations(self, declarations)
|
|
if self.declarations:
|
|
decl = self.GetDeclaration(self.info.name)
|
|
if isinstance(decl, Typedef):
|
|
self.class_ = self.GetDeclaration(decl.type.name)
|
|
if not self.info.rename:
|
|
self.info.rename = decl.name
|
|
else:
|
|
self.class_ = decl
|
|
self.class_ = copy.deepcopy(self.class_)
|
|
else:
|
|
self.class_ = None
|
|
|
|
|
|
def ClassBases(self):
|
|
all_bases = []
|
|
for level in self.class_.hierarchy:
|
|
for base in level:
|
|
all_bases.append(base)
|
|
return [self.GetDeclaration(x.name) for x in all_bases]
|
|
|
|
|
|
def Order(self):
|
|
'''Return the TOTAL number of bases that this class has, including the
|
|
bases' bases. Do this because base classes must be instantialized
|
|
before the derived classes in the module definition.
|
|
'''
|
|
num_bases = len(self.ClassBases())
|
|
return num_bases, self.class_.FullName()
|
|
|
|
|
|
def Export(self, codeunit, exported_names):
|
|
self.InheritMethods(exported_names)
|
|
self.MakeNonVirtual()
|
|
if not self.info.exclude:
|
|
self.ExportBasics()
|
|
self.ExportBases(exported_names)
|
|
self.ExportConstructors()
|
|
self.ExportVariables()
|
|
self.ExportVirtualMethods(codeunit)
|
|
self.ExportMethods()
|
|
self.ExportOperators()
|
|
self.ExportNestedClasses(exported_names)
|
|
self.ExportNestedEnums(exported_names)
|
|
self.ExportSmartPointer()
|
|
self.ExportOpaquePointerPolicies()
|
|
self.ExportAddedCode()
|
|
self.Write(codeunit)
|
|
exported_names[self.Name()] = 1
|
|
|
|
|
|
def InheritMethods(self, exported_names):
|
|
'''Go up in the class hierarchy looking for classes that were not
|
|
exported yet, and then add their public members to this classes
|
|
members, as if they were members of this class. This allows the user to
|
|
just export one type and automatically get all the members from the
|
|
base classes.
|
|
'''
|
|
valid_members = (Method, ClassVariable, NestedClass, ClassEnumeration)
|
|
fullnames = [x.FullName() for x in self.class_]
|
|
pointers = [x.PointerDeclaration(True) for x in self.class_ if isinstance(x, Method)]
|
|
fullnames = dict([(x, None) for x in fullnames])
|
|
pointers = dict([(x, None) for x in pointers])
|
|
for level in self.class_.hierarchy:
|
|
level_exported = False
|
|
for base in level:
|
|
base = self.GetDeclaration(base.name)
|
|
if base.FullName() not in exported_names:
|
|
for member in base:
|
|
if type(member) in valid_members:
|
|
member_copy = copy.deepcopy(member)
|
|
member_copy.class_ = self.class_.FullName()
|
|
if isinstance(member_copy, Method):
|
|
pointer = member_copy.PointerDeclaration(True)
|
|
if pointer not in pointers:
|
|
self.class_.AddMember(member)
|
|
pointers[pointer] = None
|
|
elif member_copy.FullName() not in fullnames:
|
|
self.class_.AddMember(member)
|
|
else:
|
|
level_exported = True
|
|
if level_exported:
|
|
break
|
|
def IsValid(member):
|
|
return isinstance(member, valid_members) and member.visibility == Scope.public
|
|
self.public_members = [x for x in self.class_ if IsValid(x)]
|
|
|
|
|
|
def Write(self, codeunit):
|
|
indent = self.INDENT
|
|
boost_ns = namespaces.python
|
|
pyste_ns = namespaces.pyste
|
|
code = ''
|
|
# begin a scope for this class if needed
|
|
nested_codeunits = self.nested_codeunits
|
|
needs_scope = self.sections['scope'] or nested_codeunits
|
|
if needs_scope:
|
|
scope_name = self.ScopeName()
|
|
code += indent + boost_ns + 'scope* %s = new %sscope(\n' %\
|
|
(scope_name, boost_ns)
|
|
# export the template section
|
|
template_params = ', '.join(self.sections['template'])
|
|
code += indent + boost_ns + 'class_< %s >' % template_params
|
|
# export the constructor section
|
|
constructor_params = ', '.join(self.sections['constructor'])
|
|
code += '(%s)\n' % constructor_params
|
|
# export the inside section
|
|
in_indent = indent*2
|
|
for line in self.sections['inside']:
|
|
code += in_indent + line + '\n'
|
|
# write the scope section and end it
|
|
if not needs_scope:
|
|
code += indent + ';\n'
|
|
else:
|
|
code += indent + ');\n'
|
|
for line in self.sections['scope']:
|
|
code += indent + line + '\n'
|
|
# write the contents of the nested classes
|
|
for nested_unit in nested_codeunits:
|
|
code += '\n' + nested_unit.Section('module')
|
|
# close the scope
|
|
code += indent + 'delete %s;\n' % scope_name
|
|
|
|
# write the code to the module section in the codeunit
|
|
codeunit.Write('module', code + '\n')
|
|
|
|
# write the declarations to the codeunit
|
|
declarations = '\n'.join(self.sections['declaration'])
|
|
for nested_unit in nested_codeunits:
|
|
declarations += nested_unit.Section('declaration')
|
|
if declarations:
|
|
codeunit.Write('declaration', declarations + '\n')
|
|
declarations_outside = '\n'.join(self.sections['declaration-outside'])
|
|
if declarations_outside:
|
|
codeunit.Write('declaration-outside', declarations_outside + '\n')
|
|
|
|
# write the includes to the codeunit
|
|
includes = '\n'.join(self.sections['include'])
|
|
for nested_unit in nested_codeunits:
|
|
includes += nested_unit.Section('include')
|
|
if includes:
|
|
codeunit.Write('include', includes)
|
|
|
|
|
|
def Add(self, section, item):
|
|
'Add the item into the corresponding section'
|
|
self.sections[section].append(item)
|
|
|
|
|
|
def ExportBasics(self):
|
|
'''Export the name of the class and its class_ statement.'''
|
|
class_name = self.class_.FullName()
|
|
self.Add('template', class_name)
|
|
name = self.info.rename or self.class_.name
|
|
self.Add('constructor', '"%s"' % name)
|
|
|
|
|
|
def ExportBases(self, exported_names):
|
|
'Expose the bases of the class into the template section'
|
|
hierarchy = self.class_.hierarchy
|
|
exported = []
|
|
for level in hierarchy:
|
|
for base in level:
|
|
if base.visibility == Scope.public and base.name in exported_names:
|
|
exported.append(base.name)
|
|
if exported:
|
|
break
|
|
if exported:
|
|
code = namespaces.python + 'bases< %s > ' % (', '.join(exported))
|
|
self.Add('template', code)
|
|
|
|
|
|
def ExportConstructors(self):
|
|
'''Exports all the public contructors of the class, plus indicates if the
|
|
class is noncopyable.
|
|
'''
|
|
py_ns = namespaces.python
|
|
indent = self.INDENT
|
|
|
|
def init_code(cons):
|
|
'return the init<>() code for the given contructor'
|
|
param_list = [p.FullName() for p in cons.parameters]
|
|
min_params_list = param_list[:cons.minArgs]
|
|
max_params_list = param_list[cons.minArgs:]
|
|
min_params = ', '.join(min_params_list)
|
|
max_params = ', '.join(max_params_list)
|
|
init = py_ns + 'init< '
|
|
init += min_params
|
|
if max_params:
|
|
if min_params:
|
|
init += ', '
|
|
init += py_ns + ('optional< %s >' % max_params)
|
|
init += ' >()'
|
|
return init
|
|
|
|
constructors = [x for x in self.public_members if isinstance(x, Constructor)]
|
|
self.constructors = constructors[:]
|
|
# don't export constructors if the class is abstract
|
|
if self.class_.abstract:
|
|
for cons in constructors:
|
|
if cons.IsCopy():
|
|
constructors.remove(cons)
|
|
break
|
|
|
|
if not constructors:
|
|
# declare no_init
|
|
self.Add('constructor', py_ns + 'no_init')
|
|
else:
|
|
# write the constructor with less parameters to the constructor section
|
|
smaller = None
|
|
for cons in constructors:
|
|
if smaller is None or len(cons.parameters) < len(smaller.parameters):
|
|
smaller = cons
|
|
assert smaller is not None
|
|
self.Add('constructor', init_code(smaller))
|
|
constructors.remove(smaller)
|
|
# write the rest to the inside section, using def()
|
|
for cons in constructors:
|
|
code = '.def(%s)' % init_code(cons)
|
|
self.Add('inside', code)
|
|
# check if the class is copyable
|
|
if not self.class_.HasCopyConstructor() or self.class_.abstract:
|
|
self.Add('template', namespaces.boost + 'noncopyable')
|
|
|
|
|
|
def ExportVariables(self):
|
|
'Export the variables of the class, both static and simple variables'
|
|
vars = [x for x in self.public_members if isinstance(x, Variable)]
|
|
for var in vars:
|
|
if self.info[var.name].exclude:
|
|
continue
|
|
name = self.info[var.name].rename or var.name
|
|
fullname = var.FullName()
|
|
if var.type.const:
|
|
def_ = '.def_readonly'
|
|
else:
|
|
def_ = '.def_readwrite'
|
|
code = '%s("%s", &%s)' % (def_, name, fullname)
|
|
self.Add('inside', code)
|
|
|
|
|
|
def OverloadName(self, method):
|
|
'Returns the name of the overloads struct for the given method'
|
|
name = makeid(method.FullName())
|
|
overloads = '_overloads_%i_%i' % (method.minArgs, method.maxArgs)
|
|
return name + overloads
|
|
|
|
|
|
def GetAddedMethods(self):
|
|
added_methods = self.info.__added__
|
|
result = []
|
|
if added_methods:
|
|
for name, rename in added_methods:
|
|
decl = self.GetDeclaration(name)
|
|
self.info[name].rename = rename
|
|
result.append(decl)
|
|
return result
|
|
|
|
|
|
def ExportMethods(self):
|
|
'''Export all the non-virtual methods of this class, plus any function
|
|
that is to be exported as a method'''
|
|
|
|
declared = {}
|
|
def DeclareOverloads(m):
|
|
'Declares the macro for the generation of the overloads'
|
|
if (isinstance(m, Method) and m.static) or type(m) == Function:
|
|
func = m.FullName()
|
|
macro = 'BOOST_PYTHON_FUNCTION_OVERLOADS'
|
|
else:
|
|
func = m.name
|
|
macro = 'BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS'
|
|
code = '%s(%s, %s, %i, %i)\n' % (macro, self.OverloadName(m), func, m.minArgs, m.maxArgs)
|
|
if code not in declared:
|
|
declared[code] = True
|
|
self.Add('declaration', code)
|
|
|
|
|
|
def Pointer(m):
|
|
'returns the correct pointer declaration for the method m'
|
|
# check if this method has a wrapper set for him
|
|
wrapper = self.info[m.name].wrapper
|
|
if wrapper:
|
|
return '&' + wrapper.FullName()
|
|
else:
|
|
return m.PointerDeclaration()
|
|
|
|
def IsExportable(m):
|
|
'Returns true if the given method is exportable by this routine'
|
|
ignore = (Constructor, ClassOperator, Destructor)
|
|
return isinstance(m, Function) and not isinstance(m, ignore) and not m.virtual
|
|
|
|
methods = [x for x in self.public_members if IsExportable(x)]
|
|
methods.extend(self.GetAddedMethods())
|
|
|
|
staticmethods = {}
|
|
|
|
for method in methods:
|
|
method_info = self.info[method.name]
|
|
|
|
# skip this method if it was excluded by the user
|
|
if method_info.exclude:
|
|
continue
|
|
|
|
# rename the method if the user requested
|
|
name = method_info.rename or method.name
|
|
|
|
# warn the user if this method needs a policy and doesn't have one
|
|
method_info.policy = exporterutils.HandlePolicy(method, method_info.policy)
|
|
|
|
# check for policies
|
|
policy = method_info.policy or ''
|
|
if policy:
|
|
policy = ', %s%s()' % (namespaces.python, policy.Code())
|
|
# check for overloads
|
|
overload = ''
|
|
if method.minArgs != method.maxArgs and not method_info.wrapper:
|
|
# add the overloads for this method
|
|
DeclareOverloads(method)
|
|
overload_name = self.OverloadName(method)
|
|
overload = ', %s%s()' % (namespaces.pyste, overload_name)
|
|
|
|
# build the .def string to export the method
|
|
pointer = Pointer(method)
|
|
code = '.def("%s", %s' % (name, pointer)
|
|
code += policy
|
|
code += overload
|
|
code += ')'
|
|
self.Add('inside', code)
|
|
# static method
|
|
if isinstance(method, Method) and method.static:
|
|
staticmethods[name] = 1
|
|
# add wrapper code if this method has one
|
|
wrapper = method_info.wrapper
|
|
if wrapper and wrapper.code:
|
|
self.Add('declaration-outside', wrapper.code)
|
|
|
|
# export staticmethod statements
|
|
for name in staticmethods:
|
|
code = '.staticmethod("%s")' % name
|
|
self.Add('inside', code)
|
|
|
|
|
|
|
|
def MakeNonVirtual(self):
|
|
'''Make all methods that the user indicated to no_override no more virtual, delegating their
|
|
export to the ExportMethods routine'''
|
|
for member in self.class_:
|
|
if type(member) == Method and member.virtual:
|
|
member.virtual = not self.info[member.name].no_override
|
|
|
|
|
|
def ExportVirtualMethods(self, codeunit):
|
|
# check if this class has any virtual methods
|
|
has_virtual_methods = False
|
|
for member in self.class_:
|
|
if type(member) == Method and member.virtual:
|
|
has_virtual_methods = True
|
|
break
|
|
|
|
holder = self.info.holder
|
|
if has_virtual_methods:
|
|
generator = _VirtualWrapperGenerator(self.class_, self.ClassBases(), self.info, codeunit)
|
|
if holder:
|
|
self.Add('template', holder(generator.FullName()))
|
|
else:
|
|
self.Add('template', generator.FullName())
|
|
for definition in generator.GenerateDefinitions():
|
|
self.Add('inside', definition)
|
|
self.Add('declaration', generator.GenerateVirtualWrapper(self.INDENT))
|
|
else:
|
|
if holder:
|
|
self.Add('template', holder(self.class_.FullName()))
|
|
|
|
# operators natively supported by boost
|
|
BOOST_SUPPORTED_OPERATORS = '+ - * / % ^ & ! ~ | < > == != <= >= << >> && || += -= '\
|
|
'*= /= %= ^= &= |= <<= >>='.split()
|
|
# create a map for faster lookup
|
|
BOOST_SUPPORTED_OPERATORS = dict(zip(BOOST_SUPPORTED_OPERATORS, range(len(BOOST_SUPPORTED_OPERATORS))))
|
|
|
|
# a dict of operators that are not directly supported by boost, but can be exposed
|
|
# simply as a function with a special name
|
|
BOOST_RENAME_OPERATORS = {
|
|
'()' : '__call__',
|
|
}
|
|
|
|
# converters which have a special name in python
|
|
# it's a map of a regular expression of the converter's result to the
|
|
# appropriate python name
|
|
SPECIAL_CONVERTERS = {
|
|
re.compile(r'(const)?\s*double$') : '__float__',
|
|
re.compile(r'(const)?\s*float$') : '__float__',
|
|
re.compile(r'(const)?\s*int$') : '__int__',
|
|
re.compile(r'(const)?\s*long$') : '__long__',
|
|
re.compile(r'(const)?\s*char\s*\*?$') : '__str__',
|
|
re.compile(r'(const)?.*::basic_string<.*>\s*(\*|\&)?$') : '__str__',
|
|
}
|
|
|
|
|
|
def ExportOperators(self):
|
|
'Export all member operators and free operators related to this class'
|
|
|
|
def GetFreeOperators():
|
|
'Get all the free (global) operators related to this class'
|
|
operators = []
|
|
for decl in self.declarations:
|
|
if isinstance(decl, Operator):
|
|
# check if one of the params is this class
|
|
for param in decl.parameters:
|
|
if param.name == self.class_.FullName():
|
|
operators.append(decl)
|
|
break
|
|
return operators
|
|
|
|
def GetOperand(param):
|
|
'Returns the operand of this parameter (either "self", or "other<type>")'
|
|
if param.name == self.class_.FullName():
|
|
return namespaces.python + 'self'
|
|
else:
|
|
return namespaces.python + ('other< %s >()' % param.name)
|
|
|
|
|
|
def HandleSpecialOperator(operator):
|
|
# gatter information about the operator and its parameters
|
|
result_name = operator.result.name
|
|
param1_name = ''
|
|
if operator.parameters:
|
|
param1_name = operator.parameters[0].name
|
|
|
|
# check for str
|
|
ostream = 'basic_ostream'
|
|
is_str = result_name.find(ostream) != -1 and param1_name.find(ostream) != -1
|
|
if is_str:
|
|
namespace = namespaces.python + 'self_ns::'
|
|
self_ = namespaces.python + 'self'
|
|
return '.def(%sstr(%s))' % (namespace, self_)
|
|
|
|
# is not a special operator
|
|
return None
|
|
|
|
|
|
|
|
frees = GetFreeOperators()
|
|
members = [x for x in self.public_members if type(x) == ClassOperator]
|
|
all_operators = frees + members
|
|
operators = [x for x in all_operators if not self.info['operator'][x.name].exclude]
|
|
|
|
for operator in operators:
|
|
# gatter information about the operator, for use later
|
|
wrapper = self.info['operator'][operator.name].wrapper
|
|
if wrapper:
|
|
pointer = '&' + wrapper.FullName()
|
|
if wrapper.code:
|
|
self.Add('declaration-outside', wrapper.code)
|
|
else:
|
|
pointer = operator.PointerDeclaration()
|
|
rename = self.info['operator'][operator.name].rename
|
|
|
|
# check if this operator will be exported as a method
|
|
export_as_method = wrapper or rename or operator.name in self.BOOST_RENAME_OPERATORS
|
|
|
|
# check if this operator has a special representation in boost
|
|
special_code = HandleSpecialOperator(operator)
|
|
has_special_representation = special_code is not None
|
|
|
|
if export_as_method:
|
|
# export this operator as a normal method, renaming or using the given wrapper
|
|
if not rename:
|
|
if wrapper:
|
|
rename = wrapper.name
|
|
else:
|
|
rename = self.BOOST_RENAME_OPERATORS[operator.name]
|
|
policy = ''
|
|
policy_obj = self.info['operator'][operator.name].policy
|
|
if policy_obj:
|
|
policy = ', %s()' % policy_obj.Code()
|
|
self.Add('inside', '.def("%s", %s%s)' % (rename, pointer, policy))
|
|
|
|
elif has_special_representation:
|
|
self.Add('inside', special_code)
|
|
|
|
elif operator.name in self.BOOST_SUPPORTED_OPERATORS:
|
|
# export this operator using boost's facilities
|
|
op = operator
|
|
is_unary = isinstance(op, Operator) and len(op.parameters) == 1 or\
|
|
isinstance(op, ClassOperator) and len(op.parameters) == 0
|
|
if is_unary:
|
|
self.Add('inside', '.def( %s%sself )' % \
|
|
(operator.name, namespaces.python))
|
|
else:
|
|
# binary operator
|
|
if len(operator.parameters) == 2:
|
|
left_operand = GetOperand(operator.parameters[0])
|
|
right_operand = GetOperand(operator.parameters[1])
|
|
else:
|
|
left_operand = namespaces.python + 'self'
|
|
right_operand = GetOperand(operator.parameters[0])
|
|
self.Add('inside', '.def( %s %s %s )' % \
|
|
(left_operand, operator.name, right_operand))
|
|
|
|
# export the converters.
|
|
# export them as simple functions with a pre-determined name
|
|
|
|
converters = [x for x in self.public_members if type(x) == ConverterOperator]
|
|
|
|
def ConverterMethodName(converter):
|
|
result_fullname = converter.result.FullName()
|
|
result_name = converter.result.name
|
|
for regex, method_name in self.SPECIAL_CONVERTERS.items():
|
|
if regex.match(result_fullname):
|
|
return method_name
|
|
else:
|
|
# extract the last name from the full name
|
|
result_name = makeid(result_name)
|
|
return 'to_' + result_name
|
|
|
|
for converter in converters:
|
|
info = self.info['operator'][converter.result.FullName()]
|
|
# check if this operator should be excluded
|
|
if info.exclude:
|
|
continue
|
|
|
|
special_code = HandleSpecialOperator(converter)
|
|
if info.rename or not special_code:
|
|
# export as method
|
|
name = info.rename or ConverterMethodName(converter)
|
|
pointer = converter.PointerDeclaration()
|
|
policy_code = ''
|
|
if info.policy:
|
|
policy_code = ', %s()' % info.policy.Code()
|
|
self.Add('inside', '.def("%s", %s%s)' % (name, pointer, policy_code))
|
|
|
|
elif special_code:
|
|
self.Add('inside', special_code)
|
|
|
|
|
|
|
|
def ExportNestedClasses(self, exported_names):
|
|
nested_classes = [x for x in self.public_members if isinstance(x, NestedClass)]
|
|
for nested_class in nested_classes:
|
|
nested_info = self.info[nested_class.name]
|
|
nested_info.include = self.info.include
|
|
nested_info.name = nested_class.FullName()
|
|
exporter = self.__class__(nested_info)
|
|
exporter.SetDeclarations(self.declarations)
|
|
codeunit = SingleCodeUnit(None, None)
|
|
exporter.Export(codeunit, exported_names)
|
|
self.nested_codeunits.append(codeunit)
|
|
|
|
|
|
def ExportNestedEnums(self, exported_names):
|
|
nested_enums = [x for x in self.public_members if isinstance(x, ClassEnumeration)]
|
|
for enum in nested_enums:
|
|
enum_info = self.info[enum.name]
|
|
enum_info.include = self.info.include
|
|
enum_info.name = enum.FullName()
|
|
exporter = EnumExporter(enum_info)
|
|
exporter.SetDeclarations(self.declarations)
|
|
codeunit = SingleCodeUnit(None, None)
|
|
exporter.Export(codeunit, exported_names)
|
|
self.nested_codeunits.append(codeunit)
|
|
|
|
|
|
def ExportSmartPointer(self):
|
|
smart_ptr = self.info.smart_ptr
|
|
if smart_ptr:
|
|
class_name = self.class_.FullName()
|
|
smart_ptr = smart_ptr % class_name
|
|
self.Add('scope', '%sregister_ptr_to_python< %s >();' % (namespaces.python, smart_ptr))
|
|
|
|
|
|
def ExportOpaquePointerPolicies(self):
|
|
# check all methods for 'return_opaque_pointer' policies
|
|
methods = [x for x in self.public_members if isinstance(x, Method)]
|
|
for method in methods:
|
|
return_opaque_policy = return_value_policy(return_opaque_pointer)
|
|
if self.info[method.name].policy == return_opaque_policy:
|
|
macro = exporterutils.EspecializeTypeID(method.result.name)
|
|
if macro:
|
|
self.Add('declaration-outside', macro)
|
|
|
|
def ExportAddedCode(self):
|
|
if self.info.__code__:
|
|
for code in self.info.__code__:
|
|
self.Add('inside', code)
|
|
|
|
|
|
#==============================================================================
|
|
# Virtual Wrapper utils
|
|
#==============================================================================
|
|
|
|
def _ParamsInfo(m, count=None):
|
|
if count is None:
|
|
count = len(m.parameters)
|
|
param_names = ['p%i' % i for i in range(count)]
|
|
param_types = [x.FullName() for x in m.parameters[:count]]
|
|
params = ['%s %s' % (t, n) for t, n in zip(param_types, param_names)]
|
|
#for i, p in enumerate(m.parameters[:count]):
|
|
# if p.default is not None:
|
|
# #params[i] += '=%s' % p.default
|
|
# params[i] += '=%s' % (p.name + '()')
|
|
params = ', '.join(params)
|
|
return params, param_names, param_types
|
|
|
|
|
|
class _VirtualWrapperGenerator(object):
|
|
'Generates code to export the virtual methods of the given class'
|
|
|
|
def __init__(self, class_, bases, info, codeunit):
|
|
self.class_ = copy.deepcopy(class_)
|
|
self.bases = bases[:]
|
|
self.info = info
|
|
self.wrapper_name = makeid(class_.FullName()) + '_Wrapper'
|
|
self.virtual_methods = None
|
|
self._method_count = {}
|
|
self.codeunit = codeunit
|
|
self.GenerateVirtualMethods()
|
|
|
|
|
|
SELF = 'py_self'
|
|
|
|
|
|
def DefaultImplementationNames(self, method):
|
|
'''Returns a list of default implementations for this method, one for each
|
|
number of default arguments. Always returns at least one name, and return from
|
|
the one with most arguments to the one with the least.
|
|
'''
|
|
base_name = 'default_' + method.name
|
|
minArgs = method.minArgs
|
|
maxArgs = method.maxArgs
|
|
if minArgs == maxArgs:
|
|
return [base_name]
|
|
else:
|
|
return [base_name + ('_%i' % i) for i in range(minArgs, maxArgs+1)]
|
|
|
|
|
|
def Declaration(self, method, indent):
|
|
'''Returns a string with the declarations of the virtual wrapper and
|
|
its default implementations. This string must be put inside the Wrapper
|
|
body.
|
|
'''
|
|
pyste = namespaces.pyste
|
|
python = namespaces.python
|
|
rename = self.info[method.name].rename or method.name
|
|
result = method.result.FullName()
|
|
return_str = 'return '
|
|
if result == 'void':
|
|
return_str = ''
|
|
params, param_names, param_types = _ParamsInfo(method)
|
|
constantness = ''
|
|
if method.const:
|
|
constantness = ' const'
|
|
|
|
# call_method callback
|
|
decl = indent + '%s %s(%s)%s%s {\n' % (result, method.name, params, constantness, method.Exceptions())
|
|
param_names_str = ', '.join(param_names)
|
|
if param_names_str:
|
|
param_names_str = ', ' + param_names_str
|
|
|
|
self_str = self.SELF
|
|
|
|
decl += indent*2 + '%(return_str)s%(python)scall_method< %(result)s >' \
|
|
'(%(self_str)s, "%(rename)s"%(param_names_str)s);\n' % locals()
|
|
decl += indent + '}\n'
|
|
|
|
# default implementations (with overloading)
|
|
def DefaultImpl(method, param_names):
|
|
'Return the body of a default implementation wrapper'
|
|
indent2 = indent * 2
|
|
wrapper = self.info[method.name].wrapper
|
|
if not wrapper:
|
|
# return the default implementation of the class
|
|
return indent2 + '%s%s(%s);\n' % \
|
|
(return_str, method.FullName(), ', '.join(param_names))
|
|
else:
|
|
if wrapper.code:
|
|
self.codeunit.Write('declaration-outside', wrapper.code)
|
|
# return a call for the wrapper
|
|
params = ', '.join(['this'] + param_names)
|
|
return indent2 + '%s%s(%s);\n' % (return_str, wrapper.FullName(), params)
|
|
|
|
if not method.abstract and method.visibility != Scope.private:
|
|
minArgs = method.minArgs
|
|
maxArgs = method.maxArgs
|
|
impl_names = self.DefaultImplementationNames(method)
|
|
for impl_name, argNum in zip(impl_names, range(minArgs, maxArgs+1)):
|
|
params, param_names, param_types = _ParamsInfo(method, argNum)
|
|
decl += '\n'
|
|
decl += indent + '%s %s(%s)%s {\n' % (result, impl_name, params, constantness)
|
|
decl += DefaultImpl(method, param_names)
|
|
decl += indent + '}\n'
|
|
return decl
|
|
|
|
|
|
def MethodDefinition(self, method):
|
|
'''Returns a list of lines, which should be put inside the class_
|
|
statement to export this method.'''
|
|
# dont define abstract methods
|
|
pyste = namespaces.pyste
|
|
rename = self.info[method.name].rename or method.name
|
|
default_names = self.DefaultImplementationNames(method)
|
|
class_name = self.class_.FullName()
|
|
wrapper_name = pyste + self.wrapper_name
|
|
result = method.result.FullName()
|
|
is_method_unique = method.is_unique
|
|
constantness = ''
|
|
if method.const:
|
|
constantness = ' const'
|
|
|
|
# create a list of default-impl pointers
|
|
minArgs = method.minArgs
|
|
maxArgs = method.maxArgs
|
|
if method.abstract:
|
|
default_pointers = []
|
|
elif is_method_unique:
|
|
default_pointers = ['&%s::%s' % (wrapper_name, x) for x in default_names]
|
|
else:
|
|
default_pointers = []
|
|
for impl_name, argNum in zip(default_names, range(minArgs, maxArgs+1)):
|
|
param_list = [x.FullName() for x in method.parameters[:argNum]]
|
|
params = ', '.join(param_list)
|
|
signature = '%s (%s::*)(%s)%s' % (result, wrapper_name, params, constantness)
|
|
default_pointer = '(%s)&%s::%s' % (signature, wrapper_name, impl_name)
|
|
default_pointers.append(default_pointer)
|
|
|
|
# get the pointer of the method
|
|
pointer = method.PointerDeclaration()
|
|
if method.abstract:
|
|
pointer = namespaces.python + ('pure_virtual(%s)' % pointer)
|
|
|
|
# warn the user if this method needs a policy and doesn't have one
|
|
method_info = self.info[method.name]
|
|
method_info.policy = exporterutils.HandlePolicy(method, method_info.policy)
|
|
|
|
# Add policy to overloaded methods also
|
|
policy = method_info.policy or ''
|
|
if policy:
|
|
policy = ', %s%s()' % (namespaces.python, policy.Code())
|
|
|
|
# generate the defs
|
|
definitions = []
|
|
# basic def
|
|
if default_pointers:
|
|
definitions.append('.def("%s", %s, %s%s)' % (rename, pointer, default_pointers[-1], policy))
|
|
for default_pointer in default_pointers[:-1]:
|
|
definitions.append('.def("%s", %s%s)' % (rename, default_pointer, policy))
|
|
else:
|
|
definitions.append('.def("%s", %s%s)' % (rename, pointer, policy))
|
|
return definitions
|
|
|
|
|
|
def FullName(self):
|
|
return namespaces.pyste + self.wrapper_name
|
|
|
|
|
|
def GenerateVirtualMethods(self):
|
|
'''To correctly export all virtual methods, we must also make wrappers
|
|
for the virtual methods of the bases of this class, as if the methods
|
|
were from this class itself.
|
|
This method creates the instance variable self.virtual_methods.
|
|
'''
|
|
def IsVirtual(m):
|
|
if type(m) is Method:
|
|
pure_virtual = m.abstract and m.virtual
|
|
virtual = m.virtual and m.visibility != Scope.private
|
|
return virtual or pure_virtual
|
|
else:
|
|
return False
|
|
|
|
# extract the virtual methods, avoiding duplications. The duplication
|
|
# must take in account the full signature without the class name, so
|
|
# that inherited members are correctly excluded if the subclass overrides
|
|
# them.
|
|
def MethodSig(method):
|
|
if method.const:
|
|
const = ' const'
|
|
else:
|
|
const = ''
|
|
if method.result:
|
|
result = method.result.FullName()
|
|
else:
|
|
result = ''
|
|
params = ', '.join([x.FullName() for x in method.parameters])
|
|
return '%s %s(%s)%s%s' % (
|
|
result, method.name, params, const, method.Exceptions())
|
|
|
|
already_added = {}
|
|
self.virtual_methods = []
|
|
for member in self.class_:
|
|
if IsVirtual(member):
|
|
already_added[MethodSig(member)] = None
|
|
self.virtual_methods.append(member)
|
|
|
|
for base in self.bases:
|
|
base_methods = [copy.deepcopy(x) for x in base if IsVirtual(x)]
|
|
for base_method in base_methods:
|
|
self.class_.AddMember(base_method)
|
|
|
|
all_methods = [x for x in self.class_ if IsVirtual(x)]
|
|
|
|
for member in all_methods:
|
|
sig = MethodSig(member)
|
|
if IsVirtual(member) and not sig in already_added:
|
|
self.virtual_methods.append(member)
|
|
already_added[sig] = 0
|
|
|
|
|
|
def Constructors(self):
|
|
return self.class_.Constructors(publics_only=True)
|
|
|
|
|
|
def GenerateDefinitions(self):
|
|
defs = []
|
|
for method in self.virtual_methods:
|
|
exclude = self.info[method.name].exclude
|
|
# generate definitions only for public methods and non-abstract methods
|
|
if method.visibility == Scope.public and not exclude:
|
|
defs.extend(self.MethodDefinition(method))
|
|
return defs
|
|
|
|
|
|
def GenerateVirtualWrapper(self, indent):
|
|
'Return the wrapper for this class'
|
|
|
|
# generate the class code
|
|
class_name = self.class_.FullName()
|
|
code = 'struct %s: %s\n' % (self.wrapper_name, class_name)
|
|
code += '{\n'
|
|
# generate constructors (with the overloads for each one)
|
|
for cons in self.Constructors(): # only public constructors
|
|
minArgs = cons.minArgs
|
|
maxArgs = cons.maxArgs
|
|
# from the min number of arguments to the max number, generate
|
|
# all version of the given constructor
|
|
cons_code = ''
|
|
for argNum in range(minArgs, maxArgs+1):
|
|
params, param_names, param_types = _ParamsInfo(cons, argNum)
|
|
if params:
|
|
params = ', ' + params
|
|
cons_code += indent + '%s(PyObject* %s_%s):\n' % \
|
|
(self.wrapper_name, self.SELF, params)
|
|
cons_code += indent*2 + '%s(%s), %s(%s_) {}\n\n' % \
|
|
(class_name, ', '.join(param_names), self.SELF, self.SELF)
|
|
code += cons_code
|
|
# generate the body
|
|
body = []
|
|
for method in self.virtual_methods:
|
|
if not self.info[method.name].exclude:
|
|
body.append(self.Declaration(method, indent))
|
|
body = '\n'.join(body)
|
|
code += body + '\n'
|
|
# add the self member
|
|
code += indent + 'PyObject* %s;\n' % self.SELF
|
|
code += '};\n'
|
|
return code
|