Commits

Armin Rigo  committed 2b7869e

Implementation

  • Participants
  • Parent commits 71656eb
  • Branches enum-as-int

Comments (0)

Files changed (6)

File c/_cffi_backend.c

     return (PyObject *)cd;
 }
 
-static PyObject *convert_enum_string_to_int(CTypeDescrObject *ct, PyObject *ob)
+static PyObject *convert_cdata_to_enum_string(CDataObject *cd, int both)
 {
-    PyObject *d_value;
-    char *p = PyText_AsUTF8(ob);
-    if (p == NULL)
+    int value;
+    PyObject *d_key, *d_value;
+    CTypeDescrObject *ct = cd->c_type;
+
+    assert(ct->ct_flags & CT_IS_ENUM);
+    value = (int)read_raw_signed_data(cd->c_data, ct->ct_size);
+    d_key = PyInt_FromLong(value);
+    if (d_key == NULL)
         return NULL;
 
-    if (p[0] == '#') {
-        char *number = p + 1;       /* strip initial '#' */
-        PyObject *ob2 = PyText_FromString(number);
-        if (ob2 == NULL)
-            return NULL;
-
-        d_value = PyNumber_Long(ob2);
-        Py_DECREF(ob2);
-    }
-    else {
-        d_value = PyDict_GetItem(PyTuple_GET_ITEM(ct->ct_stuff, 0), ob);
-        if (d_value == NULL) {
-            PyErr_Format(PyExc_ValueError,
-                         "'%s' is not an enumerator for %s",
-                         p, ct->ct_name);
-            return NULL;
-        }
-        Py_INCREF(d_value);
-    }
+    d_value = PyDict_GetItem(PyTuple_GET_ITEM(ct->ct_stuff, 1), d_key);
+    if (d_value != NULL) {
+        if (both)
+            d_value = PyText_FromFormat("%d: %s", value,
+                                        PyText_AS_UTF8(d_value));
+        else
+            Py_INCREF(d_value);
+    }
+    else
+        d_value = PyObject_Str(d_key);
+    Py_DECREF(d_key);
     return d_value;
 }
 
         PY_LONG_LONG value;
         /*READ(data, ct->ct_size)*/
         value = read_raw_signed_data(data, ct->ct_size);
-
-        if (ct->ct_flags & CT_IS_ENUM) {
-            PyObject *d_value, *d_key = PyInt_FromLong((int)value);
-            if (d_key == NULL)
-                return NULL;
-
-            d_value = PyDict_GetItem(PyTuple_GET_ITEM(ct->ct_stuff, 1), d_key);
-            Py_DECREF(d_key);
-            if (d_value != NULL)
-                Py_INCREF(d_value);
-            else
-                d_value = PyText_FromFormat("#%d", (int)value);
-            return d_value;
-        }
-        else if (ct->ct_flags & CT_PRIMITIVE_FITS_LONG)
+        if (ct->ct_flags & CT_PRIMITIVE_FITS_LONG)
             return PyInt_FromLong((long)value);
         else
             return PyLong_FromLongLong(value);
     }
     if (ct->ct_flags & CT_PRIMITIVE_SIGNED) {
         PY_LONG_LONG value = _my_PyLong_AsLongLong(init);
-
-        if (value == -1 && PyErr_Occurred()) {
-            if (!(ct->ct_flags & CT_IS_ENUM))
-                return -1;
-            else {
-                PyObject *ob;
-                PyErr_Clear();
-                if (!PyTextAny_Check(init)) {
-                    expected = "str or int";
-                    goto cannot_convert;
-                }
-
-                ob = convert_enum_string_to_int(ct, init);
-                if (ob == NULL)
-                    return -1;
-                value = PyLong_AsLongLong(ob);
-                Py_DECREF(ob);
-                if (value == -1 && PyErr_Occurred())
-                    return -1;
-            }
-        }
+        if (value == -1 && PyErr_Occurred())
+            return -1;
         write_raw_integer_data(buf, value, ct->ct_size);
         if (value != read_raw_signed_data(buf, ct->ct_size))
             goto overflow;
     PyObject *result, *s;
 
     if (cd->c_type->ct_flags & CT_PRIMITIVE_ANY) {
-        if (!(cd->c_type->ct_flags & CT_IS_LONGDOUBLE)) {
-            PyObject *o = convert_to_object(cd->c_data, cd->c_type);
-            if (o == NULL)
-                return NULL;
-            s = PyObject_Repr(o);
-            Py_DECREF(o);
+        if (cd->c_type->ct_flags & CT_IS_ENUM) {
+            s = convert_cdata_to_enum_string(cd, 1);
         }
-        else {
+        else if (cd->c_type->ct_flags & CT_IS_LONGDOUBLE) {
             long double lvalue;
             char buffer[128];   /* big enough */
             /*READ(cd->c_data, sizeof(long double)*/
             sprintf(buffer, "%LE", lvalue);
             s = PyText_FromString(buffer);
         }
+        else {
+            PyObject *o = convert_to_object(cd->c_data, cd->c_type);
+            if (o == NULL)
+                return NULL;
+            s = PyObject_Repr(o);
+            Py_DECREF(o);
+        }
     }
     else {
         if (cd->c_data != NULL) {
                                  (CT_POINTER|CT_FUNCTIONPTR|CT_ARRAY)) {
         value = (Py_intptr_t)((CDataObject *)ob)->c_data;
     }
-    else if ((ct->ct_flags & CT_IS_ENUM) && PyTextAny_Check(ob)) {
-        ob = convert_enum_string_to_int(ct, ob);
-        if (ob == NULL)
-            return NULL;
-        cd = cast_to_integer_or_char(ct, ob);
-        Py_DECREF(ob);
-        return cd;
-    }
 #if PY_MAJOR_VERSION < 3
     else if (PyString_Check(ob)) {
         if (PyString_GET_SIZE(ob) != 1) {
     dict1 = PyDict_New();
     if (dict1 == NULL)
         goto error;
+    dict2 = PyDict_New();
+    if (dict2 == NULL)
+        goto error;
+
     for (i=n; --i >= 0; ) {
         long lvalue;
         PyObject *value = PyTuple_GET_ITEM(enumvalues, i);
         }
         if (PyDict_SetItem(dict1, tmpkey, value) < 0)
             goto error;
+        if (PyDict_SetItem(dict2, value, tmpkey) < 0)
+            goto error;
         Py_DECREF(tmpkey);
         tmpkey = NULL;
     }
 
-    dict2 = PyDict_New();
-    if (dict2 == NULL)
-        goto error;
-    for (i=n; --i >= 0; ) {
-        if (PyDict_SetItem(dict2, PyTuple_GET_ITEM(enumvalues, i),
-                                  PyTuple_GET_ITEM(enumerators, i)) < 0)
-            goto error;
-    }
-
     combined = PyTuple_Pack(2, dict1, dict2);
     if (combined == NULL)
         goto error;
 #endif
     }
     else if (cd->c_type->ct_flags & CT_IS_ENUM) {
-        return convert_to_object(cd->c_data, cd->c_type);
+        return convert_cdata_to_enum_string(cd, 0);
     }
     else if (cd->c_type->ct_flags & CT_IS_BOOL) {
         /* fall through to TypeError */
 def test_cast_to_enum():
     BEnum = new_enum_type("foo", ('def', 'c', 'ab'), (0, 1, -20))
     e = cast(BEnum, 0)
-    assert repr(e) == "<cdata 'enum foo' 'def'>"
+    assert repr(e) == "<cdata 'enum foo' 0: def>"
+    assert repr(cast(BEnum, -42)) == "<cdata 'enum foo' -42>"
+    assert repr(cast(BEnum, -20)) == "<cdata 'enum foo' -20: ab>"
     assert string(e) == 'def'
     assert string(cast(BEnum, -20)) == 'ab'
-    assert string(cast(BEnum, 'c')) == 'c'
-    assert int(cast(BEnum, 'c')) == 1
-    assert int(cast(BEnum, 'def')) == 0
+    assert int(cast(BEnum, 1)) == 1
+    assert int(cast(BEnum, 0)) == 0
     assert int(cast(BEnum, -242 + 2**128)) == -242
-    assert string(cast(BEnum, -242 + 2**128)) == '#-242'
-    assert string(cast(BEnum, '#-20')) == 'ab'
-    assert repr(cast(BEnum, '#-20')) == "<cdata 'enum foo' 'ab'>"
-    assert repr(cast(BEnum, '#-21')) == "<cdata 'enum foo' '#-21'>"
+    assert string(cast(BEnum, -242 + 2**128)) == '-242'
 
 def test_enum_with_non_injective_mapping():
     BEnum = new_enum_type("foo", ('ab', 'cd'), (7, 7))
     e = cast(BEnum, 7)
-    assert repr(e) == "<cdata 'enum foo' 'ab'>"
+    assert repr(e) == "<cdata 'enum foo' 7: ab>"
     assert string(e) == 'ab'
 
 def test_enum_in_struct():
     BStructPtr = new_pointer_type(BStruct)
     complete_struct_or_union(BStruct, [('a1', BEnum, -1)])
     p = newp(BStructPtr, [-20])
-    assert p.a1 == "ab"
-    p = newp(BStructPtr, ["c"])
-    assert p.a1 == "c"
+    assert p.a1 == -20
+    p = newp(BStructPtr, [12])
+    assert p.a1 == 12
     e = py.test.raises(TypeError, newp, BStructPtr, [None])
-    assert "must be a str or int, not NoneType" in str(e.value)
+    assert "an integer is required" in str(e.value)
+    py.test.raises(TypeError, 'p.a1 = "def"')
     if sys.version_info < (3,):
-        p.a1 = unicode("def")
-        assert p.a1 == "def" and type(p.a1) is str
-        py.test.raises(UnicodeEncodeError, "p.a1 = unichr(1234)")
         BEnum2 = new_enum_type(unicode("foo"), (unicode('abc'),), (5,))
-        assert string(cast(BEnum2, unicode('abc'))) == 'abc'
+        assert string(cast(BEnum2, 5)) == 'abc'
+        assert type(string(cast(BEnum2, 5))) is str
 
 def test_enum_overflow():
     for ovf in (2**63, -2**63-1, 2**31, -2**31-1):
     BInt = new_primitive_type("int")
     BEnum = new_enum_type("foo", ('def', 'c', 'ab'), (0, 1, -20))
     def cb(n):
-        return '#%d' % n
+        if n & 1:
+            return cast(BEnum, n)
+        else:
+            return n
     BFunc = new_function_type((BInt,), BEnum)
     f = callback(BFunc, cb)
-    assert f(0) == 'def'
-    assert f(1) == 'c'
-    assert f(-20) == 'ab'
-    assert f(20) == '#20'
+    assert f(0) == 0
+    assert f(1) == 1
+    assert f(-20) == -20
+    assert f(20) == 20
+    assert f(21) == 21
 
 def test_callback_returning_char():
     BInt = new_primitive_type("int")

File cffi/backend_ctypes.py

 
     def new_enum_type(self, name, enumerators, enumvalues):
         assert isinstance(name, str)
-        mapping = dict(zip(enumerators, enumvalues))
         reverse_mapping = dict(zip(reversed(enumvalues),
                                    reversed(enumerators)))
         CTypesInt = self.ffi._get_cached_btype(model.PrimitiveType('int'))
         #
-        def forward_map(source):
-            if not isinstance(source, str):
-                return source
-            try:
-                return mapping[source]
-            except KeyError:
-                if source.startswith('#'):
-                    try:
-                        return int(source[1:])
-                    except ValueError:
-                        pass
-            raise ValueError("%r is not an enumerator for %r" % (
-                source, CTypesEnum))
-        #
         class CTypesEnum(CTypesInt):
             __slots__ = []
             _reftypename = 'enum %s &' % name
 
+            def _get_own_repr(self):
+                value = self._value
+                try:
+                    return '%d: %s' % (value, reverse_mapping[value])
+                except KeyError:
+                    return str(value)
+
             def _to_string(self, maxlen):
-                return str(CTypesEnum._from_ctypes(self._value))
-
-            @classmethod
-            def _cast_from(cls, source):
-                source = forward_map(source)
-                return super(CTypesEnum, cls)._cast_from(source)
-
-            @staticmethod
-            def _to_ctypes(x):
-                x = forward_map(x)
-                return CTypesInt._to_ctypes(x)
-
-            @staticmethod
-            def _from_ctypes(value):
-                value = CTypesInt._from_ctypes(value)
+                value = self._value
                 try:
                     return reverse_mapping[value]
                 except KeyError:
-                    return '#%s' % value
+                    return str(value)
         #
         CTypesEnum._fix_class()
         return CTypesEnum

File testing/backend_tests.py

     def test_enum(self):
         ffi = FFI(backend=self.Backend())
         ffi.cdef("enum foo { A, B, CC, D };")
-        assert int(ffi.cast("enum foo", "A")) == 0
-        assert int(ffi.cast("enum foo", "B")) == 1
-        assert int(ffi.cast("enum foo", "CC")) == 2
-        assert int(ffi.cast("enum foo", "D")) == 3
-        ffi.cdef("enum bar { A, B=-2, CC, D };")
-        assert int(ffi.cast("enum bar", "A")) == 0
-        assert int(ffi.cast("enum bar", "B")) == -2
-        assert int(ffi.cast("enum bar", "CC")) == -1
-        assert int(ffi.cast("enum bar", "D")) == 0
-        assert ffi.cast("enum bar", "B") != ffi.cast("enum bar", "B")
-        assert ffi.cast("enum foo", "A") != ffi.cast("enum bar", "A")
-        assert ffi.cast("enum bar", "A") != ffi.cast("int", 0)
-        assert repr(ffi.cast("enum bar", "CC")) == "<cdata 'enum bar' 'CC'>"
-        py.test.raises(ValueError, ffi.cast, "enum bar", "UNKNOWN")
+        assert ffi.string(ffi.cast("enum foo", 0)) == "A"
+        assert ffi.string(ffi.cast("enum foo", 2)) == "CC"
+        assert ffi.string(ffi.cast("enum foo", 3)) == "D"
+        assert ffi.string(ffi.cast("enum foo", 4)) == "4"
+        ffi.cdef("enum bar { A, B=-2, CC, D, E };")
+        assert ffi.string(ffi.cast("enum bar", 0)) == "A"
+        assert ffi.string(ffi.cast("enum bar", -2)) == "B"
+        assert ffi.string(ffi.cast("enum bar", -1)) == "CC"
+        assert ffi.string(ffi.cast("enum bar", 1)) == "E"
+        assert ffi.cast("enum bar", -2) != ffi.cast("enum bar", -2)
+        assert ffi.cast("enum foo", 0) != ffi.cast("enum bar", 0)
+        assert ffi.cast("enum bar", 0) != ffi.cast("int", 0)
+        assert repr(ffi.cast("enum bar", -1)) == "<cdata 'enum bar' -1: CC>"
         ffi.cdef("enum baz { A=0x1000, B=0x2000 };")
-        assert int(ffi.cast("enum baz", "A")) == 0x1000
-        assert int(ffi.cast("enum baz", "B")) == 0x2000
+        assert ffi.string(ffi.cast("enum baz", 0x1000)) == "A"
+        assert ffi.string(ffi.cast("enum baz", 0x2000)) == "B"
 
     def test_enum_in_struct(self):
         ffi = FFI(backend=self.Backend())
         ffi.cdef("enum foo { A, B, C, D }; struct bar { enum foo e; };")
         s = ffi.new("struct bar *")
         s.e = 0
-        assert s.e == "A"
-        s.e = "D"
-        assert s.e == "D"
-        assert s[0].e == "D"
-        s[0].e = "C"
-        assert s.e == "C"
-        assert s[0].e == "C"
+        assert s.e == 0
+        s.e = 3
+        assert s.e == 3
+        assert s[0].e == 3
+        s[0].e = 2
+        assert s.e == 2
+        assert s[0].e == 2
         s.e = ffi.cast("enum foo", -1)
-        assert s.e == '#-1'
-        assert s[0].e == '#-1'
+        assert s.e == -1
+        assert s[0].e == -1
         s.e = s.e
-        py.test.raises(TypeError, "s.e = None")
+        py.test.raises(TypeError, "s.e = 'B'")
+        py.test.raises(TypeError, "s.e = '#2'")
+        py.test.raises(TypeError, "s.e = '#7'")
 
     def test_enum_non_contiguous(self):
         ffi = FFI(backend=self.Backend())
         ffi.cdef("enum foo { A, B=42, C };")
-        assert int(ffi.cast("enum foo", "A")) == 0
-        assert int(ffi.cast("enum foo", "B")) == 42
-        assert int(ffi.cast("enum foo", "C")) == 43
         assert ffi.string(ffi.cast("enum foo", 0)) == "A"
         assert ffi.string(ffi.cast("enum foo", 42)) == "B"
         assert ffi.string(ffi.cast("enum foo", 43)) == "C"
         invalid_value = ffi.cast("enum foo", 2)
         assert int(invalid_value) == 2
-        assert ffi.string(invalid_value) == "#2"
+        assert ffi.string(invalid_value) == "2"
 
     def test_array_of_struct(self):
         ffi = FFI(backend=self.Backend())
     def test_enum_with_non_injective_mapping(self):
         ffi = FFI(backend=self.Backend())
         ffi.cdef("enum e { AA=0, BB=0, CC=0, DD=0 };")
-        e = ffi.cast("enum e", 'CC')
+        e = ffi.cast("enum e", 0)
         assert ffi.string(e) == "AA"     # pick the first one arbitrarily
 
     def test_nested_anonymous_struct(self):

File testing/test_unicode_literals.py

 def test_enum():
     ffi = FFI()
     ffi.cdef("enum foo_e { AA, BB, CC };")        # unicode literal
-    x = ffi.cast("enum foo_e", "BB")
+    x = ffi.cast("enum foo_e", 1)
     assert int(ffi.cast("int", x)) == 1
 
 def test_dlopen():

File testing/test_verify.py

     ffi.cdef("enum ee { EE1, EE2, EE3, ... \n \t };")
     py.test.raises(VerificationMissing, ffi.cast, 'enum ee', 'EE2')
     ffi.verify("enum ee { EE1=10, EE2, EE3=-10, EE4 };")
-    assert int(ffi.cast('enum ee', 'EE2')) == 11
-    assert int(ffi.cast('enum ee', 'EE3')) == -10
-    py.test.raises(ValueError, ffi.cast, 'enum ee', '__dotdotdot0__')
+    assert ffi.string(ffi.cast('enum ee', 11)) == "EE2"
+    assert ffi.string(ffi.cast('enum ee', -10)) == "EE3"
     #
     # try again
     ffi.verify("enum ee { EE1=10, EE2, EE3=-10, EE4 };")
-    assert int(ffi.cast('enum ee', 'EE2')) == 11
+    assert ffi.string(ffi.cast('enum ee', 11)) == "EE2"
 
 def test_full_enum():
     ffi = FFI()
     lib = ffi.verify("enum ee { EE1, EE2, EE3, EE4 };")
     assert lib.EE3 == 2
 
+def test_enum_usage():
+    ffi = FFI()
+    ffi.cdef("enum ee { EE1,EE2 }; typedef struct { enum ee x; } *sp;")
+    lib = ffi.verify("enum ee { EE1,EE2 }; typedef struct { enum ee x; } *sp;")
+    assert lib.EE2 == 1
+    s = ffi.new("sp", [lib.EE2])
+    assert s.x == 1
+    s.x = 17
+    assert s.x == 17
+
 def test_nonfull_enum_syntax2():
     ffi = FFI()
     ffi.cdef("enum ee { EE1, EE2=\t..., EE3 };")
     py.test.raises(VerificationMissing, ffi.cast, 'enum ee', 'EE1')
     ffi.verify("enum ee { EE1=10, EE2, EE3=-10, EE4 };")
-    assert int(ffi.cast('enum ee', 'EE2')) == 11
-    assert int(ffi.cast('enum ee', 'EE3')) == -10
+    assert ffi.string(ffi.cast('enum ee', 11)) == 'EE2'
+    assert ffi.string(ffi.cast('enum ee', -10)) == 'EE3'
     #
     ffi = FFI()
     ffi.cdef("enum ee { EE1, EE2=\t... };")
     py.test.raises(VerificationMissing, ffi.cast, 'enum ee', 'EE1')
     ffi.verify("enum ee { EE1=10, EE2, EE3=-10, EE4 };")
-    assert int(ffi.cast('enum ee', 'EE2')) == 11
+    assert ffi.string(ffi.cast('enum ee', 11)) == 'EE2'
     #
     ffi = FFI()
     ffi.cdef("enum ee2 { EE4=..., EE5=..., ... };")
     ffi.verify("enum ee2 { EE4=-1234-5, EE5 }; ")
-    assert int(ffi.cast('enum ee2', 'EE4')) == -1239
-    assert int(ffi.cast('enum ee2', 'EE5')) == -1238
+    assert ffi.string(ffi.cast('enum ee2', -1239)) == 'EE4'
+    assert ffi.string(ffi.cast('enum ee2', -1238)) == 'EE5'
 
 def test_get_set_errno():
     ffi = FFI()
         int foo_func(enum foo_e e) { return e; }
     """)
     assert lib.foo_func(lib.BB) == 2
-    assert lib.foo_func("BB") == 2
+    py.test.raises(TypeError, lib.foo_func, "BB")
 
 def test_enum_as_function_result():
     ffi = FFI()
         enum foo_e { AA, CC, BB };
         enum foo_e foo_func(int x) { return x; }
     """)
-    assert lib.foo_func(lib.BB) == "BB"
+    assert lib.foo_func(lib.BB) == lib.BB == 2
 
 def test_enum_values():
     ffi = FFI()
     ffi = FFI()
     ffi.cdef("typedef enum { AA, BB, ... } enum1_t;")
     lib = ffi.verify("typedef enum { AA, CC, BB } enum1_t;")
-    assert ffi.string(ffi.cast("enum1_t", 1)) == '#1'
+    assert ffi.string(ffi.cast("enum1_t", 1)) == '1'
     assert ffi.string(ffi.cast("enum1_t", 2)) == 'BB'
     assert lib.AA == 0
     assert lib.BB == 2
         typedef enum { AA, CC, BB } foo_t;
         foo_t foo_func(int x) { return x; }
     """)
-    assert lib.foo_func(lib.BB) == "BB"
+    assert lib.foo_func(lib.BB) == lib.BB == 2
 
 def test_callback_calling_convention():
     py.test.skip("later")
     ffi2.cdef("int myfunc(enum foo_e);")
     lib2 = ffi2.verify("enum foo_e { CC, BB, AA };"
                        "int myfunc(enum foo_e x) { return (int)x; }")
-    res = lib2.myfunc("AA")
+    res = lib2.myfunc(lib2.AA)
     assert res == 2
 
 def test_string_to_voidp_arg():