Commit 109ee984 authored by Olivier Aubert's avatar Olivier Aubert

python-ctypes: refactor generate.py to be more modular

parent 33043ba3
...@@ -4,14 +4,3 @@ ...@@ -4,14 +4,3 @@
* Support multiple VLC versions: define a front-end module which will * Support multiple VLC versions: define a front-end module which will
load the appropriate versionned module from a subdirectory. load the appropriate versionned module from a subdirectory.
* Support options: --debug/-c, --output/-o, --check-symbols/-s, ...
* Use setup.py
* Refactor code:
class Parser(list_of_includes) -> properties enums, methods...,
dump()
check()...
class PythonGenerator(parser) -> p.save( filename )
...@@ -33,6 +33,7 @@ import re ...@@ -33,6 +33,7 @@ import re
import time import time
import operator import operator
import itertools import itertools
from optparse import OptionParser
# DefaultDict from ASPN python cookbook # DefaultDict from ASPN python cookbook
import copy import copy
...@@ -91,75 +92,16 @@ special_enum_re=re.compile('^(enum)\s*(\S+\s*)?\{\s*(.+)\s*\};') ...@@ -91,75 +92,16 @@ special_enum_re=re.compile('^(enum)\s*(\S+\s*)?\{\s*(.+)\s*\};')
parameter_passing=DefaultDict(default=1) parameter_passing=DefaultDict(default=1)
parameter_passing['libvlc_exception_t*']=3 parameter_passing['libvlc_exception_t*']=3
# C-type to ctypes/python type conversion. class Parser(object):
# Note that enum types conversions are generated (cf convert_enum_names) def __init__(self, list_of_files):
typ2class={ self.methods=[]
'libvlc_exception_t*': 'ctypes.POINTER(VLCException)', self.enums=[]
'libvlc_media_player_t*': 'MediaPlayer', for name in list_of_files:
'libvlc_instance_t*': 'Instance', self.enums.extend(self.parse_typedef(name))
'libvlc_media_t*': 'Media', self.methods.extend(self.parse_include(name))
'libvlc_log_t*': 'Log',
'libvlc_log_iterator_t*': 'LogIterator',
'libvlc_log_message_t*': 'LogMessage',
'libvlc_event_type_t': 'ctypes.c_uint',
'libvlc_event_manager_t*': 'EventManager',
'libvlc_media_discoverer_t*': 'MediaDiscoverer',
'libvlc_media_library_t*': 'MediaLibrary',
'libvlc_media_list_t*': 'MediaList',
'libvlc_media_list_player_t*': 'MediaListPlayer',
'libvlc_media_list_view_t*': 'MediaListView',
'libvlc_track_description_t*': 'TrackDescription',
'libvlc_audio_output_t*': 'AudioOutput',
'mediacontrol_Instance*': 'MediaControl',
'mediacontrol_Exception*': 'MediaControlException',
'mediacontrol_RGBPicture*': 'ctypes.POINTER(RGBPicture)',
'mediacontrol_PlaylistSeq*': 'MediaControlPlaylistSeq',
'mediacontrol_Position*': 'ctypes.POINTER(MediaControlPosition)',
'mediacontrol_StreamInformation*': 'ctypes.POINTER(MediaControlStreamInformation)',
'WINDOWHANDLE': 'ctypes.c_ulong',
'void': 'None', def parse_param(self, s):
'void*': 'ctypes.c_void_p',
'short': 'ctypes.c_short',
'char*': 'ctypes.c_char_p',
'char**': 'ListPOINTER(ctypes.c_char_p)',
'uint32_t': 'ctypes.c_uint',
'float': 'ctypes.c_float',
'unsigned': 'ctypes.c_uint',
'int': 'ctypes.c_int',
'...': 'FIXMEva_list',
'libvlc_callback_t': 'ctypes.c_void_p',
'libvlc_time_t': 'ctypes.c_longlong',
}
# Defined python classes, i.e. classes for which we want to generate
# class wrappers around libvlc functions
defined_classes=(
'MediaPlayer',
'Instance',
'Media',
'Log',
'LogIterator',
#'LogMessage',
'EventManager',
'MediaDiscoverer',
'MediaLibrary',
'MediaList',
'MediaListPlayer',
'MediaListView',
'TrackDescription',
'AudioOutput',
'MediaControl',
)
# Definition of prefixes that we can strip from method names when
# wrapping them into class methods
prefixes=dict( (v, k[:-2]) for (k, v) in typ2class.iteritems() if v in defined_classes )
prefixes['MediaControl']='mediacontrol_'
def parse_param(s):
"""Parse a C parameter expression. """Parse a C parameter expression.
It is used to parse both the type/name for methods, and type/name It is used to parse both the type/name for methods, and type/name
...@@ -187,65 +129,7 @@ def parse_param(s): ...@@ -187,65 +129,7 @@ def parse_param(s):
# K&R definition: only type # K&R definition: only type
return s.replace(' ', ''), '' return s.replace(' ', ''), ''
def insert_code(filename): def parse_typedef(self, name):
"""Generate header/footer code.
"""
f=open(filename, 'r')
for l in f:
if 'build_date' in l:
print 'build_date="%s"' % time.ctime()
else:
print l,
f.close()
def convert_enum_names(enums):
res={}
for (typ, name, values, comment) in enums:
if typ != 'enum':
raise Exception('This method only handles enums')
pyname=re.findall('(libvlc|mediacontrol)_(.+?)(_t)?$', name)[0][1]
if '_' in pyname:
pyname=pyname.title().replace('_', '')
elif not pyname[0].isupper():
pyname=pyname.capitalize()
res[name]=pyname
return res
def generate_enums(enums):
for (typ, name, values, comment) in enums:
if typ != 'enum':
raise Exception('This method only handles enums')
pyname=typ2class[name]
print "class %s(ctypes.c_uint):" % pyname
print ' """%s\n """' % comment
conv={}
# Convert symbol names
for k, v in values:
n=k.split('_')[-1]
if len(n) == 1:
# Single character. Some symbols use 1_1, 5_1, etc.
n="_".join( k.split('_')[-2:] )
if re.match('^[0-9]', n):
# Cannot start an identifier with a number
n='_'+n
conv[k]=n
for k, v in values:
print " %s=%s" % (conv[k], v)
print " _names={"
for k, v in values:
print " %s: '%s'," % (v, conv[k])
print " }"
print """
def __repr__(self):
return ".".join((self.__class__.__module__, self.__class__.__name__, self._names[self.value]))
"""
def parse_typedef(name):
"""Parse include file for typedef expressions. """Parse include file for typedef expressions.
This generates a tuple for each typedef: This generates a tuple for each typedef:
...@@ -319,7 +203,7 @@ def parse_typedef(name): ...@@ -319,7 +203,7 @@ def parse_typedef(name):
comment='' comment=''
continue continue
def parse_include(name): def parse_include(self, name):
"""Parse include file. """Parse include file.
This generates a tuple for each function: This generates a tuple for each function:
...@@ -356,11 +240,11 @@ def parse_include(name): ...@@ -356,11 +240,11 @@ def parse_include(name):
if m: if m:
(ret, param)=m.groups() (ret, param)=m.groups()
rtype, method=parse_param(ret) rtype, method=self.parse_param(ret)
params=[] params=[]
for p in paramlist_re.split(param): for p in paramlist_re.split(param):
params.append( parse_param(p) ) params.append( self.parse_param(p) )
if len(params) == 1 and params[0][0] == 'void': if len(params) == 1 and params[0][0] == 'void':
# Empty parameter list # Empty parameter list
...@@ -384,10 +268,6 @@ def parse_include(name): ...@@ -384,10 +268,6 @@ def parse_include(name):
# but it prevented code generation for a minor detail (some bad descriptions). # but it prevented code generation for a minor detail (some bad descriptions).
params=[ (p[0], names[i]) for (i, p) in enumerate(params) ] params=[ (p[0], names[i]) for (i, p) in enumerate(params) ]
for typ, name in params:
if not typ in typ2class:
raise Exception("No conversion for %s (from %s:%s)" % (typ, method, name))
# Transform Doxygen syntax into epydoc syntax # Transform Doxygen syntax into epydoc syntax
comment=comment.replace('\\param', '@param').replace('\\return', '@return') comment=comment.replace('\\param', '@param').replace('\\return', '@return')
...@@ -405,7 +285,193 @@ def parse_include(name): ...@@ -405,7 +285,193 @@ def parse_include(name):
comment) comment)
comment='' comment=''
def output_ctypes(rtype, method, params, comment): def dump_methods(self):
print "** Defined functions **"
for (rtype, name, params, comment) in self.methods:
print "%(name)s (%(rtype)s):" % locals()
for t, n in params:
print " %(n)s (%(t)s)" % locals()
def dump_enums(self):
print "** Defined enums **"
for (typ, name, values, comment) in self.enums:
print "%(name)s (%(typ)s):" % locals()
for k, v in values:
print " %(k)s=%(v)s" % locals()
class PythonGenerator(object):
# C-type to ctypes/python type conversion.
# Note that enum types conversions are generated (cf convert_enum_names)
type2class={
'libvlc_exception_t*': 'ctypes.POINTER(VLCException)',
'libvlc_media_player_t*': 'MediaPlayer',
'libvlc_instance_t*': 'Instance',
'libvlc_media_t*': 'Media',
'libvlc_log_t*': 'Log',
'libvlc_log_iterator_t*': 'LogIterator',
'libvlc_log_message_t*': 'LogMessage',
'libvlc_event_type_t': 'ctypes.c_uint',
'libvlc_event_manager_t*': 'EventManager',
'libvlc_media_discoverer_t*': 'MediaDiscoverer',
'libvlc_media_library_t*': 'MediaLibrary',
'libvlc_media_list_t*': 'MediaList',
'libvlc_media_list_player_t*': 'MediaListPlayer',
'libvlc_media_list_view_t*': 'MediaListView',
'libvlc_track_description_t*': 'TrackDescription',
'libvlc_audio_output_t*': 'AudioOutput',
'mediacontrol_Instance*': 'MediaControl',
'mediacontrol_Exception*': 'MediaControlException',
'mediacontrol_RGBPicture*': 'ctypes.POINTER(RGBPicture)',
'mediacontrol_PlaylistSeq*': 'MediaControlPlaylistSeq',
'mediacontrol_Position*': 'ctypes.POINTER(MediaControlPosition)',
'mediacontrol_StreamInformation*': 'ctypes.POINTER(MediaControlStreamInformation)',
'WINDOWHANDLE': 'ctypes.c_ulong',
'void': 'None',
'void*': 'ctypes.c_void_p',
'short': 'ctypes.c_short',
'char*': 'ctypes.c_char_p',
'char**': 'ListPOINTER(ctypes.c_char_p)',
'uint32_t': 'ctypes.c_uint',
'float': 'ctypes.c_float',
'unsigned': 'ctypes.c_uint',
'int': 'ctypes.c_int',
'...': 'FIXMEva_list',
'libvlc_callback_t': 'ctypes.c_void_p',
'libvlc_time_t': 'ctypes.c_longlong',
}
# Defined python classes, i.e. classes for which we want to generate
# class wrappers around libvlc functions
defined_classes=(
'MediaPlayer',
'Instance',
'Media',
'Log',
'LogIterator',
'EventManager',
'MediaDiscoverer',
'MediaLibrary',
'MediaList',
'MediaListPlayer',
'MediaListView',
'TrackDescription',
'AudioOutput',
'MediaControl',
)
def __init__(self, parser=None):
self.parser=parser
# Generate python names for enums
self.type2class.update(self.convert_enum_names(parser.enums))
self.check_types()
# Definition of prefixes that we can strip from method names when
# wrapping them into class methods
self.prefixes=dict( (v, k[:-2])
for (k, v) in self.type2class.iteritems()
if v in self.defined_classes )
self.prefixes['MediaControl']='mediacontrol_'
def save(self, filename=None):
if filename is None or filename == '-':
self.fd=sys.stdout
else:
self.fd=open(filename, 'w')
self.insert_code('header.py')
self.generate_enums(self.parser.enums)
wrapped_methods=self.generate_wrappers(self.parser.methods)
for l in self.parser.methods:
self.output_ctypes(*l)
self.insert_code('footer.py')
all_methods=set( t[1] for t in self.parser.methods )
not_wrapped=all_methods.difference(wrapped_methods)
self.output("# Not wrapped methods:")
for m in not_wrapped:
self.output("# ", m)
if self.fd != sys.stdout:
self.fd.close()
def output(self, *p):
self.fd.write(" ".join(p))
self.fd.write("\n")
def check_types(self):
"""Make sure that all types are properly translated.
This method must be called *after* convert_enum_names, since
the latter populates type2class with converted enum names.
"""
for (rt, met, params, c) in self.parser.methods:
for typ, name in params:
if not typ in self.type2class:
raise Exception("No conversion for %s (from %s:%s)" % (typ, met, name))
def insert_code(self, filename):
"""Generate header/footer code.
"""
f=open(filename, 'r')
for l in f:
if 'build_date' in l:
self.output('build_date="%s"' % time.ctime())
else:
self.output(l.rstrip())
f.close()
def convert_enum_names(self, enums):
res={}
for (typ, name, values, comment) in enums:
if typ != 'enum':
raise Exception('This method only handles enums')
pyname=re.findall('(libvlc|mediacontrol)_(.+?)(_t)?$', name)[0][1]
if '_' in pyname:
pyname=pyname.title().replace('_', '')
elif not pyname[0].isupper():
pyname=pyname.capitalize()
res[name]=pyname
return res
def generate_enums(self, enums):
for (typ, name, values, comment) in enums:
if typ != 'enum':
raise Exception('This method only handles enums')
pyname=self.type2class[name]
self.output("class %s(ctypes.c_uint):" % pyname)
self.output(' """%s\n """' % comment)
conv={}
# Convert symbol names
for k, v in values:
n=k.split('_')[-1]
if len(n) == 1:
# Single character. Some symbols use 1_1, 5_1, etc.
n="_".join( k.split('_')[-2:] )
if re.match('^[0-9]', n):
# Cannot start an identifier with a number
n='_'+n
conv[k]=n
for k, v in values:
self.output(" %s=%s" % (conv[k], v))
self.output(" _names={")
for k, v in values:
self.output(" %s: '%s'," % (v, conv[k]))
self.output(" }")
self.output("""
def __repr__(self):
return ".".join((self.__class__.__module__, self.__class__.__name__, self._names[self.value]))
""")
def output_ctypes(self, rtype, method, params, comment):
"""Output ctypes decorator for the given method. """Output ctypes decorator for the given method.
""" """
if method in blacklist: if method in blacklist:
...@@ -413,10 +479,10 @@ def output_ctypes(rtype, method, params, comment): ...@@ -413,10 +479,10 @@ def output_ctypes(rtype, method, params, comment):
return return
if params: if params:
print "prototype=ctypes.CFUNCTYPE(%s, %s)" % (typ2class.get(rtype, 'FIXME_%s' % rtype), self.output("prototype=ctypes.CFUNCTYPE(%s, %s)" % (self.type2class.get(rtype, 'FIXME_%s' % rtype),
",".join( typ2class[p[0]] for p in params )) ",".join( self.type2class[p[0]] for p in params )))
else: else:
print "prototype=ctypes.CFUNCTYPE(%s)" % typ2class.get(rtype, 'FIXME_%s' % rtype) self.output("prototype=ctypes.CFUNCTYPE(%s)" % self.type2class.get(rtype, 'FIXME_%s' % rtype))
if not params: if not params:
...@@ -425,15 +491,15 @@ def output_ctypes(rtype, method, params, comment): ...@@ -425,15 +491,15 @@ def output_ctypes(rtype, method, params, comment):
flags="paramflags=( (%d, ), )" % parameter_passing[params[0][0]] flags="paramflags=( (%d, ), )" % parameter_passing[params[0][0]]
else: else:
flags="paramflags=%s" % ",".join( '(%d,)' % parameter_passing[p[0]] for p in params ) flags="paramflags=%s" % ",".join( '(%d,)' % parameter_passing[p[0]] for p in params )
print flags self.output(flags)
print '%s = prototype( ("%s", dll), paramflags )' % (method, method) self.output('%s = prototype( ("%s", dll), paramflags )' % (method, method))
if '3' in flags: if '3' in flags:
# A VLCException is present. Process it. # A VLCException is present. Process it.
print "%s.errcheck = check_vlc_exception" % method self.output("%s.errcheck = check_vlc_exception" % method)
print '%s.__doc__ = """%s"""' % (method, comment) self.output('%s.__doc__ = """%s"""' % (method, comment))
print self.output()
def parse_override(name): def parse_override(self, name):
"""Parse override definitions file. """Parse override definitions file.
It is possible to override methods definitions in classes. It is possible to override methods definitions in classes.
...@@ -470,7 +536,7 @@ def parse_override(name): ...@@ -470,7 +536,7 @@ def parse_override(name):
return code, overridden_methods, docstring return code, overridden_methods, docstring
def fix_python_comment(c): def fix_python_comment(self, c):
"""Fix comment by removing first and last parameters (self and exception) """Fix comment by removing first and last parameters (self and exception)
""" """
data=c.replace('@{', '').replace('@see', 'See').splitlines() data=c.replace('@{', '').replace('@see', 'See').splitlines()
...@@ -485,32 +551,32 @@ def fix_python_comment(c): ...@@ -485,32 +551,32 @@ def fix_python_comment(c):
return "\n".join(itertools.chain(body, param, ret)) return "\n".join(itertools.chain(body, param, ret))
def generate_wrappers(methods): def generate_wrappers(self, methods):
"""Generate class wrappers for all appropriate methods. """Generate class wrappers for all appropriate methods.
@return: the set of wrapped method names @return: the set of wrapped method names
""" """
ret=set() ret=set()
# Sort methods against the element they apply to. # Sort methods against the element they apply to.
elements=sorted( ( (typ2class.get(params[0][0]), rt, met, params, c) elements=sorted( ( (self.type2class.get(params[0][0]), rt, met, params, c)
for (rt, met, params, c) in methods for (rt, met, params, c) in methods
if params and typ2class.get(params[0][0], '_') in defined_classes if params and self.type2class.get(params[0][0], '_') in self.defined_classes
), ),
key=operator.itemgetter(0)) key=operator.itemgetter(0))
overrides, overriden_methods, docstring=parse_override('override.py') overrides, overriden_methods, docstring=self.parse_override('override.py')
for classname, el in itertools.groupby(elements, key=operator.itemgetter(0)): for classname, el in itertools.groupby(elements, key=operator.itemgetter(0)):
print """class %(name)s(object):""" % {'name': classname} self.output("""class %(name)s(object):""" % {'name': classname})
if classname in docstring: if classname in docstring:
print ' """%s\n """' % docstring[classname] self.output(' """%s\n """' % docstring[classname])
print """ self.output("""
def __new__(cls, pointer=None): def __new__(cls, pointer=None):
'''Internal method used for instanciating wrappers from ctypes. '''Internal method used for instanciating wrappers from ctypes.
''' '''
if pointer is None: if pointer is None:
raise Exception("Internal method. You should instanciate objects through other class methods (probably named 'new' or ending with 'new')") raise Exception("Internal method. Surely this class cannot be instanciated by itself.")
if pointer == 0: if pointer == 0:
return None return None
else: else:
...@@ -523,12 +589,12 @@ def generate_wrappers(methods): ...@@ -523,12 +589,12 @@ def generate_wrappers(methods):
'''(INTERNAL) ctypes parameter conversion method. '''(INTERNAL) ctypes parameter conversion method.
''' '''
return arg._as_parameter_ return arg._as_parameter_
""" % {'name': classname} """ % {'name': classname})
if classname in overrides: if classname in overrides:
print overrides[classname] self.output(overrides[classname])
prefix=prefixes.get(classname, '') prefix=self.prefixes.get(classname, '')
for cl, rtype, method, params, comment in el: for cl, rtype, method, params, comment in el:
if method in blacklist: if method in blacklist:
...@@ -547,64 +613,69 @@ def generate_wrappers(methods): ...@@ -547,64 +613,69 @@ def generate_wrappers(methods):
else: else:
args=", ".join( p[1] for p in params ) args=", ".join( p[1] for p in params )
print " def %s(%s):" % (name, args) self.output(" def %s(%s):" % (name, args))
print ' """%s\n """' % fix_python_comment(comment) self.output(' """%s\n """' % self.fix_python_comment(comment))
if params and params[-1][0] == 'libvlc_exception_t*': if params and params[-1][0] == 'libvlc_exception_t*':
# Exception handling # Exception handling
print " e=VLCException()" self.output(" e=VLCException()")
print " return %s(%s, e)" % (method, args) self.output(" return %s(%s, e)" % (method, args))
elif params and params[-1][0] == 'mediacontrol_Exception*': elif params and params[-1][0] == 'mediacontrol_Exception*':
# Exception handling # Exception handling
print " e=MediaControlException()" self.output(" e=MediaControlException()")
print " return %s(%s, e)" % (method, args) self.output(" return %s(%s, e)" % (method, args))
else: else:
print " return %s(%s)" % (method, args) self.output(" return %s(%s)" % (method, args))
print self.output()
# Check for standard methods # Check for standard methods
if name == 'count': if name == 'count':
# There is a count method. Generate a __len__ one. # There is a count method. Generate a __len__ one.
print " def __len__(self):" self.output(""" def __len__(self):
print " e=VLCException()" e=VLCException()
print " return %s(self, e)" % method return %s(self, e)
print """ % method)
elif name.endswith('item_at_index'): elif name.endswith('item_at_index'):
# Indexable (and thus iterable)" # Indexable (and thus iterable)"
print " def __getitem__(self, i):" self.output(""" def __getitem__(self, i):
print " e=VLCException()" e=VLCException()
print " return %s(self, i, e)" % method return %s(self, i, e)
print
print " def __iter__(self):" def __iter__(self):
print " e=VLCException()" e=VLCException()
print " for i in xrange(len(self)):" for i in xrange(len(self)):
print " yield self[i]" yield self[i]
print """ % method)
return ret return ret
def process(output, list_of_includes):
p=Parser(list_of_includes)
g=PythonGenerator(p)
g.save(output)
if __name__ == '__main__': if __name__ == '__main__':
enums=[] opt=OptionParser(usage="""Parse VLC include files and generate bindings code.
for name in sys.argv[1:]: %prog [options] include_file.h [...]""")
enums.extend(list(parse_typedef(name)))
# Generate python names for enums
typ2class.update(convert_enum_names(enums))
methods=[] opt.add_option("-d", "--debug", dest="debug", action="store_true",
for name in sys.argv[1:]: default=False,
methods.extend(list(parse_include(name))) help="Debug mode")
if debug:
sys.exit(0)
insert_code('header.py') opt.add_option("-o", "--output", dest="output", action="store",
generate_enums(enums) type="str", default="-",
wrapped=generate_wrappers(methods) help="Output filename")
for l in methods:
output_ctypes(*l)
insert_code('footer.py')
all=set( t[1] for t in methods ) (options, args) = opt.parse_args()
not_wrapped=all.difference(wrapped)
print "# Not wrapped methods:" if not args:
for m in not_wrapped: opt.print_help()
print "# ", m sys.exit(1)
p=Parser(args)
if options.debug:
p.dump_methods()
p.dump_enums()
sys.exit(0)
g=PythonGenerator(p)
g.save(options.output)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment