diff --git a/cffi/test/test_bindgen.h b/cffi/test/test_bindgen.h index 363faa5..f011548 100644 --- a/cffi/test/test_bindgen.h +++ b/cffi/test/test_bindgen.h @@ -543,3 +543,57 @@ class CustomCppMethodsClass return this->custom_pycode_and_cppcode(cb); } }; + +class CharTypesClass +{ +public: + int char_scalar(char c) + { + return c; + } + + int schar_scalar(signed char c) + { + return c; + } + + int uchar_scalar(unsigned char c) + { + return c; + } + + int char_vector(char *c) + { + return c[0]; + } + + int schar_vector(signed char *c) + { + return c[0]; + } + + int uchar_vector(unsigned char *c) + { + return c[0]; + } + +}; + +class UnsignedTypesClass +{ +public: + unsigned u(unsigned i) + { + return -i; + } + + unsigned int ui(unsigned int i) + { + return -i; + } + + unsigned long long ull(unsigned long long i) + { + return -i; + } +}; diff --git a/cffi/test/test_bindgen.py b/cffi/test/test_bindgen.py index e21a3a9..1a4dc3c 100644 --- a/cffi/test/test_bindgen.py +++ b/cffi/test/test_bindgen.py @@ -369,6 +369,28 @@ def create_generators(cls): items=[ParamDef(type='char', name='c', pyInt=True)])])) module.addItem(c) + c = ClassDef(name='CharTypesClass') + c.addMethod('int', 'char_scalar', '', items=ArgsString('(char i)')) + c.addMethod('int', 'schar_scalar', '', + items=ArgsString('(signed char iii)')) + c.addMethod('int', 'uchar_scalar', '', + items=ArgsString('(unsigned char i)')) + c.addMethod('int', 'char_vector', '', items=ArgsString('(char *i)')) + c.addMethod('int', 'schar_vector', '', + items=ArgsString('(signed char *i)')) + c.addMethod('int', 'uchar_vector', '', + items=ArgsString('(unsigned char *i)')) + module.addItem(c) + + c = ClassDef(name='UnsignedTypesClass') + c.addMethod('unsigned', 'u', '(unsigned i)', + items=ArgsString('(unsigned i)')) + c.addMethod('unsigned int', 'ui', '(unsigned int i)', + items=ArgsString('(unsigned int i)')) + c.addMethod('unsigned long long', 'ull', '(unsigned long long i)', + items=ArgsString('(unsigned long long i)')) + module.addItem(c) + c = ClassDef(name='ArrayClass') c.addItem(MethodDef( name='ArrayClass', argsString='()', isCtor=True, overloads=[ @@ -1329,6 +1351,21 @@ def test_pyint(self): assert obj.overloaded() == ord('c') assert obj.overloaded(11) == 11 + def test_char_types(self): + obj = self.mod.CharTypesClass() + assert obj.char_scalar(chr(255)) == -1 + assert obj.uchar_scalar(chr(255)) == 255 + + assert obj.schar_scalar(chr(1)) == 1 + with pytest.raises(OverflowError): + obj.schar_scalar(chr(255)) + + def test_unsigned_types(self): + obj = self.mod.UnsignedTypesClass() + assert obj.u(1) == 2 ** 32 - 1 + assert obj.ui(1) == 2 ** 32 - 1 + assert obj.ull(1) == 2 ** 64 - 1 + def test_array(self): AC = self.mod.ArrayClass diff --git a/etgtools/cffi/base.py b/etgtools/cffi/base.py index d622698..9c95c6b 100644 --- a/etgtools/cffi/base.py +++ b/etgtools/cffi/base.py @@ -25,6 +25,9 @@ def __init__(self, item): def __getattr__(self, attr): return self[attr] + def __setattr__(self, attr, val): + self[attr] = val + # TODO: cache instantiations class TypeInfo(object): ARRAY_SIZE_PARAM = '_array_size_' diff --git a/etgtools/cffi/basictype.py b/etgtools/cffi/basictype.py index 3a49923..cf76071 100644 --- a/etgtools/cffi/basictype.py +++ b/etgtools/cffi/basictype.py @@ -1,4 +1,5 @@ +import re import sys import warnings from binascii import crc32 @@ -9,10 +10,12 @@ # C basic types -> Python conversion functions BASIC_CTYPES = { + '' : 'int', # unsigned 'int': 'int', 'short': 'int', 'long': 'int', 'long long': 'int', + 'signed': 'int', 'unsigned': 'int', 'size_t' : 'int', 'ssize_t' : 'int', @@ -20,10 +23,7 @@ 'double': 'float', 'char': 'str', 'wchar_t': 'unicode', - 'char*': 'str', - 'char *': 'str', - 'signed char': 'int', - 'unsigned char': 'int', + 'char': 'str', 'bool': 'bool', } @@ -33,6 +33,7 @@ def getbasictype(name, typeinfo): elif name in ('', None, 'void'): return VoidType() elif typeinfo.ptrcount and 'char' in name: + # One length strings (ie `char`) are not StringType return StringType(name) elif (name in BASIC_CTYPES or name.replace('unsigned ', '').strip() in BASIC_CTYPES or @@ -47,9 +48,6 @@ def __new__(cls, name): if name in cls._cache: return cls._cache[name] - if not name in BASIC_CTYPES: - raise UnknownTypeException(name) - type = super(BasicType, cls).__new__(cls, name) cls._cache[name] = type return type @@ -57,14 +55,23 @@ def __new__(cls, name): def __init__(self, name): self.name = name self.unscopedname = name - self.unscopedpyname = BASIC_CTYPES[name] + + self.stripped_name = (self.name.replace('unsigned ', '') + .replace('signed ', '').strip()) + + self.unscopedpyname = BASIC_CTYPES[self.stripped_name] + + is_char_re = re.compile(r'(^| )char\s*$') + @property + def is_char(self): + return self.is_char_re.search(self.name) is not None def build_typeinfo(self, typeinfo): if typeinfo.flags.array: raise TypeError('use of the Array annotation is unsupported on ' "'%s' parameters" % typeinfo.original) - if typeinfo.flags.pyint and 'char' not in typeinfo.name: + if typeinfo.flags.pyint and not self.is_char: raise TypeError('use of the PyInt annotation is unsupported on ' "'%s' parameters" % typeinfo.original) @@ -74,14 +81,11 @@ def build_typeinfo(self, typeinfo): typeinfo.c_type = self.name typeinfo.cdef_type = self.name - if typeinfo.flags.pyint and not 'signed' in self.name: - typeinfo.cdef_type = typeinfo.cdef_type.replace('char', 'signed char') - if typeinfo.const and 'const ' not in typeinfo.c_type: typeinfo.c_type = 'const ' + typeinfo.c_type if typeinfo.name == 'bool': - # MSVC doesn't support _Bool, so pass bools as into through cffi + # MSVC doesn't support _Bool, so pass bools as ints through cffi typeinfo.c_type = 'int' typeinfo.cdef_type = 'int' @@ -105,7 +109,7 @@ def build_typeinfo(self, typeinfo): typeinfo.py_type = 'numbers.Number' typeinfo.default_placeholder = '0' - if not typeinfo.flags.pyint and 'char' in self.name: + if not typeinfo.flags.pyint and self.is_char: # Treat all non-pyint chars as strings. # TODO: This is actually incorrect, we should only accept length 1 # strings. Add type to wrapper_lib to handle this @@ -122,17 +126,27 @@ def call_cdef_param_setup(self, typeinfo, name): return """\ {0} = {1}({0}) {0}{2.OUT_PARAM_SUFFIX} = ffi.new('{2.cdef_type}', {0}) - """.format(name, BASIC_CTYPES[self.name], typeinfo) + """.format(name, BASIC_CTYPES[self.stripped_name], typeinfo) def call_cdef_param_inline(self, typeinfo, name): if typeinfo.flags.out or typeinfo.flags.inout: return name + typeinfo.OUT_PARAM_SUFFIX - if 'signed ' in self.name or typeinfo.flags.pyint: - # A special case for integer chars - return 'int(%s)' % name - - return "%s(%s)" % (BASIC_CTYPES[self.name], name) + if self.is_char: + if 'signed ' in self.name: + # CFFI expects an int + if typeinfo.flags.pyint: + return 'int(%s)' % name + else: + return 'ord(%s)' % name + else: + # CFFI expects a length-1 string + if typeinfo.flags.pyint: + return 'chr(%s)' % name + else: + return 'str(%s)' % name + + return "%s(%s)" % (BASIC_CTYPES[self.stripped_name], name) def virt_py_param_inline(self, typeinfo, name): if typeinfo.flags.inout: @@ -143,7 +157,7 @@ def virt_py_param_cleanup(self, typeinfo, name): if typeinfo.flags.out or typeinfo.flags.inout: # For out and inout cases, we're writing into a pointer return "{0}[0] = {1}({0}{2.PY_RETURN_SUFFIX})".format( - name, BASIC_CTYPES[self.name], typeinfo) + name, BASIC_CTYPES[self.stripped_name], typeinfo) def virt_cpp_param_inline(self, typeinfo, name): ref = '&' if typeinfo.refcount else '' @@ -171,21 +185,38 @@ def convert_variable_c_to_py(self, typeinfo, name): if typeinfo.flags.out or typeinfo.flags.inout: return '%s%s[0]' % (name, typeinfo.OUT_PARAM_SUFFIX) - if 'signed ' in self.name or typeinfo.flags.pyint: - # A special case for integer chars - return 'int(%s)' % name - return "%s(%s)" % (BASIC_CTYPES[self.name], name) + if self.is_char: + if 'signed ' in self.name: + # CFFI gives us an int + if typeinfo.flags.pyint: + return name + else: + return 'chr(%s)' % name + else: + # CFFI gives us a length-1 string + if typeinfo.flags.pyint: + return 'ord(%s)' % name + else: + return name + + return "%s(%s)" % (BASIC_CTYPES[self.stripped_name], name) class StringType(CppType): _cache = {} def __new__(cls, name): - if name in cls._cache: - return cls._cache[name] + signedness = int('signed ' in name) - 2*int('unsigned ' in name) + unicodeness = 'wchar_t' in name + identifier = (signedness, unicodeness) - assert name in ('char', 'wchar_t') + if identifier in cls._cache: + return cls._cache[(identifier)] type = super(StringType, cls).__new__(cls, name) cls._cache[name] = type + + type.signed = signedness + type.unicode = unicodeness + return type def __init__(self, name): @@ -193,7 +224,18 @@ def __init__(self, name): self.unscopedname = name def build_typeinfo(self, typeinfo): - typeinfo.c_type = self.name + '*' + if self.signed == 1: + typeinfo.c_type = 'signed ' + elif self.signed == 0: + typeinfo.c_type = '' + elif self.signed == -1: + typeinfo.c_type = 'unsigned ' + + if self.unicode: + typeinfo.c_type += 'wchar_t *' + else: + typeinfo.c_type += 'char *' + typeinfo.cdef_type = typeinfo.c_type if typeinfo.const: @@ -211,9 +253,9 @@ def build_typeinfo(self, typeinfo): typeinfo.default_placeholder = 'ffi.NULL' def virt_py_return(self, typeinfo, name): - if self.name == 'char': - conversion = 'string' - elif self.name == 'wchar_t': + if not self.unicode: + conversion = 'str' + else: conversion = 'unicode' return '{0} = wrapper_lib.allocate_c{1}({0}, clib)'.format(name, conversion) diff --git a/etgtools/cffi/bindgen.py b/etgtools/cffi/bindgen.py index 3dbbc82..8f7a29e 100644 --- a/etgtools/cffi/bindgen.py +++ b/etgtools/cffi/bindgen.py @@ -24,7 +24,7 @@ def generate(self, module_name): module = pickle.load(f) for mod in module.includes: - with open(path_pattern % mod, 'rb') as f: + with open(self.path_pattern % mod, 'rb') as f: mod = pickle.load(f) for attr in ('headerCode', 'cppCode', 'initializerCode', 'preInitializerCode', 'postInitializerCode', diff --git a/etgtools/cffi/typedef.py b/etgtools/cffi/typedef.py index fae0cda..b315faf 100644 --- a/etgtools/cffi/typedef.py +++ b/etgtools/cffi/typedef.py @@ -54,6 +54,7 @@ def print_pycode(self, pyfile, indent=0): indent)) def build_typeinfo(self, typeinfo): + typeinfo.original = self.type.name self.type.build_typeinfo(typeinfo) typeinfo.type = self diff --git a/etgtools/cffi_generator.py b/etgtools/cffi_generator.py index 499d7f3..915585b 100644 --- a/etgtools/cffi_generator.py +++ b/etgtools/cffi_generator.py @@ -43,7 +43,7 @@ def stripIgnoredItems(cls, items): if hasattr(e, 'overloads') and len(e.overloads) > 0: # If a method is ignored, replace it with the first # overload that isn't ignored - self.stripIgnoredItems(e.overloads) + cls.stripIgnoredItems(e.overloads) if len(e.overloads) > 0: e.overloads[0].overloads = e.overloads[1:] items[i] = e.overloads[0] diff --git a/etgtools/extractors.py b/etgtools/extractors.py index f469600..c48ecf8 100644 --- a/etgtools/extractors.py +++ b/etgtools/extractors.py @@ -14,6 +14,7 @@ import sys import os +import re import pprint import xml.etree.ElementTree as et @@ -1693,6 +1694,8 @@ def skippingMsg(kind, element): class ArgsString(list): + re = None + re_pattern = r'(?P[a-zA-Z0-9_ ]+[ *&]+)(?P[a-zA-Z0-9_]+)' """ Formating arguments strings: Parameters are only supported in the format of '