1#!/usr/bin/env python
2""" genpyx.py - parse c declarations
3
4(c) 2002, 2003, 2004, 2005 Simon Burton <simon@arrowtheory.com>
5Released under GNU LGPL license.
6
7version 0.xx
8
9This is a module of mixin classes for ir.py .
10
11Towards the end of ir.py our global class definitions
12are remapped to point to the class definitions in ir.py .
13So, for example, when we refer to Node we get ir.Node .
14
15"""
16
17import sys
18from datetime import datetime
19
20# XX use this Context class instead of all those kw dicts !! XX
21class Context(object):
22    " just a record (struct) "
23    def __init__( self, **kw ):
24        for key, value in kw.items():
25            setattr( self, key, value )
26    def __getattr__( self, name ):
27        return None # ?
28    def __getitem__( self, name ):
29        return getattr(self, name)
30
31class OStream(object):
32    def __init__( self, filename=None ):
33        self.filename = filename
34        self.tokens = []
35        self._indent = 0
36    def put( self, token="" ):
37        assert type(token) is str
38        self.tokens.append( token )
39    def startln( self, token="" ):
40        assert type(token) is str
41        self.tokens.append( '    '*self._indent + token )
42    def putln( self, ln="" ):
43        assert type(ln) is str
44        self.tokens.append( '    '*self._indent + ln + '\n')
45    def endln( self, token="" ):
46        assert type(token) is str
47        self.tokens.append( token + '\n')
48    def indent( self ):
49        self._indent += 1
50    def dedent( self ):
51        self._indent -= 1
52        assert self._indent >= 0, self._indent
53    def join( self ):
54        return ''.join( self.tokens )
55    def close( self ):
56        s = ''.join( self.tokens )
57        f = open( self.filename, 'w' )
58        f.write(s)
59
60#
61###############################################################################
62#
63
64class Node(object):
65    """
66        tree structure
67    """
68    _unique_id = 0
69    def get_unique_id(cls):
70        Node._unique_id += 1
71        return Node._unique_id
72    get_unique_id = classmethod(get_unique_id)
73
74# XX toks: use a tree of tokens: a list that can be push'ed and pop'ed XX
75    def pyxstr(self,toks=None,indent=0,**kw):
76        """
77            Build a list of tokens; return the joined tokens string
78        """
79        if toks is None:
80            toks = []
81        for x in self:
82            if isinstance(x,Node):
83                x.pyxstr(toks, indent, **kw)
84            else:
85                toks.insert(0,str(x)+' ')
86        s = ''.join(toks)
87        return s
88
89#
90#################################################
91
92class Named(object):
93    "has a .name property"
94    pass
95
96class BasicType(object):
97    "float double void char int"
98    pass
99
100class Qualifier(object):
101    "register signed unsigned short long const volatile inline"
102    def pyxstr(self,toks=None,indent=0,**kw):
103        if toks is None:
104            toks = []
105        x = self[0]
106        if x not in ( 'const','volatile','inline','register'): # ignore these
107            toks.insert(0,str(x)+' ')
108        s = ''.join(toks)
109        return s
110
111class StorageClass(object):
112    "extern static auto"
113    def pyxstr(self,toks=None,indent=0,**kw):
114        return ""
115
116class Ellipses(object):
117    "..."
118    pass
119
120class GCCBuiltin(BasicType):
121    "things with __builtin prefix"
122    pass
123
124class Identifier(object):
125    """
126    """
127    def pyxstr(self,toks=None,indent=0,**kw):
128        if toks is None:
129            toks=[]
130        if self.name:
131            toks.append( self.name )
132        return " ".join(toks)
133
134class TypeAlias(object):
135    """
136     typedefed things, eg. size_t
137    """
138    def pyxstr(self,toks=None,indent=0,cprefix="",**kw):
139        if toks is None:
140            toks = []
141        for x in self:
142            if isinstance(x,Node):
143                x.pyxstr(toks, indent, cprefix=cprefix, **kw)
144            else:
145                s = str(x)+' '
146                if cprefix:
147                    s = cprefix+s
148                toks.insert(0,s)
149        s = ''.join(toks)
150        return s
151
152class Function(object):
153    """
154    """
155    def pyxstr(self,toks,indent=0,**kw):
156        #print '%s.pyxstr(%s)'%(self,toks)
157        _toks=[]
158        assert len(self)
159        i=0
160        while isinstance(self[i],Declarator):
161            if not self[i].is_void():
162                _toks.append( self[i].pyxstr(indent=indent, **kw) )
163            i=i+1
164        toks.append( '(%s)'% ', '.join(_toks) )
165        while i<len(self):
166            self[i].pyxstr(toks, indent=indent, **kw)
167            i=i+1
168        return " ".join(toks)
169
170class Pointer(object):
171    """
172    """
173    def pyxstr(self,toks,indent=0,**kw):
174        assert len(self)
175        node=self[0]
176        toks.insert(0,'*')
177        if isinstance(node,Function):
178            toks.insert(0,'(')
179            toks.append(')')
180        elif isinstance(node,Array):
181            toks.insert(0,'(')
182            toks.append(')')
183        return Node.pyxstr(self,toks,indent, **kw)
184
185class Array(object):
186    """
187    """
188    def pyxstr(self,toks,indent=0,**kw):
189        if self.size is None:
190            toks.append('[]')
191        else:
192            try:
193                int(self.size)
194                toks.append('[%s]'%self.size)
195            except:
196                toks.append('[]')
197        return Node( *self[:-1] ).pyxstr( toks,indent, **kw )
198
199class Tag(object):
200    " the tag of a Struct, Union or Enum "
201    pass
202
203class Taged(object):
204    "Struct, Union or Enum "
205    pass
206
207class Compound(Taged):
208    "Struct or Union"
209    def pyxstr(self,_toks=None,indent=0,cprefix="",shadow_name=True,**kw):
210        if _toks is None:
211            _toks=[]
212        names = kw.get('names',{})
213        kw['names'] = names
214        tag_lookup = kw.get('tag_lookup')
215        if self.tag:
216            tag=self.tag.name
217        else:
218            tag = ''
219        if isinstance(self,Struct):
220            descr = 'struct'
221        elif isinstance(self,Union):
222            descr = 'union'
223        _node = names.get(self.tag.name,None)
224        if ( _node is not None and _node.has_members() ) or \
225                ( _node is not None and not self.has_members() ):
226            descr = '' # i am not defining myself here
227        #print "Compound.pyxstr", tag
228        #print self.deepstr()
229        if descr:
230            if cprefix and shadow_name:
231                tag = '%s%s "%s"'%(cprefix,tag,tag)
232            elif cprefix:
233                tag = cprefix+tag
234            toks = [ descr+' '+tag ] # struct foo
235            if self.has_members():
236                toks.append(':\n')
237                for decl in self[1:]: # XX self.members
238                    toks.append( decl.pyxstr(indent=indent+1, cprefix=cprefix, shadow_name=shadow_name, **kw)+"\n" ) # shadow_name = False ?
239            #elif not tag_lookup.get( self.tag.name, self ).has_members():
240                # define empty struct here, it's the best we're gonna get
241                #pass
242        else:
243            if cprefix: # and shadow_name:
244                tag = cprefix+tag
245            toks = [ ' '+tag+' ' ] # foo
246        while toks:
247            _toks.insert( 0, toks.pop() )
248        return "".join( _toks )
249
250class Struct(Compound):
251    """
252    """
253    pass
254
255class Union(Compound):
256    """
257    """
258    pass
259
260
261class Enum(Taged):
262    """
263    """
264    def pyxstr(self,_toks=None,indent=0,cprefix="",shadow_name=True,**kw):
265        if _toks is None:
266            _toks=[]
267        names = kw.get('names',{})
268        kw['names'] = names
269        if self.tag:
270            tag=self.tag.name
271        else:
272            tag = ''
273        _node = names.get(self.tag.name,None)
274        if ( _node is not None and _node.has_members() ) or \
275                ( _node is not None and not self.has_members() ):
276            descr = '' # i am not defining myself here
277        else:
278            descr = 'enum'
279        if descr:
280        #if not names.has_key(self.tag.name):
281            toks = [ descr+' '+tag ] # enum foo
282            toks.append(':\n')
283            idents = [ ident for ident in self.members if ident.name not in names ]
284            for ident in idents:
285                if cprefix and shadow_name:
286                    ident = ident.clone()
287                    ident.name = '%s%s "%s"' % ( cprefix, ident.name, ident.name )
288                #else: assert 0
289                toks.append( '    '+'    '*indent + ident.pyxstr(**kw)+"\n" )
290                names[ ident.name ] = ident
291            if not idents:
292                # empty enum def'n !
293                #assert 0 # should be handled by parents...
294                toks.append( '    '+'    '*indent + "pass\n" )
295        else:
296            toks = [ ' '+tag+' ' ] # foo
297        while toks:
298            _toks.insert( 0, toks.pop() )
299        return "".join( _toks )
300
301class Declarator(object):
302    def is_pyxnative( self ):
303        # pyrex handles char* too
304        # but i don't know if we should make this the default
305        # sometimes we want to send a NULL, so ... XX
306        self = self.cbasetype() # WARNING: cbasetype may be cached
307        if self.is_void():
308            return False
309        if self.is_primative():
310            return True
311        if self.enum:
312            return True
313        #pointer = None
314        #if self.pointer:
315            #pointer = self.pointer
316        #elif self.array:
317            #pointer = self.array
318        #if pointer and pointer.spec:
319            #spec = pointer.spec
320            #if BasicType("char") in spec and not Qualifier("unsigned") in spec:
321                # char*, const char*
322                ##print self.deepstr()
323                #return True
324        return False
325
326    def _pyxstr( self, toks, indent, cprefix, use_cdef, shadow_name, **kw ):
327        " this is the common part of pyxstr that gets called from both Declarator and Typedef "
328        names = kw.get('names',{}) # what names have been defined ?
329        kw['names']=names
330        for node in self.nodes(): # depth-first
331            if isinstance(node,Taged):
332                #print "Declarator.pyxstr", node.cstr()
333                if not node.tag.name:
334                    node.tag.name = "_anon_%s" % Node.get_unique_id()
335                _node = names.get(node.tag.name,None)
336                #tag_lookup = kw.get('tag_lookup')
337                #other = tag_lookup.get(node.tag.name, node)
338                #if ((_node is None and (not isinstance(other,Compound) or not other.has_members()))
339                #    or node.has_members()):
340                if _node is None or node.has_members():
341                    # either i am not defined at all, or this is my _real_ definition
342                    # emit def'n of this node
343                    #if isinstance(self,Typedef):
344                        #toks.append( '    '*indent + 'ctypedef ' + node.pyxstr(indent=indent, cprefix=cprefix, shadow_name=shadow_name, **kw).strip() )
345                    #else:
346                    toks.append( '    '*indent + 'cdef ' + node.pyxstr(indent=indent, cprefix=cprefix, shadow_name=shadow_name, **kw).strip() )
347                    names[ node.tag.name ] = node
348            elif isinstance(node,GCCBuiltin) and node[0] not in names:
349                #toks.append( '    '*indent + 'ctypedef long ' + node.pyxstr(indent=indent, **kw).strip() + ' # XX ??'    ) # XX ??
350                toks.append( '    '*indent + 'struct __unknown_builtin ' )
351                toks.append( '    '*indent + 'ctypedef __unknown_builtin ' + node.pyxstr(indent=indent, **kw).strip() )
352                names[ node[0] ] = node
353            for idx, child in enumerate(node):
354                if type(child)==Array and not child.has_size():
355                    # mutate this mystery array into a pointer XX method: Array.to_pointer()
356                    node[idx] = Pointer()
357                    node[idx].init_from( child ) # warning: shallow init
358                    node[idx].pop() # pop the size element
359
360    def pyxstr(self,toks=None,indent=0,cprefix="",use_cdef=True,shadow_name=True,**kw):
361        " note: i do not check if my name is already in 'names' "
362        self = self.clone() # <----- NOTE
363        toks=[]
364        names = kw.get('names',{}) # what names have been defined ?
365        kw['names']=names
366
367        self._pyxstr( toks, indent, cprefix, use_cdef, shadow_name, **kw )
368
369        if self.name and not names.has_key( self.name ):
370            names[ self.name ] = self
371        if self.identifier is not None:
372            comment = ""
373            if self.name in python_kws:
374                comment = "#"
375            if cprefix and use_cdef and shadow_name:
376                # When we are defining this guy, we refer to it using the pyrex shadow syntax.
377                self.name = '%s%s "%s" ' % ( cprefix, self.name, self.name )
378            cdef = 'cdef '
379            if not use_cdef: cdef = '' # sometimes we don't want the cdef (eg. in a cast)
380            # this may need shadow_name=False:
381            toks.append( '    '*indent + comment + cdef + Node.pyxstr(self,indent=indent, cprefix=cprefix, **kw).strip() ) # + "(cprefix=%s)"%cprefix)
382        #else: i am just a struct def (so i already did that) # huh ?? XX bad comment
383        return ' \n'.join(toks)
384
385    def pyxsym(self, ostream, names=None, tag_lookup=None, cprefix="", modname=None, cobjects=None):
386        assert self.name is not None, self.deepstr()
387        ostream.putln( '# ' + self.cstr() )
388# This cdef is no good: it does not expose a python object
389# and we can't reliably set a global var
390        #ostream.putln( 'cdef %s %s' % ( self.pyx_adaptor_decl(cobjects), self.name ) ) # _CObject
391        #ostream.putln( '%s = %s()' % (self.name, self.pyx_adaptor_name(cobjects)) )
392        #ostream.putln( '%s.p = <void*>&%s' % (self.name, cprefix+self.name) )
393        ## expose a python object:
394        #ostream.putln( '%s.%s = %s' % (modname,self.name, self.name) )
395        ostream.putln( '%s = %s( addr = <long>&%s )' % (self.name, self.pyx_adaptor_name(cobjects), cprefix+self.name) )
396        return ostream
397
398
399class Typedef(Declarator):
400    def pyxstr(self,toks=None,indent=0,cprefix="",use_cdef=True,shadow_name=True,**kw): # shadow_name=True
401        " warning: i do not check if my name is already in 'names' "
402        assert shadow_name == True
403        self = self.clone() # <----- NOTE
404        toks=[]
405        names = kw.get('names',{}) # what names have been defined ?
406        kw['names']=names
407
408        #if self.tagged and not self.tagged.tag.name:
409            ## "typedef struct {...} foo;" => "typedef struct foo {...} foo;"
410            ## (to be emitted in the node loop below, and suppressed in the final toks.append)
411            #self.tagged.tag = Tag( self.name ) # this is how pyrex does it: tag.name == self.name
412        # XX that doesn't work (the resulting c fails to compile) XX
413
414        self._pyxstr( toks, indent, cprefix, use_cdef, shadow_name, **kw )
415
416        #print self.deepstr()
417        if self.name and not names.has_key( self.name ):
418            names[ self.name ] = self
419        if not (self.tagged and self.name == self.tagged.tag.name):
420            comment = ""
421            if self.name in python_kws:
422                comment = "#"
423                #if cprefix:
424                #  self.name = '%s%s "%s" ' % ( cprefix, self.name, self.name ) # XX pyrex can't do this
425            if cprefix: # shadow_name=True
426                # My c-name gets this prefix. See also TypeAlias.pyxstr(): it also prepends the cprefix.
427                self.name = '%s%s "%s" ' % ( cprefix, self.name, self.name )
428            toks.append( '    '*indent + comment + 'ctypedef ' + Node.pyxstr(self,indent=indent, cprefix=cprefix, **kw).strip() )
429        return ' \n'.join(toks)
430
431
432class AbstractDeclarator(Declarator):
433    """ used in Function; may lack an identifier """
434    def pyxstr(self,toks=None,indent=0,**kw):
435        if self.name in python_kws:
436            # Would be better to do this in __init__, but our subclass doesn't call our __init__.
437            self.name = '_' + self.name
438        #return '    '*indent + Node.pyxstr(self,toks,indent, **kw).strip()
439        return Node.pyxstr(self,toks,indent, **kw).strip()
440
441
442class FieldLength(object):
443    """
444    """
445    def pyxstr(self,toks,indent,**kw):
446        pass
447
448
449class StructDeclarator(Declarator): # also used in Union
450    """
451    """
452    def pyxstr(self,toks=None,indent=0,**kw):
453        comment = ""
454        if self.name in python_kws:
455            comment = "#"
456        return '    '*indent + comment + Node.pyxstr(self,toks,indent, **kw).strip()
457
458class DeclarationSpecifiers(object):
459    """
460    """
461    pass
462
463class TypeSpecifiers(DeclarationSpecifiers):
464    """
465    """
466    pass
467
468class Initializer(object):
469    """
470    """
471    pass
472
473class Declaration(object):
474    """
475    """
476    pass
477
478class ParameterDeclaration(Declaration):
479    """
480    """
481    pass
482
483class StructDeclaration(Declaration):
484    """
485    """
486    pass
487
488class TransUnit(object):
489    """
490        Top level node.
491    """
492    def pyx_decls(self, filenames, modname, macros = {}, names = {}, func_cb=None, cprefix="", **kw):
493        # PART 1: emit extern declarations
494        ostream = OStream()
495        now = datetime.today()
496        ostream.putln( now.strftime('# Code generated by pyxelator on %x at %X') + '\n' )
497        ostream.putln("# PART 1: extern declarations")
498        for filename in filenames:
499            ostream.putln( 'cdef extern from "%s":\n    pass\n' % filename )
500        ostream.putln( 'cdef extern from *:' )
501        file = None # current file
502        for node in self:
503            ostream.putln('')
504            ostream.putln('    # ' + node.cstr() )
505            assert node.marked
506            comment = False
507            if node.name and node.name in names:
508                comment = True # redeclaration
509            #ostream.putln( node.deepstr( comment=True ) )
510            s = node.pyxstr(indent=1, names=names, tag_lookup = self.tag_lookup, cprefix=cprefix, **kw)
511            if s.split():
512                if comment:
513                    s = "#"+s.replace( '\n', '\n#' ) + " # redeclaration "
514                if node.file != file:
515                    file = node.file
516                    #ostream.putln( 'cdef extern from "%s":' % file )
517                    ostream.putln( '    # "%s"' % file )
518                ostream.putln( s )
519        ostream.putln('\n')
520        #s = '\n'.join(toks)
521        return ostream.join()
522
523# XX warn when we find a python keyword XX
524python_kws = """
525break continue del def except exec finally pass print raise
526return try global assert lambda yield
527for while if elif else and in is not or import from """.split()
528python_kws = dict( zip( python_kws, (None,)*len(python_kws) ) )
529
530
531