blob: 00cfa3f5335da850b07bc2c343fdb3f7dda74c3e [file] [log] [blame]
#!/usr/bin/env python
""" genpyx.py - parse c declarations
(c) 2002, 2003, 2004, 2005 Simon Burton <simon@arrowtheory.com>
Released under GNU LGPL license.
version 0.xx
This is a module of mixin classes for ir.py .
Towards the end of ir.py our global class definitions
are remapped to point to the class definitions in ir.py .
So, for example, when we refer to Node we get ir.Node .
"""
import sys
from datetime import datetime
from sets import Set
# XX use this Context class instead of all those kw dicts !! XX
class Context(object):
" just a record (struct) "
def __init__( self, **kw ):
for key, value in kw.items():
setattr( self, key, value )
def __getattr__( self, name ):
return None # ?
def __getitem__( self, name ):
return getattr(self, name)
class OStream(object):
def __init__( self, filename=None ):
self.filename = filename
self.tokens = []
self._indent = 0
def put( self, token="" ):
assert type(token) is str
self.tokens.append( token )
def startln( self, token="" ):
assert type(token) is str
self.tokens.append( ' '*self._indent + token )
def putln( self, ln="" ):
assert type(ln) is str
self.tokens.append( ' '*self._indent + ln + '\n')
def endln( self, token="" ):
assert type(token) is str
self.tokens.append( token + '\n')
def indent( self ):
self._indent += 1
def dedent( self ):
self._indent -= 1
assert self._indent >= 0, self._indent
def join( self ):
return ''.join( self.tokens )
def close( self ):
s = ''.join( self.tokens )
f = open( self.filename, 'w' )
f.write(s)
#
###############################################################################
#
class Node(object):
"""
tree structure
"""
_unique_id = 0
def get_unique_id(cls):
Node._unique_id += 1
return Node._unique_id
get_unique_id = classmethod(get_unique_id)
# XX toks: use a tree of tokens: a list that can be push'ed and pop'ed XX
def pyxstr(self,toks=None,indent=0,**kw):
"""
Build a list of tokens; return the joined tokens string
"""
if toks is None:
toks = []
for x in self:
if isinstance(x,Node):
x.pyxstr(toks, indent, **kw)
else:
toks.insert(0,str(x)+' ')
s = ''.join(toks)
return s
#
#################################################
class Named(object):
"has a .name property"
pass
class BasicType(object):
"float double void char int"
pass
class Qualifier(object):
"register signed unsigned short long const volatile inline"
def pyxstr(self,toks=None,indent=0,**kw):
if toks is None:
toks = []
x = self[0]
if x not in ( 'const','volatile','inline','register'): # ignore these
toks.insert(0,str(x)+' ')
s = ''.join(toks)
return s
class StorageClass(object):
"extern static auto"
def pyxstr(self,toks=None,indent=0,**kw):
return ""
class Ellipses(object):
"..."
pass
class GCCBuiltin(BasicType):
"things with __builtin prefix"
pass
class Identifier(object):
"""
"""
def pyxstr(self,toks=None,indent=0,**kw):
if toks is None:
toks=[]
if self.name:
toks.append( self.name )
return " ".join(toks)
class TypeAlias(object):
"""
typedefed things, eg. size_t
"""
def pyxstr(self,toks=None,indent=0,cprefix="",**kw):
if toks is None:
toks = []
for x in self:
if isinstance(x,Node):
x.pyxstr(toks, indent, cprefix=cprefix, **kw)
else:
s = str(x)+' '
if cprefix:
s = cprefix+s
toks.insert(0,s)
s = ''.join(toks)
return s
class Function(object):
"""
"""
def pyxstr(self,toks,indent=0,**kw):
#print '%s.pyxstr(%s)'%(self,toks)
_toks=[]
assert len(self)
i=0
while isinstance(self[i],Declarator):
if not self[i].is_void():
_toks.append( self[i].pyxstr(indent=indent, **kw) )
i=i+1
toks.append( '(%s)'% ', '.join(_toks) )
while i<len(self):
self[i].pyxstr(toks, indent=indent, **kw)
i=i+1
return " ".join(toks)
class Pointer(object):
"""
"""
def pyxstr(self,toks,indent=0,**kw):
assert len(self)
node=self[0]
toks.insert(0,'*')
if isinstance(node,Function):
toks.insert(0,'(')
toks.append(')')
elif isinstance(node,Array):
toks.insert(0,'(')
toks.append(')')
return Node.pyxstr(self,toks,indent, **kw)
class Array(object):
"""
"""
def pyxstr(self,toks,indent=0,**kw):
if self.size is None:
toks.append('[]')
else:
try:
int(self.size)
toks.append('[%s]'%self.size)
except:
toks.append('[]')
return Node( *self[:-1] ).pyxstr( toks,indent, **kw )
class Tag(object):
" the tag of a Struct, Union or Enum "
pass
class Taged(object):
"Struct, Union or Enum "
pass
class Compound(Taged):
"Struct or Union"
def pyxstr(self,_toks=None,indent=0,cprefix="",shadow_name=True,**kw):
if _toks is None:
_toks=[]
names = kw.get('names',{})
kw['names'] = names
tag_lookup = kw.get('tag_lookup')
if self.tag:
tag=self.tag.name
else:
tag = ''
if isinstance(self,Struct):
descr = 'struct'
elif isinstance(self,Union):
descr = 'union'
_node = names.get(self.tag.name,None)
if ( _node is not None and _node.has_members() ) or \
( _node is not None and not self.has_members() ):
descr = '' # i am not defining myself here
#print "Compound.pyxstr", tag
#print self.deepstr()
if descr:
if cprefix and shadow_name:
tag = '%s%s "%s"'%(cprefix,tag,tag)
elif cprefix:
tag = cprefix+tag
toks = [ descr+' '+tag ] # struct foo
if self.has_members():
toks.append(':\n')
for decl in self[1:]: # XX self.members
toks.append( decl.pyxstr(indent=indent+1, cprefix=cprefix, shadow_name=shadow_name, **kw)+"\n" ) # shadow_name = False ?
#elif not tag_lookup.get( self.tag.name, self ).has_members():
# define empty struct here, it's the best we're gonna get
#pass
else:
if cprefix: # and shadow_name:
tag = cprefix+tag
toks = [ ' '+tag+' ' ] # foo
while toks:
_toks.insert( 0, toks.pop() )
return "".join( _toks )
class Struct(Compound):
"""
"""
pass
class Union(Compound):
"""
"""
pass
class Enum(Taged):
"""
"""
def pyxstr(self,_toks=None,indent=0,cprefix="",shadow_name=True,**kw):
if _toks is None:
_toks=[]
names = kw.get('names',{})
kw['names'] = names
if self.tag:
tag=self.tag.name
else:
tag = ''
_node = names.get(self.tag.name,None)
if ( _node is not None and _node.has_members() ) or \
( _node is not None and not self.has_members() ):
descr = '' # i am not defining myself here
else:
descr = 'enum'
if descr:
#if not names.has_key(self.tag.name):
toks = [ descr+' '+tag ] # enum foo
toks.append(':\n')
idents = [ ident for ident in self.members if ident.name not in names ]
for ident in idents:
if cprefix and shadow_name:
ident = ident.clone()
ident.name = '%s%s "%s"' % ( cprefix, ident.name, ident.name )
#else: assert 0
toks.append( ' '+' '*indent + ident.pyxstr(**kw)+"\n" )
names[ ident.name ] = ident
if not idents:
# empty enum def'n !
#assert 0 # should be handled by parents...
toks.append( ' '+' '*indent + "pass\n" )
else:
toks = [ ' '+tag+' ' ] # foo
while toks:
_toks.insert( 0, toks.pop() )
return "".join( _toks )
class Declarator(object):
def is_pyxnative( self ):
# pyrex handles char* too
# but i don't know if we should make this the default
# sometimes we want to send a NULL, so ... XX
self = self.cbasetype() # WARNING: cbasetype may be cached
if self.is_void():
return False
if self.is_primative():
return True
if self.enum:
return True
#pointer = None
#if self.pointer:
#pointer = self.pointer
#elif self.array:
#pointer = self.array
#if pointer and pointer.spec:
#spec = pointer.spec
#if BasicType("char") in spec and not Qualifier("unsigned") in spec:
# char*, const char*
##print self.deepstr()
#return True
return False
def _pyxstr( self, toks, indent, cprefix, use_cdef, shadow_name, **kw ):
" this is the common part of pyxstr that gets called from both Declarator and Typedef "
names = kw.get('names',{}) # what names have been defined ?
kw['names']=names
for node in self.nodes(): # depth-first
if isinstance(node,Taged):
#print "Declarator.pyxstr", node.cstr()
if not node.tag.name:
node.tag.name = "_anon_%s" % Node.get_unique_id()
_node = names.get(node.tag.name,None)
#tag_lookup = kw.get('tag_lookup')
#other = tag_lookup.get(node.tag.name, node)
#if ((_node is None and (not isinstance(other,Compound) or not other.has_members()))
# or node.has_members()):
if _node is None or node.has_members():
# either i am not defined at all, or this is my _real_ definition
# emit def'n of this node
#if isinstance(self,Typedef):
#toks.append( ' '*indent + 'ctypedef ' + node.pyxstr(indent=indent, cprefix=cprefix, shadow_name=shadow_name, **kw).strip() )
#else:
toks.append( ' '*indent + 'cdef ' + node.pyxstr(indent=indent, cprefix=cprefix, shadow_name=shadow_name, **kw).strip() )
names[ node.tag.name ] = node
elif isinstance(node,GCCBuiltin) and node[0] not in names:
#toks.append( ' '*indent + 'ctypedef long ' + node.pyxstr(indent=indent, **kw).strip() + ' # XX ??' ) # XX ??
toks.append( ' '*indent + 'struct __unknown_builtin ' )
toks.append( ' '*indent + 'ctypedef __unknown_builtin ' + node.pyxstr(indent=indent, **kw).strip() )
names[ node[0] ] = node
for idx, child in enumerate(node):
if type(child)==Array and not child.has_size():
# mutate this mystery array into a pointer XX method: Array.to_pointer()
node[idx] = Pointer()
node[idx].init_from( child ) # warning: shallow init
node[idx].pop() # pop the size element
def pyxstr(self,toks=None,indent=0,cprefix="",use_cdef=True,shadow_name=True,**kw):
" note: i do not check if my name is already in 'names' "
self = self.clone() # <----- NOTE
toks=[]
names = kw.get('names',{}) # what names have been defined ?
kw['names']=names
self._pyxstr( toks, indent, cprefix, use_cdef, shadow_name, **kw )
if self.name and not names.has_key( self.name ):
names[ self.name ] = self
if self.identifier is not None:
comment = ""
if self.name in python_kws:
comment = "#"
if cprefix and use_cdef and shadow_name:
# When we are defining this guy, we refer to it using the pyrex shadow syntax.
self.name = '%s%s "%s" ' % ( cprefix, self.name, self.name )
cdef = 'cdef '
if not use_cdef: cdef = '' # sometimes we don't want the cdef (eg. in a cast)
# this may need shadow_name=False:
toks.append( ' '*indent + comment + cdef + Node.pyxstr(self,indent=indent, cprefix=cprefix, **kw).strip() ) # + "(cprefix=%s)"%cprefix)
#else: i am just a struct def (so i already did that) # huh ?? XX bad comment
return ' \n'.join(toks)
def pyxsym(self, ostream, names=None, tag_lookup=None, cprefix="", modname=None, cobjects=None):
assert self.name is not None, self.deepstr()
ostream.putln( '# ' + self.cstr() )
# This cdef is no good: it does not expose a python object
# and we can't reliably set a global var
#ostream.putln( 'cdef %s %s' % ( self.pyx_adaptor_decl(cobjects), self.name ) ) # _CObject
#ostream.putln( '%s = %s()' % (self.name, self.pyx_adaptor_name(cobjects)) )
#ostream.putln( '%s.p = <void*>&%s' % (self.name, cprefix+self.name) )
## expose a python object:
#ostream.putln( '%s.%s = %s' % (modname,self.name, self.name) )
ostream.putln( '%s = %s( addr = <long>&%s )' % (self.name, self.pyx_adaptor_name(cobjects), cprefix+self.name) )
return ostream
class Typedef(Declarator):
def pyxstr(self,toks=None,indent=0,cprefix="",use_cdef=True,shadow_name=True,**kw): # shadow_name=True
" warning: i do not check if my name is already in 'names' "
assert shadow_name == True
self = self.clone() # <----- NOTE
toks=[]
names = kw.get('names',{}) # what names have been defined ?
kw['names']=names
#if self.tagged and not self.tagged.tag.name:
## "typedef struct {...} foo;" => "typedef struct foo {...} foo;"
## (to be emitted in the node loop below, and suppressed in the final toks.append)
#self.tagged.tag = Tag( self.name ) # this is how pyrex does it: tag.name == self.name
# XX that doesn't work (the resulting c fails to compile) XX
self._pyxstr( toks, indent, cprefix, use_cdef, shadow_name, **kw )
#print self.deepstr()
if self.name and not names.has_key( self.name ):
names[ self.name ] = self
if not (self.tagged and self.name == self.tagged.tag.name):
comment = ""
if self.name in python_kws:
comment = "#"
#if cprefix:
# self.name = '%s%s "%s" ' % ( cprefix, self.name, self.name ) # XX pyrex can't do this
if cprefix: # shadow_name=True
# My c-name gets this prefix. See also TypeAlias.pyxstr(): it also prepends the cprefix.
self.name = '%s%s "%s" ' % ( cprefix, self.name, self.name )
toks.append( ' '*indent + comment + 'ctypedef ' + Node.pyxstr(self,indent=indent, cprefix=cprefix, **kw).strip() )
return ' \n'.join(toks)
class AbstractDeclarator(Declarator):
""" used in Function; may lack an identifier """
def pyxstr(self,toks=None,indent=0,**kw):
if self.name in python_kws:
# Would be better to do this in __init__, but our subclass doesn't call our __init__.
self.name = '_' + self.name
#return ' '*indent + Node.pyxstr(self,toks,indent, **kw).strip()
return Node.pyxstr(self,toks,indent, **kw).strip()
class FieldLength(object):
"""
"""
def pyxstr(self,toks,indent,**kw):
pass
class StructDeclarator(Declarator): # also used in Union
"""
"""
def pyxstr(self,toks=None,indent=0,**kw):
comment = ""
if self.name in python_kws:
comment = "#"
return ' '*indent + comment + Node.pyxstr(self,toks,indent, **kw).strip()
class DeclarationSpecifiers(object):
"""
"""
pass
class TypeSpecifiers(DeclarationSpecifiers):
"""
"""
pass
class Initializer(object):
"""
"""
pass
class Declaration(object):
"""
"""
pass
class ParameterDeclaration(Declaration):
"""
"""
pass
class StructDeclaration(Declaration):
"""
"""
pass
class TransUnit(object):
"""
Top level node.
"""
def pyx_decls(self, filenames, modname, macros = {}, names = {}, func_cb=None, cprefix="", **kw):
# PART 1: emit extern declarations
ostream = OStream()
now = datetime.today()
ostream.putln( now.strftime('# Code generated by pyxelator on %x at %X') + '\n' )
ostream.putln("# PART 1: extern declarations")
for filename in filenames:
ostream.putln( 'cdef extern from "%s":\n pass\n' % filename )
ostream.putln( 'cdef extern from *:' )
file = None # current file
for node in self:
ostream.putln('')
ostream.putln(' # ' + node.cstr() )
assert node.marked
comment = False
if node.name and node.name in names:
comment = True # redeclaration
#ostream.putln( node.deepstr( comment=True ) )
s = node.pyxstr(indent=1, names=names, tag_lookup = self.tag_lookup, cprefix=cprefix, **kw)
if s.split():
if comment:
s = "#"+s.replace( '\n', '\n#' ) + " # redeclaration "
if node.file != file:
file = node.file
#ostream.putln( 'cdef extern from "%s":' % file )
ostream.putln( ' # "%s"' % file )
ostream.putln( s )
ostream.putln('\n')
#s = '\n'.join(toks)
return ostream.join()
# XX warn when we find a python keyword XX
python_kws = """
break continue del def except exec finally pass print raise
return try global assert lambda yield
for while if elif else and in is not or import from """.split()
python_kws = dict( zip( python_kws, (None,)*len(python_kws) ) )