Skip to content

Commit

Permalink
Fix some issues with basic C types (mostly signed/unsigned types)
Browse files Browse the repository at this point in the history
  • Loading branch information
aprilwade committed Dec 26, 2013
1 parent 5d4a48c commit 66255c2
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 35 deletions.
54 changes: 54 additions & 0 deletions cffi/test/test_bindgen.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
};
37 changes: 37 additions & 0 deletions cffi/test/test_bindgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -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=[
Expand Down Expand Up @@ -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

Expand Down
3 changes: 3 additions & 0 deletions etgtools/cffi/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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_'
Expand Down
106 changes: 74 additions & 32 deletions etgtools/cffi/basictype.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@

import re
import sys
import warnings
from binascii import crc32
Expand All @@ -9,21 +10,20 @@

# 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',
'float': 'float',
'double': 'float',
'char': 'str',
'wchar_t': 'unicode',
'char*': 'str',
'char *': 'str',
'signed char': 'int',
'unsigned char': 'int',
'char': 'str',
'bool': 'bool',
}

Expand All @@ -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
Expand All @@ -47,24 +48,30 @@ 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

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)

Expand All @@ -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'

Expand All @@ -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
Expand All @@ -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:
Expand All @@ -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 ''
Expand Down Expand Up @@ -171,29 +185,57 @@ 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):
self.name = 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:
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion etgtools/cffi/bindgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
1 change: 1 addition & 0 deletions etgtools/cffi/typedef.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion etgtools/cffi_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
Loading

0 comments on commit 66255c2

Please sign in to comment.