Skip to content

Commit 24c575b

Browse files
committed
Implement mutable mapping API for MapMutation; add after-finalize checks
1 parent 4276e0c commit 24c575b

File tree

3 files changed

+271
-69
lines changed

3 files changed

+271
-69
lines changed

immutables/_map.c

Lines changed: 156 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Now let's partition this bit representation of the hash into blocks of
3737
0b00_00000_10010_11101_00101_01011_10000 = 19830128
3838
(6) (5) (4) (3) (2) (1)
3939
40-
Each block of 5 bits represents a number betwen 0 and 31. So if we have
40+
Each block of 5 bits represents a number between 0 and 31. So if we have
4141
a tree that consists of nodes, each of which is an array of 32 pointers,
4242
those 5-bit blocks will encode a position on a single tree level.
4343
@@ -885,7 +885,7 @@ map_node_bitmap_assoc(MapNode_Bitmap *self,
885885
pairs.
886886
887887
Small map objects (<30 keys) usually don't have any
888-
Array nodes at all. Betwen ~30 and ~400 keys map
888+
Array nodes at all. Between ~30 and ~400 keys map
889889
objects usually have one Array node, and usually it's
890890
a root node.
891891
*/
@@ -2460,7 +2460,7 @@ map_without(MapObject *o, PyObject *key)
24602460
return NULL;
24612461
}
24622462

2463-
MapNode *new_root;
2463+
MapNode *new_root = NULL;
24642464

24652465
map_without_t res = map_node_without(
24662466
(MapNode *)(o->h_root),
@@ -3715,31 +3715,74 @@ map_update(uint64_t mutid, MapObject *o, PyObject *src)
37153715
return new;
37163716
}
37173717

3718+
static int
3719+
mapmut_check_finalized(MapMutationObject *o)
3720+
{
3721+
if (o->m_mutid == 0) {
3722+
PyErr_Format(
3723+
PyExc_ValueError,
3724+
"mutation %R has been finalized",
3725+
o, NULL);
3726+
return -1;
3727+
}
37183728

3719-
static PyObject *
3720-
mapmut_py_set(MapMutationObject *o, PyObject *args)
3729+
return 0;
3730+
}
3731+
3732+
static int
3733+
mapmut_delete(MapMutationObject *o, PyObject *key, int32_t key_hash)
37213734
{
3722-
PyObject *key;
3723-
PyObject *val;
3735+
MapNode *new_root = NULL;
37243736

3725-
if (!PyArg_UnpackTuple(args, "set", 2, 2, &key, &val)) {
3726-
return NULL;
3727-
}
3737+
assert(key_hash != -1);
3738+
map_without_t res = map_node_without(
3739+
(MapNode *)(o->m_root),
3740+
0, key_hash, key,
3741+
&new_root,
3742+
o->m_mutid);
37283743

3729-
int32_t key_hash;
3730-
int added_leaf = 0;
3744+
switch (res) {
3745+
case W_ERROR:
3746+
return -1;
37313747

3732-
key_hash = map_hash(key);
3733-
if (key_hash == -1) {
3734-
return NULL;
3748+
case W_EMPTY:
3749+
new_root = map_node_bitmap_new(0, o->m_mutid);
3750+
if (new_root == NULL) {
3751+
return -1;
3752+
}
3753+
Py_SETREF(o->m_root, new_root);
3754+
o->m_count = 0;
3755+
return 0;
3756+
3757+
case W_NOT_FOUND:
3758+
PyErr_SetObject(PyExc_KeyError, key);
3759+
return -1;
3760+
3761+
case W_NEWNODE: {
3762+
assert(new_root != NULL);
3763+
Py_SETREF(o->m_root, new_root);
3764+
o->m_count--;
3765+
return 0;
3766+
}
3767+
3768+
default:
3769+
abort();
37353770
}
3771+
}
3772+
3773+
static int
3774+
mapmut_set(MapMutationObject *o, PyObject *key, int32_t key_hash,
3775+
PyObject *val)
3776+
{
3777+
int added_leaf = 0;
37363778

3779+
assert(key_hash != -1);
37373780
MapNode *new_root = map_node_assoc(
37383781
(MapNode *)(o->m_root),
37393782
0, key_hash, key, val, &added_leaf,
37403783
o->m_mutid);
37413784
if (new_root == NULL) {
3742-
return NULL;
3785+
return -1;
37433786
}
37443787

37453788
if (added_leaf) {
@@ -3748,62 +3791,39 @@ mapmut_py_set(MapMutationObject *o, PyObject *args)
37483791

37493792
if (new_root == o->m_root) {
37503793
Py_DECREF(new_root);
3751-
goto done;
3794+
return 0;
37523795
}
37533796

37543797
Py_SETREF(o->m_root, new_root);
3755-
3756-
done:
3757-
Py_RETURN_NONE;
3798+
return 0;
37583799
}
37593800

3760-
37613801
static PyObject *
3762-
mapmut_py_delete(MapMutationObject *o, PyObject *key)
3802+
mapmut_py_set(MapMutationObject *o, PyObject *args)
37633803
{
3764-
int32_t key_hash = map_hash(key);
3765-
if (key_hash == -1) {
3804+
PyObject *key;
3805+
PyObject *val;
3806+
3807+
if (!PyArg_UnpackTuple(args, "set", 2, 2, &key, &val)) {
37663808
return NULL;
37673809
}
37683810

3769-
MapNode *new_root;
3770-
3771-
map_without_t res = map_node_without(
3772-
(MapNode *)(o->m_root),
3773-
0, key_hash, key,
3774-
&new_root,
3775-
o->m_mutid);
3811+
if (mapmut_check_finalized(o)) {
3812+
return NULL;
3813+
}
37763814

3777-
switch (res) {
3778-
case W_ERROR:
3779-
return NULL;
3780-
case W_EMPTY:
3781-
new_root = map_node_bitmap_new(0, o->m_mutid);
3782-
if (new_root == NULL) {
3783-
return NULL;
3784-
}
3785-
Py_SETREF(o->m_root, new_root);
3786-
o->m_count = 0;
3787-
goto done;
3815+
int32_t key_hash = map_hash(key);
3816+
if (key_hash == -1) {
3817+
return NULL;
3818+
}
37883819

3789-
case W_NOT_FOUND:
3790-
PyErr_SetObject(PyExc_KeyError, key);
3791-
return NULL;
3792-
case W_NEWNODE: {
3793-
assert(new_root != NULL);
3794-
Py_SETREF(o->m_root, new_root);
3795-
o->m_count--;
3796-
goto done;
3797-
}
3798-
default:
3799-
abort();
3820+
if (mapmut_set(o, key, key_hash, val)) {
3821+
return NULL;
38003822
}
38013823

3802-
done:
38033824
Py_RETURN_NONE;
38043825
}
38053826

3806-
38073827
static PyObject *
38083828
mapmut_tp_richcompare(PyObject *v, PyObject *w, int op)
38093829
{
@@ -3848,11 +3868,88 @@ mapmut_py_finalize(MapMutationObject *self, PyObject *args)
38483868
return (PyObject *)o;
38493869
}
38503870

3871+
static int
3872+
mapmut_tp_ass_sub(MapMutationObject *self, PyObject *key, PyObject *val)
3873+
{
3874+
if (mapmut_check_finalized(self)) {
3875+
return -1;
3876+
}
3877+
3878+
int32_t key_hash = map_hash(key);
3879+
if (key_hash == -1) {
3880+
return -1;
3881+
}
3882+
3883+
if (val == NULL) {
3884+
return mapmut_delete(self, key, key_hash);
3885+
}
3886+
else {
3887+
return mapmut_set(self, key, key_hash, val);
3888+
}
3889+
}
3890+
3891+
static PyObject *
3892+
mapmut_py_pop(MapMutationObject *self, PyObject *args)
3893+
{
3894+
PyObject *key, *deflt = NULL, *val = NULL;
3895+
3896+
if(!PyArg_UnpackTuple(args, "pop", 1, 2, &key, &deflt)) {
3897+
return NULL;
3898+
}
3899+
3900+
if (mapmut_check_finalized(self)) {
3901+
return NULL;
3902+
}
3903+
3904+
if (!self->m_count) {
3905+
goto not_found;
3906+
}
3907+
3908+
int32_t key_hash = map_hash(key);
3909+
if (key_hash == -1) {
3910+
return NULL;
3911+
}
3912+
3913+
map_find_t find_res = map_node_find(self->m_root, 0, key_hash, key, &val);
3914+
3915+
switch (find_res) {
3916+
case F_ERROR:
3917+
return NULL;
3918+
3919+
case F_NOT_FOUND:
3920+
goto not_found;
3921+
3922+
case F_FOUND:
3923+
break;
3924+
3925+
default:
3926+
abort();
3927+
}
3928+
3929+
Py_INCREF(val);
3930+
3931+
if (mapmut_delete(self, key, key_hash)) {
3932+
Py_DECREF(val);
3933+
return NULL;
3934+
}
3935+
3936+
return val;
3937+
3938+
not_found:
3939+
if (deflt) {
3940+
Py_INCREF(deflt);
3941+
return deflt;
3942+
}
3943+
3944+
PyErr_SetObject(PyExc_KeyError, key);
3945+
return NULL;
3946+
}
3947+
38513948

38523949
static PyMethodDef MapMutation_methods[] = {
38533950
{"set", (PyCFunction)mapmut_py_set, METH_VARARGS, NULL},
38543951
{"get", (PyCFunction)map_py_get, METH_VARARGS, NULL},
3855-
{"delete", (PyCFunction)mapmut_py_delete, METH_O, NULL},
3952+
{"pop", (PyCFunction)mapmut_py_pop, METH_VARARGS, NULL},
38563953
{"finalize", (PyCFunction)mapmut_py_finalize, METH_NOARGS, NULL},
38573954
{NULL, NULL}
38583955
};
@@ -3871,8 +3968,9 @@ static PySequenceMethods MapMutation_as_sequence = {
38713968
};
38723969

38733970
static PyMappingMethods MapMutation_as_mapping = {
3874-
(lenfunc)map_tp_len, /* mp_length */
3875-
(binaryfunc)map_tp_subscript, /* mp_subscript */
3971+
(lenfunc)map_tp_len, /* mp_length */
3972+
(binaryfunc)map_tp_subscript, /* mp_subscript */
3973+
(objobjargproc)mapmut_tp_ass_sub, /* mp_subscript */
38763974
};
38773975

38783976
PyTypeObject _MapMutation_Type = {

immutables/map.py

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def map_bitindex(bitmap, bit):
4444

4545

4646
W_EMPTY, W_NEWNODE, W_NOT_FOUND = range(3)
47+
void = object()
4748

4849

4950
class BitmapNode:
@@ -630,16 +631,9 @@ def __init__(self, count, root):
630631
self.__mutid = _mut_id()
631632

632633
def set(self, key, val):
633-
if self.__mutid == 0:
634-
raise ValueError(f'mutation {self!r} has been finalized')
635-
636-
self.__root, added = self.__root.assoc(
637-
0, map_hash(key), key, val, self.__mutid)
638-
639-
if added:
640-
self.__count += 1
634+
self[key] = val
641635

642-
def delete(self, key):
636+
def __delitem__(self, key):
643637
if self.__mutid == 0:
644638
raise ValueError(f'mutation {self!r} has been finalized')
645639

@@ -654,6 +648,41 @@ def delete(self, key):
654648
self.__root = new_root
655649
self.__count -= 1
656650

651+
def __setitem__(self, key, val):
652+
if self.__mutid == 0:
653+
raise ValueError(f'mutation {self!r} has been finalized')
654+
655+
self.__root, added = self.__root.assoc(
656+
0, map_hash(key), key, val, self.__mutid)
657+
658+
if added:
659+
self.__count += 1
660+
661+
def pop(self, key, *args):
662+
if self.__mutid == 0:
663+
raise ValueError(f'mutation {self!r} has been finalized')
664+
665+
if len(args) > 1:
666+
raise TypeError(
667+
'pop() accepts 1 to 2 positional arguments, '
668+
'got {}'.format(len(args) + 1))
669+
elif len(args) == 1:
670+
default = args[0]
671+
else:
672+
default = void
673+
674+
val = self.get(key, default)
675+
676+
try:
677+
del self[key]
678+
except KeyError:
679+
if val is void:
680+
raise
681+
return val
682+
else:
683+
assert val is not void
684+
return val
685+
657686
def get(self, key, default=None):
658687
try:
659688
return self.__root.find(0, map_hash(key), key)

0 commit comments

Comments
 (0)