blob: f3f5ca15c4300c27d2783072a42675ca9a23b9a5 [file] [log] [blame]
/* Implementation of a C object with the 'buffer' or 'memoryview'
* interface at C-level (as approriate for the version of Python we're
* compiling for), but only a minimal but *consistent* part of the
* 'buffer' interface at application level.
*/
typedef struct {
PyObject_HEAD
char *mb_data;
Py_ssize_t mb_size;
PyObject *mb_keepalive;
PyObject *mb_weakreflist; /* weakref support */
} MiniBufferObj;
static Py_ssize_t mb_length(MiniBufferObj *self)
{
return self->mb_size;
}
static PyObject *mb_item(MiniBufferObj *self, Py_ssize_t idx)
{
if (idx < 0 || idx >= self->mb_size ) {
PyErr_SetString(PyExc_IndexError, "buffer index out of range");
return NULL;
}
return PyBytes_FromStringAndSize(self->mb_data + idx, 1);
}
static PyObject *mb_slice(MiniBufferObj *self,
Py_ssize_t left, Py_ssize_t right)
{
Py_ssize_t size = self->mb_size;
if (left < 0) left = 0;
if (right > size) right = size;
if (left > right) left = right;
return PyBytes_FromStringAndSize(self->mb_data + left, right - left);
}
static int mb_ass_item(MiniBufferObj *self, Py_ssize_t idx, PyObject *other)
{
if (idx < 0 || idx >= self->mb_size) {
PyErr_SetString(PyExc_IndexError,
"buffer assignment index out of range");
return -1;
}
if (PyBytes_Check(other) && PyBytes_GET_SIZE(other) == 1) {
self->mb_data[idx] = PyBytes_AS_STRING(other)[0];
return 0;
}
else {
PyErr_Format(PyExc_TypeError,
"must assign a "STR_OR_BYTES
" of length 1, not %.200s", Py_TYPE(other)->tp_name);
return -1;
}
}
/* forward: from _cffi_backend.c */
static int _fetch_as_buffer(PyObject *x, Py_buffer *view, int writable_only);
static int mb_ass_slice(MiniBufferObj *self,
Py_ssize_t left, Py_ssize_t right, PyObject *other)
{
Py_ssize_t count;
Py_ssize_t size = self->mb_size;
Py_buffer src_view;
if (_fetch_as_buffer(other, &src_view, 0) < 0)
return -1;
if (left < 0) left = 0;
if (right > size) right = size;
if (left > right) left = right;
count = right - left;
if (count != src_view.len) {
PyBuffer_Release(&src_view);
PyErr_SetString(PyExc_ValueError,
"right operand length must match slice length");
return -1;
}
memcpy(self->mb_data + left, src_view.buf, count);
PyBuffer_Release(&src_view);
return 0;
}
#if PY_MAJOR_VERSION < 3
static Py_ssize_t mb_getdata(MiniBufferObj *self, Py_ssize_t idx, void **pp)
{
*pp = self->mb_data;
return self->mb_size;
}
static Py_ssize_t mb_getsegcount(MiniBufferObj *self, Py_ssize_t *lenp)
{
if (lenp)
*lenp = self->mb_size;
return 1;
}
static PyObject *mb_str(MiniBufferObj *self)
{
/* Python 2: we want str(buffer) to behave like buffer[:], because
that's what bytes(buffer) does on Python 3 and there is no way
we can prevent this. */
return PyString_FromStringAndSize(self->mb_data, self->mb_size);
}
#endif
static int mb_getbuf(MiniBufferObj *self, Py_buffer *view, int flags)
{
return PyBuffer_FillInfo(view, (PyObject *)self,
self->mb_data, self->mb_size,
/*readonly=*/0, flags);
}
static PySequenceMethods mb_as_sequence = {
(lenfunc)mb_length, /*sq_length*/
(binaryfunc)0, /*sq_concat*/
(ssizeargfunc)0, /*sq_repeat*/
(ssizeargfunc)mb_item, /*sq_item*/
(ssizessizeargfunc)mb_slice, /*sq_slice*/
(ssizeobjargproc)mb_ass_item, /*sq_ass_item*/
(ssizessizeobjargproc)mb_ass_slice, /*sq_ass_slice*/
};
static PyBufferProcs mb_as_buffer = {
#if PY_MAJOR_VERSION < 3
(readbufferproc)mb_getdata,
(writebufferproc)mb_getdata,
(segcountproc)mb_getsegcount,
(charbufferproc)mb_getdata,
#endif
(getbufferproc)mb_getbuf,
(releasebufferproc)0,
};
static void
mb_dealloc(MiniBufferObj *ob)
{
PyObject_GC_UnTrack(ob);
if (ob->mb_weakreflist != NULL)
PyObject_ClearWeakRefs((PyObject *)ob);
Py_XDECREF(ob->mb_keepalive);
Py_TYPE(ob)->tp_free((PyObject *)ob);
}
static int
mb_traverse(MiniBufferObj *ob, visitproc visit, void *arg)
{
Py_VISIT(ob->mb_keepalive);
return 0;
}
static int
mb_clear(MiniBufferObj *ob)
{
Py_CLEAR(ob->mb_keepalive);
return 0;
}
static PyObject *
mb_richcompare(PyObject *self, PyObject *other, int op)
{
Py_ssize_t self_size, other_size;
Py_buffer self_bytes, other_bytes;
PyObject *res;
Py_ssize_t minsize;
int cmp, rc;
/* Bytes can be compared to anything that supports the (binary)
buffer API. Except that a comparison with Unicode is always an
error, even if the comparison is for equality. */
rc = PyObject_IsInstance(self, (PyObject*)&PyUnicode_Type);
if (!rc)
rc = PyObject_IsInstance(other, (PyObject*)&PyUnicode_Type);
if (rc < 0)
return NULL;
if (rc) {
Py_INCREF(Py_NotImplemented);
return Py_NotImplemented;
}
if (PyObject_GetBuffer(self, &self_bytes, PyBUF_SIMPLE) != 0) {
PyErr_Clear();
Py_INCREF(Py_NotImplemented);
return Py_NotImplemented;
}
self_size = self_bytes.len;
if (PyObject_GetBuffer(other, &other_bytes, PyBUF_SIMPLE) != 0) {
PyErr_Clear();
PyBuffer_Release(&self_bytes);
Py_INCREF(Py_NotImplemented);
return Py_NotImplemented;
}
other_size = other_bytes.len;
if (self_size != other_size && (op == Py_EQ || op == Py_NE)) {
/* Shortcut: if the lengths differ, the objects differ */
cmp = (op == Py_NE);
}
else {
minsize = self_size;
if (other_size < minsize)
minsize = other_size;
cmp = memcmp(self_bytes.buf, other_bytes.buf, minsize);
/* In ISO C, memcmp() guarantees to use unsigned bytes! */
if (cmp == 0) {
if (self_size < other_size)
cmp = -1;
else if (self_size > other_size)
cmp = 1;
}
switch (op) {
case Py_LT: cmp = cmp < 0; break;
case Py_LE: cmp = cmp <= 0; break;
case Py_EQ: cmp = cmp == 0; break;
case Py_NE: cmp = cmp != 0; break;
case Py_GT: cmp = cmp > 0; break;
case Py_GE: cmp = cmp >= 0; break;
}
}
res = cmp ? Py_True : Py_False;
PyBuffer_Release(&self_bytes);
PyBuffer_Release(&other_bytes);
Py_INCREF(res);
return res;
}
#if PY_MAJOR_VERSION >= 3
/* pfffffffffffff pages of copy-paste from listobject.c */
/* pfffffffffffff#2: the PySlice_GetIndicesEx() *macro* should not
be called, because C extension modules compiled with it differ
on ABI between 3.6.0, 3.6.1 and 3.6.2. */
#if PY_VERSION_HEX < 0x03070000 && defined(PySlice_GetIndicesEx) && !defined(PYPY_VERSION)
#undef PySlice_GetIndicesEx
#endif
static PyObject *mb_subscript(MiniBufferObj *self, PyObject *item)
{
if (PyIndex_Check(item)) {
Py_ssize_t i;
i = PyNumber_AsSsize_t(item, PyExc_IndexError);
if (i == -1 && PyErr_Occurred())
return NULL;
if (i < 0)
i += self->mb_size;
return mb_item(self, i);
}
else if (PySlice_Check(item)) {
Py_ssize_t start, stop, step, slicelength;
if (PySlice_GetIndicesEx(item, self->mb_size,
&start, &stop, &step, &slicelength) < 0)
return NULL;
if (step == 1)
return mb_slice(self, start, stop);
else {
PyErr_SetString(PyExc_TypeError,
"buffer doesn't support slicing with step != 1");
return NULL;
}
}
else {
PyErr_Format(PyExc_TypeError,
"buffer indices must be integers, not %.200s",
item->ob_type->tp_name);
return NULL;
}
}
static int
mb_ass_subscript(MiniBufferObj* self, PyObject* item, PyObject* value)
{
if (PyIndex_Check(item)) {
Py_ssize_t i = PyNumber_AsSsize_t(item, PyExc_IndexError);
if (i == -1 && PyErr_Occurred())
return -1;
if (i < 0)
i += self->mb_size;
return mb_ass_item(self, i, value);
}
else if (PySlice_Check(item)) {
Py_ssize_t start, stop, step, slicelength;
if (PySlice_GetIndicesEx(item, self->mb_size,
&start, &stop, &step, &slicelength) < 0) {
return -1;
}
if (step == 1)
return mb_ass_slice(self, start, stop, value);
else {
PyErr_SetString(PyExc_TypeError,
"buffer doesn't support slicing with step != 1");
return -1;
}
}
else {
PyErr_Format(PyExc_TypeError,
"buffer indices must be integers, not %.200s",
item->ob_type->tp_name);
return -1;
}
}
static PyMappingMethods mb_as_mapping = {
(lenfunc)mb_length, /*mp_length*/
(binaryfunc)mb_subscript, /*mp_subscript*/
(objobjargproc)mb_ass_subscript, /*mp_ass_subscript*/
};
#endif
#if PY_MAJOR_VERSION >= 3
# define MINIBUF_TPFLAGS 0
#else
# define MINIBUF_TPFLAGS (Py_TPFLAGS_HAVE_GETCHARBUFFER | Py_TPFLAGS_HAVE_NEWBUFFER)
#endif
PyDoc_STRVAR(ffi_buffer_doc,
"ffi.buffer(cdata[, byte_size]):\n"
"Return a read-write buffer object that references the raw C data\n"
"pointed to by the given 'cdata'. The 'cdata' must be a pointer or an\n"
"array. Can be passed to functions expecting a buffer, or directly\n"
"manipulated with:\n"
"\n"
" buf[:] get a copy of it in a regular string, or\n"
" buf[idx] as a single character\n"
" buf[:] = ...\n"
" buf[idx] = ... change the content");
static PyObject * /* forward, implemented in _cffi_backend.c */
b_buffer_new(PyTypeObject *type, PyObject *args, PyObject *kwds);
static PyTypeObject MiniBuffer_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
"_cffi_backend.buffer",
sizeof(MiniBufferObj),
0,
(destructor)mb_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_compare */
0, /* tp_repr */
0, /* tp_as_number */
&mb_as_sequence, /* tp_as_sequence */
#if PY_MAJOR_VERSION < 3
0, /* tp_as_mapping */
#else
&mb_as_mapping, /* tp_as_mapping */
#endif
0, /* tp_hash */
0, /* tp_call */
#if PY_MAJOR_VERSION < 3
(reprfunc)mb_str, /* tp_str */
#else
0, /* tp_str */
#endif
PyObject_GenericGetAttr, /* tp_getattro */
0, /* tp_setattro */
&mb_as_buffer, /* tp_as_buffer */
(Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
MINIBUF_TPFLAGS), /* tp_flags */
ffi_buffer_doc, /* tp_doc */
(traverseproc)mb_traverse, /* tp_traverse */
(inquiry)mb_clear, /* tp_clear */
(richcmpfunc)mb_richcompare, /* tp_richcompare */
offsetof(MiniBufferObj, mb_weakreflist), /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
0, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
0, /* tp_init */
0, /* tp_alloc */
b_buffer_new, /* tp_new */
0, /* tp_free */
};
static PyObject *minibuffer_new(char *data, Py_ssize_t size,
PyObject *keepalive)
{
MiniBufferObj *ob = PyObject_GC_New(MiniBufferObj, &MiniBuffer_Type);
if (ob != NULL) {
ob->mb_data = data;
ob->mb_size = size;
ob->mb_keepalive = keepalive; Py_INCREF(keepalive);
ob->mb_weakreflist = NULL;
PyObject_GC_Track(ob);
}
return (PyObject *)ob;
}