Source code

Revision control

Copy as Markdown

Other Tools

#include <Python.h>
#include <structmember.h>
/*
Persistent/Immutable/Functional vector and helper types.
Please note that they are anything but immutable at this level since
there is a whole lot of reference counting going on. That's the way
CPython works though and the GIL makes them appear immutable.
To the programmer using them from Python they appear immutable and
behave immutably at least.
Naming conventions
------------------
initpyrsistentc - This is the method that initializes the whole module
pyrsistent_* - Methods part of the interface
<typename>_* - Instance methods of types. For examle PVector_append(...)
All other methods are camel cased without prefix. All methods are static, none should
require to be exposed outside of this module.
*/
#define SHIFT 5
#define BRANCH_FACTOR (1 << SHIFT)
#define BIT_MASK (BRANCH_FACTOR - 1)
static PyTypeObject PVectorType;
static PyTypeObject PVectorEvolverType;
typedef struct {
void *items[BRANCH_FACTOR];
unsigned int refCount;
} VNode;
#define NODE_CACHE_MAX_SIZE 1024
typedef struct {
unsigned int size;
VNode* nodes[NODE_CACHE_MAX_SIZE];
} vNodeCache;
static vNodeCache nodeCache;
typedef struct {
PyObject_HEAD
unsigned int count; // Perhaps ditch this one in favor of ob_size/Py_SIZE()
unsigned int shift;
VNode *root;
VNode *tail;
PyObject *in_weakreflist; /* List of weak references */
} PVector;
typedef struct {
PyObject_HEAD
PVector* originalVector;
PVector* newVector;
PyObject* appendList;
} PVectorEvolver;
static PVector* EMPTY_VECTOR = NULL;
static PyObject* transform_fn = NULL;
static PyObject* transform(PVector* self, PyObject* args) {
if(transform_fn == NULL) {
// transform to avoid circular import problems
transform_fn = PyObject_GetAttrString(PyImport_ImportModule("pyrsistent._transformations"), "transform");
}
return PyObject_CallFunctionObjArgs(transform_fn, self, args, NULL);
}
// No access to internal members
static PyMemberDef PVector_members[] = {
{NULL} /* Sentinel */
};
#define debug(...)
// #define debug printf
#define NODE_REF_COUNT(n) ((n)->refCount)
#define SET_NODE_REF_COUNT(n, c) (NODE_REF_COUNT(n) = (c))
#define INC_NODE_REF_COUNT(n) (NODE_REF_COUNT(n)++)
#define DEC_NODE_REF_COUNT(n) (NODE_REF_COUNT(n)--)
static VNode* allocNode(void) {
if(nodeCache.size > 0) {
nodeCache.size--;
return nodeCache.nodes[nodeCache.size];
}
return PyMem_Malloc(sizeof(VNode));
}
static void freeNode(VNode *node) {
if(nodeCache.size < NODE_CACHE_MAX_SIZE) {
nodeCache.nodes[nodeCache.size] = node;
nodeCache.size++;
} else {
PyMem_Free(node);
}
}
static VNode* newNode(void) {
VNode* result = allocNode();
memset(result, 0x0, sizeof(VNode));
SET_NODE_REF_COUNT(result, 1);
debug("newNode() %p\n", result);
return result;
}
static VNode* copyNode(VNode* source) {
/* NB: Only to be used for internal nodes, eg. nodes that do not
hold direct references to python objects but only to other nodes. */
int i;
VNode* result = allocNode();
debug("copyNode() %p\n", result);
memcpy(result->items, source->items, sizeof(source->items));
for(i = 0; i < BRANCH_FACTOR; i++) {
// TODO-OPT: Any need to go on when the first NULL has been found?
if(result->items[i] != NULL) {
INC_NODE_REF_COUNT((VNode*)result->items[i]);
}
}
SET_NODE_REF_COUNT(result, 1);
return result;
}
static PVector* emptyNewPvec(void);
static PVector* copyPVector(PVector *original);
static void extendWithItem(PVector *newVec, PyObject *item);
static PyObject *PVectorEvolver_persistent(PVectorEvolver *);
static int PVectorEvolver_set_item(PVectorEvolver *, PyObject*, PyObject*);
static Py_ssize_t PVector_len(PVector *self) {
return self->count;
}
/* Convenience macros */
#define ROOT_NODE_FULL(vec) ((vec->count >> SHIFT) > (1 << vec->shift))
#define TAIL_OFF(vec) ((vec->count < BRANCH_FACTOR) ? 0 : (((vec->count - 1) >> SHIFT) << SHIFT))
#define TAIL_SIZE(vec) (vec->count - TAIL_OFF(vec))
#define PVector_CheckExact(op) (Py_TYPE(op) == &PVectorType)
static VNode* nodeFor(PVector *self, int i){
int level;
if((i >= 0) && (i < self->count)) {
if(i >= TAIL_OFF(self)) {
return self->tail;
}
VNode* node = self->root;
for(level = self->shift; level > 0; level -= SHIFT) {
node = (VNode*) node->items[(i >> level) & BIT_MASK];
}
return node;
}
PyErr_Format(PyExc_IndexError, "Index out of range: %i", i);
return NULL;
}
static PyObject* _get_item(PVector *self, Py_ssize_t pos) {
VNode* node = nodeFor((PVector*)self, pos);
PyObject *result = NULL;
if(node != NULL) {
result = node->items[pos & BIT_MASK];
}
return result;
}
/*
Returns a new reference as specified by the PySequence_GetItem function.
*/
static PyObject* PVector_get_item(PVector *self, Py_ssize_t pos) {
if (pos < 0) {
pos += self->count;
}
PyObject* obj = _get_item(self, pos);
Py_XINCREF(obj);
return obj;
}
static void releaseNode(int level, VNode *node) {
if(node == NULL) {
return;
}
debug("releaseNode(): node=%p, level=%i, refCount=%i\n", node, level, NODE_REF_COUNT(node));
int i;
DEC_NODE_REF_COUNT(node);
debug("Refcount when trying to release: %u\n", NODE_REF_COUNT(node));
if(NODE_REF_COUNT(node) == 0) {
if(level > 0) {
for(i = 0; i < BRANCH_FACTOR; i++) {
if(node->items[i] != NULL) {
releaseNode(level - SHIFT, node->items[i]);
}
}
freeNode(node);
} else {
for(i = 0; i < BRANCH_FACTOR; i++) {
Py_XDECREF(node->items[i]);
}
freeNode(node);
}
}
debug("releaseNode(): Done! node=%p!\n", node);
}
/*
Returns all references to PyObjects that have been stolen. Also decrements
the internal reference counts used for shared memory structures and deallocates
those if needed.
*/
static void PVector_dealloc(PVector *self) {
debug("Dealloc(): self=%p, self->count=%u, tail->refCount=%u, root->refCount=%u, self->shift=%u, self->tail=%p, self->root=%p\n",
self, self->count, NODE_REF_COUNT(self->tail), NODE_REF_COUNT(self->root), self->shift, self->tail, self->root);
if (self->in_weakreflist != NULL) {
PyObject_ClearWeakRefs((PyObject *) self);
}
PyObject_GC_UnTrack((PyObject*)self);
Py_TRASHCAN_SAFE_BEGIN(self);
releaseNode(0, self->tail);
releaseNode(self->shift, self->root);
PyObject_GC_Del(self);
Py_TRASHCAN_SAFE_END(self);
}
static PyObject *PVector_toList(PVector *self) {
Py_ssize_t i;
PyObject *list = PyList_New(self->count);
for (i = 0; i < self->count; ++i) {
PyObject *o = _get_item(self, i);
Py_INCREF(o);
PyList_SET_ITEM(list, i, o);
}
return list;
}
static PyObject *PVector_repr(PVector *self) {
// Reuse the list repr code, a bit less efficient but saves some code
PyObject *list = PVector_toList(self);
PyObject *list_repr = PyObject_Repr(list);
Py_DECREF(list);
if(list_repr == NULL) {
// Exception raised during call to repr
return NULL;
}
// Repr for list implemented differently in python 2 and 3. Need to
// handle this or core dump will occur.
#if PY_MAJOR_VERSION >= 3
PyObject *s = PyUnicode_FromFormat("%s%U%s", "pvector(", list_repr, ")");
Py_DECREF(list_repr);
#else
PyObject *s = PyString_FromString("pvector(");
PyString_ConcatAndDel(&s, list_repr);
PyString_ConcatAndDel(&s, PyString_FromString(")"));
#endif
return s;
}
static long PVector_hash(PVector *self) {
// Follows the pattern of the tuple hash
long x, y;
Py_ssize_t i;
long mult = 1000003L;
x = 0x456789L;
for(i=0; i<self->count; i++) {
y = PyObject_Hash(_get_item(self, i));
if (y == -1) {
return -1;
}
x = (x ^ y) * mult;
mult += (long)(82520L + i + i);
}
x += 97531L;
if(x == -1) {
x = -2;
}
return x;
}
static PyObject* compareSizes(long vlen, long wlen, int op) {
int cmp;
PyObject *res;
switch (op) {
case Py_LT: cmp = vlen < wlen; break;
case Py_LE: cmp = vlen <= wlen; break;
case Py_EQ: cmp = vlen == wlen; break;
case Py_NE: cmp = vlen != wlen; break;
case Py_GT: cmp = vlen > wlen; break;
case Py_GE: cmp = vlen >= wlen; break;
default: return NULL; /* cannot happen */
}
if (cmp) {
res = Py_True;
} else {
res = Py_False;
}
Py_INCREF(res);
return res;
}
static PyObject* PVector_richcompare(PyObject *v, PyObject *w, int op) {
// Follows the principles of the tuple comparison
PVector *vt, *wt;
Py_ssize_t i;
Py_ssize_t vlen, wlen;
PyObject *list;
PyObject *result;
if(!PVector_CheckExact(v) || !PVector_CheckExact(w)) {
if(PVector_CheckExact(v)) {
list = PVector_toList((PVector*)v);
result = PyObject_RichCompare(list , w, op);
Py_DECREF(list);
return result;
}
if(PVector_CheckExact(w)) {
list = PVector_toList((PVector*)w);
result = PyObject_RichCompare(v, list, op);
Py_DECREF(list);
return result;
}
Py_INCREF(Py_NotImplemented);
return Py_NotImplemented;
}
if((op == Py_EQ) && (v == w)) {
Py_INCREF(Py_True);
return Py_True;
}
vt = (PVector *)v;
wt = (PVector *)w;
vlen = vt->count;
wlen = wt->count;
if (vlen != wlen) {
if (op == Py_EQ) {
Py_INCREF(Py_False);
return Py_False;
} else if (op == Py_NE) {
Py_INCREF(Py_True);
return Py_True;
}
}
/* Search for the first index where items are different. */
PyObject *left = NULL;
PyObject *right = NULL;
for (i = 0; i < vlen && i < wlen; i++) {
left = _get_item(vt, i);
right = _get_item(wt, i);
int k = PyObject_RichCompareBool(left, right, Py_EQ);
if (k < 0) {
return NULL;
}
if (!k) {
break;
}
}
if (i >= vlen || i >= wlen) {
/* No more items to compare -- compare sizes */
return compareSizes(vlen, wlen, op);
}
/* We have an item that differs -- shortcuts for EQ/NE */
if (op == Py_EQ) {
Py_INCREF(Py_False);
return Py_False;
} else if (op == Py_NE) {
Py_INCREF(Py_True);
return Py_True;
} else {
/* Compare the final item again using the proper operator */
return PyObject_RichCompare(left, right, op);
}
}
static PyObject* PVector_repeat(PVector *self, Py_ssize_t n) {
if (n < 0) {
n = 0;
}
if ((n == 0) || (self->count == 0)) {
Py_INCREF(EMPTY_VECTOR);
return (PyObject *)EMPTY_VECTOR;
} else if (n == 1) {
Py_INCREF(self);
return (PyObject *)self;
} else if ((self->count * n)/self->count != n) {
return PyErr_NoMemory();
} else {
int i, j;
PVector *newVec = copyPVector(self);
for(i=0; i<(n-1); i++) {
for(j=0; j<self->count; j++) {
extendWithItem(newVec, PVector_get_item(self, j));
}
}
return (PyObject*)newVec;
}
}
static int PVector_traverse(PVector *o, visitproc visit, void *arg) {
// Naive traverse
Py_ssize_t i;
for (i = o->count; --i >= 0; ) {
Py_VISIT(_get_item(o, i));
}
return 0;
}
static PyObject* PVector_index(PVector *self, PyObject *args) {
// A direct rip-off of the tuple version
Py_ssize_t i, start=0, stop=self->count;
PyObject *value;
if (!PyArg_ParseTuple(args, "O|O&O&:index", &value,
_PyEval_SliceIndex, &start,
_PyEval_SliceIndex, &stop)) {
return NULL;
}
if (start < 0) {
start += self->count;
if (start < 0) {
start = 0;
}
}
if (stop < 0) {
stop += self->count;
if (stop < 0) {
stop = 0;
}
}
for (i = start; i < stop && i < self->count; i++) {
int cmp = PyObject_RichCompareBool(_get_item(self, i), value, Py_EQ);
if (cmp > 0) {
#if PY_MAJOR_VERSION >= 3
return PyLong_FromSsize_t(i);
#else
return PyInt_FromSsize_t(i);
#endif
} else if (cmp < 0) {
return NULL;
}
}
PyErr_SetString(PyExc_ValueError, "PVector.index(x): x not in vector");
return NULL;
}
static PyObject* PVector_count(PVector *self, PyObject *value) {
Py_ssize_t count = 0;
Py_ssize_t i;
for (i = 0; i < self->count; i++) {
int cmp = PyObject_RichCompareBool(_get_item(self, i), value, Py_EQ);
if (cmp > 0) {
count++;
} else if (cmp < 0) {
return NULL;
}
}
#if PY_MAJOR_VERSION >= 3
return PyLong_FromSsize_t(count);
#else
return PyInt_FromSsize_t(count);
#endif
}
static PyObject* PVector_pickle_reduce(PVector *self) {
PyObject* module = PyImport_ImportModule("pvectorc");
PyObject* pvector_fn = PyObject_GetAttrString(module, "pvector");
Py_DECREF(module);
PyObject *list = PVector_toList(self);
PyObject *arg_tuple = PyTuple_New(1);
PyTuple_SET_ITEM(arg_tuple, 0, list);
PyObject *result_tuple = PyTuple_New(2);
PyTuple_SET_ITEM(result_tuple, 0, pvector_fn);
PyTuple_SET_ITEM(result_tuple, 1, arg_tuple);
return result_tuple;
}
static PVector* rawCopyPVector(PVector* vector) {
PVector* newVector = PyObject_GC_New(PVector, &PVectorType);
newVector->count = vector->count;
newVector->shift = vector->shift;
newVector->root = vector->root;
newVector->tail = vector->tail;
newVector->in_weakreflist = NULL;
PyObject_GC_Track((PyObject*)newVector);
return newVector;
}
static void initializeEvolver(PVectorEvolver* evolver, PVector* vector, PyObject* appendList) {
// Need to hold a reference to the underlying vector to manage
// the ref counting properly.
evolver->originalVector = vector;
evolver->newVector = vector;
if(appendList == NULL) {
evolver->appendList = PyList_New(0);
} else {
evolver->appendList = appendList;
}
}
static PyObject * PVector_evolver(PVector *self) {
PVectorEvolver *evolver = PyObject_GC_New(PVectorEvolver, &PVectorEvolverType);
if (evolver == NULL) {
return NULL;
}
initializeEvolver(evolver, self, NULL);
PyObject_GC_Track(evolver);
Py_INCREF(self);
return (PyObject *)evolver;
}
static void copyInsert(void** dest, void** src, Py_ssize_t pos, void *obj) {
memcpy(dest, src, BRANCH_FACTOR * sizeof(void*));
dest[pos] = obj;
}
static PyObject* PVector_append(PVector *self, PyObject *obj);
static PyObject* PVector_transform(PVector *self, PyObject *obj);
static PyObject* PVector_set(PVector *self, PyObject *obj);
static PyObject* PVector_mset(PVector *self, PyObject *args);
static PyObject* PVector_subscript(PVector* self, PyObject* item);
static PyObject* PVector_extend(PVector *self, PyObject *args);
static PyObject* PVector_delete(PVector *self, PyObject *args);
static PyObject* PVector_remove(PVector *self, PyObject *args);
static PySequenceMethods PVector_sequence_methods = {
(lenfunc)PVector_len, /* sq_length */
(binaryfunc)PVector_extend, /* sq_concat */
(ssizeargfunc)PVector_repeat, /* sq_repeat */
(ssizeargfunc)PVector_get_item, /* sq_item */
// TODO might want to move the slice function to here
NULL, /* sq_slice */
NULL, /* sq_ass_item */
NULL, /* sq_ass_slice */
NULL, /* sq_contains */
NULL, /* sq_inplace_concat */
NULL, /* sq_inplace_repeat */
};
static PyMappingMethods PVector_mapping_methods = {
(lenfunc)PVector_len,
(binaryfunc)PVector_subscript,
NULL
};
static PyMethodDef PVector_methods[] = {
{"append", (PyCFunction)PVector_append, METH_O, "Appends an element"},
{"set", (PyCFunction)PVector_set, METH_VARARGS, "Inserts an element at the specified position"},
{"extend", (PyCFunction)PVector_extend, METH_O|METH_COEXIST, "Extend"},
{"transform", (PyCFunction)PVector_transform, METH_VARARGS, "Apply one or more transformations"},
{"index", (PyCFunction)PVector_index, METH_VARARGS, "Return first index of value"},
{"count", (PyCFunction)PVector_count, METH_O, "Return number of occurrences of value"},
{"__reduce__", (PyCFunction)PVector_pickle_reduce, METH_NOARGS, "Pickle support method"},
{"evolver", (PyCFunction)PVector_evolver, METH_NOARGS, "Return new evolver for pvector"},
{"mset", (PyCFunction)PVector_mset, METH_VARARGS, "Inserts multiple elements at the specified positions"},
{"tolist", (PyCFunction)PVector_toList, METH_NOARGS, "Convert to list"},
{"delete", (PyCFunction)PVector_delete, METH_VARARGS, "Delete element(s) by index"},
{"remove", (PyCFunction)PVector_remove, METH_VARARGS, "Remove element(s) by equality"},
{NULL}
};
static PyObject * PVectorIter_iter(PyObject *seq);
static PyTypeObject PVectorType = {
PyVarObject_HEAD_INIT(NULL, 0)
"pvectorc.PVector", /* tp_name */
sizeof(PVector), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)PVector_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_compare */
(reprfunc)PVector_repr, /* tp_repr */
0, /* tp_as_number */
&PVector_sequence_methods, /* tp_as_sequence */
&PVector_mapping_methods, /* tp_as_mapping */
(hashfunc)PVector_hash, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */
"Persistent vector", /* tp_doc */
(traverseproc)PVector_traverse, /* tp_traverse */
0, /* tp_clear */
PVector_richcompare, /* tp_richcompare */
offsetof(PVector, in_weakreflist), /* tp_weaklistoffset */
PVectorIter_iter, /* tp_iter */
0, /* tp_iternext */
PVector_methods, /* tp_methods */
PVector_members, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
};
static PyObject* pyrsistent_pvec(PyObject *self, PyObject *args) {
debug("pyrsistent_pvec(): %x\n", args);
PyObject *argObj = NULL; /* list of arguments */
if(!PyArg_ParseTuple(args, "|O", &argObj)) {
return NULL;
}
if(argObj == NULL) {
Py_INCREF(EMPTY_VECTOR);
return (PyObject*)EMPTY_VECTOR;
}
return PVector_extend(EMPTY_VECTOR, argObj);
}
static PVector* emptyNewPvec(void) {
PVector *pvec = PyObject_GC_New(PVector, &PVectorType);
debug("pymem alloc_new %x, ref cnt: %u\n", pvec, pvec->ob_refcnt);
pvec->count = (Py_ssize_t)0;
pvec->shift = SHIFT;
pvec->root = newNode();
pvec->tail = newNode();
pvec->in_weakreflist = NULL;
PyObject_GC_Track((PyObject*)pvec);
return pvec;
}
static void incRefs(PyObject **obj) {
// TODO-OPT: Would it be OK to exit on first NULL? Should not be any
// non NULLs beyond a NULL.
int i;
for(i = 0; i < BRANCH_FACTOR; i++) {
Py_XINCREF(obj[i]);
}
}
static PVector* newPvec(unsigned int count, unsigned int shift, VNode *root) {
// TODO-OPT: Introduce object cache
PVector *pvec = PyObject_GC_New(PVector, &PVectorType);
debug("pymem alloc_copy %x, ref cnt: %u\n", pvec, pvec->ob_refcnt);
pvec->count = count;
pvec->shift = shift;
pvec->root = root;
pvec->tail = newNode();
pvec->in_weakreflist = NULL;
PyObject_GC_Track((PyObject*)pvec);
return pvec;
}
static VNode* newPath(unsigned int level, VNode* node){
if(level == 0) {
INC_NODE_REF_COUNT(node);
return node;
}
VNode* result = newNode();
result->items[0] = newPath(level - SHIFT, node);
return result;
}
static VNode* pushTail(unsigned int level, unsigned int count, VNode* parent, VNode* tail) {
int subIndex = ((count - 1) >> level) & BIT_MASK;
VNode* result = copyNode(parent);
VNode* nodeToInsert;
VNode* child;
debug("pushTail(): count = %i, subIndex = %i\n", count, subIndex);
if(level == SHIFT) {
// We're at the bottom
INC_NODE_REF_COUNT(tail);
nodeToInsert = tail;
} else {
// More levels available in the tree
child = parent->items[subIndex];
if(child != NULL) {
nodeToInsert = pushTail(level - SHIFT, count, child, tail);
// Need to make an adjustment of the ref COUNT for the child node here since
// it was incremented in an earlier stage when the node was copied. Now the child
// node will be part of the path copy so the number of references to the original
// child will not increase at all.
DEC_NODE_REF_COUNT(child);
} else {
nodeToInsert = newPath(level - SHIFT, tail);
}
}
result->items[subIndex] = nodeToInsert;
return result;
}
static PVector* copyPVector(PVector *original) {
PVector *newVec = newPvec(original->count, original->shift, original->root);
INC_NODE_REF_COUNT(original->root);
memcpy(newVec->tail->items, original->tail->items, TAIL_SIZE(original) * sizeof(void*));
incRefs((PyObject**)newVec->tail->items);
return newVec;
}
/* Does not steal a reference, this must be managed outside of this function */
static void extendWithItem(PVector *newVec, PyObject *item) {
unsigned int tail_size = TAIL_SIZE(newVec);
if(tail_size >= BRANCH_FACTOR) {
VNode* new_root;
if(ROOT_NODE_FULL(newVec)) {
new_root = newNode();
new_root->items[0] = newVec->root;
new_root->items[1] = newPath(newVec->shift, newVec->tail);
newVec->shift += SHIFT;
} else {
new_root = pushTail(newVec->shift, newVec->count, newVec->root, newVec->tail);
releaseNode(newVec->shift, newVec->root);
}
newVec->root = new_root;
// Need to adjust the ref count of the old tail here since no new references were
// actually created, we just moved the tail.
DEC_NODE_REF_COUNT(newVec->tail);
newVec->tail = newNode();
tail_size = 0;
}
newVec->tail->items[tail_size] = item;
newVec->count++;
}
#if PY_MAJOR_VERSION >= 3
// This was changed in 3.2 but we do not claim compatibility with any older version of python 3.
#define SLICE_CAST
#else
#define SLICE_CAST (PySliceObject *)
#endif
static PyObject *PVector_subscript(PVector* self, PyObject* item) {
if (PyIndex_Check(item)) {
Py_ssize_t i = PyNumber_AsSsize_t(item, PyExc_IndexError);
if (i == -1 && PyErr_Occurred()) {
return NULL;
}
return PVector_get_item(self, i);
} else if (PySlice_Check(item)) {
Py_ssize_t start, stop, step, slicelength, cur, i;
if (PySlice_GetIndicesEx(SLICE_CAST item, self->count,
&start, &stop, &step, &slicelength) < 0) {
return NULL;
}
debug("start=%i, stop=%i, step=%i\n", start, stop, step);
if (slicelength <= 0) {
Py_INCREF(EMPTY_VECTOR);
return (PyObject*)EMPTY_VECTOR;
} else if((slicelength == self->count) && (step > 0)) {
Py_INCREF(self);
return (PyObject*)self;
} else {
PVector *newVec = copyPVector(EMPTY_VECTOR);
for (cur=start, i=0; i<slicelength; cur += (size_t)step, i++) {
extendWithItem(newVec, PVector_get_item(self, cur));
}
return (PyObject*)newVec;
}
} else {
PyErr_Format(PyExc_TypeError, "pvector indices must be integers, not %.200s", Py_TYPE(item)->tp_name);
return NULL;
}
}
/* A hack to get some of the error handling code away from the function
doing the actual work */
#define HANDLE_ITERATION_ERROR() \
if (PyErr_Occurred()) { \
if (PyErr_ExceptionMatches(PyExc_StopIteration)) { \
PyErr_Clear(); \
} else { \
return NULL; \
} \
}
/* Returns a new vector that is extended with the iterable b.
Takes a copy of the original vector and performs the extension in place on this
one for efficiency.
These are some optimizations that could be done to this function,
these are not considered important enough yet though.
- Use the PySequence_Fast ops if the iterable is a list or a tuple (which it
whould probably often be)
- Only copy the original tail if it is not full
- No need to try to increment ref count in tail for the whole tail
*/
static PyObject* PVector_extend(PVector *self, PyObject *iterable) {
PyObject *it;
PyObject *(*iternext)(PyObject *);
it = PyObject_GetIter(iterable);
if (it == NULL) {
return NULL;
}
// TODO-OPT: Use special fast iterator if available
iternext = *Py_TYPE(it)->tp_iternext;
PyObject *item = iternext(it);
if (item == NULL) {
Py_DECREF(it);
HANDLE_ITERATION_ERROR()
Py_INCREF(self);
return (PyObject *)self;
} else {
PVector *newVec = copyPVector(self);
// TODO-OPT test using special case code here for extension to
// avoid recalculating tail length all the time.
while(item != NULL) {
extendWithItem(newVec, item);
item = iternext(it);
}
Py_DECREF(it);
HANDLE_ITERATION_ERROR()
return (PyObject*)newVec;
}
}
/*
Steals a reference to the object that is appended to the list.
*/
static PyObject* PVector_append(PVector *self, PyObject *obj) {
assert (obj != NULL);
unsigned int tail_size = TAIL_SIZE(self);
debug("append(): count = %u, tail_size = %u\n", self->count, tail_size);
// Does the new object fit in the tail? If so, take a copy of the tail and
// insert the new element in that.
if(tail_size < BRANCH_FACTOR) {
INC_NODE_REF_COUNT(self->root);
PVector *new_pvec = newPvec(self->count + 1, self->shift, self->root);
// TODO-OPT No need to copy more than the current tail length
// TODO-OPT No need to incRefs for all elements all the time
copyInsert(new_pvec->tail->items, self->tail->items, tail_size, obj);
incRefs((PyObject**)new_pvec->tail->items);
debug("append(): new_pvec=%p, new_pvec->tail=%p, new_pvec->root=%p\n",
new_pvec, new_pvec->tail, new_pvec->root);
return (PyObject*)new_pvec;
}
// Tail is full, need to push it into the tree
VNode* new_root;
unsigned int new_shift;
if(ROOT_NODE_FULL(self)) {
new_root = newNode();
new_root->items[0] = self->root;
INC_NODE_REF_COUNT(self->root);
new_root->items[1] = newPath(self->shift, self->tail);
new_shift = self->shift + SHIFT;
} else {
new_root = pushTail(self->shift, self->count, self->root, self->tail);
new_shift = self->shift;
}
PVector* pvec = newPvec(self->count + 1, new_shift, new_root);
pvec->tail->items[0] = obj;
Py_XINCREF(obj);
debug("append_push(): pvec=%p, pvec->tail=%p, pvec->root=%p\n", pvec, pvec->tail, pvec->root);
return (PyObject*)pvec;
}
static VNode* doSet(VNode* node, unsigned int level, unsigned int position, PyObject* value) {
debug("doSet(): level == %i\n", level);
if(level == 0) {
// TODO-OPT: Perhaps an alloc followed by a reset of reference
// count is enough here since we overwrite all subnodes below.
VNode* theNewNode = newNode();
copyInsert(theNewNode->items, node->items, position & BIT_MASK, value);
incRefs((PyObject**)theNewNode->items);
return theNewNode;
} else {
VNode* theNewNode = copyNode(node);
Py_ssize_t index = (position >> level) & BIT_MASK;
// Drop reference to this node since we're about to replace it
DEC_NODE_REF_COUNT((VNode*)theNewNode->items[index]);
theNewNode->items[index] = doSet(node->items[index], level - SHIFT, position, value);
return theNewNode;
}
}
static PyObject* internalSet(PVector *self, Py_ssize_t position, PyObject *argObj) {
if(position < 0) {
position += self->count;
}
if((0 <= position) && (position < self->count)) {
if(position >= TAIL_OFF(self)) {
// Reuse the root, replace the tail
INC_NODE_REF_COUNT(self->root);
PVector *new_pvec = newPvec(self->count, self->shift, self->root);
copyInsert(new_pvec->tail->items, self->tail->items, position & BIT_MASK, argObj);
incRefs((PyObject**)new_pvec->tail->items);
return (PyObject*)new_pvec;
} else {
// Keep the tail, replace the root
VNode *newRoot = doSet(self->root, self->shift, position, argObj);
PVector *new_pvec = newPvec(self->count, self->shift, newRoot);
// Free the tail and replace it with a reference to the tail of the original vector
freeNode(new_pvec->tail);
new_pvec->tail = self->tail;
INC_NODE_REF_COUNT(self->tail);
return (PyObject*)new_pvec;
}
} else if (position == self->count) {
// TODO Remove this case?
return PVector_append(self, argObj);
} else {
PyErr_Format(PyExc_IndexError, "Index out of range: %zd", position);
return NULL;
}
}
static PyObject* PVector_transform(PVector *self, PyObject *obj) {
return transform(self, obj);
}
/*
Steals a reference to the object that is inserted in the vector.
*/
static PyObject* PVector_set(PVector *self, PyObject *args) {
PyObject *argObj = NULL; /* argument to insert */
Py_ssize_t position;
/* The n parses for size, the O parses for a Python object */
if(!PyArg_ParseTuple(args, "nO", &position, &argObj)) {
return NULL;
}
return internalSet(self, position, argObj);
}
static PyObject* PVector_mset(PVector *self, PyObject *args) {
Py_ssize_t size = PyTuple_Size(args);
if(size % 2) {
PyErr_SetString(PyExc_TypeError, "mset expected an even number of arguments");
return NULL;
}
PVectorEvolver* evolver = (PVectorEvolver*)PVector_evolver(self);
Py_ssize_t i;
for(i=0; i<size; i+=2) {
if(PVectorEvolver_set_item(evolver, PyTuple_GetItem(args, i), PyTuple_GetItem(args, i + 1)) < 0) {
Py_DECREF(evolver);
return NULL;
}
}
PyObject* vector = PVectorEvolver_persistent(evolver);
Py_DECREF(evolver);
return vector;
}
static PyObject* internalDelete(PVector *self, Py_ssize_t index, PyObject *stop_obj) {
Py_ssize_t stop;
PyObject *list;
PyObject *result;
if (index < 0) {
index += self->count;
}
if (stop_obj != NULL) {
if (PyIndex_Check(stop_obj)) {
stop = PyNumber_AsSsize_t(stop_obj, PyExc_IndexError);
if (stop == -1 && PyErr_Occurred()) {
return NULL;
}
} else {
PyErr_Format(PyExc_TypeError, "Stop index must be integer, not %.200s", Py_TYPE(stop_obj)->tp_name);
return NULL;
}
if (stop < 0) {
stop += self->count;
}
} else {
if (index < 0 || index >= self->count) {
PyErr_SetString(PyExc_IndexError, "delete index out of range");
return NULL;
}
stop = index + 1;
}
list = PVector_toList(self);
if(PyList_SetSlice(list, index, stop, NULL) < 0) {
return NULL;
}
result = PVector_extend(EMPTY_VECTOR, list);
Py_DECREF(list);
return result;
}
static PyObject* PVector_delete(PVector *self, PyObject *args) {
Py_ssize_t index;
PyObject *stop_obj = NULL;
if(!PyArg_ParseTuple(args, "n|O:delete", &index, &stop_obj)) {
return NULL;
}
return internalDelete(self, index, stop_obj);
}
static PyObject* PVector_remove(PVector *self, PyObject *args) {
Py_ssize_t index;
PyObject* py_index = PVector_index(self, args);
if(py_index != NULL) {
#if PY_MAJOR_VERSION >= 3
index = PyLong_AsSsize_t(py_index);
#else
index = PyInt_AsSsize_t(py_index);
#endif
Py_DECREF(py_index);
return internalDelete(self, index, NULL);
}
PyErr_SetString(PyExc_ValueError, "PVector.remove(x): x not in vector");
return NULL;
}
/*********************** PVector Iterator **************************/
/*
The Sequence class provides us with a default iterator but the runtime
overhead of using that compared to the iterator below is huge.
*/
typedef struct {
PyObject_HEAD
Py_ssize_t it_index;
PVector *it_seq; /* Set to NULL when iterator is exhausted */
} PVectorIter;
static void PVectorIter_dealloc(PVectorIter *);
static int PVectorIter_traverse(PVectorIter *, visitproc, void *);
static PyObject *PVectorIter_next(PVectorIter *);
static PyMethodDef PVectorIter_methods[] = {
{NULL, NULL} /* sentinel */
};
static PyTypeObject PVectorIterType = {
PyVarObject_HEAD_INIT(NULL, 0)
"pvector_iterator", /* tp_name */
sizeof(PVectorIter), /* tp_basicsize */
0, /* tp_itemsize */
/* methods */
(destructor)PVectorIter_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_compare */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
PyObject_GenericGetAttr, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */
0, /* tp_doc */
(traverseproc)PVectorIter_traverse, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
PyObject_SelfIter, /* tp_iter */
(iternextfunc)PVectorIter_next, /* tp_iternext */
PVectorIter_methods, /* tp_methods */
0, /* tp_members */
};
static PyObject *PVectorIter_iter(PyObject *seq) {
PVectorIter *it = PyObject_GC_New(PVectorIter, &PVectorIterType);
if (it == NULL) {
return NULL;
}
it->it_index = 0;
Py_INCREF(seq);
it->it_seq = (PVector *)seq;
PyObject_GC_Track(it);
return (PyObject *)it;
}
static void PVectorIter_dealloc(PVectorIter *it) {
PyObject_GC_UnTrack(it);
Py_XDECREF(it->it_seq);
PyObject_GC_Del(it);
}
static int PVectorIter_traverse(PVectorIter *it, visitproc visit, void *arg) {
Py_VISIT(it->it_seq);
return 0;
}
static PyObject *PVectorIter_next(PVectorIter *it) {
assert(it != NULL);
PVector *seq = it->it_seq;
if (seq == NULL) {
return NULL;
}
if (it->it_index < seq->count) {
PyObject *item = _get_item(seq, it->it_index);
++it->it_index;
Py_INCREF(item);
return item;
}
Py_DECREF(seq);
it->it_seq = NULL;
return NULL;
}
/*********************** PVector Evolver **************************/
/*
Evolver to make multi updates easier to work with and more efficient.
*/
static void PVectorEvolver_dealloc(PVectorEvolver *);
static PyObject *PVectorEvolver_append(PVectorEvolver *, PyObject *);
static PyObject *PVectorEvolver_extend(PVectorEvolver *, PyObject *);
static PyObject *PVectorEvolver_set(PVectorEvolver *, PyObject *);
static PyObject *PVectorEvolver_delete(PVectorEvolver *self, PyObject *args);
static PyObject *PVectorEvolver_subscript(PVectorEvolver *, PyObject *);
static PyObject *PVectorEvolver_persistent(PVectorEvolver *);
static Py_ssize_t PVectorEvolver_len(PVectorEvolver *);
static PyObject *PVectorEvolver_is_dirty(PVectorEvolver *);
static int PVectorEvolver_traverse(PVectorEvolver *self, visitproc visit, void *arg);
static PyMappingMethods PVectorEvolver_mapping_methods = {
(lenfunc)PVectorEvolver_len,
(binaryfunc)PVectorEvolver_subscript,
(objobjargproc)PVectorEvolver_set_item,
};
static PyMethodDef PVectorEvolver_methods[] = {
{"append", (PyCFunction)PVectorEvolver_append, METH_O, "Appends an element"},
{"extend", (PyCFunction)PVectorEvolver_extend, METH_O|METH_COEXIST, "Extend"},
{"set", (PyCFunction)PVectorEvolver_set, METH_VARARGS, "Set item"},
{"delete", (PyCFunction)PVectorEvolver_delete, METH_VARARGS, "Delete item"},
{"persistent", (PyCFunction)PVectorEvolver_persistent, METH_NOARGS, "Create PVector from evolver"},
{"is_dirty", (PyCFunction)PVectorEvolver_is_dirty, METH_NOARGS, "Check if evolver contains modifications"},
{NULL, NULL} /* sentinel */
};
static PyTypeObject PVectorEvolverType = {
PyVarObject_HEAD_INIT(NULL, 0)
"pvector_evolver", /* tp_name */
sizeof(PVectorEvolver), /* tp_basicsize */
0, /* tp_itemsize */
/* methods */
(destructor)PVectorEvolver_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_compare */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
&PVectorEvolver_mapping_methods, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
PyObject_GenericGetAttr, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */
0, /* tp_doc */
(traverseproc)PVectorEvolver_traverse, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
PVectorEvolver_methods, /* tp_methods */
0, /* tp_members */
};
// Indicate that a node is "dirty" (has been updated by the evolver)
// by setting the MSB of the refCount. This will be cleared when
// creating a pvector from the evolver (cleaning it).
#define DIRTY_BIT 0x80000000
#define REF_COUNT_MASK (~DIRTY_BIT)
#define IS_DIRTY(node) ((node)->refCount & DIRTY_BIT)
#define SET_DIRTY(node) ((node)->refCount |= DIRTY_BIT)
#define CLEAR_DIRTY(node) ((node)->refCount &= REF_COUNT_MASK)
static void cleanNodeRecursively(VNode *node, int level) {
debug("Cleaning recursively node=%p, level=%u\n", node, level);
int i;
CLEAR_DIRTY(node);
SET_NODE_REF_COUNT(node, 1);
if(level > 0) {
for(i = 0; i < BRANCH_FACTOR; i++) {
VNode *nextNode = (VNode*)node->items[i];
if((nextNode != NULL) && IS_DIRTY(nextNode)) {
cleanNodeRecursively(nextNode, level - SHIFT);
}
}
}
}
static void cleanVector(PVector *vector) {
// Cleaning the vector means that all dirty indications are cleared
// and that the nodes that were dirty get a ref count of 1 since
// they are brand new. Once cleaned the vector can be released into
// the wild.
if(IS_DIRTY(vector->tail)) {
cleanNodeRecursively(vector->tail, 0);
} else {
INC_NODE_REF_COUNT(vector->tail);
}
if(IS_DIRTY(vector->root)) {
cleanNodeRecursively(vector->root, vector->shift);
} else {
INC_NODE_REF_COUNT(vector->root);
}
}
static void PVectorEvolver_dealloc(PVectorEvolver *self) {
PyObject_GC_UnTrack(self);
Py_TRASHCAN_SAFE_BEGIN(self);
if(self->originalVector != self->newVector) {
cleanVector(self->newVector);
Py_DECREF(self->newVector);
}
Py_DECREF(self->originalVector);
Py_DECREF(self->appendList);
PyObject_GC_Del(self);
Py_TRASHCAN_SAFE_END(self);
}
static PyObject *PVectorEvolver_append(PVectorEvolver *self, PyObject *args) {
if (PyList_Append(self->appendList, args) == 0) {
Py_INCREF(self);
return (PyObject*)self;
}
return NULL;
}
static PyObject *PVectorEvolver_extend(PVectorEvolver *self, PyObject *args) {
PyObject *retVal = _PyList_Extend((PyListObject *)self->appendList, args);
if (retVal == NULL) {
return NULL;
}
Py_DECREF(retVal);
Py_INCREF(self);
return (PyObject*)self;
}
static PyObject *PVectorEvolver_subscript(PVectorEvolver *self, PyObject *item) {
if (PyIndex_Check(item)) {
Py_ssize_t position = PyNumber_AsSsize_t(item, PyExc_IndexError);
if (position == -1 && PyErr_Occurred()) {
return NULL;
}
if (position < 0) {
position += self->newVector->count + PyList_GET_SIZE(self->appendList);
}
if(0 <= position && position < self->newVector->count) {
PyObject *result = _get_item(self->newVector, position);
Py_XINCREF(result);
return result;
} else if (0 <= position && position < (self->newVector->count + PyList_GET_SIZE(self->appendList))) {
PyObject *result = PyList_GetItem(self->appendList, position - self->newVector->count);
Py_INCREF(result);
return result;
} else {
PyErr_SetString(PyExc_IndexError, "Index out of range");
}
} else {
PyErr_Format(PyExc_TypeError, "Indices must be integers, not %.200s", item->ob_type->tp_name);
}
return NULL;
}
static VNode* doSetWithDirty(VNode* node, unsigned int level, unsigned int position, PyObject* value) {
VNode* resultNode;
debug("doSetWithDirty(): level == %i\n", level);
if(level == 0) {
if(!IS_DIRTY(node)) {
resultNode = allocNode();
copyInsert(resultNode->items, node->items, position & BIT_MASK, value);
incRefs((PyObject**)resultNode->items);
SET_DIRTY(resultNode);
} else {
resultNode = node;
Py_INCREF(value);
Py_DECREF(resultNode->items[position & BIT_MASK]);
resultNode->items[position & BIT_MASK] = value;
}
} else {
if(!IS_DIRTY(node)) {
resultNode = copyNode(node);
SET_DIRTY(resultNode);
} else {
resultNode = node;
}
Py_ssize_t index = (position >> level) & BIT_MASK;
VNode* oldNode = (VNode*)resultNode->items[index];
resultNode->items[index] = doSetWithDirty(resultNode->items[index], level - SHIFT, position, value);
if(resultNode->items[index] != oldNode) {
// Node replaced, drop references to old node
DEC_NODE_REF_COUNT(oldNode);
}
}
return resultNode;
}
/*
Steals a reference to the object that is inserted in the vector.
*/
static PyObject *PVectorEvolver_set(PVectorEvolver *self, PyObject *args) {
PyObject *argObj = NULL; /* argument to insert */
PyObject *position = NULL;
/* The n parses for size, the O parses for a Python object */
if(!PyArg_ParseTuple(args, "OO", &position, &argObj)) {
return NULL;
}
if(PVectorEvolver_set_item(self, position, argObj) < 0) {
return NULL;
}
Py_INCREF(self);
return (PyObject*)self;
}
static PyObject *PVectorEvolver_delete(PVectorEvolver *self, PyObject *args) {
PyObject *position = NULL;
/* The n parses for size, the O parses for a Python object */
if(!PyArg_ParseTuple(args, "O", &position)) {
return NULL;
}
if(PVectorEvolver_set_item(self, position, NULL) < 0) {
return NULL;
}
Py_INCREF(self);
return (PyObject*)self;
}
static int internalPVectorDelete(PVectorEvolver *self, Py_ssize_t position) {
// Delete element. Should be unusual. Simple but expensive operation
// that reuses the delete code for the vector. Realize the vector, delete on it and
// then reset the evolver to work on the new vector.
PVector *temp = (PVector*)PVectorEvolver_persistent(self);
PVector *temp2 = (PVector*)internalDelete(temp, position, NULL);
Py_DECREF(temp);
if(temp2 == NULL) {
return -1;
}
Py_DECREF(self->originalVector);
self->originalVector = temp2;
self->newVector = self->originalVector;
return 0;
}
static int PVectorEvolver_set_item(PVectorEvolver *self, PyObject* item, PyObject* value) {
if (PyIndex_Check(item)) {
Py_ssize_t position = PyNumber_AsSsize_t(item, PyExc_IndexError);
if (position == -1 && PyErr_Occurred()) {
return -1;
}
if (position < 0) {
position += self->newVector->count + PyList_GET_SIZE(self->appendList);
}
if((0 <= position) && (position < self->newVector->count)) {
if(self->originalVector == self->newVector) {
// Create new vector since we're about to modify the original
self->newVector = rawCopyPVector(self->originalVector);
}
if(value != NULL) {
if(position < TAIL_OFF(self->newVector)) {
self->newVector->root = doSetWithDirty(self->newVector->root, self->newVector->shift, position, value);
} else {
self->newVector->tail = doSetWithDirty(self->newVector->tail, 0, position, value);
}
return 0;
}
return internalPVectorDelete(self, position);
} else if((0 <= position) && (position < (self->newVector->count + PyList_GET_SIZE(self->appendList)))) {
if (value != NULL) {
int result = PyList_SetItem(self->appendList, position - self->newVector->count, value);
if(result == 0) {
Py_INCREF(value);
}
return result;
}
return internalPVectorDelete(self, position);
} else if((0 <= position)
&& (position < (self->newVector->count + PyList_GET_SIZE(self->appendList) + 1))
&& (value != NULL)) {
return PyList_Append(self->appendList, value);
} else {
PyErr_Format(PyExc_IndexError, "Index out of range: %zd", position);
}
} else {
PyErr_Format(PyExc_TypeError, "Indices must be integers, not %.200s", item->ob_type->tp_name);
}
return -1;
}
static PyObject *PVectorEvolver_persistent(PVectorEvolver *self) {
PVector *resultVector;
if(self->newVector != self->originalVector) {
cleanVector(self->newVector);
Py_DECREF(self->originalVector);
}
resultVector = self->newVector;
if(PyList_GET_SIZE(self->appendList)) {
PVector *oldVector = resultVector;
resultVector = (PVector*)PVector_extend(resultVector, self->appendList);
Py_DECREF(oldVector);
Py_DECREF(self->appendList);
self->appendList = NULL;
}
initializeEvolver(self, resultVector, self->appendList);
Py_INCREF(resultVector);
return (PyObject*)resultVector;
}
static Py_ssize_t PVectorEvolver_len(PVectorEvolver *self) {
return self->newVector->count + PyList_GET_SIZE(self->appendList);
}
static PyObject* PVectorEvolver_is_dirty(PVectorEvolver *self) {
if((self->newVector != self->originalVector) || (PyList_GET_SIZE(self->appendList) > 0)) {
Py_INCREF(Py_True);
return Py_True;
}
Py_INCREF(Py_False);
return Py_False;
}
static int PVectorEvolver_traverse(PVectorEvolver *self, visitproc visit, void *arg) {
Py_VISIT(self->newVector);
if (self->newVector != self->originalVector) {
Py_VISIT(self->originalVector);
}
Py_VISIT(self->appendList);
return 0;
}
static PyMethodDef PyrsistentMethods[] = {
{"pvector", pyrsistent_pvec, METH_VARARGS,
"pvector([iterable])\n"
"Create a new persistent vector containing the elements in iterable.\n\n"
">>> v1 = pvector([1, 2, 3])\n"
">>> v1\n"
"pvector([1, 2, 3])"},
{NULL, NULL, 0, NULL}
};
/********************* Python module initialization ************************/
#if PY_MAJOR_VERSION >= 3
static struct PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT,
"pvectorc", /* m_name */
"Persistent vector", /* m_doc */
-1, /* m_size */
PyrsistentMethods, /* m_methods */
NULL, /* m_reload */
NULL, /* m_traverse */
NULL, /* m_clear */
NULL, /* m_free */
};
#endif
static PyObject* pyrsistent_pvectorc_moduleinit(void) {
PyObject* m;
// Only allow creation/initialization through factory method pvec
PVectorType.tp_init = NULL;
PVectorType.tp_new = NULL;
if (PyType_Ready(&PVectorType) < 0) {
return NULL;
}
if (PyType_Ready(&PVectorIterType) < 0) {
return NULL;
}
if (PyType_Ready(&PVectorEvolverType) < 0) {
return NULL;
}
#if PY_MAJOR_VERSION >= 3
m = PyModule_Create(&moduledef);
#else
m = Py_InitModule3("pvectorc", PyrsistentMethods, "Persistent vector");
#endif
if (m == NULL) {
return NULL;
}
if(EMPTY_VECTOR == NULL) {
EMPTY_VECTOR = emptyNewPvec();
}
nodeCache.size = 0;
Py_INCREF(&PVectorType);
PyModule_AddObject(m, "PVector", (PyObject *)&PVectorType);
return m;
}
#if PY_MAJOR_VERSION >= 3
PyMODINIT_FUNC PyInit_pvectorc(void) {
return pyrsistent_pvectorc_moduleinit();
}
#else
PyMODINIT_FUNC initpvectorc(void) {
pyrsistent_pvectorc_moduleinit();
}
#endif