Attachment 'ClassExporter.py'

Download

   1 # Copyright Bruno da Silva de Oliveira 2003. Use, modification and 
   2 # distribution is subject to the Boost Software License, Version 1.0.
   3 # (See accompanying file LICENSE_1_0.txt or copy at 
   4 # http:#www.boost.org/LICENSE_1_0.txt)
   5 
   6 import exporters
   7 from Exporter import Exporter
   8 from declarations import *
   9 from settings import *
  10 from policies import *
  11 from SingleCodeUnit import SingleCodeUnit
  12 from EnumExporter import EnumExporter
  13 from utils import makeid, enumerate
  14 import copy
  15 import exporterutils
  16 import re
  17 
  18 #==============================================================================
  19 # ClassExporter
  20 #==============================================================================
  21 class ClassExporter(Exporter):
  22     'Generates boost.python code to export a class declaration'
  23  
  24     def __init__(self, info, parser_tail=None):
  25         Exporter.__init__(self, info, parser_tail)
  26         # sections of code
  27         self.sections = {}
  28         # template: each item in the list is an item into the class_<...> 
  29         # section.
  30         self.sections['template'] = []  
  31         # constructor: each item in the list is a parameter to the class_ 
  32         # constructor, like class_<C>(...)
  33         self.sections['constructor'] = []
  34         # inside: everything within the class_<> statement        
  35         self.sections['inside'] = []        
  36         # scope: items outside the class statement but within its scope.
  37         # scope* s = new scope(class<>());
  38         # ...
  39         # delete s;
  40         self.sections['scope'] = []
  41         # declarations: outside the BOOST_PYTHON_MODULE macro
  42         self.sections['declaration'] = []
  43         self.sections['declaration-outside'] = []
  44         self.sections['include'] = []
  45         # a list of Constructor instances
  46         self.constructors = []
  47         # a list of code units, generated by nested declarations
  48         self.nested_codeunits = []
  49 
  50 
  51     def ScopeName(self):
  52         return makeid(self.class_.FullName()) + '_scope'
  53 
  54 
  55     def Name(self):
  56         return self.info.name
  57 
  58 
  59     def SetDeclarations(self, declarations):
  60         Exporter.SetDeclarations(self, declarations)
  61         if self.declarations:
  62             decl = self.GetDeclaration(self.info.name)
  63             if isinstance(decl, Typedef):
  64                 self.class_ = self.GetDeclaration(decl.type.name)
  65                 if not self.info.rename:
  66                     self.info.rename = decl.name
  67             else:
  68                 self.class_ = decl
  69             self.class_ = copy.deepcopy(self.class_)
  70         else:
  71             self.class_ = None
  72         
  73         
  74     def ClassBases(self):
  75         all_bases = []       
  76         for level in self.class_.hierarchy:
  77             for base in level:
  78                 all_bases.append(base)
  79         return [self.GetDeclaration(x.name) for x in all_bases] 
  80 
  81     
  82     def Order(self):
  83         '''Return the TOTAL number of bases that this class has, including the
  84         bases' bases.  Do this because base classes must be instantialized
  85         before the derived classes in the module definition.  
  86         '''
  87         num_bases = len(self.ClassBases())
  88         return num_bases, self.class_.FullName()
  89         
  90     
  91     def Export(self, codeunit, exported_names):
  92         self.InheritMethods(exported_names)
  93         self.MakeNonVirtual()
  94         if not self.info.exclude:
  95             self.ExportBasics()
  96             self.ExportBases(exported_names)
  97             self.ExportConstructors()
  98             self.ExportVariables()
  99             self.ExportVirtualMethods(codeunit)
 100             self.ExportMethods()
 101             self.ExportOperators()
 102             self.ExportNestedClasses(exported_names)
 103             self.ExportNestedEnums(exported_names)
 104             self.ExportSmartPointer()
 105             self.ExportOpaquePointerPolicies()
 106             self.ExportAddedCode()
 107             self.Write(codeunit)
 108             exported_names[self.Name()] = 1
 109 
 110 
 111     def InheritMethods(self, exported_names):
 112         '''Go up in the class hierarchy looking for classes that were not
 113         exported yet, and then add their public members to this classes
 114         members, as if they were members of this class. This allows the user to
 115         just export one type and automatically get all the members from the
 116         base classes.
 117         '''
 118         valid_members = (Method, ClassVariable, NestedClass, ClassEnumeration)
 119         fullnames = [x.FullName() for x in self.class_]
 120         pointers = [x.PointerDeclaration(True) for x in self.class_ if isinstance(x, Method)]
 121         fullnames = dict([(x, None) for x in fullnames])
 122         pointers = dict([(x, None) for x in pointers])
 123         for level in self.class_.hierarchy:
 124             level_exported = False
 125             for base in level:
 126                 base = self.GetDeclaration(base.name)
 127                 if base.FullName() not in exported_names:
 128                     for member in base:
 129                         if type(member) in valid_members:
 130                             member_copy = copy.deepcopy(member)   
 131                             member_copy.class_ = self.class_.FullName()
 132                             if isinstance(member_copy, Method):
 133                                 pointer = member_copy.PointerDeclaration(True)
 134                                 if pointer not in pointers:
 135                                     self.class_.AddMember(member)
 136                                     pointers[pointer] = None
 137                             elif member_copy.FullName() not in fullnames:
 138                                 self.class_.AddMember(member)        
 139                 else:
 140                     level_exported = True
 141             if level_exported:
 142                 break
 143         def IsValid(member):
 144             return isinstance(member, valid_members) and member.visibility == Scope.public
 145         self.public_members = [x for x in self.class_ if IsValid(x)] 
 146 
 147 
 148     def Write(self, codeunit):
 149         indent = self.INDENT
 150         boost_ns = namespaces.python
 151         pyste_ns = namespaces.pyste
 152         code = ''
 153         # begin a scope for this class if needed
 154         nested_codeunits = self.nested_codeunits
 155         needs_scope = self.sections['scope'] or nested_codeunits
 156         if needs_scope:
 157             scope_name = self.ScopeName()
 158             code += indent + boost_ns + 'scope* %s = new %sscope(\n' %\
 159                 (scope_name, boost_ns)
 160         # export the template section
 161         template_params = ', '.join(self.sections['template'])
 162         code += indent + boost_ns + 'class_< %s >' % template_params
 163         # export the constructor section
 164         constructor_params = ', '.join(self.sections['constructor'])
 165         code += '(%s)\n' % constructor_params
 166         # export the inside section
 167         in_indent = indent*2
 168         for line in self.sections['inside']:
 169             code += in_indent + line + '\n' 
 170         # write the scope section and end it
 171         if not needs_scope:
 172             code += indent + ';\n'
 173         else:
 174             code += indent + ');\n'
 175             for line in self.sections['scope']:
 176                 code += indent + line + '\n'
 177             # write the contents of the nested classes
 178             for nested_unit in nested_codeunits:
 179                 code += '\n' + nested_unit.Section('module')
 180             # close the scope
 181             code += indent + 'delete %s;\n' % scope_name
 182             
 183         # write the code to the module section in the codeunit        
 184         codeunit.Write('module', code + '\n')
 185         
 186         # write the declarations to the codeunit        
 187         declarations = '\n'.join(self.sections['declaration'])
 188         for nested_unit in nested_codeunits:
 189             declarations += nested_unit.Section('declaration')
 190         if declarations:
 191             codeunit.Write('declaration', declarations + '\n')
 192         declarations_outside = '\n'.join(self.sections['declaration-outside'])
 193         if declarations_outside:
 194             codeunit.Write('declaration-outside', declarations_outside + '\n')
 195 
 196         # write the includes to the codeunit
 197         includes = '\n'.join(self.sections['include'])
 198         for nested_unit in nested_codeunits:
 199             includes += nested_unit.Section('include')
 200         if includes:
 201             codeunit.Write('include', includes)
 202 
 203 
 204     def Add(self, section, item):
 205         'Add the item into the corresponding section'
 206         self.sections[section].append(item)
 207 
 208         
 209     def ExportBasics(self):
 210         '''Export the name of the class and its class_ statement.'''
 211         class_name = self.class_.FullName()
 212         self.Add('template', class_name)
 213         name = self.info.rename or self.class_.name
 214         self.Add('constructor', '"%s"' % name)
 215         
 216         
 217     def ExportBases(self, exported_names):
 218         'Expose the bases of the class into the template section'        
 219         hierarchy = self.class_.hierarchy
 220         exported = []
 221         for level in hierarchy:
 222             for base in level:
 223                 if base.visibility == Scope.public and base.name in exported_names:
 224                     exported.append(base.name)
 225             if exported:
 226                 break
 227         if exported:
 228             code = namespaces.python + 'bases< %s > ' %  (', '.join(exported))
 229             self.Add('template', code)         
 230 
 231 
 232     def ExportConstructors(self):
 233         '''Exports all the public contructors of the class, plus indicates if the 
 234         class is noncopyable.
 235         '''
 236         py_ns = namespaces.python
 237         indent = self.INDENT
 238         
 239         def init_code(cons):
 240             'return the init<>() code for the given contructor'
 241             param_list = [p.FullName() for p in cons.parameters]
 242             min_params_list = param_list[:cons.minArgs]
 243             max_params_list = param_list[cons.minArgs:]
 244             min_params = ', '.join(min_params_list)
 245             max_params = ', '.join(max_params_list)
 246             init = py_ns + 'init< '
 247             init += min_params
 248             if max_params:
 249                 if min_params:
 250                     init += ', '
 251                 init += py_ns + ('optional< %s >' % max_params)
 252             init += ' >()'    
 253             return init
 254         
 255         constructors = [x for x in self.public_members if isinstance(x, Constructor)]
 256         self.constructors = constructors[:]
 257         # don't export constructors if the class is abstract
 258         if self.class_.abstract:
 259             for cons in constructors:
 260                 if cons.IsCopy():
 261                     constructors.remove(cons)
 262                     break
 263             
 264         if not constructors:
 265             # declare no_init
 266             self.Add('constructor', py_ns + 'no_init') 
 267         else:
 268             # write the constructor with less parameters to the constructor section
 269             smaller = None
 270             for cons in constructors:
 271                 if smaller is None or len(cons.parameters) < len(smaller.parameters):
 272                     smaller = cons
 273             assert smaller is not None
 274             self.Add('constructor', init_code(smaller))
 275             constructors.remove(smaller)
 276             # write the rest to the inside section, using def()
 277             for cons in constructors:
 278                 code = '.def(%s)' % init_code(cons) 
 279                 self.Add('inside', code)
 280         # check if the class is copyable
 281         if not self.class_.HasCopyConstructor() or self.class_.abstract:
 282             self.Add('template', namespaces.boost + 'noncopyable')
 283             
 284         
 285     def ExportVariables(self):
 286         'Export the variables of the class, both static and simple variables'
 287         vars = [x for x in self.public_members if isinstance(x, Variable)]
 288         for var in vars:
 289             if self.info[var.name].exclude: 
 290                 continue
 291             name = self.info[var.name].rename or var.name
 292             fullname = var.FullName() 
 293             if var.type.const:
 294                 def_ = '.def_readonly'
 295             else:
 296                 def_ = '.def_readwrite'
 297             code = '%s("%s", &%s)' % (def_, name, fullname)
 298             self.Add('inside', code)
 299 
 300     
 301     def OverloadName(self, method):
 302         'Returns the name of the overloads struct for the given method'
 303         name = makeid(method.FullName())
 304         overloads = '_overloads_%i_%i' % (method.minArgs, method.maxArgs)    
 305         return name + overloads
 306 
 307     
 308     def GetAddedMethods(self):
 309         added_methods = self.info.__added__
 310         result = []
 311         if added_methods:
 312             for name, rename in added_methods:
 313                 decl = self.GetDeclaration(name)
 314                 self.info[name].rename = rename
 315                 result.append(decl)
 316         return result
 317 
 318                 
 319     def ExportMethods(self):
 320         '''Export all the non-virtual methods of this class, plus any function
 321         that is to be exported as a method'''
 322             
 323         declared = {}
 324         def DeclareOverloads(m):
 325             'Declares the macro for the generation of the overloads'
 326             if (isinstance(m, Method) and m.static) or type(m) == Function:
 327                 func = m.FullName()
 328                 macro = 'BOOST_PYTHON_FUNCTION_OVERLOADS'
 329             else:
 330                 func = m.name
 331                 macro = 'BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS' 
 332             code = '%s(%s, %s, %i, %i)\n' % (macro, self.OverloadName(m), func, m.minArgs, m.maxArgs)
 333             if code not in declared:
 334                 declared[code] = True
 335                 self.Add('declaration', code)
 336 
 337 
 338         def Pointer(m):
 339             'returns the correct pointer declaration for the method m'
 340             # check if this method has a wrapper set for him
 341             wrapper = self.info[m.name].wrapper
 342             if wrapper:
 343                 return '&' + wrapper.FullName()
 344             else:
 345                 return m.PointerDeclaration() 
 346 
 347         def IsExportable(m):
 348             'Returns true if the given method is exportable by this routine'
 349             ignore = (Constructor, ClassOperator, Destructor)
 350             return isinstance(m, Function) and not isinstance(m, ignore) and not m.virtual        
 351         
 352         methods = [x for x in self.public_members if IsExportable(x)]        
 353         methods.extend(self.GetAddedMethods())
 354         
 355         staticmethods = {}
 356         
 357         for method in methods:
 358             method_info = self.info[method.name]
 359             
 360             # skip this method if it was excluded by the user
 361             if method_info.exclude:
 362                 continue 
 363 
 364             # rename the method if the user requested
 365             name = method_info.rename or method.name
 366             
 367             # warn the user if this method needs a policy and doesn't have one
 368             method_info.policy = exporterutils.HandlePolicy(method, method_info.policy)
 369             
 370             # check for policies
 371             policy = method_info.policy or ''
 372             policy_code = ''
 373             if policy:
 374                 policy_code = '%s%s()' % (namespaces.python, policy.Code())
 375                 policy = ', %s' % policy_code
 376             # check for overloads
 377             overload = ''
 378             if method.minArgs != method.maxArgs and not method_info.wrapper: 
 379                 # add the overloads for this method
 380                 DeclareOverloads(method)
 381                 overload_name = self.OverloadName(method)
 382                 overload = ', %s%s()' % (namespaces.pyste, overload_name)
 383                 if policy:
 384                     overload += '[ %s ]' % policy_code
 385                     policy = ''
 386         
 387             # build the .def string to export the method
 388             pointer = Pointer(method)
 389             code = '.def("%s", %s' % (name, pointer)
 390             code += policy
 391             code += overload
 392             code += ')'
 393             self.Add('inside', code)
 394             # static method
 395             if isinstance(method, Method) and method.static:
 396                 staticmethods[name] = 1
 397             # add wrapper code if this method has one
 398             wrapper = method_info.wrapper
 399             if wrapper and wrapper.code:
 400                 self.Add('declaration-outside', wrapper.code)
 401         
 402         # export staticmethod statements
 403         for name in staticmethods:
 404             code = '.staticmethod("%s")' % name
 405             self.Add('inside', code) 
 406 
 407 
 408                 
 409     def MakeNonVirtual(self):
 410         '''Make all methods that the user indicated to no_override no more virtual, delegating their
 411         export to the ExportMethods routine'''
 412         for member in self.class_:
 413             if type(member) == Method and member.virtual:
 414                 member.virtual = not self.info[member.name].no_override 
 415 
 416 
 417     def ExportVirtualMethods(self, codeunit):        
 418         # check if this class has any virtual methods
 419         has_virtual_methods = False
 420         for member in self.class_:
 421             if type(member) == Method and member.virtual:
 422                 has_virtual_methods = True
 423                 break
 424 
 425         holder = self.info.holder
 426         if has_virtual_methods:
 427             generator = _VirtualWrapperGenerator(self.class_, self.ClassBases(), self.info, codeunit)
 428             if holder:
 429                 self.Add('template', holder(generator.FullName()))
 430             else:
 431                 self.Add('template', generator.FullName())
 432             for definition in generator.GenerateDefinitions():
 433                 self.Add('inside', definition)
 434             self.Add('declaration', generator.GenerateVirtualWrapper(self.INDENT))
 435         else:
 436             if holder:
 437                 self.Add('template', holder(self.class_.FullName()))
 438 
 439     # operators natively supported by boost
 440     BOOST_SUPPORTED_OPERATORS = '+ - * / % ^ & ! ~ | < > == != <= >= << >> && || += -= '\
 441         '*= /= %= ^= &= |= <<= >>='.split()
 442     # create a map for faster lookup
 443     BOOST_SUPPORTED_OPERATORS = dict(zip(BOOST_SUPPORTED_OPERATORS, range(len(BOOST_SUPPORTED_OPERATORS))))
 444 
 445     # a dict of operators that are not directly supported by boost, but can be exposed
 446     # simply as a function with a special name
 447     BOOST_RENAME_OPERATORS = {
 448         '()' : '__call__',
 449     }
 450 
 451     # converters which have a special name in python
 452     # it's a map of a regular expression of the converter's result to the
 453     # appropriate python name
 454     SPECIAL_CONVERTERS = {
 455         re.compile(r'(const)?\s*double$') : '__float__',
 456         re.compile(r'(const)?\s*float$') : '__float__',
 457         re.compile(r'(const)?\s*int$') : '__int__',
 458         re.compile(r'(const)?\s*long$') : '__long__',
 459         re.compile(r'(const)?\s*char\s*\*?$') : '__str__',
 460         re.compile(r'(const)?.*::basic_string<.*>\s*(\*|\&)?$') : '__str__',
 461     }
 462         
 463     
 464     def ExportOperators(self):
 465         'Export all member operators and free operators related to this class'
 466         
 467         def GetFreeOperators():
 468             'Get all the free (global) operators related to this class'
 469             operators = []
 470             for decl in self.declarations:
 471                 if isinstance(decl, Operator):
 472                     # check if one of the params is this class
 473                     for param in decl.parameters:
 474                         if param.name == self.class_.FullName():
 475                             operators.append(decl)
 476                             break
 477             return operators
 478 
 479         def GetOperand(param):
 480             'Returns the operand of this parameter (either "self", or "other<type>")'
 481             if param.name == self.class_.FullName():
 482                 return namespaces.python + 'self'
 483             else:
 484                 return namespaces.python + ('other< %s >()' % param.name)
 485 
 486 
 487         def HandleSpecialOperator(operator):
 488             # gatter information about the operator and its parameters
 489             result_name = operator.result.name                        
 490             param1_name = ''
 491             if operator.parameters:
 492                 param1_name = operator.parameters[0].name
 493                 
 494             # check for str
 495             ostream = 'basic_ostream'
 496             is_str = result_name.find(ostream) != -1 and param1_name.find(ostream) != -1
 497             if is_str:
 498                 namespace = namespaces.python + 'self_ns::'
 499                 self_ = namespaces.python + 'self'
 500                 return '.def(%sstr(%s))' % (namespace, self_)
 501 
 502             # is not a special operator
 503             return None
 504                 
 505 
 506         
 507         frees = GetFreeOperators()
 508         members = [x for x in self.public_members if type(x) == ClassOperator]
 509         all_operators = frees + members
 510         operators = [x for x in all_operators if not self.info['operator'][x.name].exclude]
 511         
 512         for operator in operators:
 513             # gatter information about the operator, for use later
 514             wrapper = self.info['operator'][operator.name].wrapper
 515             if wrapper:
 516                 pointer = '&' + wrapper.FullName()
 517                 if wrapper.code:
 518                     self.Add('declaration-outside', wrapper.code)
 519             else:
 520                 pointer = operator.PointerDeclaration()                 
 521             rename = self.info['operator'][operator.name].rename
 522 
 523             # check if this operator will be exported as a method
 524             export_as_method = wrapper or rename or operator.name in self.BOOST_RENAME_OPERATORS
 525             
 526             # check if this operator has a special representation in boost
 527             special_code = HandleSpecialOperator(operator)
 528             has_special_representation = special_code is not None
 529             
 530             if export_as_method:
 531                 # export this operator as a normal method, renaming or using the given wrapper
 532                 if not rename:
 533                     if wrapper:
 534                         rename = wrapper.name
 535                     else:
 536                         rename = self.BOOST_RENAME_OPERATORS[operator.name]
 537                 policy = ''
 538                 policy_obj = self.info['operator'][operator.name].policy
 539                 if policy_obj:
 540                     policy = ', %s()' % policy_obj.Code() 
 541                 self.Add('inside', '.def("%s", %s%s)' % (rename, pointer, policy))
 542             
 543             elif has_special_representation:
 544                 self.Add('inside', special_code)
 545                 
 546             elif operator.name in self.BOOST_SUPPORTED_OPERATORS:
 547                 # export this operator using boost's facilities
 548                 op = operator
 549                 is_unary = isinstance(op, Operator) and len(op.parameters) == 1 or\
 550                            isinstance(op, ClassOperator) and len(op.parameters) == 0
 551                 if is_unary:
 552                     self.Add('inside', '.def( %s%sself )' % \
 553                         (operator.name, namespaces.python))
 554                 else:
 555                     # binary operator
 556                     if len(operator.parameters) == 2:
 557                         left_operand = GetOperand(operator.parameters[0])
 558                         right_operand = GetOperand(operator.parameters[1])
 559                     else:
 560                         left_operand = namespaces.python + 'self'
 561                         right_operand = GetOperand(operator.parameters[0])
 562                     self.Add('inside', '.def( %s %s %s )' % \
 563                         (left_operand, operator.name, right_operand))
 564 
 565         # export the converters.
 566         # export them as simple functions with a pre-determined name
 567 
 568         converters = [x for x in self.public_members if type(x) == ConverterOperator]
 569                 
 570         def ConverterMethodName(converter):
 571             result_fullname = converter.result.FullName()
 572             result_name = converter.result.name
 573             for regex, method_name in self.SPECIAL_CONVERTERS.items():
 574                 if regex.match(result_fullname):
 575                     return method_name
 576             else:
 577                 # extract the last name from the full name
 578                 result_name = makeid(result_name)
 579                 return 'to_' + result_name
 580             
 581         for converter in converters:
 582             info = self.info['operator'][converter.result.FullName()]
 583             # check if this operator should be excluded
 584             if info.exclude:
 585                 continue
 586             
 587             special_code = HandleSpecialOperator(converter)
 588             if info.rename or not special_code:
 589                 # export as method
 590                 name = info.rename or ConverterMethodName(converter)
 591                 pointer = converter.PointerDeclaration()
 592                 policy_code = ''
 593                 if info.policy:
 594                     policy_code = ', %s()' % info.policy.Code()
 595                 self.Add('inside', '.def("%s", %s%s)' % (name, pointer, policy_code))
 596                     
 597             elif special_code:
 598                 self.Add('inside', special_code)
 599 
 600 
 601 
 602     def ExportNestedClasses(self, exported_names):
 603         nested_classes = [x for x in self.public_members if isinstance(x, NestedClass)]
 604         for nested_class in nested_classes:
 605             nested_info = self.info[nested_class.name]
 606             nested_info.include = self.info.include
 607             nested_info.name = nested_class.FullName()
 608             exporter = self.__class__(nested_info)
 609             exporter.SetDeclarations(self.declarations)
 610             codeunit = SingleCodeUnit(None, None)
 611             exporter.Export(codeunit, exported_names)
 612             self.nested_codeunits.append(codeunit)
 613 
 614 
 615     def ExportNestedEnums(self, exported_names):
 616         nested_enums = [x for x in self.public_members if isinstance(x, ClassEnumeration)]
 617         for enum in nested_enums:
 618             enum_info = self.info[enum.name]
 619             enum_info.include = self.info.include
 620             enum_info.name = enum.FullName()
 621             exporter = EnumExporter(enum_info)
 622             exporter.SetDeclarations(self.declarations)
 623             codeunit = SingleCodeUnit(None, None)
 624             exporter.Export(codeunit, exported_names)
 625             self.nested_codeunits.append(codeunit)
 626 
 627 
 628     def ExportSmartPointer(self):
 629         smart_ptr = self.info.smart_ptr
 630         if smart_ptr:
 631             class_name = self.class_.FullName()
 632             smart_ptr = smart_ptr % class_name
 633             self.Add('scope', '%sregister_ptr_to_python< %s >();' % (namespaces.python, smart_ptr))
 634             
 635 
 636     def ExportOpaquePointerPolicies(self):
 637         # check all methods for 'return_opaque_pointer' policies
 638         methods = [x for x in self.public_members if isinstance(x, Method)]
 639         for method in methods:
 640             return_opaque_policy = return_value_policy(return_opaque_pointer)
 641             if self.info[method.name].policy == return_opaque_policy:
 642                 macro = exporterutils.EspecializeTypeID(method.result.name) 
 643                 if macro:
 644                     self.Add('declaration-outside', macro)
 645 
 646     def ExportAddedCode(self):
 647         if self.info.__code__:
 648             for code in self.info.__code__:
 649                 self.Add('inside', code)
 650  
 651 
 652 #==============================================================================
 653 # Virtual Wrapper utils
 654 #==============================================================================
 655 
 656 def _ParamsInfo(m, count=None):
 657     if count is None:
 658         count = len(m.parameters)
 659     param_names = ['p%i' % i for i in range(count)]
 660     param_types = [x.FullName() for x in m.parameters[:count]]
 661     params = ['%s %s' % (t, n) for t, n in zip(param_types, param_names)]
 662     #for i, p in enumerate(m.parameters[:count]):
 663     #    if p.default is not None:
 664     #        #params[i] += '=%s' % p.default
 665     #        params[i] += '=%s' % (p.name + '()')
 666     params = ', '.join(params) 
 667     return params, param_names, param_types
 668 
 669             
 670 class _VirtualWrapperGenerator(object):
 671     'Generates code to export the virtual methods of the given class'
 672 
 673     def __init__(self, class_, bases, info, codeunit):
 674         self.class_ = copy.deepcopy(class_)
 675         self.bases = bases[:]
 676         self.info = info
 677         self.wrapper_name = makeid(class_.FullName()) + '_Wrapper'
 678         self.virtual_methods = None
 679         self._method_count = {}
 680         self.codeunit = codeunit
 681         self.GenerateVirtualMethods()
 682 
 683 
 684     SELF = 'py_self'
 685 
 686     
 687     def DefaultImplementationNames(self, method):
 688         '''Returns a list of default implementations for this method, one for each
 689         number of default arguments. Always returns at least one name, and return from 
 690         the one with most arguments to the one with the least.
 691         '''
 692         base_name = 'default_' + method.name 
 693         minArgs = method.minArgs
 694         maxArgs = method.maxArgs
 695         if minArgs == maxArgs:
 696             return [base_name]
 697         else:
 698             return [base_name + ('_%i' % i) for i in range(minArgs, maxArgs+1)]                
 699 
 700 
 701     def Declaration(self, method, indent):
 702         '''Returns a string with the declarations of the virtual wrapper and
 703         its default implementations. This string must be put inside the Wrapper
 704         body.        
 705         '''
 706         pyste = namespaces.pyste
 707         python = namespaces.python
 708         rename = self.info[method.name].rename or method.name
 709         result = method.result.FullName()
 710         return_str = 'return '
 711         if result == 'void':
 712             return_str = ''
 713         params, param_names, param_types = _ParamsInfo(method)
 714         constantness = ''
 715         if method.const:
 716             constantness = ' const'
 717         
 718         # call_method callback
 719         decl  = indent + '%s %s(%s)%s%s {\n' % (result, method.name, params, constantness, method.Exceptions())
 720         param_names_str = ', '.join(param_names)
 721         if param_names_str:
 722             param_names_str = ', ' + param_names_str
 723         
 724         self_str = self.SELF
 725         
 726         decl += indent*2 + '%(return_str)s%(python)scall_method< %(result)s >'  \
 727                            '(%(self_str)s, "%(rename)s"%(param_names_str)s);\n' % locals()
 728         decl += indent + '}\n'
 729 
 730         # default implementations (with overloading)
 731         def DefaultImpl(method, param_names):
 732             'Return the body of a default implementation wrapper'
 733             indent2 = indent * 2
 734             wrapper = self.info[method.name].wrapper
 735             if not wrapper:
 736                 # return the default implementation of the class
 737                 return indent2 + '%s%s(%s);\n' % \
 738                     (return_str, method.FullName(), ', '.join(param_names)) 
 739             else:
 740                 if wrapper.code:
 741                     self.codeunit.Write('declaration-outside', wrapper.code) 
 742                 # return a call for the wrapper
 743                 params = ', '.join(['this'] + param_names)
 744                 return indent2 + '%s%s(%s);\n' % (return_str, wrapper.FullName(), params)
 745                 
 746         if not method.abstract and method.visibility != Scope.private:
 747             minArgs = method.minArgs
 748             maxArgs = method.maxArgs
 749             impl_names = self.DefaultImplementationNames(method)
 750             for impl_name, argNum in zip(impl_names, range(minArgs, maxArgs+1)): 
 751                 params, param_names, param_types = _ParamsInfo(method, argNum)            
 752                 decl += '\n'
 753                 decl += indent + '%s %s(%s)%s {\n' % (result, impl_name, params, constantness)
 754                 decl += DefaultImpl(method, param_names)
 755                 decl += indent + '}\n'                
 756         return decl
 757             
 758 
 759     def MethodDefinition(self, method):
 760         '''Returns a list of lines, which should be put inside the class_
 761         statement to export this method.'''
 762         # dont define abstract methods
 763         pyste = namespaces.pyste
 764         rename = self.info[method.name].rename or method.name
 765         default_names = self.DefaultImplementationNames(method)
 766         class_name = self.class_.FullName()
 767         wrapper_name = pyste + self.wrapper_name
 768         result = method.result.FullName()
 769         is_method_unique = method.is_unique
 770         constantness = ''
 771         if method.const:
 772             constantness = ' const'
 773         
 774         # create a list of default-impl pointers
 775         minArgs = method.minArgs
 776         maxArgs = method.maxArgs 
 777         if method.abstract:
 778             default_pointers = []
 779         elif is_method_unique:
 780             default_pointers = ['&%s::%s' % (wrapper_name, x) for x in default_names]
 781         else:
 782             default_pointers = []
 783             for impl_name, argNum in zip(default_names, range(minArgs, maxArgs+1)):
 784                 param_list = [x.FullName() for x in method.parameters[:argNum]]
 785                 params = ', '.join(param_list)             
 786                 signature = '%s (%s::*)(%s)%s' % (result, wrapper_name, params, constantness)
 787                 default_pointer = '(%s)&%s::%s' % (signature, wrapper_name, impl_name)
 788                 default_pointers.append(default_pointer)
 789             
 790         # get the pointer of the method
 791         pointer = method.PointerDeclaration()
 792         if method.abstract:
 793             pointer = namespaces.python + ('pure_virtual(%s)' % pointer)
 794 
 795         # warn the user if this method needs a policy and doesn't have one
 796         method_info = self.info[method.name]
 797         method_info.policy = exporterutils.HandlePolicy(method, method_info.policy)
 798 
 799         # Add policy to overloaded methods also
 800         policy = method_info.policy or '' 
 801         if policy:
 802             policy = ', %s%s()' % (namespaces.python, policy.Code())
 803 
 804         # generate the defs
 805         definitions = []
 806         # basic def
 807         if default_pointers:
 808             definitions.append('.def("%s", %s, %s%s)' % (rename, pointer, default_pointers[-1], policy))
 809             for default_pointer in default_pointers[:-1]:
 810                 definitions.append('.def("%s", %s%s)' % (rename, default_pointer, policy))
 811         else:
 812             definitions.append('.def("%s", %s%s)' % (rename, pointer, policy))
 813         return definitions
 814 
 815     
 816     def FullName(self):
 817         return namespaces.pyste + self.wrapper_name
 818 
 819 
 820     def GenerateVirtualMethods(self):
 821         '''To correctly export all virtual methods, we must also make wrappers
 822         for the virtual methods of the bases of this class, as if the methods
 823         were from this class itself.
 824         This method creates the instance variable self.virtual_methods.
 825         '''        
 826         def IsVirtual(m):
 827             if type(m) is Method:
 828                 pure_virtual = m.abstract and m.virtual
 829                 virtual = m.virtual and m.visibility != Scope.private
 830                 return virtual or pure_virtual
 831             else:
 832                 return False
 833                 
 834         # extract the virtual methods, avoiding duplications. The duplication
 835         # must take in account the full signature without the class name, so 
 836         # that inherited members are correctly excluded if the subclass overrides
 837         # them.
 838         def MethodSig(method):
 839             if method.const:
 840                 const = ' const'
 841             else:
 842                 const = ''
 843             if method.result:
 844                 result = method.result.FullName()
 845             else:
 846                 result = ''
 847             params = ', '.join([x.FullName() for x in method.parameters]) 
 848             return '%s %s(%s)%s%s' % (
 849                 result, method.name, params, const, method.Exceptions())  
 850 
 851         already_added = {}
 852         self.virtual_methods = []
 853         for member in self.class_:
 854             if IsVirtual(member):
 855                 already_added[MethodSig(member)] = None
 856                 self.virtual_methods.append(member)
 857 
 858         for base in self.bases:
 859             base_methods = [copy.deepcopy(x) for x in base if IsVirtual(x)]
 860             for base_method in base_methods:
 861                 self.class_.AddMember(base_method)
 862                 
 863         all_methods = [x for x in self.class_ if IsVirtual(x)]
 864         
 865         for member in all_methods:
 866             sig = MethodSig(member)
 867             if IsVirtual(member) and not sig in already_added:
 868                 self.virtual_methods.append(member)
 869                 already_added[sig] = 0
 870             
 871     
 872     def Constructors(self):
 873         return self.class_.Constructors(publics_only=True)
 874     
 875         
 876     def GenerateDefinitions(self):
 877         defs = []
 878         for method in self.virtual_methods:
 879             exclude = self.info[method.name].exclude
 880             # generate definitions only for public methods and non-abstract methods
 881             if method.visibility == Scope.public and not exclude:
 882                 defs.extend(self.MethodDefinition(method))
 883         return defs
 884 
 885         
 886     def GenerateVirtualWrapper(self, indent):
 887         'Return the wrapper for this class'
 888         
 889         # generate the class code
 890         class_name = self.class_.FullName()
 891         code = 'struct %s: %s\n' % (self.wrapper_name, class_name)
 892         code += '{\n'
 893         # generate constructors (with the overloads for each one)
 894         for cons in self.Constructors(): # only public constructors
 895             minArgs = cons.minArgs
 896             maxArgs = cons.maxArgs
 897             # from the min number of arguments to the max number, generate
 898             # all version of the given constructor
 899             cons_code = ''
 900             for argNum in range(minArgs, maxArgs+1):
 901                 params, param_names, param_types = _ParamsInfo(cons, argNum)
 902                 if params:
 903                     params = ', ' + params
 904                 cons_code += indent + '%s(PyObject* %s_%s):\n' % \
 905                     (self.wrapper_name, self.SELF, params)
 906                 cons_code += indent*2 + '%s(%s), %s(%s_) {}\n\n' % \
 907                     (class_name, ', '.join(param_names), self.SELF, self.SELF)
 908             code += cons_code
 909         # generate the body
 910         body = []
 911         for method in self.virtual_methods:
 912             if not self.info[method.name].exclude:
 913                 body.append(self.Declaration(method, indent))            
 914         body = '\n'.join(body) 
 915         code += body + '\n'
 916         # add the self member
 917         code += indent + 'PyObject* %s;\n' % self.SELF
 918         code += '};\n'
 919         return code 

Attached Files

To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.

You are not allowed to attach a file to this page.