llvmpy/newbinding/binding.py
2013-02-05 13:47:35 -06:00

681 lines
21 KiB
Python

import inspect, textwrap
import functools
import codegen as cg
_rank = 0
namespaces = {}
RESERVED = frozenset(['None'])
class Namespace(object):
def __init__(self, name):
self.name = name
self.enums = []
self.classes = []
self.functions = []
self.includes = set()
namespaces[name] = self
def Class(self, *bases):
cls = Class(self, *bases)
self.classes.append(cls)
return cls
def Function(self, *args):
fn = Function(self, *args)
self.functions.append(fn)
return fn
def Enum(self, name, *value_names):
enum = Enum(*value_names)
enum.parent = self
enum.name = name
self.enums.append(enum)
return enum
@property
def fullname(self):
return self.name
def __str__(self):
return self.name
class _Type(object):
pass
class BuiltinTypes(_Type):
def __init__(self, name):
self.name = name
@property
def fullname(self):
return self.name
def wrap(self, writer, var):
return var
def unwrap(self, writer, var):
return var
Void = BuiltinTypes('void')
Unsigned = BuiltinTypes('unsigned')
UnsignedLongLong = BuiltinTypes('unsigned long long') # used in llvm-3.2
LongLong = BuiltinTypes('long long')
Float = BuiltinTypes('float')
Double = BuiltinTypes('double')
Uint64 = BuiltinTypes('uint64_t')
Size_t = BuiltinTypes('size_t')
VoidPtr = BuiltinTypes('void*')
Bool = BuiltinTypes('bool')
StdString = BuiltinTypes('std::string')
ConstStdString = BuiltinTypes('const std::string')
ConstCharPtr = BuiltinTypes('const char*')
PyObjectPtr = BuiltinTypes('PyObject*')
PyObjectPtr.format='O'
class Class(_Type):
format = 'O'
def __init__(self, ns, *bases):
self.ns = ns
self.bases = bases
self._is_defined = False
self.methods = []
self.pymethods = []
self.enums = []
self.attrs = []
self.includes = set()
self.downcastables = set()
def __call__(self, defn):
assert not self._is_defined
# process the definition in "defn"
self.name = defn.__name__
for k, v in defn.__dict__.items():
if isinstance(v, Method):
self.methods.append(v)
if isinstance(v, Constructor):
for sig in v.signatures:
sig[0] = ptr(self)
v.name = k
v.parent = self
elif isinstance(v, Enum):
self.enums.append(v)
v.name = k
v.parent = self
setattr(self, k, v)
elif isinstance(v, Attr):
self.attrs.append(v)
v.name = k
v.parent = self
elif isinstance(v, CustomPythonMethod):
self.pymethods.append(v)
elif k == '_include_':
if isinstance(v, str):
self.includes.add(v)
else:
for i in v:
self.includes.add(i)
elif k == '_realname_':
self.realname = v
elif k == '_downcast_':
if isinstance(v, Class):
self.downcastables.add(v)
else:
for i in v:
self.downcastables.add(i)
return self
def compile_cpp(self, writer):
# generate methods
for meth in self.methods:
meth.compile_cpp(writer)
for enum in self.enums:
enum.compile_cpp(writer)
for attr in self.attrs:
attr.compile_cpp(writer)
# generate method table
writer.println('static')
writer.println('PyMethodDef %s[] = {' % cg.mangle(self.fullname))
with writer.indent():
fmt = '{ "%(name)s", (PyCFunction)%(func)s, METH_VARARGS, NULL },'
for meth in self.methods:
name = meth.name
func = meth.c_name
writer.println(fmt % locals())
for enumkind in self.enums:
for enum in enumkind.value_names:
name = enum
func = enumkind.c_name(enum)
writer.println(fmt % locals())
for attr in self.attrs:
# getter
name = attr.getter_name
func = attr.getter_c_name
writer.println(fmt % locals())
# setter
name = attr.setter_name
func = attr.setter_c_name
writer.println(fmt % locals())
writer.println('{ NULL },')
writer.println('};')
writer.println()
def compile_py(self, writer):
clsname = self.name
bases = 'capsule.Wrapper'
if self.bases:
bases = ', '.join(x.name for x in self.bases)
writer.println('@capsule.register_class("%s")' % self.fullname)
with writer.block('class %(clsname)s(%(bases)s):' % locals()):
writer.println('_llvm_type_ = "%s"' % self.fullname)
for enum in self.enums:
enum.compile_py(writer)
for meth in self.methods:
meth.compile_py(writer)
for meth in self.pymethods:
meth.compile_py(writer)
for attr in self.attrs:
attr.compile_py(writer)
writer.println()
@property
def capsule_name(self):
if self.bases:
return self.bases[-1].capsule_name
else:
return self.fullname
@property
def fullname(self):
try:
name = self.realname
except AttributeError:
name = self.name
return '::'.join([self.ns.fullname, name])
def __str__(self):
return self.fullname
def unwrap(self, writer, val):
fmt = 'PyCapsule_GetPointer(%(val)s, "%(name)s")'
name = self.capsule_name
raw = writer.declare('void*', fmt % locals())
writer.die_if_false(raw)
ptrty = ptr(self).fullname
ty = self.fullname
fmt = 'typecast<%(ty)s >::from(%(raw)s)'
casted = writer.declare(ptrty, fmt % locals())
writer.die_if_false(casted)
return casted
def wrap(self, writer, val):
copy = 'new %s(%s)' % (self.fullname, val)
return writer.pycapsule_new(copy, self.capsule_name, self.fullname)
class Enum(object):
format = 'O'
def __init__(self, *value_names):
self.parent = None
if len(value_names) == 1:
value_names = filter(bool, value_names[0].replace(',', ' ').split())
self.value_names = value_names
self.includes = set()
@property
def fullname(self):
try:
name = self.realname
except AttributeError:
name = self.name
return '::'.join([self.parent.fullname, name])
def __str__(self):
return self.fullname
def wrap(self, writer, val):
ret = writer.declare('PyObject*', 'PyInt_FromLong(%s)' % val)
return ret
def unwrap(self, writer, val):
convert_long_to_enum = '(%s)PyInt_AsLong(%s)' % (self.fullname, val)
ret = writer.declare(self.fullname, convert_long_to_enum)
return ret
def c_name(self, enum):
return cg.mangle("%s_%s_%s" % (self.parent, self.name, enum))
def compile_cpp(self, writer):
for enum in self.value_names:
with writer.py_function(self.c_name(enum)):
ret = self.wrap(writer, '::'.join([self.parent.fullname, enum]))
writer.return_value(ret)
def compile_py(self, writer):
with writer.block('class %s:' % self.name):
writer.println('_llvm_type_ = "%s"' % self.fullname)
for v in self.value_names:
if v in RESERVED:
k = '%s_' % v
fmt = '%(k)s = getattr(%(p)s, "%(v)s")()'
else:
k = v
fmt = '%(k)s = %(p)s.%(v)s()'
p = '.'.join(['_api'] + self.parent.fullname.split('::')[1:])
writer.println(fmt % locals())
writer.println()
class Method(object):
_kind_ = 'meth'
def __init__(self, return_type=Void, *args):
self.parent = None
self.signatures = []
self.includes = set()
self._add_signature(return_type, *args)
def _add_signature(self, return_type, *args):
prev_lens = set(map(len, self.signatures))
cur_len = len(args) + 1
if cur_len in prev_lens:
raise Exception('Only support overloading with different number'
' of arguments')
self.signatures.append([return_type] + list(args))
def __ior__(self, method):
assert type(self) is type(method)
for sig in method.signatures:
self._add_signature(sig[0], *sig[1:])
return self
@property
def fullname(self):
return '::'.join([self.parent.fullname, self.realname])
@property
def realname(self):
try:
return self.__realname
except AttributeError:
return self.name
@realname.setter
def realname(self, v):
self.__realname = v
@property
def c_name(self):
return cg.mangle("%s_%s" % (self.parent, self.name))
def __str__(self):
return self.fullname
def compile_cpp(self, writer):
with writer.py_function(self.c_name):
if len(self.signatures) == 1:
sig = self.signatures[0]
retty = sig[0]
argtys = sig[1:]
self.compile_cpp_body(writer, retty, argtys)
else:
nargs = writer.declare('Py_ssize_t', 'PyTuple_Size(args)')
for sig in self.signatures:
retty = sig[0]
argtys = sig[1:]
expect = len(argtys)
if (not isinstance(self, StaticMethod) and
isinstance(self.parent, Class)):
# Is a instance method, add 1 for "this".
expect += 1
with writer.block('if (%(expect)d == %(nargs)s)' % locals()):
self.compile_cpp_body(writer, retty, argtys)
writer.raises(TypeError, 'Invalid number of args')
def compile_cpp_body(self, writer, retty, argtys):
args = writer.parse_arguments('args', ptr(self.parent), *argtys)
ret = writer.method_call(self.realname, retty.fullname, *args)
writer.return_value(retty.wrap(writer, ret))
def compile_py(self, writer):
decl = writer.function(self.name, args=('self',), varargs='args')
with decl as (this, varargs):
unwrap_this = writer.unwrap(this)
unwrapped = writer.unwrap_many(varargs)
self.process_ownedptr_args(writer, unwrapped)
func = '.'.join([self.parent.name, self.name])
ret = writer.call('_api.%s' % func,
args=(unwrap_this,), varargs=unwrapped)
wrapped = writer.wrap(ret, self.is_return_ownedptr())
writer.return_value(wrapped)
writer.println()
def require_only(self, num):
'''Require only "num" of argument.
'''
assert len(self.signatures) == 1
sig = self.signatures[0]
ret = sig[0]
args = sig[1:]
arg_ct = len(args)
for i in range(num, arg_ct):
self._add_signature(ret, *args[:i])
return self
def is_return_ownedptr(self):
retty = self.signatures[0][0]
return isinstance(retty, ownedptr)
def process_ownedptr_args(self, writer, unwrapped):
argtys = self.signatures[0][1:]
for i, ty in enumerate(argtys):
if isinstance(ty, ownedptr):
with writer.block('if len(%s) > %d:' % (unwrapped, i)):
writer.release_ownership('%s[%d]' % (unwrapped, i))
class CustomMethod(Method):
def __init__(self, methodname, retty, *argtys):
super(CustomMethod, self).__init__(retty, *argtys)
self.methodname = methodname
def compile_cpp_body(self, writer, retty, argtys):
args = writer.parse_arguments('args', ptr(self.parent), *argtys)
ret = writer.call(self.methodname, retty.fullname, *args)
writer.return_value(retty.wrap(writer, ret))
class StaticMethod(Method):
def compile_cpp_body(self, writer, retty, argtys):
assert isinstance(self.parent, Class)
args = writer.parse_arguments('args', *argtys)
ret = self.compile_cpp_call(writer, retty, args)
writer.return_value(retty.wrap(writer, ret))
def compile_cpp_call(self, writer, retty, args):
ret = writer.call(self.fullname, retty.fullname, *args)
return ret
def compile_py(self, writer):
writer.println('@staticmethod')
decl = writer.function(self.name, varargs='args')
with decl as varargs:
unwrapped = writer.unwrap_many(varargs)
self.process_ownedptr_args(writer, unwrapped)
func = '.'.join([self.parent.name, self.name])
ret = writer.call('_api.%s' % func, varargs=unwrapped)
wrapped = writer.wrap(ret, self.is_return_ownedptr())
writer.return_value(wrapped)
writer.println()
class CustomStaticMethod(StaticMethod):
def __init__(self, methodname, retty, *argtys):
super(CustomStaticMethod, self).__init__(retty, *argtys)
self.methodname = methodname
def compile_cpp_body(self, writer, retty, argtys):
args = writer.parse_arguments('args', *argtys)
ret = writer.call(self.methodname, retty.fullname, *args)
writer.return_value(retty.wrap(writer, ret))
class Function(Method):
_kind_ = 'func'
def __init__(self, parent, name, return_type=Void, *args):
super(Function, self).__init__(return_type, *args)
self.parent = parent
self.name = name
def compile_cpp_body(self, writer, retty, argtys):
args = writer.parse_arguments('args', *argtys)
ret = writer.call(self.fullname, retty.fullname, *args)
writer.return_value(retty.wrap(writer, ret))
def compile_py(self, writer):
with writer.function(self.name, varargs='args') as varargs:
unwrapped = writer.unwrap_many(varargs)
self.process_ownedptr_args(writer, unwrapped)
func = self.fullname.split('::', 1)[1].replace('::', '.')
ret = writer.call('_api.%s' % func,
varargs=unwrapped)
wrapped = writer.wrap(ret, self.is_return_ownedptr())
writer.return_value(wrapped)
writer.println()
class Destructor(Method):
_kind_ = 'dtor'
def __init__(self):
super(Destructor, self).__init__()
def compile_cpp_body(self, writer, retty, argtys):
assert isinstance(self.parent, Class)
assert not argtys
args = writer.parse_arguments('args', ptr(self.parent), *argtys)
writer.println('delete %s;' % args[0])
writer.return_value(None)
def compile_py(self, writer):
func = '.'.join([self.parent.name, self.name])
writer.println('_delete_ = _api.%s' % func)
class Constructor(StaticMethod):
_kind_ = 'ctor'
def __init__(self, *args):
super(Constructor, self).__init__(Void, *args)
def compile_cpp_call(self, writer, retty, args):
alloctype = retty.fullname.rstrip(' *')
arglist = ', '.join(args)
stmt = 'new %(alloctype)s(%(arglist)s)' % locals()
ret = writer.declare(retty.fullname, stmt)
return ret
class ref(_Type):
def __init__(self, element):
assert isinstance(element, Class), type(element)
self.element = element
self.const = False
def __str__(self):
return self.fullname
@property
def fullname(self):
if self.const:
return 'const %s&' % self.element.fullname
else:
return '%s&' % self.element.fullname
@property
def capsule_name(self):
return self.element.capsule_name
@property
def format(self):
return self.element.format
def wrap(self, writer, val):
p = writer.declare(const(ptr(self.element)).fullname, '&%s' % val)
return writer.pycapsule_new(p, self.capsule_name, self.element.fullname)
def unwrap(self, writer, val):
p = self.element.unwrap(writer, val)
return writer.declare(self.fullname, '*%s' % p)
class ptr(_Type):
def __init__(self, element):
assert isinstance(element, Class)
self.element = element
self.const = False
@property
def fullname(self):
if self.const:
return 'const %s*' % self.element
else:
return '%s*' % self.element
@property
def format(self):
return self.element.format
def unwrap(self, writer, val):
ret = writer.declare(self.fullname, 'NULL')
with writer.block('if (%(val)s != Py_None)' % locals()):
val = self.element.unwrap(writer, val)
writer.println('%(ret)s = %(val)s;' % locals())
return ret
def wrap(self, writer, val):
return writer.pycapsule_new(val, self.element.capsule_name,
self.element.fullname)
class ownedptr(ptr):
pass
def const(ptr_or_ref):
ptr_or_ref.const = True
return ptr_or_ref
class cast(_Type):
format = 'O'
def __init__(self, original, target):
self.original = original
self.target = target
@property
def fullname(self):
return self.binding_type.fullname
@property
def python_type(self):
if not isinstance(self.target, _Type):
return self.target
else:
return self.original
@property
def binding_type(self):
if isinstance(self.target, _Type):
return self.target
else:
return self.original
def wrap(self, writer, val):
dst = self.python_type.__name__
return writer.call('py_%(dst)s_from' % locals(), 'PyObject*', val)
def unwrap(self, writer, val):
src = self.python_type.__name__
dst = self.binding_type.fullname
ret = writer.declare(dst)
status = writer.call('py_%(src)s_to' % locals(), 'int', val, ret)
writer.die_if_false(status)
return ret
class CustomPythonMethod(object):
def __init__(self, fn):
src = inspect.getsource(fn)
lines = textwrap.dedent(src).splitlines()
for i, line in enumerate(lines):
if not line.startswith('@'):
break
self.sourcelines = lines[i:]
def compile_py(self, writer):
for line in self.sourcelines:
writer.println(line)
class CustomPythonStaticMethod(CustomPythonMethod):
def compile_py(self, writer):
writer.println('@staticmethod')
super(CustomPythonStaticMethod, self).compile_py(writer)
class Attr(object):
def __init__(self, getter, setter):
self.getter = getter
self.setter = setter
@property
def fullname(self):
try:
name = self.realname
except AttributeError:
name = self.name
return '::'.join([self.parent.fullname, name])
def __str__(self):
return self.fullname
@property
def getter_name(self):
return '%s_get' % self.name
@property
def setter_name(self):
return '%s_set' % self.name
@property
def getter_c_name(self):
return cg.mangle('%s_get' % self.fullname)
@property
def setter_c_name(self):
return cg.mangle('%s_set' % self.fullname)
def compile_cpp(self, writer):
# getter
with writer.py_function(self.getter_c_name):
(this,) = writer.parse_arguments('args', ptr(self.parent))
attr = self.name
ret = writer.declare(self.getter.fullname,
'%(this)s->%(attr)s' % locals())
writer.return_value(self.getter.wrap(writer, ret))
# setter
with writer.py_function(self.setter_c_name):
(this, value) = writer.parse_arguments('args', ptr(self.parent),
self.setter)
attr = self.name
writer.println('%(this)s->%(attr)s = %(value)s;' % locals())
writer.return_value(None)
def compile_py(self, writer):
name = self.name
parent = '.'.join(self.parent.fullname.split('::')[1:])
getter = '.'.join([parent, self.getter_name])
setter = '.'.join([parent, self.setter_name])
writer.println('@property')
with writer.block('def %(name)s(self):' % locals()):
unself = writer.unwrap('self')
ret = writer.new_symbol('ret')
writer.println('%(ret)s = _api.%(getter)s(%(unself)s)' % locals())
is_ownedptr = isinstance(self.getter, ownedptr)
writer.return_value(writer.wrap(ret, is_ownedptr))
writer.println()
writer.println('@%(name)s.setter' % locals())
with writer.block('def %(name)s(self, value):' % locals()):
unself = writer.unwrap('self')
unvalue = writer.unwrap('value')
if isinstance(self.setter, ownedptr):
writer.release_ownership(unvalue)
writer.println('return _api.%(setter)s(%(unself)s, %(unvalue)s)' %
locals())
writer.println()