Commits

Armin Rigo  committed 78787a1

Support cffi's "slicing" branch

  • Participants
  • Parent commits 66d1e56

Comments (0)

Files changed (5)

File pypy/module/_cffi_backend/cdataobj.py

 
     def getitem(self, w_index):
         space = self.space
-        i = space.getindex_w(w_index, space.w_IndexError)
-        ctype = self.ctype._check_subscript_index(self, i)
-        w_o = self._do_getitem(ctype, i)
+        if space.isinstance_w(w_index, space.w_slice):
+            w_o = self._do_getslice(w_index)
+        else:
+            i = space.getindex_w(w_index, space.w_IndexError)
+            ctype = self.ctype._check_subscript_index(self, i)
+            w_o = self._do_getitem(ctype, i)
         keepalive_until_here(self)
         return w_o
 
 
     def setitem(self, w_index, w_value):
         space = self.space
-        i = space.getindex_w(w_index, space.w_IndexError)
-        ctype = self.ctype._check_subscript_index(self, i)
-        ctitem = ctype.ctitem
-        ctitem.convert_from_object(
-            rffi.ptradd(self._cdata, i * ctitem.size),
-            w_value)
+        if space.isinstance_w(w_index, space.w_slice):
+            self._do_setslice(w_index, w_value)
+        else:
+            i = space.getindex_w(w_index, space.w_IndexError)
+            ctype = self.ctype._check_subscript_index(self, i)
+            ctitem = ctype.ctitem
+            ctitem.convert_from_object(
+                rffi.ptradd(self._cdata, i * ctitem.size),
+                w_value)
         keepalive_until_here(self)
 
+    def _do_getslicearg(self, w_slice):
+        from pypy.module._cffi_backend.ctypeptr import W_CTypePointer
+        from pypy.objspace.std.sliceobject import W_SliceObject
+        assert isinstance(w_slice, W_SliceObject)
+        space = self.space
+        #
+        if space.is_w(w_slice.w_start, space.w_None):
+            raise OperationError(space.w_IndexError,
+                                 space.wrap("slice start must be specified"))
+        start = space.int_w(w_slice.w_start)
+        #
+        if space.is_w(w_slice.w_stop, space.w_None):
+            raise OperationError(space.w_IndexError,
+                                 space.wrap("slice stop must be specified"))
+        stop = space.int_w(w_slice.w_stop)
+        #
+        if not space.is_w(w_slice.w_step, space.w_None):
+            raise OperationError(space.w_IndexError,
+                                 space.wrap("slice with step not supported"))
+        #
+        if start > stop:
+            raise OperationError(space.w_IndexError,
+                                 space.wrap("slice start > stop"))
+        #
+        ctype = self.ctype._check_slice_index(self, start, stop)
+        assert isinstance(ctype, W_CTypePointer)
+        #
+        return ctype, start, stop - start
+
+    def _do_getslice(self, w_slice):
+        ctptr, start, length = self._do_getslicearg(w_slice)
+        #
+        space = self.space
+        ctarray = ctptr.cache_array_type
+        if ctarray is None:
+            from pypy.module._cffi_backend import newtype
+            ctarray = newtype.new_array_type(space, ctptr, space.w_None)
+            ctptr.cache_array_type = ctarray
+        #
+        p = rffi.ptradd(self._cdata, start * ctarray.ctitem.size)
+        return W_CDataSliced(space, p, ctarray, length)
+
+    def _do_setslice(self, w_slice, w_value):
+        ctptr, start, length = self._do_getslicearg(w_slice)
+        ctitem = ctptr.ctitem
+        ctitemsize = ctitem.size
+        cdata = rffi.ptradd(self._cdata, start * ctitemsize)
+        #
+        if isinstance(w_value, W_CData):
+            from pypy.module._cffi_backend import ctypearray
+            ctv = w_value.ctype
+            if (isinstance(ctv, ctypearray.W_CTypeArray) and
+                ctv.ctitem is ctitem and
+                w_value.get_array_length() == length):
+                # fast path: copying from exactly the correct type
+                s = w_value._cdata
+                for i in range(ctitemsize * length):
+                    cdata[i] = s[i]
+                keepalive_until_here(w_value)
+                return
+        #
+        space = self.space
+        w_iter = space.iter(w_value)
+        for i in range(length):
+            try:
+                w_item = space.next(w_iter)
+            except OperationError, e:
+                if not e.match(space, space.w_StopIteration):
+                    raise
+                raise operationerrfmt(space.w_ValueError,
+                                      "need %d values to unpack, got %d",
+                                      length, i)
+            ctitem.convert_from_object(cdata, w_item)
+            cdata = rffi.ptradd(cdata, ctitemsize)
+        try:
+            space.next(w_iter)
+        except OperationError, e:
+            if not e.match(space, space.w_StopIteration):
+                raise
+        else:
+            raise operationerrfmt(space.w_ValueError,
+                                  "got more than %d values to unpack", length)
+
     def _add_or_sub(self, w_other, sign):
         space = self.space
         i = sign * space.getindex_w(w_other, space.w_OverflowError)
         return self.structobj
 
 
+class W_CDataSliced(W_CData):
+    """Subclass with an explicit length, for slices."""
+    _attrs_ = ['length']
+    _immutable_fields_ = ['length']
+
+    def __init__(self, space, cdata, ctype, length):
+        W_CData.__init__(self, space, cdata, ctype)
+        self.length = length
+
+    def _repr_extra(self):
+        return "sliced length %d" % (self.length,)
+
+    def get_array_length(self):
+        return self.length
+
+
 W_CData.typedef = TypeDef(
     'CData',
     __module__ = '_cffi_backend',

File pypy/module/_cffi_backend/ctypearray.py

                 self.name, i, w_cdata.get_array_length())
         return self
 
+    def _check_slice_index(self, w_cdata, start, stop):
+        space = self.space
+        if start < 0:
+            raise OperationError(space.w_IndexError,
+                                 space.wrap("negative index not supported"))
+        if stop > w_cdata.get_array_length():
+            raise operationerrfmt(space.w_IndexError,
+                "index too large (expected %d <= %d)",
+                stop, w_cdata.get_array_length())
+        return self.ctptr
+
     def convert_from_object(self, cdata, w_ob):
         self.convert_array_from_object(cdata, w_ob)
 

File pypy/module/_cffi_backend/ctypeobj.py

                                    "not %s", self.name, expected,
                                    space.type(w_got).getname(space))
 
-    def _check_subscript_index(self, w_cdata, i):
+    def _cannot_index(self):
         space = self.space
         raise operationerrfmt(space.w_TypeError,
                               "cdata of type '%s' cannot be indexed",
                               self.name)
 
+    def _check_subscript_index(self, w_cdata, i):
+        raise self._cannot_index()
+
+    def _check_slice_index(self, w_cdata, start, stop):
+        raise self._cannot_index()
+
     def string(self, cdataobj, maxlen):
         space = self.space
         raise operationerrfmt(space.w_TypeError,

File pypy/module/_cffi_backend/ctypeptr.py

 
 
 class W_CTypePointer(W_CTypePtrBase):
-    _attrs_ = ['is_file']
+    _attrs_ = ['is_file', 'cache_array_type']
     _immutable_fields_ = ['is_file']
     kind = "pointer"
+    cache_array_type = None
 
     def __init__(self, space, ctitem):
         from pypy.module._cffi_backend import ctypearray
                                       self.name)
         return self
 
+    def _check_slice_index(self, w_cdata, start, stop):
+        return self
+
     def add(self, cdata, i):
         space = self.space
         ctitem = self.ctitem

File pypy/module/_cffi_backend/test/_backend_test_c.py

 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'
+    #
+    BEnum = new_enum_type("bar", ('def', 'c', 'ab'), (0, 1, 20))
+    e = cast(BEnum, -1)
+    assert repr(e) == "<cdata 'enum bar' 4294967295>"     # unsigned int
 
 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):
-        e = py.test.raises(OverflowError, new_enum_type, "foo", ('a', 'b'),
-                           (5, ovf))
-        assert str(e.value) == (
-            "enum 'foo' declaration for 'b' does not fit an int")
+    max_uint = 2 ** (size_of_int()*8) - 1
+    max_int = max_uint // 2
+    max_ulong = 2 ** (size_of_long()*8) - 1
+    max_long = max_ulong // 2
+    # 'unsigned int' case
+    e = new_enum_type("foo", ('a', 'b'), (0, 3))
+    assert sizeof(e) == size_of_int()
+    assert int(cast(e, -1)) == max_uint     # 'e' is unsigned
+    e = new_enum_type("foo", ('a', 'b'), (0, max_uint))
+    assert sizeof(e) == size_of_int()
+    assert int(cast(e, -1)) == max_uint
+    assert e.elements == {0: 'a', max_uint: 'b'}
+    assert e.relements == {'a': 0, 'b': max_uint}
+    # 'signed int' case
+    e = new_enum_type("foo", ('a', 'b'), (-1, max_int))
+    assert sizeof(e) == size_of_int()
+    assert int(cast(e, -1)) == -1
+    assert e.elements == {-1: 'a', max_int: 'b'}
+    assert e.relements == {'a': -1, 'b': max_int}
+    e = new_enum_type("foo", ('a', 'b'), (-max_int-1, max_int))
+    assert sizeof(e) == size_of_int()
+    assert int(cast(e, -1)) == -1
+    assert e.elements == {-max_int-1: 'a', max_int: 'b'}
+    assert e.relements == {'a': -max_int-1, 'b': max_int}
+    # 'unsigned long' case
+    e = new_enum_type("foo", ('a', 'b'), (0, max_long))
+    assert sizeof(e) == size_of_long()
+    assert int(cast(e, -1)) == max_ulong     # 'e' is unsigned
+    e = new_enum_type("foo", ('a', 'b'), (0, max_ulong))
+    assert sizeof(e) == size_of_long()
+    assert int(cast(e, -1)) == max_ulong
+    assert e.elements == {0: 'a', max_ulong: 'b'}
+    assert e.relements == {'a': 0, 'b': max_ulong}
+    # 'signed long' case
+    e = new_enum_type("foo", ('a', 'b'), (-1, max_long))
+    assert sizeof(e) == size_of_long()
+    assert int(cast(e, -1)) == -1
+    assert e.elements == {-1: 'a', max_long: 'b'}
+    assert e.relements == {'a': -1, 'b': max_long}
+    e = new_enum_type("foo", ('a', 'b'), (-max_long-1, max_long))
+    assert sizeof(e) == size_of_long()
+    assert int(cast(e, -1)) == -1
+    assert e.elements == {-max_long-1: 'a', max_long: 'b'}
+    assert e.relements == {'a': -max_long-1, 'b': max_long}
+    # overflow: both negative items and items larger than max_long
+    e = py.test.raises(OverflowError, new_enum_type, "foo", ('a', 'b'),
+                       (-1, max_long + 1))
+    assert str(e.value) == (
+        "enum 'foo' values don't all fit into either 'long' "
+        "or 'unsigned long'")
+    # overflow: items smaller than -max_long-1
+    e = py.test.raises(OverflowError, new_enum_type, "foo", ('a', 'b'),
+                       (-max_long-2, 5))
+    assert str(e.value) == (
+        "enum 'foo' declaration for 'a' does not fit a long or unsigned long")
+    # overflow: items larger than max_ulong
+    e = py.test.raises(OverflowError, new_enum_type, "foo", ('a', 'b'),
+                       (5, max_ulong+1))
+    assert str(e.value) == (
+        "enum 'foo' declaration for 'b' does not fit a long or unsigned long")
 
 def test_callback_returning_enum():
     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_enum_unsigned():
+    BInt = new_primitive_type("int")
+    BEnum = new_enum_type("foo", ('def', 'c', 'ab'), (0, 1, 20))
+    def cb(n):
+        print n
+        if n & 1:
+            return cast(BEnum, n)
+        else:
+            return n
+    BFunc = new_function_type((BInt,), BEnum)
+    f = callback(BFunc, cb)
+    assert f(0) == 0
+    assert f(1) == 1
+    assert f(-21) == 2**32 - 21
+    assert f(20) == 20
+    assert f(21) == 21
 
 def test_callback_returning_char():
     BInt = new_primitive_type("int")
     if sys.platform == "win32":
         py.test.skip("testing FILE not implemented")
     #
-    BFILE = new_struct_type("_IO_FILE")
+    BFILE = new_struct_type("$FILE")
     BFILEP = new_pointer_type(BFILE)
     BChar = new_primitive_type("char")
     BCharP = new_pointer_type(BChar)
     for i in range(20):
         buf = buflist[i]
         assert buf[:] == str2bytes("hi there %d\x00" % i)
+
+def test_slice():
+    BIntP = new_pointer_type(new_primitive_type("int"))
+    BIntArray = new_array_type(BIntP, None)
+    c = newp(BIntArray, 5)
+    assert len(c) == 5
+    assert repr(c) == "<cdata 'int[]' owning 20 bytes>"
+    d = c[1:4]
+    assert len(d) == 3
+    assert repr(d) == "<cdata 'int[]' sliced length 3>"
+    d[0] = 123
+    d[2] = 456
+    assert c[1] == 123
+    assert c[3] == 456
+    assert d[2] == 456
+    py.test.raises(IndexError, "d[3]")
+    py.test.raises(IndexError, "d[-1]")
+
+def test_slice_ptr():
+    BIntP = new_pointer_type(new_primitive_type("int"))
+    BIntArray = new_array_type(BIntP, None)
+    c = newp(BIntArray, 5)
+    d = (c+1)[0:2]
+    assert len(d) == 2
+    assert repr(d) == "<cdata 'int[]' sliced length 2>"
+    d[1] += 50
+    assert c[2] == 50
+
+def test_slice_array_checkbounds():
+    BIntP = new_pointer_type(new_primitive_type("int"))
+    BIntArray = new_array_type(BIntP, None)
+    c = newp(BIntArray, 5)
+    c[0:5]
+    assert len(c[5:5]) == 0
+    py.test.raises(IndexError, "c[-1:1]")
+    cp = c + 0
+    cp[-1:1]
+
+def test_nonstandard_slice():
+    BIntP = new_pointer_type(new_primitive_type("int"))
+    BIntArray = new_array_type(BIntP, None)
+    c = newp(BIntArray, 5)
+    e = py.test.raises(IndexError, "c[:5]")
+    assert str(e.value) == "slice start must be specified"
+    e = py.test.raises(IndexError, "c[4:]")
+    assert str(e.value) == "slice stop must be specified"
+    e = py.test.raises(IndexError, "c[1:2:3]")
+    assert str(e.value) == "slice with step not supported"
+    e = py.test.raises(IndexError, "c[1:2:1]")
+    assert str(e.value) == "slice with step not supported"
+    e = py.test.raises(IndexError, "c[4:2]")
+    assert str(e.value) == "slice start > stop"
+    e = py.test.raises(IndexError, "c[6:6]")
+    assert str(e.value) == "index too large (expected 6 <= 5)"
+
+def test_setslice():
+    BIntP = new_pointer_type(new_primitive_type("int"))
+    BIntArray = new_array_type(BIntP, None)
+    c = newp(BIntArray, 5)
+    c[1:3] = [100, 200]
+    assert list(c) == [0, 100, 200, 0, 0]
+    cp = c + 3
+    cp[-1:1] = [300, 400]
+    assert list(c) == [0, 100, 300, 400, 0]
+    cp[-1:1] = iter([500, 600])
+    assert list(c) == [0, 100, 500, 600, 0]
+    py.test.raises(ValueError, "cp[-1:1] = [1000]")
+    assert list(c) == [0, 100, 1000, 600, 0]
+    py.test.raises(ValueError, "cp[-1:1] = (700, 800, 900)")
+    assert list(c) == [0, 100, 700, 800, 0]
+
+def test_setslice_array():
+    BIntP = new_pointer_type(new_primitive_type("int"))
+    BIntArray = new_array_type(BIntP, None)
+    c = newp(BIntArray, 5)
+    d = newp(BIntArray, [10, 20, 30])
+    c[1:4] = d
+    assert list(c) == [0, 10, 20, 30, 0]
+    #
+    BShortP = new_pointer_type(new_primitive_type("short"))
+    BShortArray = new_array_type(BShortP, None)
+    d = newp(BShortArray, [40, 50])
+    c[1:3] = d
+    assert list(c) == [0, 40, 50, 30, 0]