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 @@
* Support multiple VLC versions: define a front-end module which will
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
import time
import operator
import itertools
from optparse import OptionParser
# DefaultDict from ASPN python cookbook
import copy
......@@ -91,75 +92,16 @@ special_enum_re=re.compile('^(enum)\s*(\S+\s*)?\{\s*(.+)\s*\};')
parameter_passing=DefaultDict(default=1)
parameter_passing['libvlc_exception_t*']=3
# C-type to ctypes/python type conversion.
# Note that enum types conversions are generated (cf convert_enum_names)
typ2class={
'libvlc_exception_t*': 'ctypes.POINTER(VLCException)',
class Parser(object):
def __init__(self, list_of_files):
self.methods=[]
self.enums=[]
'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',
for name in list_of_files:
self.enums.extend(self.parse_typedef(name))
self.methods.extend(self.parse_include(name))
'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',
#'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):
def parse_param(self, s):
"""Parse a C parameter expression.
It is used to parse both the type/name for methods, and type/name
......@@ -187,65 +129,7 @@ def parse_param(s):
# K&R definition: only type
return s.replace(' ', ''), ''
def insert_code(filename):
"""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):
def parse_typedef(self, name):
"""Parse include file for typedef expressions.
This generates a tuple for each typedef:
......@@ -319,7 +203,7 @@ def parse_typedef(name):
comment=''
continue
def parse_include(name):
def parse_include(self, name):
"""Parse include file.
This generates a tuple for each function:
......@@ -356,11 +240,11 @@ def parse_include(name):
if m:
(ret, param)=m.groups()
rtype, method=parse_param(ret)
rtype, method=self.parse_param(ret)
params=[]
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':
# Empty parameter list
......@@ -384,10 +268,6 @@ def parse_include(name):
# but it prevented code generation for a minor detail (some bad descriptions).
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
comment=comment.replace('\\param', '@param').replace('\\return', '@return')
......@@ -405,7 +285,193 @@ def parse_include(name):
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.
"""
if method in blacklist:
......@@ -413,10 +479,10 @@ def output_ctypes(rtype, method, params, comment):
return
if params:
print "prototype=ctypes.CFUNCTYPE(%s, %s)" % (typ2class.get(rtype, 'FIXME_%s' % rtype),
",".join( typ2class[p[0]] for p in params ))
self.output("prototype=ctypes.CFUNCTYPE(%s, %s)" % (self.type2class.get(rtype, 'FIXME_%s' % rtype),
",".join( self.type2class[p[0]] for p in params )))
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:
......@@ -425,15 +491,15 @@ def output_ctypes(rtype, method, params, comment):
flags="paramflags=( (%d, ), )" % parameter_passing[params[0][0]]
else:
flags="paramflags=%s" % ",".join( '(%d,)' % parameter_passing[p[0]] for p in params )
print flags
print '%s = prototype( ("%s", dll), paramflags )' % (method, method)
self.output(flags)
self.output('%s = prototype( ("%s", dll), paramflags )' % (method, method))
if '3' in flags:
# A VLCException is present. Process it.
print "%s.errcheck = check_vlc_exception" % method
print '%s.__doc__ = """%s"""' % (method, comment)
print
self.output("%s.errcheck = check_vlc_exception" % method)
self.output('%s.__doc__ = """%s"""' % (method, comment))
self.output()
def parse_override(name):
def parse_override(self, name):
"""Parse override definitions file.
It is possible to override methods definitions in classes.
......@@ -470,7 +536,7 @@ def parse_override(name):
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)
"""
data=c.replace('@{', '').replace('@see', 'See').splitlines()
......@@ -485,32 +551,32 @@ def fix_python_comment(c):
return "\n".join(itertools.chain(body, param, ret))
def generate_wrappers(methods):
def generate_wrappers(self, methods):
"""Generate class wrappers for all appropriate methods.
@return: the set of wrapped method names
"""
ret=set()
# 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
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))
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)):
print """class %(name)s(object):""" % {'name': classname}
self.output("""class %(name)s(object):""" % {'name': classname})
if classname in docstring:
print ' """%s\n """' % docstring[classname]
self.output(' """%s\n """' % docstring[classname])
print """
self.output("""
def __new__(cls, pointer=None):
'''Internal method used for instanciating wrappers from ctypes.
'''
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:
return None
else:
......@@ -523,12 +589,12 @@ def generate_wrappers(methods):
'''(INTERNAL) ctypes parameter conversion method.
'''
return arg._as_parameter_
""" % {'name': classname}
""" % {'name': classname})
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:
if method in blacklist:
......@@ -547,64 +613,69 @@ def generate_wrappers(methods):
else:
args=", ".join( p[1] for p in params )
print " def %s(%s):" % (name, args)
print ' """%s\n """' % fix_python_comment(comment)
self.output(" def %s(%s):" % (name, args))
self.output(' """%s\n """' % self.fix_python_comment(comment))
if params and params[-1][0] == 'libvlc_exception_t*':
# Exception handling
print " e=VLCException()"
print " return %s(%s, e)" % (method, args)
self.output(" e=VLCException()")
self.output(" return %s(%s, e)" % (method, args))
elif params and params[-1][0] == 'mediacontrol_Exception*':
# Exception handling
print " e=MediaControlException()"
print " return %s(%s, e)" % (method, args)
self.output(" e=MediaControlException()")
self.output(" return %s(%s, e)" % (method, args))
else:
print " return %s(%s)" % (method, args)
print
self.output(" return %s(%s)" % (method, args))
self.output()
# Check for standard methods
if name == 'count':
# There is a count method. Generate a __len__ one.
print " def __len__(self):"
print " e=VLCException()"
print " return %s(self, e)" % method
print
self.output(""" def __len__(self):
e=VLCException()
return %s(self, e)
""" % method)
elif name.endswith('item_at_index'):
# Indexable (and thus iterable)"
print " def __getitem__(self, i):"
print " e=VLCException()"
print " return %s(self, i, e)" % method
print
print " def __iter__(self):"
print " e=VLCException()"
print " for i in xrange(len(self)):"
print " yield self[i]"
print
self.output(""" def __getitem__(self, i):
e=VLCException()
return %s(self, i, e)
def __iter__(self):
e=VLCException()
for i in xrange(len(self)):
yield self[i]
""" % method)
return ret
def process(output, list_of_includes):
p=Parser(list_of_includes)
g=PythonGenerator(p)
g.save(output)
if __name__ == '__main__':
enums=[]
for name in sys.argv[1:]:
enums.extend(list(parse_typedef(name)))
# Generate python names for enums
typ2class.update(convert_enum_names(enums))
opt=OptionParser(usage="""Parse VLC include files and generate bindings code.
%prog [options] include_file.h [...]""")
methods=[]
for name in sys.argv[1:]:
methods.extend(list(parse_include(name)))
if debug:
sys.exit(0)
opt.add_option("-d", "--debug", dest="debug", action="store_true",
default=False,
help="Debug mode")
insert_code('header.py')
generate_enums(enums)
wrapped=generate_wrappers(methods)
for l in methods:
output_ctypes(*l)
insert_code('footer.py')
opt.add_option("-o", "--output", dest="output", action="store",
type="str", default="-",
help="Output filename")
all=set( t[1] for t in methods )
not_wrapped=all.difference(wrapped)
print "# Not wrapped methods:"
for m in not_wrapped:
print "# ", m
(options, args) = opt.parse_args()
if not args:
opt.print_help()
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