1# Copyright 2004-2008 Roman Yakovenko.
2# Distributed under the Boost Software License, Version 1.0. (See
3# accompanying file LICENSE_1_0.txt or copy at
4# http://www.boost.org/LICENSE_1_0.txt)
5
6import os
7import types
8from . import scoped
9from . import calldef
10from . import compound
11from . import algorithm
12from . import code_creator
13from . import smart_pointers
14from . import declaration_based
15from . import registration_based
16from pygccxml import declarations
17
18class class_declaration_t( scoped.scoped_t
19                           , registration_based.registration_based_t ):
20    def __init__(self, class_inst ):
21        scoped.scoped_t.__init__( self, declaration=class_inst )
22        registration_based.registration_based_t.__init__( self )
23        self.works_on_instance = False
24
25    def _generate_class_definition(self):
26        class_identifier = algorithm.create_identifier( self, '::boost::python::class_' )
27        return declarations.templates.join( class_identifier, [self.decl_identifier] )
28
29    def _generate_code_no_scope(self):
30        result = []
31        result.append( self._generate_class_definition() + '("%s")' % self.declaration.alias )
32        for x in self.creators:
33            code = x.create()
34            tmpl = '%s.%s'
35            if self.is_comment( code ):
36                tmpl = '%s%s'
37            if code:
38                result.append( self.indent( tmpl % ( os.linesep, code ) ) )
39        result.append( ';' )
40        return ''.join( result )
41
42    @property
43    def class_var_name(self):
44        return self.declaration.class_var_name
45
46    def is_exposed_using_scope(self):
47        if self.declaration.always_expose_using_scope:
48            return True
49        return bool( [cc for cc in self.creators if not cc.works_on_instance] )
50
51    @property
52    def typedef_name( self ):
53        return self.class_var_name + '_t'
54
55    def _generate_code_with_scope(self):
56        result = []
57        scope_var_name = self.alias + '_scope'
58        result.append( 'typedef ' + self._generate_class_definition() + ' ' + self.typedef_name + ';')
59        result.append( self.typedef_name + ' ' + self.class_var_name )
60        result[-1] = result[-1] + ' = '+ self.typedef_name + '("%s");' % self.declaration.alias
61
62        result.append( algorithm.create_identifier( self, '::boost::python::scope' ) )
63        result[-1] = result[-1] + ' ' + scope_var_name
64        result[-1] = result[-1] + '( %s );' % self.class_var_name
65
66        for x in self.creators:
67            if not x.works_on_instance:
68                result.append( x.create() )
69            else:
70                result.append( '%s.%s;' % ( self.class_var_name, x.create() ) )
71
72        code = os.linesep.join( result )
73
74        result = [ '{ //scope begin' ]
75        result.append( self.indent( code ) )
76        result.append( '} //scope end' )
77
78        return os.linesep.join( result )
79
80    def _create_impl(self):
81        if self.declaration.already_exposed:
82            return ''
83        if self.is_exposed_using_scope():
84            return self._generate_code_with_scope()
85        else:
86            return self._generate_code_no_scope()
87
88    def _get_system_files_impl( self ):
89        return []
90
91class class_t( scoped.scoped_t, registration_based.registration_based_t ):
92    """
93    Creates boost.python code that needed to export a class
94    """
95    def __init__(self, class_inst, wrapper=None ):
96        scoped.scoped_t.__init__( self, declaration=class_inst )
97        registration_based.registration_based_t.__init__( self )
98        self._wrapper = wrapper
99        self.works_on_instance = False
100
101    def _get_wrapper( self ):
102        return self._wrapper
103    def _set_wrapper( self, new_wrapper ):
104        self._wrapper = new_wrapper
105    wrapper = property( _get_wrapper, _set_wrapper )
106
107    def _get_held_type(self):
108        return self.declaration.held_type
109    def _set_held_type(self, held_type):
110        assert isinstance( held_type, type(None) ) \
111               or isinstance( held_type, smart_pointers.held_type_t ) \
112               or isinstance( held_type, str )
113        if isinstance( held_type, str ):
114            assert held_type # should be non emptry string
115        self.declaration.held_type = held_type
116    held_type = property( _get_held_type, _set_held_type )
117
118    def _exported_base_classes(self):
119        if not self.declaration.bases:
120            return {}, {}
121        base_classes = {}
122        for hierarchy_info in self.declaration.recursive_bases:
123            if hierarchy_info.access_type == declarations.ACCESS_TYPES.PRIVATE:
124                continue
125            base_classes[ id( hierarchy_info.related_class ) ] = hierarchy_info
126        base_classes_size = len( base_classes )
127        creators = {}
128        creators_len = 0
129        for creator in algorithm.make_flatten_generator( self.top_parent.body.creators ):
130            if isinstance( creator, class_t ) and id(creator.declaration) in base_classes:
131                creators[ id(creator.declaration) ] = creator
132                if len( creators ) == base_classes_size:
133                    break #all classes has been found
134        return base_classes, creators
135
136    def _get_base_operators(self, base_classes, base_creators):
137        #May be in future I will redefine operators on wrapper class
138        #thus I will support [protected|private] [ [not|pure|] virtual] operators.
139        operator_creators = []
140        for base_creator in list(base_creators.values()):
141            hierarchy_info = base_classes[ id( base_creator.declaration )]
142            if hierarchy_info.access_type != declarations.ACCESS_TYPES.PUBLIC:
143                continue
144            base_operator_creators = [creator for creator in base_creator.creators if isinstance( creator, calldef.operator_t )
145                                                and isinstance( creator.declaration, declarations.member_operator_t )
146                                                and creator.declaration.access_type
147                                                    == declarations.ACCESS_TYPES.PUBLIC]
148            operator_creators.extend( base_operator_creators )
149        return operator_creators
150
151    def _generate_noncopyable(self):
152        noncopyable_vars = declarations.find_noncopyable_vars(self.declaration)
153        copy_constr = declarations.find_copy_constructor(self.declaration)
154
155        if self.declaration.noncopyable \
156           or copy_constr and copy_constr.is_artificial and noncopyable_vars:
157            return algorithm.create_identifier( self, '::boost::noncopyable' )
158
159    def _generate_bases(self, base_creators):
160        bases = []
161        assert isinstance( self.declaration, declarations.class_t )
162        for base_desc in self.declaration.bases:
163            assert isinstance( base_desc, declarations.hierarchy_info_t )
164            if base_desc.access != declarations.ACCESS_TYPES.PUBLIC:
165                continue
166            if id(base_desc.related_class) in base_creators:
167                bases.append( algorithm.create_identifier( self, base_desc.related_class.partial_decl_string ) )
168            elif base_desc.related_class.already_exposed:
169                bases.append( base_desc.related_class.partial_decl_string )
170        if not bases:
171            return None
172        bases_identifier = algorithm.create_identifier( self, '::boost::python::bases' )
173        return declarations.templates.join( bases_identifier, bases )
174
175    def _generated_held_type(self):
176        if isinstance( self.held_type, smart_pointers.held_type_t ):
177            return self.held_type.create( self )
178        elif isinstance( self.held_type, str):
179            return self.held_type
180        else:
181            return None
182
183    def _generate_class_definition(self, base_creators):
184        class_identifier = algorithm.create_identifier( self, '::boost::python::class_' )
185        args = []
186
187        held_type = self._generated_held_type()
188        if self.wrapper:
189            if self.declaration.exposed_class_type == self.declaration.EXPOSED_CLASS_TYPE.WRAPPER:
190                args.append( self.wrapper.full_name )
191            else:
192                if not self.target_configuration.boost_python_has_wrapper_held_type \
193                   or self.declaration.require_self_reference:
194                    args.append( self.decl_identifier )
195                if self.declaration.require_self_reference:
196                    if not held_type:
197                        args.append( self.wrapper.full_name )
198                else:
199                    args.append( self.wrapper.full_name )
200        else:
201            args.append( self.decl_identifier )
202
203        bases = self._generate_bases(base_creators)
204        if bases:
205            args.append( bases )
206
207        if held_type:
208            args.append( held_type )
209        notcopyable = self._generate_noncopyable()
210        if notcopyable:
211            args.append( notcopyable )
212        return declarations.templates.join( class_identifier, args)
213
214    def _generate_constructor(self):
215        result = []
216        result.append( '(' )
217        result.append( ' "%s"' % self.alias )
218        if self.documentation:
219            result.append( ', %s' % self.documentation )
220        used_init = None
221        inits = [x for x in self.creators if isinstance( x, calldef.constructor_t )]
222
223        trivial_constructor = declarations.find_trivial_constructor(self.declaration)
224
225        if self.declaration.no_init:
226            result.append( ", " )
227            result.append( algorithm.create_identifier( self, '::boost::python::no_init' ) )
228        else:
229            if inits:
230                used_init = inits[0]
231                result.append( ", " )
232                result.append( used_init.create_init_code() )
233        result.append( ' )' )
234        return ( ''.join( result ), used_init )
235
236    def _generate_code_no_scope(self):
237        result = []
238        base_classes, base_creators = self._exported_base_classes()
239        result.append( self._generate_class_definition(base_creators) )
240        class_constructor, used_init = self._generate_constructor()
241        result.append( class_constructor )
242        creators = self.creators
243        if self.declaration.redefine_operators:
244            creators = self.creators + self._get_base_operators(base_classes, base_creators)
245        for x in creators:
246            if not ( x is used_init ):
247                code = x.create()
248                tmpl = '%s.%s'
249                if self.is_comment( code ):
250                    tmpl = '%s%s'
251                if code:
252                    result.append( self.indent( tmpl % ( os.linesep, code ) ) )
253        result.append( ';' )
254        return ''.join( result )
255
256    @property
257    def class_var_name(self):
258        return self.declaration.class_var_name
259
260    @property
261    def typedef_name( self ):
262        return self.class_var_name + '_t'
263
264    def create_typedef_code( self ):
265        base_classes, base_creators = self._exported_base_classes()
266        return 'typedef ' + self._generate_class_definition(base_creators) + ' ' + self.typedef_name + ';'
267
268
269    def _generate_code_with_scope(self):
270        result = []
271        scope_var_name = self.alias + '_scope'
272        base_classes, base_creators = self._exported_base_classes()
273        result.append( 'typedef ' + self._generate_class_definition(base_creators) + ' ' + self.typedef_name + ';')
274        result.append( self.typedef_name + ' ' + self.class_var_name )
275        result[-1] = result[-1] + ' = '
276        class_constructor, used_init = self._generate_constructor()
277        result[-1] = result[-1] + self.typedef_name + class_constructor
278        result[-1] = result[-1] + ';'
279
280        result.append( algorithm.create_identifier( self, '::boost::python::scope' ) )
281        result[-1] = result[-1] + ' ' + scope_var_name
282        result[-1] = result[-1] + '( %s );' % self.class_var_name
283
284        creators = self.creators
285        if self.declaration.redefine_operators:
286            creators = self.creators + self._get_base_operators(base_classes, base_creators)
287
288        for x in creators:
289            if x is used_init:
290                continue
291            if isinstance( x, ( calldef.calldef_t, calldef.calldef_overloads_t ) ):
292                x.works_on_instance = False
293                code = x.create()
294                if code:
295                    result.append( code )
296                continue
297            if not x.works_on_instance:
298                code = x.create()
299                if code:
300                    result.append( code )
301            else:
302                result.append( '%s.%s;' % ( self.class_var_name, x.create() ) )
303
304        code = os.linesep.join( result )
305
306        result = [ '{ //%s' % declarations.full_name( self.declaration, with_defaults=False ) ]
307        result.append( self.indent( code ) )
308        result.append( '}' )
309
310        return os.linesep.join( result )
311
312    def is_exposed_using_scope(self):
313        if self.declaration.always_expose_using_scope:
314            return True
315        return bool( [cc for cc in self.creators if not cc.works_on_instance] )
316
317    def _create_impl(self):
318        if self.declaration.already_exposed:
319            return ''
320        if self.is_exposed_using_scope():
321            return self._generate_code_with_scope()
322        else:
323            return self._generate_code_no_scope()
324
325    def _get_system_files_impl( self ):
326        return []
327
328class class_wrapper_t( scoped.scoped_t ):
329    """
330    creates C++ code, which creates wrapper around a class
331    """
332
333    def __init__(self, declaration, class_creator ):
334        scoped.scoped_t.__init__( self, declaration=declaration )
335        self._class_creator = class_creator
336        self._base_wrappers = []
337
338    def _get_wrapper_alias( self ):
339        return self.declaration.wrapper_alias
340    def _set_wrapper_alias( self, walias ):
341        self.declaration.wrapper_alias = walias
342    wrapper_alias = property( _get_wrapper_alias, _set_wrapper_alias )
343
344    @property
345    def base_wrappers( self ):
346        if self.declaration.is_abstract and not self._base_wrappers:
347            bases = [ hi.related_class for hi in self.declaration.bases ]
348            creators_before_me = algorithm.creators_affect_on_me( self )
349            self._base_wrappers \
350                = [creator for creator in creators_before_me if isinstance( creator, class_wrapper_t )
351                                          and creator.declaration in bases]
352        return self._base_wrappers
353
354    @property
355    def exposed_identifier(self):
356        return algorithm.create_identifier( self, self.declaration.partial_decl_string )
357
358    @property
359    def class_creator(self):
360        return self._class_creator
361
362    @property
363    def full_name( self ):
364        if not isinstance( self.parent, class_wrapper_t ):
365            return self.declaration.wrapper_alias
366        else:
367            full_name = [self.wrapper_alias]
368            #may be we deal with enum
369            parent = self.parent
370            while isinstance( parent, class_wrapper_t ):
371                full_name.append( parent.wrapper_alias )
372                parent = parent.parent
373            full_name.reverse()
374            return '::'.join( full_name )
375
376    @property
377    def held_type(self):
378        return self._class_creator.held_type
379
380    @property
381    def boost_wrapper_identifier(self):
382        boost_wrapper = algorithm.create_identifier( self, '::boost::python::wrapper' )
383        return declarations.templates.join( boost_wrapper, [self.exposed_identifier] )
384
385    def _create_bases(self):
386        return ', '.join( [self.exposed_identifier, self.boost_wrapper_identifier] )
387
388    def _create_impl(self):
389        if self.declaration.already_exposed:
390            return ''
391        answer = ['struct %s : %s {' % ( self.wrapper_alias, self._create_bases() )]
392        answer.append( '' )
393        answer.append( self.create_internal_code( self.creators )  )
394        answer.append( '' )
395        answer.append( '};' )
396        return os.linesep.join( answer )
397
398    def _get_system_files_impl( self ):
399        return []
400
401
402ctypes_base_classes = {
403    declarations.CLASS_TYPES.CLASS : 'Structure'
404    , declarations.CLASS_TYPES.UNION : 'Union'
405    , declarations.CLASS_TYPES.STRUCT : 'Structure'
406}
407
408class class_introduction_t(compound.compound_t, declaration_based.declaration_based_t):
409    def __init__( self, class_ ):
410        compound.compound_t.__init__(self)
411        declaration_based.declaration_based_t.__init__( self, class_ )
412
413    @property
414    def ctypes_base_class( self ):
415        global ctypes_base_classes
416        return ctypes_base_classes[ self.declaration.class_type ]
417
418    def _create_impl(self):
419        result = []
420        result.append( "class %(alias)s(ctypes.%(base)s):"
421                       % dict( alias=self.alias, base=self.ctypes_base_class ) )
422        result.append( self.indent( '"""class %s"""' % self.decl_identifier ) )
423        if self.creators:
424            result.append( self.indent( '' ) )
425        result.append( compound.compound_t.create_internal_code( self.creators ) )
426
427        if isinstance( self.declaration.parent, declarations.namespace_t ) \
428           and self.declaration.parent is not self.declaration.top_parent: #not a global namespace
429            result.append("")
430            result.append( '%(ns_full_name)s = %(name)s'
431                           % dict( ns_full_name=self.complete_py_name, name=self.alias ))
432        return os.linesep.join( result )
433
434    def _get_system_files_impl( self ):
435        return []
436
437
438class class_declaration_introduction_t(code_creator.code_creator_t, declaration_based.declaration_based_t):
439    def __init__( self, class_declaration ):
440        code_creator.code_creator_t.__init__(self)
441        declaration_based.declaration_based_t.__init__( self, class_declaration )
442
443    def _create_impl(self):
444        result = []
445        result.append( "class %s(ctypes.Structure):" % self.alias )
446        result.append( self.indent( '"""class declaration %s"""' % self.decl_identifier ) )
447        result.append( self.indent( '_fields_  = []' ) )
448
449        if isinstance( self.declaration.parent, declarations.namespace_t ) \
450           and self.declaration.parent is not self.declaration.top_parent: #not a global namespace
451            result.append( '%(ns_full_name)s = %(name)s'
452                           % dict( ns_full_name=self.complete_py_name, name=self.alias ))
453        return os.linesep.join( result )
454
455    def _get_system_files_impl( self ):
456        return []
457