[pypy-commit] cffi default: Merge branch sirtom67/float_complex, adding support for complex numbers.
arigo
pypy.commits at gmail.com
Tue May 30 13:10:26 EDT 2017
Author: Armin Rigo <arigo at tunes.org>
Branch:
Changeset: r2948:71fd95269a8f
Date: 2017-05-30 19:10 +0200
http://bitbucket.org/cffi/cffi/changeset/71fd95269a8f/
Log: Merge branch sirtom67/float_complex, adding support for complex
numbers. Thanks sirtom67 for doing most of the work!
diff --git a/c/_cffi_backend.c b/c/_cffi_backend.c
--- a/c/_cffi_backend.c
+++ b/c/_cffi_backend.c
@@ -116,36 +116,38 @@
/************************************************************/
/* base type flag: exactly one of the following: */
-#define CT_PRIMITIVE_SIGNED 1 /* signed integer */
-#define CT_PRIMITIVE_UNSIGNED 2 /* unsigned integer */
-#define CT_PRIMITIVE_CHAR 4 /* char, wchar_t */
-#define CT_PRIMITIVE_FLOAT 8 /* float, double, long double */
-#define CT_POINTER 16 /* pointer, excluding ptr-to-func */
-#define CT_ARRAY 32 /* array */
-#define CT_STRUCT 64 /* struct */
-#define CT_UNION 128 /* union */
-#define CT_FUNCTIONPTR 256 /* pointer to function */
-#define CT_VOID 512 /* void */
+#define CT_PRIMITIVE_SIGNED 0x001 /* signed integer */
+#define CT_PRIMITIVE_UNSIGNED 0x002 /* unsigned integer */
+#define CT_PRIMITIVE_CHAR 0x004 /* char, wchar_t */
+#define CT_PRIMITIVE_FLOAT 0x008 /* float, double, long double */
+#define CT_POINTER 0x010 /* pointer, excluding ptr-to-func */
+#define CT_ARRAY 0x020 /* array */
+#define CT_STRUCT 0x040 /* struct */
+#define CT_UNION 0x080 /* union */
+#define CT_FUNCTIONPTR 0x100 /* pointer to function */
+#define CT_VOID 0x200 /* void */
+#define CT_PRIMITIVE_COMPLEX 0x400 /* float _Complex, double _Complex */
/* other flags that may also be set in addition to the base flag: */
-#define CT_IS_VOIDCHAR_PTR 1024
-#define CT_PRIMITIVE_FITS_LONG 2048
-#define CT_IS_OPAQUE 4096
-#define CT_IS_ENUM 8192
-#define CT_IS_PTR_TO_OWNED 16384 /* only owned if CDataOwning_Type */
-#define CT_CUSTOM_FIELD_POS 32768
-#define CT_IS_LONGDOUBLE 65536
-#define CT_IS_BOOL 131072
-#define CT_IS_FILE 262144
-#define CT_IS_VOID_PTR 524288
-#define CT_WITH_VAR_ARRAY 1048576
-#define CT_IS_UNSIZED_CHAR_A 2097152
-#define CT_LAZY_FIELD_LIST 4194304
-#define CT_WITH_PACKED_CHANGE 8388608
+#define CT_IS_VOIDCHAR_PTR 0x00001000
+#define CT_PRIMITIVE_FITS_LONG 0x00002000
+#define CT_IS_OPAQUE 0x00004000
+#define CT_IS_ENUM 0x00008000
+#define CT_IS_PTR_TO_OWNED 0x00010000 /* only owned if CDataOwning_Type */
+#define CT_CUSTOM_FIELD_POS 0x00020000
+#define CT_IS_LONGDOUBLE 0x00040000
+#define CT_IS_BOOL 0x00080000
+#define CT_IS_FILE 0x00100000
+#define CT_IS_VOID_PTR 0x00200000
+#define CT_WITH_VAR_ARRAY 0x00400000
+#define CT_IS_UNSIZED_CHAR_A 0x00800000
+#define CT_LAZY_FIELD_LIST 0x01000000
+#define CT_WITH_PACKED_CHANGE 0x02000000
#define CT_PRIMITIVE_ANY (CT_PRIMITIVE_SIGNED | \
CT_PRIMITIVE_UNSIGNED | \
CT_PRIMITIVE_CHAR | \
- CT_PRIMITIVE_FLOAT)
+ CT_PRIMITIVE_FLOAT | \
+ CT_PRIMITIVE_COMPLEX)
typedef struct _ctypedescr {
PyObject_VAR_HEAD
@@ -883,6 +885,26 @@
return 0;
}
+static Py_complex
+read_raw_complex_data(char *target, int size)
+{
+ Py_complex r = {0.0, 0.0};
+ if (size == 2*sizeof(float)) {
+ float real_part, imag_part;
+ memcpy(&real_part, target + 0, sizeof(float));
+ memcpy(&imag_part, target + sizeof(float), sizeof(float));
+ r.real = real_part;
+ r.imag = imag_part;
+ return r;
+ }
+ if (size == 2*sizeof(double)) {
+ memcpy(&r, target, 2*sizeof(double));
+ return r;
+ }
+ Py_FatalError("read_raw_complex_data: bad complex size");
+ return r;
+}
+
static void
write_raw_float_data(char *target, double source, int size)
{
@@ -898,6 +920,25 @@
_write_raw_data(long double);
}
+#define _write_raw_complex_data(type) \
+ do { \
+ if (size == 2*sizeof(type)) { \
+ type r = (type)source.real; \
+ type i = (type)source.imag; \
+ memcpy(target, &r, sizeof(type)); \
+ memcpy(target+sizeof(type), &i, sizeof(type)); \
+ return; \
+ } \
+ } while(0)
+
+static void
+write_raw_complex_data(char *target, Py_complex source, int size)
+{
+ _write_raw_complex_data(float);
+ _write_raw_complex_data(double);
+ Py_FatalError("write_raw_complex_data: bad complex size");
+}
+
static PyObject *
new_simple_cdata(char *data, CTypeDescrObject *ct)
{
@@ -1015,6 +1056,10 @@
return _my_PyUnicode_FromWideChar((wchar_t *)data, 1);
#endif
}
+ else if (ct->ct_flags & CT_PRIMITIVE_COMPLEX) {
+ Py_complex value = read_raw_complex_data(data, ct->ct_size);
+ return PyComplex_FromCComplex(value);
+ }
PyErr_Format(PyExc_SystemError,
"convert_to_object: '%s'", ct->ct_name);
@@ -1519,6 +1564,13 @@
}
return convert_struct_from_object(data, ct, init, NULL);
}
+ if (ct->ct_flags & CT_PRIMITIVE_COMPLEX) {
+ Py_complex value = PyComplex_AsCComplex(init);
+ if (PyErr_Occurred())
+ return -1;
+ write_raw_complex_data(data, value, ct->ct_size);
+ return 0;
+ }
PyErr_Format(PyExc_SystemError,
"convert_from_object: '%s'", ct->ct_name);
return -1;
@@ -1956,6 +2008,11 @@
return read_raw_longdouble_data(cd->c_data) != 0.0;
return read_raw_float_data(cd->c_data, cd->c_type->ct_size) != 0.0;
}
+ if (cd->c_type->ct_flags & CT_PRIMITIVE_COMPLEX) {
+ Py_complex value = read_raw_complex_data(cd->c_data,
+ cd->c_type->ct_size);
+ return value.real != 0.0 || value.imag != 0.0;
+ }
}
return cd->c_data != NULL;
}
@@ -2904,6 +2961,24 @@
}
}
+static PyObject *cdata_complex(PyObject *cd_, PyObject *noarg)
+{
+ CDataObject *cd = (CDataObject *)cd_;
+
+ if (cd->c_type->ct_flags & CT_PRIMITIVE_COMPLEX) {
+ Py_complex value = read_raw_complex_data(cd->c_data, cd->c_type->ct_size);
+ PyObject *op = PyComplex_FromCComplex(value);
+ return op;
+ }
+ /* <cdata 'float'> or <cdata 'int'> cannot be directly converted by
+ calling complex(), just like <cdata 'int'> cannot be directly
+ converted by calling float() */
+
+ PyErr_Format(PyExc_TypeError, "complex() not supported on cdata '%s'",
+ cd->c_type->ct_name);
+ return NULL;
+}
+
static PyObject *cdata_iter(CDataObject *);
static PyNumberMethods CData_as_number = {
@@ -2953,8 +3028,9 @@
};
static PyMethodDef cdata_methods[] = {
- {"__dir__", cdata_dir, METH_NOARGS},
- {NULL, NULL} /* sentinel */
+ {"__dir__", cdata_dir, METH_NOARGS},
+ {"__complex__", cdata_complex, METH_NOARGS},
+ {NULL, NULL} /* sentinel */
};
static PyTypeObject CData_Type = {
@@ -3587,6 +3663,31 @@
return cd;
}
+/* returns -1 if cannot cast, 0 if we don't get a value, 1 if we do */
+static int check_bytes_for_float_compatible(PyObject *io, double *out_value)
+{
+ if (PyBytes_Check(io)) {
+ if (PyBytes_GET_SIZE(io) != 1) {
+ Py_DECREF(io);
+ return -1;
+ }
+ *out_value = (unsigned char)PyBytes_AS_STRING(io)[0];
+ return 1;
+ }
+#if HAVE_WCHAR_H
+ else if (PyUnicode_Check(io)) {
+ wchar_t ordinal;
+ if (_my_PyUnicode_AsSingleWideChar(io, &ordinal) < 0) {
+ Py_DECREF(io);
+ return -1;
+ }
+ *out_value = (long)ordinal;
+ return 1;
+ }
+#endif
+ return 0;
+}
+
static PyObject *do_cast(CTypeDescrObject *ct, PyObject *ob)
{
CDataObject *cd;
@@ -3628,6 +3729,7 @@
/* cast to a float */
double value;
PyObject *io;
+ int res;
if (CData_Check(ob)) {
CDataObject *cdsrc = (CDataObject *)ob;
@@ -3643,37 +3745,23 @@
Py_INCREF(io);
}
- if (PyBytes_Check(io)) {
- if (PyBytes_GET_SIZE(io) != 1) {
- Py_DECREF(io);
- goto cannot_cast;
- }
- value = (unsigned char)PyBytes_AS_STRING(io)[0];
- }
-#if HAVE_WCHAR_H
- else if (PyUnicode_Check(io)) {
- wchar_t ordinal;
- if (_my_PyUnicode_AsSingleWideChar(io, &ordinal) < 0) {
- Py_DECREF(io);
- goto cannot_cast;
- }
- value = (long)ordinal;
- }
-#endif
- else if ((ct->ct_flags & CT_IS_LONGDOUBLE) &&
+ res = check_bytes_for_float_compatible(io, &value);
+ if (res == -1)
+ goto cannot_cast;
+ if (res == 0) {
+ if ((ct->ct_flags & CT_IS_LONGDOUBLE) &&
CData_Check(io) &&
(((CDataObject *)io)->c_type->ct_flags & CT_IS_LONGDOUBLE)) {
- long double lvalue;
- char *data = ((CDataObject *)io)->c_data;
- /*READ(data, sizeof(long double)*/
- lvalue = read_raw_longdouble_data(data);
- Py_DECREF(io);
- cd = _new_casted_primitive(ct);
- if (cd != NULL)
- write_raw_longdouble_data(cd->c_data, lvalue);
- return (PyObject *)cd;
- }
- else {
+ long double lvalue;
+ char *data = ((CDataObject *)io)->c_data;
+ /*READ(data, sizeof(long double)*/
+ lvalue = read_raw_longdouble_data(data);
+ Py_DECREF(io);
+ cd = _new_casted_primitive(ct);
+ if (cd != NULL)
+ write_raw_longdouble_data(cd->c_data, lvalue);
+ return (PyObject *)cd;
+ }
value = PyFloat_AsDouble(io);
}
Py_DECREF(io);
@@ -3689,6 +3777,45 @@
}
return (PyObject *)cd;
}
+ else if (ct->ct_flags & CT_PRIMITIVE_COMPLEX) {
+ /* cast to a complex */
+ Py_complex value;
+ PyObject *io;
+ int res;
+
+ if (CData_Check(ob)) {
+ CDataObject *cdsrc = (CDataObject *)ob;
+
+ if (!(cdsrc->c_type->ct_flags & CT_PRIMITIVE_ANY))
+ goto cannot_cast;
+ io = convert_to_object(cdsrc->c_data, cdsrc->c_type);
+ if (io == NULL)
+ return NULL;
+ }
+ else {
+ io = ob;
+ Py_INCREF(io);
+ }
+
+ res = check_bytes_for_float_compatible(io, &value.real);
+ if (res == -1)
+ goto cannot_cast;
+ if (res == 1) {
+ // got it from string
+ value.imag = 0.0;
+ } else {
+ value = PyComplex_AsCComplex(io);
+ }
+ Py_DECREF(io);
+ if (PyErr_Occurred()) {
+ return NULL;
+ }
+ cd = _new_casted_primitive(ct);
+ if (cd != NULL) {
+ write_raw_complex_data(cd->c_data, value, ct->ct_size);
+ }
+ return (PyObject *)cd;
+ }
else {
PyErr_Format(PyExc_TypeError, "cannot cast to ctype '%s'",
ct->ct_name);
@@ -3954,6 +4081,11 @@
return NULL;
}
+/* according to the C standard, these types should be equivalent to the
+ _Complex types for the purposes of storage (not arguments in calls!) */
+typedef float cffi_float_complex_t[2];
+typedef double cffi_double_complex_t[2];
+
static PyObject *new_primitive_type(const char *name)
{
#define ENUM_PRIMITIVE_TYPES \
@@ -3971,6 +4103,8 @@
EPTYPE(f, float, CT_PRIMITIVE_FLOAT ) \
EPTYPE(d, double, CT_PRIMITIVE_FLOAT ) \
EPTYPE(ld, long double, CT_PRIMITIVE_FLOAT | CT_IS_LONGDOUBLE ) \
+ EPTYPE2(fc, "float _Complex", cffi_float_complex_t, CT_PRIMITIVE_COMPLEX ) \
+ EPTYPE2(dc, "double _Complex", cffi_double_complex_t, CT_PRIMITIVE_COMPLEX ) \
ENUM_PRIMITIVE_TYPES_WCHAR \
EPTYPE(b, _Bool, CT_PRIMITIVE_UNSIGNED | CT_IS_BOOL ) \
/* the following types are not primitive in the C sense */ \
@@ -4081,6 +4215,13 @@
else
goto bad_ffi_type;
}
+ else if (ptypes->flags & CT_PRIMITIVE_COMPLEX) {
+ /* As of March 2017, still no libffi support for complex.
+ It fails silently if we try to use ffi_type_complex_float
+ or ffi_type_complex_double. Better not use it at all.
+ */
+ ffitype = NULL;
+ }
else {
switch (ptypes->size) {
case 1: ffitype = &ffi_type_uint8; break;
@@ -4773,7 +4914,7 @@
{
const char *place = is_result_type ? "return value" : "argument";
- if (ct->ct_flags & CT_PRIMITIVE_ANY) {
+ if (ct->ct_flags & (CT_PRIMITIVE_ANY & ~CT_PRIMITIVE_COMPLEX)) {
return (ffi_type *)ct->ct_extra;
}
else if (ct->ct_flags & (CT_POINTER|CT_FUNCTIONPTR)) {
@@ -4899,9 +5040,16 @@
return NULL;
}
else {
+ char *extra = "";
+ if (ct->ct_flags & CT_PRIMITIVE_COMPLEX)
+ extra = " (the support for complex types inside libffi "
+ "is mostly missing at this point, so CFFI only "
+ "supports complex types as arguments or return "
+ "value in API-mode functions)";
+
PyErr_Format(PyExc_NotImplementedError,
- "ctype '%s' (size %zd) not supported as %s",
- ct->ct_name, ct->ct_size, place);
+ "ctype '%s' (size %zd) not supported as %s%s",
+ ct->ct_name, ct->ct_size, place, extra);
return NULL;
}
}
@@ -6579,6 +6727,20 @@
return -42;
}
+#if 0 /* libffi doesn't properly support complexes currently */
+ /* also, MSVC might not support _Complex... */
+ /* if this is enabled one day, remember to also add _Complex
+ * arguments in addition to return values. */
+static float _Complex _testfunc24(float a, float b)
+{
+ return a + I*2.0*b;
+}
+static double _Complex _testfunc25(double a, double b)
+{
+ return a + I*2.0*b;
+}
+#endif
+
static PyObject *b__testfunc(PyObject *self, PyObject *args)
{
/* for testing only */
@@ -6611,6 +6773,10 @@
case 21: f = &_testfunc21; break;
case 22: f = &_testfunc22; break;
case 23: f = &_testfunc23; break;
+#if 0
+ case 24: f = &_testfunc24; break;
+ case 25: f = &_testfunc25; break;
+#endif
default:
PyErr_SetNone(PyExc_ValueError);
return NULL;
diff --git a/c/parse_c_type.c b/c/parse_c_type.c
--- a/c/parse_c_type.c
+++ b/c/parse_c_type.c
@@ -25,7 +25,7 @@
/* keywords */
TOK__BOOL,
TOK_CHAR,
- //TOK__COMPLEX,
+ TOK__COMPLEX,
TOK_CONST,
TOK_DOUBLE,
TOK_ENUM,
@@ -159,6 +159,7 @@
if (tok->size == 5 && !memcmp(p, "_Bool", 5)) tok->kind = TOK__BOOL;
if (tok->size == 7 && !memcmp(p,"__cdecl",7)) tok->kind = TOK_CDECL;
if (tok->size == 9 && !memcmp(p,"__stdcall",9))tok->kind = TOK_STDCALL;
+ if (tok->size == 8 && !memcmp(p,"_Complex",8)) tok->kind = TOK__COMPLEX;
break;
case 'c':
if (tok->size == 4 && !memcmp(p, "char", 4)) tok->kind = TOK_CHAR;
@@ -601,6 +602,7 @@
{
unsigned int t0;
_cffi_opcode_t t1;
+ _cffi_opcode_t t1complex;
int modifiers_length, modifiers_sign;
qualifiers:
@@ -656,6 +658,8 @@
break;
}
+ t1complex = 0;
+
if (modifiers_length || modifiers_sign) {
switch (tok->kind) {
@@ -666,6 +670,7 @@
case TOK_STRUCT:
case TOK_UNION:
case TOK_ENUM:
+ case TOK__COMPLEX:
return parse_error(tok, "invalid combination of types");
case TOK_DOUBLE:
@@ -719,9 +724,11 @@
break;
case TOK_FLOAT:
t1 = _CFFI_OP(_CFFI_OP_PRIMITIVE, _CFFI_PRIM_FLOAT);
+ t1complex = _CFFI_OP(_CFFI_OP_PRIMITIVE, _CFFI_PRIM_FLOATCOMPLEX);
break;
case TOK_DOUBLE:
t1 = _CFFI_OP(_CFFI_OP_PRIMITIVE, _CFFI_PRIM_DOUBLE);
+ t1complex = _CFFI_OP(_CFFI_OP_PRIMITIVE, _CFFI_PRIM_DOUBLECOMPLEX);
break;
case TOK_IDENTIFIER:
{
@@ -788,6 +795,13 @@
}
next_token(tok);
}
+ if (tok->kind == TOK__COMPLEX)
+ {
+ if (t1complex == 0)
+ return parse_error(tok, "_Complex type combination unsupported");
+ t1 = t1complex;
+ next_token(tok);
+ }
return parse_sequel(tok, write_ds(tok, t1));
}
diff --git a/c/realize_c_type.c b/c/realize_c_type.c
--- a/c/realize_c_type.c
+++ b/c/realize_c_type.c
@@ -151,6 +151,8 @@
"uint_fast64_t",
"intmax_t",
"uintmax_t",
+ "float _Complex",
+ "double _Complex",
};
PyObject *x;
diff --git a/c/test_c.py b/c/test_c.py
--- a/c/test_c.py
+++ b/c/test_c.py
@@ -185,37 +185,56 @@
py.test.raises(TypeError, cast, p, None)
def test_complex_types():
- py.test.skip("later")
INF = 1E200 * 1E200
for name in ["float", "double"]:
- p = new_primitive_type("_Complex " + name)
- assert bool(cast(p, 0))
+ p = new_primitive_type(name + " _Complex")
+ assert bool(cast(p, 0)) is False
assert bool(cast(p, INF))
assert bool(cast(p, -INF))
- assert bool(cast(p, 0j))
+ assert bool(cast(p, 0j)) is False
assert bool(cast(p, INF*1j))
assert bool(cast(p, -INF*1j))
+ # "can't convert complex to float", like CPython's "float(0j)"
py.test.raises(TypeError, int, cast(p, -150))
py.test.raises(TypeError, long, cast(p, -150))
py.test.raises(TypeError, float, cast(p, -150))
assert complex(cast(p, 1.25)) == 1.25
assert complex(cast(p, 1.25j)) == 1.25j
- assert float(cast(p, INF*1j)) == INF*1j
- assert float(cast(p, -INF)) == -INF
+ assert complex(cast(p, complex(0,INF))) == complex(0,INF)
+ assert complex(cast(p, -INF)) == -INF
if name == "float":
assert complex(cast(p, 1.1j)) != 1.1j # rounding error
assert complex(cast(p, 1E200+3j)) == INF+3j # limited range
- assert complex(cast(p, 3+1E200j)) == 3+INF*1j # limited range
+ assert complex(cast(p, complex(3,1E200))) == complex(3,INF) # limited range
- assert cast(p, -1.1j) != cast(p, -1.1j)
+ assert cast(p, -1.1j) == cast(p, -1.1j)
assert repr(complex(cast(p, -0.0)).real) == '-0.0'
- assert repr(complex(cast(p, -0j))) == '-0j'
- assert complex(cast(p, '\x09')) == 9.0
- assert complex(cast(p, True)) == 1.0
+ #assert repr(complex(cast(p, -0j))) == '-0j' # http://bugs.python.org/issue29602
+ assert complex(cast(p, b'\x09')) == 9.0 + 0j
+ assert complex(cast(p, u+'\x09')) == 9.0 + 0j
+ assert complex(cast(p, True)) == 1.0 + 0j
py.test.raises(TypeError, cast, p, None)
#
- py.test.raises(cast, new_primitive_type(name), 1+2j)
- py.test.raises(cast, new_primitive_type("int"), 1+2j)
+ py.test.raises(TypeError, cast, new_primitive_type(name), 1+0j)
+ #
+ for basetype in ["char", "int", "uint64_t", "float",
+ "double", "long double"]:
+ baseobj = cast(new_primitive_type(basetype), 65)
+ py.test.raises(TypeError, complex, baseobj)
+ #
+ BArray = new_array_type(new_pointer_type(p), 10)
+ x = newp(BArray, None)
+ x[5] = 12.34 + 56.78j
+ assert type(x[5]) is complex
+ assert abs(x[5] - (12.34 + 56.78j)) < 1e-5
+ assert (x[5] == 12.34 + 56.78j) == (name == "double") # rounding error
+ #
+ class Foo:
+ def __complex__(self):
+ return 2 + 3j
+ assert complex(Foo()) == 2 + 3j
+ assert complex(cast(p, Foo())) == 2 + 3j
+ py.test.raises(TypeError, cast, new_primitive_type("int"), 1+0j)
def test_character_type():
p = new_primitive_type("char")
@@ -1116,6 +1135,34 @@
BSShort = new_primitive_type("short")
assert f(3, cast(BSChar, -3), cast(BUChar, 200), cast(BSShort, -5)) == 192
+def test_call_function_24():
+ BFloat = new_primitive_type("float")
+ BFloatComplex = new_primitive_type("float _Complex")
+ BFunc3 = new_function_type((BFloat, BFloat), BFloatComplex, False)
+ if 0: # libffi returning nonsense silently, so logic disabled for now
+ f = cast(BFunc3, _testfunc(24))
+ result = f(1.25, 5.1)
+ assert type(result) == complex
+ assert result.real == 1.25 # exact
+ assert (result.imag != 2*5.1) and (abs(result.imag - 2*5.1) < 1e-5) # inexact
+ else:
+ f = cast(BFunc3, _testfunc(9))
+ py.test.raises(NotImplementedError, f, 12.3, 34.5)
+
+def test_call_function_25():
+ BDouble = new_primitive_type("double")
+ BDoubleComplex = new_primitive_type("double _Complex")
+ BFunc3 = new_function_type((BDouble, BDouble), BDoubleComplex, False)
+ if 0: # libffi returning nonsense silently, so logic disabled for now
+ f = cast(BFunc3, _testfunc(25))
+ result = f(1.25, 5.1)
+ assert type(result) == complex
+ assert result.real == 1.25 # exact
+ assert (result.imag != 2*5.1) and (abs(result.imag - 2*5.1) < 1e-10) # inexact
+ else:
+ f = cast(BFunc3, _testfunc(9))
+ py.test.raises(NotImplementedError, f, 12.3, 34.5)
+
def test_cannot_call_with_a_autocompleted_struct():
BSChar = new_primitive_type("signed char")
BDouble = new_primitive_type("double")
diff --git a/cffi/cffi_opcode.py b/cffi/cffi_opcode.py
--- a/cffi/cffi_opcode.py
+++ b/cffi/cffi_opcode.py
@@ -105,8 +105,11 @@
PRIM_UINT_FAST64 = 45
PRIM_INTMAX = 46
PRIM_UINTMAX = 47
+PRIM_FLOATCOMPLEX = 48
+PRIM_DOUBLECOMPLEX = 49
-_NUM_PRIM = 48
+
+_NUM_PRIM = 50
_UNKNOWN_PRIM = -1
_UNKNOWN_FLOAT_PRIM = -2
_UNKNOWN_LONG_DOUBLE = -3
@@ -128,6 +131,8 @@
'float': PRIM_FLOAT,
'double': PRIM_DOUBLE,
'long double': PRIM_LONGDOUBLE,
+ 'float _Complex': PRIM_FLOATCOMPLEX,
+ 'double _Complex': PRIM_DOUBLECOMPLEX,
'_Bool': PRIM_BOOL,
'wchar_t': PRIM_WCHAR,
'int8_t': PRIM_INT8,
diff --git a/cffi/model.py b/cffi/model.py
--- a/cffi/model.py
+++ b/cffi/model.py
@@ -95,7 +95,8 @@
class BasePrimitiveType(BaseType):
- pass
+ def is_complex_type(self):
+ return False
class PrimitiveType(BasePrimitiveType):
@@ -116,6 +117,8 @@
'float': 'f',
'double': 'f',
'long double': 'f',
+ 'float _Complex': 'j',
+ 'double _Complex': 'j',
'_Bool': 'i',
# the following types are not primitive in the C sense
'wchar_t': 'c',
@@ -163,6 +166,8 @@
return self.ALL_PRIMITIVE_TYPES[self.name] == 'i'
def is_float_type(self):
return self.ALL_PRIMITIVE_TYPES[self.name] == 'f'
+ def is_complex_type(self):
+ return self.ALL_PRIMITIVE_TYPES[self.name] == 'j'
def build_backend_type(self, ffi, finishlist):
return global_cache(self, ffi, 'new_primitive_type', self.name)
diff --git a/cffi/parse_c_type.h b/cffi/parse_c_type.h
--- a/cffi/parse_c_type.h
+++ b/cffi/parse_c_type.h
@@ -79,8 +79,10 @@
#define _CFFI_PRIM_UINT_FAST64 45
#define _CFFI_PRIM_INTMAX 46
#define _CFFI_PRIM_UINTMAX 47
+#define _CFFI_PRIM_FLOATCOMPLEX 48
+#define _CFFI_PRIM_DOUBLECOMPLEX 49
-#define _CFFI__NUM_PRIM 48
+#define _CFFI__NUM_PRIM 50
#define _CFFI__UNKNOWN_PRIM (-1)
#define _CFFI__UNKNOWN_FLOAT_PRIM (-2)
#define _CFFI__UNKNOWN_LONG_DOUBLE (-3)
diff --git a/cffi/recompiler.py b/cffi/recompiler.py
--- a/cffi/recompiler.py
+++ b/cffi/recompiler.py
@@ -506,7 +506,7 @@
def _convert_funcarg_to_c(self, tp, fromvar, tovar, errcode):
extraarg = ''
- if isinstance(tp, model.BasePrimitiveType):
+ if isinstance(tp, model.BasePrimitiveType) and not tp.is_complex_type():
if tp.is_integer_type() and tp.name != '_Bool':
converter = '_cffi_to_c_int'
extraarg = ', %s' % tp.name
@@ -524,8 +524,10 @@
tovar, errcode)
return
#
- elif isinstance(tp, model.StructOrUnionOrEnum):
- # a struct (not a struct pointer) as a function argument
+ elif (isinstance(tp, model.StructOrUnionOrEnum) or
+ isinstance(tp, model.BasePrimitiveType)):
+ # a struct (not a struct pointer) as a function argument;
+ # or, a complex (the same code works)
self._prnt(' if (_cffi_to_c((char *)&%s, _cffi_type(%d), %s) < 0)'
% (tovar, self._gettypenum(tp), fromvar))
self._prnt(' %s;' % errcode)
@@ -570,7 +572,7 @@
return '_cffi_from_c_int(%s, %s)' % (var, tp.name)
elif isinstance(tp, model.UnknownFloatType):
return '_cffi_from_c_double(%s)' % (var,)
- elif tp.name != 'long double':
+ elif tp.name != 'long double' and not tp.is_complex_type():
return '_cffi_from_c_%s(%s)' % (tp.name.replace(' ', '_'), var)
else:
return '_cffi_from_c_deref((char *)&%s, _cffi_type(%d))' % (
diff --git a/doc/source/cdef.rst b/doc/source/cdef.rst
--- a/doc/source/cdef.rst
+++ b/doc/source/cdef.rst
@@ -642,16 +642,15 @@
* Any ``__attribute__`` or ``#pragma pack(n)``
-* Additional types: complex numbers, special-size floating and fixed
- point types, vector types, and so on. You might be able to access an
- array of complex numbers by declaring it as an array of ``struct
- my_complex { double real, imag; }``, but in general you should declare
- them as ``struct { ...; }`` and cannot access them directly. This
- means that you cannot call any function which has an argument or
- return value of this type (this would need added support in libffi).
- You need to write wrapper functions in C, e.g. ``void
- foo_wrapper(struct my_complex c) { foo(c.real + c.imag*1j); }``, and
- call ``foo_wrapper`` rather than ``foo`` directly.
+* Additional types: special-size floating and fixed
+ point types, vector types, and so on.
+
+* The C99 types ``float _Complex`` and ``double _Complex`` are supported
+ by cffi since version 1.11, but not libffi: you cannot call C
+ functions with complex arguments or return value, except if they are
+ directly API-mode functions. The type ``long double _Complex`` is not
+ supported at all (declare and use it as if it were an array of two
+ ``long double``, and write wrapper functions in C with set_source()).
Note that declarations like ``int field[];`` in
structures are interpreted as variable-length structures. Declarations
diff --git a/doc/source/ref.rst b/doc/source/ref.rst
--- a/doc/source/ref.rst
+++ b/doc/source/ref.rst
@@ -618,8 +618,8 @@
| C type | writing into | reading from |other operations|
+===============+========================+==================+================+
| integers | an integer or anything | a Python int or | int(), bool() |
-| and enums | on which int() works | long, depending | `(******)`, |
-| `(*****)` | (but not a float!). | on the type | ``<`` |
+| and enums | on which int() works | long, depending | `[6]`, |
+| `[5]` | (but not a float!). | on the type | ``<`` |
| | Must be within range. | (ver. 1.10: or a | |
| | | bool) | |
+---------------+------------------------+------------------+----------------+
@@ -636,14 +636,19 @@
+---------------+------------------------+------------------+----------------+
|``long double``| another <cdata> with | a <cdata>, to | float(), int(),|
| | a ``long double``, or | avoid loosing | bool() |
-| | anything on which | precision `(***)`| |
+| | anything on which | precision `[3]` | |
| | float() works | | |
+---------------+------------------------+------------------+----------------+
-| pointers | another <cdata> with | a <cdata> |``[]`` `(****)`,|
+| ``float`` | a complex number | a Python complex | complex(), |
+| ``_Complex``, | or anything on which | number | bool() |
+| ``double`` | complex() works | | `[7]` |
+| ``_Complex`` | | | |
++---------------+------------------------+------------------+----------------+
+| pointers | another <cdata> with | a <cdata> |``[]`` `[4]`, |
| | a compatible type (i.e.| |``+``, ``-``, |
| | same type | |bool() |
| | or ``void*``, or as an | | |
-| | array instead) `(*)` | | |
+| | array instead) `[1]` | | |
+---------------+------------------------+ | |
| ``void *`` | another <cdata> with | | |
| | any pointer or array | | |
@@ -655,10 +660,10 @@
| | | | struct fields |
+---------------+------------------------+ +----------------+
| function | same as pointers | | bool(), |
-| pointers | | | call `(**)` |
+| pointers | | | call `[2]` |
+---------------+------------------------+------------------+----------------+
| arrays | a list or tuple of | a <cdata> |len(), iter(), |
-| | items | |``[]`` `(****)`,|
+| | items | |``[]`` `[4]`, |
| | | |``+``, ``-`` |
+---------------+------------------------+ +----------------+
| ``char[]``, | same as arrays, or a | | len(), iter(), |
@@ -680,7 +685,7 @@
| | with at most one field | | fields |
+---------------+------------------------+------------------+----------------+
-`(*)` ``item *`` is ``item[]`` in function arguments:
+`[1]` ``item *`` is ``item[]`` in function arguments:
In a function declaration, as per the C standard, a ``item *``
argument is identical to a ``item[]`` argument (and ``ffi.cdef()``
@@ -701,7 +706,7 @@
(On PyPy, this optimization is only available since PyPy 5.4
with CFFI 1.8.)
-`(**)` C function calls are done with the GIL released.
+`[2]` C function calls are done with the GIL released.
Note that we assume that the called functions are *not* using the
Python API from Python.h. For example, we don't check afterwards
@@ -713,7 +718,7 @@
``libpypy-c.dll`` on their own. But really, don't do that in the
first place.)
-`(***)` ``long double`` support:
+`[3]` ``long double`` support:
We keep ``long double`` values inside a cdata object to avoid
loosing precision. Normal Python floating-point numbers only
@@ -724,7 +729,7 @@
and use a family of C functions like ``long double add(long double
a, long double b);``.
-`(****)` Slicing with ``x[start:stop]``:
+`[4]` Slicing with ``x[start:stop]``:
Slicing is allowed, as long as you specify explicitly both ``start``
and ``stop`` (and don't give any ``step``). It gives a cdata
@@ -738,7 +743,7 @@
say ``chararray[10:15] = "hello"``, but the assigned string must be of
exactly the correct length; no implicit null character is added.)
-`(*****)` Enums are handled like ints:
+`[5]` Enums are handled like ints:
Like C, enum types are mostly int types (unsigned or signed, int or
long; note that GCC's first choice is unsigned). Reading an enum
@@ -747,7 +752,7 @@
lib.FOO``. If you really want to get their value as a string, use
``ffi.string(ffi.cast("the_enum_type", x.field))``.
-`(******)` bool() on a primitive cdata:
+`[6]` bool() on a primitive cdata:
*New in version 1.7.* In previous versions, it only worked on
pointers; for primitives it always returned True.
@@ -760,6 +765,13 @@
Also, when converting from a byte string to a ``_Bool[]``, only the
bytes ``\x00`` and ``\x01`` are accepted.
+`[7]` libffi does not support complex numbers:
+
+ *New in version 1.11:* CFFI now supports complex numbers directly.
+ Note however that libffi does not. This means that C functions that
+ take directly as argument types or return type a complex type cannot
+ be called by CFFI, unless they are directly using the API mode.
+
.. _file:
Support for FILE
diff --git a/testing/cffi0/test_verify.py b/testing/cffi0/test_verify.py
--- a/testing/cffi0/test_verify.py
+++ b/testing/cffi0/test_verify.py
@@ -239,15 +239,18 @@
tp = model.PrimitiveType(typename)
C = tp.is_char_type()
F = tp.is_float_type()
+ X = tp.is_complex_type()
I = tp.is_integer_type()
assert C == (typename in ('char', 'wchar_t'))
assert F == (typename in ('float', 'double', 'long double'))
- assert I + F + C == 1 # one and only one of them is true
+ assert X == (typename in ('float _Complex', 'double _Complex'))
+ assert I + F + C + X == 1 # one and only one of them is true
def test_all_integer_and_float_types():
typenames = []
for typename in all_primitive_types:
if (all_primitive_types[typename] == 'c' or
+ all_primitive_types[typename] == 'j' or # complex
typename == '_Bool' or typename == 'long double'):
pass
else:
diff --git a/testing/cffi1/test_new_ffi_1.py b/testing/cffi1/test_new_ffi_1.py
--- a/testing/cffi1/test_new_ffi_1.py
+++ b/testing/cffi1/test_new_ffi_1.py
@@ -1704,6 +1704,8 @@
"ptrdiff_t",
"size_t",
"ssize_t",
+ 'double _Complex',
+ 'float _Complex',
])
for name in PRIMITIVE_TO_INDEX:
x = ffi.sizeof(name)
diff --git a/testing/cffi1/test_parse_c_type.py b/testing/cffi1/test_parse_c_type.py
--- a/testing/cffi1/test_parse_c_type.py
+++ b/testing/cffi1/test_parse_c_type.py
@@ -155,6 +155,8 @@
("long int", lib._CFFI_PRIM_LONG),
("unsigned short", lib._CFFI_PRIM_USHORT),
("long double", lib._CFFI_PRIM_LONGDOUBLE),
+ (" float _Complex", lib._CFFI_PRIM_FLOATCOMPLEX),
+ ("double _Complex ", lib._CFFI_PRIM_DOUBLECOMPLEX),
]:
assert parse(simple_type) == ['->', Prim(expected)]
@@ -280,6 +282,11 @@
parse_error("int[5](*)", "unexpected symbol", 6)
parse_error("int a(*)", "identifier expected", 6)
parse_error("int[123456789012345678901234567890]", "number too large", 4)
+ #
+ parse_error("_Complex", "identifier expected", 0)
+ parse_error("int _Complex", "_Complex type combination unsupported", 4)
+ parse_error("long double _Complex", "_Complex type combination unsupported",
+ 12)
def test_number_too_large():
num_max = sys.maxsize
diff --git a/testing/cffi1/test_realize_c_type.py b/testing/cffi1/test_realize_c_type.py
--- a/testing/cffi1/test_realize_c_type.py
+++ b/testing/cffi1/test_realize_c_type.py
@@ -47,7 +47,6 @@
for name in cffi_opcode.PRIMITIVE_TO_INDEX:
check(name, name)
-
def check_func(input, expected_output=None):
import _cffi_backend
ffi = _cffi_backend.FFI()
diff --git a/testing/cffi1/test_recompiler.py b/testing/cffi1/test_recompiler.py
--- a/testing/cffi1/test_recompiler.py
+++ b/testing/cffi1/test_recompiler.py
@@ -2001,6 +2001,60 @@
""")
assert lib.f1(52).a == 52
+def test_function_returns_float_complex():
+ if sys.platform == 'win32':
+ py.test.skip("MSVC may not support _Complex")
+ ffi = FFI()
+ ffi.cdef("float _Complex f1(float a, float b);");
+ lib = verify(ffi, "test_function_returns_float_complex", """
+ #include <complex.h>
+ static float _Complex f1(float a, float b) { return a + I*2.0*b; }
+ """)
+ result = lib.f1(1.25, 5.1)
+ assert type(result) == complex
+ assert result.real == 1.25 # exact
+ assert (result.imag != 2*5.1) and (abs(result.imag - 2*5.1) < 1e-5) # inexact
+
+def test_function_returns_double_complex():
+ if sys.platform == 'win32':
+ py.test.skip("MSVC may not support _Complex")
+ ffi = FFI()
+ ffi.cdef("double _Complex f1(double a, double b);");
+ lib = verify(ffi, "test_function_returns_double_complex", """
+ #include <complex.h>
+ static double _Complex f1(double a, double b) { return a + I*2.0*b; }
+ """)
+ result = lib.f1(1.25, 5.1)
+ assert type(result) == complex
+ assert result.real == 1.25 # exact
+ assert result.imag == 2*5.1 # exact
+
+def test_function_argument_float_complex():
+ if sys.platform == 'win32':
+ py.test.skip("MSVC may not support _Complex")
+ ffi = FFI()
+ ffi.cdef("float f1(float _Complex x);");
+ lib = verify(ffi, "test_function_argument_float_complex", """
+ #include <complex.h>
+ static float f1(float _Complex x) { return cabsf(x); }
+ """)
+ x = complex(12.34, 56.78)
+ result = lib.f1(x)
+ assert abs(result - abs(x)) < 1e-5
+
+def test_function_argument_double_complex():
+ if sys.platform == 'win32':
+ py.test.skip("MSVC may not support _Complex")
+ ffi = FFI()
+ ffi.cdef("double f1(double _Complex);");
+ lib = verify(ffi, "test_function_argument_double_complex", """
+ #include <complex.h>
+ static double f1(double _Complex x) { return cabs(x); }
+ """)
+ x = complex(12.34, 56.78)
+ result = lib.f1(x)
+ assert abs(result - abs(x)) < 1e-11
+
def test_typedef_array_dotdotdot():
ffi = FFI()
ffi.cdef("""
diff --git a/testing/cffi1/test_verify1.py b/testing/cffi1/test_verify1.py
--- a/testing/cffi1/test_verify1.py
+++ b/testing/cffi1/test_verify1.py
@@ -219,15 +219,18 @@
tp = model.PrimitiveType(typename)
C = tp.is_char_type()
F = tp.is_float_type()
+ X = tp.is_complex_type()
I = tp.is_integer_type()
assert C == (typename in ('char', 'wchar_t'))
assert F == (typename in ('float', 'double', 'long double'))
- assert I + F + C == 1 # one and only one of them is true
+ assert X == (typename in ('float _Complex', 'double _Complex'))
+ assert I + F + C + X == 1 # one and only one of them is true
def test_all_integer_and_float_types():
typenames = []
for typename in all_primitive_types:
if (all_primitive_types[typename] == 'c' or
+ all_primitive_types[typename] == 'j' or # complex
typename == '_Bool' or typename == 'long double'):
pass
else:
More information about the pypy-commit
mailing list