algorithms.c: Set `KSZ.max' to `None' to indicate no bound.
authorMark Wooding <mdw@distorted.org.uk>
Sun, 13 Oct 2019 23:50:51 +0000 (00:50 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Sat, 11 Apr 2020 11:49:31 +0000 (12:49 +0100)
algorithms.c
catacomb/__init__.py
t/t-algorithms.py

index 42dbe6f..a195de9 100644 (file)
@@ -121,13 +121,20 @@ static PyObject *keyszrange_pynew(PyTypeObject *ty,
                                  PyObject *arg, PyObject *kw)
 {
   static const char *const kwlist[] = { "default", "min", "max", "mod", 0 };
-  int dfl, min = 0, max = 0, mod = 1;
+  int dfl, min = 0, max, mod = 1;
+  PyObject *maxobj = Py_None;
   keyszrange_pyobj *o;
 
-  if (!PyArg_ParseTupleAndKeywords(arg, kw, "i|iii:new", KWLIST,
-                                  &dfl, &min, &max, &mod))
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "i|iOi:new", KWLIST,
+                                  &dfl, &min, &maxobj, &mod))
     goto end;
-  if (dfl < 0 || min < 0) VALERR("key size cannot be negative");
+  if (maxobj == Py_None)
+    max = 0;
+  else {
+    max = PyInt_AsLong(maxobj);
+    if (max == -1 && PyErr_Occurred()) goto end;
+  }
+  if (dfl < 0 || min < 0 || max < 0) VALERR("key size cannot be negative");
   if (min > dfl || (max && dfl > max)) VALERR("bad key size bounds");
   if (mod <= 0 || dfl%mod || min%mod || max%mod)
     VALERR("bad key size modulus");
@@ -187,7 +194,15 @@ end:
 
 static PyObject *kaget_min(PyObject *me, void *hunoz)
   { return (PyInt_FromLong(0)); }
-#define kaget_max kaget_min
+static PyObject *kaget_max(PyObject *me, void *hunoz)
+  { RETURN_NONE; }
+
+static PyObject *krget_max(PyObject *me, void *hunoz)
+{
+  int max = ((keyszrange_pyobj *)me)->max;
+  if (max) return (PyInt_FromLong(max));
+  else RETURN_NONE;
+}
 
 static PyObject *ksget_min(PyObject *me, void *hunoz)
 {
@@ -273,13 +288,19 @@ static const PyGetSetDef keyszany_pygetset[] = {
 static const PyMemberDef keyszrange_pymembers[] = {
 #define MEMBERSTRUCT keyszrange_pyobj
   MEMBER(min,  T_INT,    READONLY, "KSZ.min -> smallest allowed key size")
-  MEMBER(max,  T_INT,    READONLY, "KSZ.max -> largest allowed key size")
   MEMBER(mod,  T_INT,    READONLY,
                            "KSZ.mod -> key size must be a multiple of this")
 #undef MEMBERSTRUCT
   { 0 }
 };
 
+static const PyGetSetDef keyszrange_pygetset[] = {
+#define GETSETNAME(op, name) kr##op##_##name
+  GET  (max,           "KSZ.max -> largest allowed key size")
+#undef GETSETNAME
+  { 0 }
+};
+
 static const PyGetSetDef keyszset_pygetset[] = {
 #define GETSETNAME(op, name) ks##op##_##name
   GET  (min,           "KSZ.min -> smallest allowed key size")
@@ -429,7 +450,7 @@ static const PyTypeObject keyszrange_pytype_skel = {
   0,                                   /* @tp_iternext@ */
   0,                                   /* @tp_methods@ */
   PYMEMBERS(keyszrange),               /* @tp_members@ */
-  0,                                   /* @tp_getset@ */
+  PYGETSET(keyszrange),                        /* @tp_getset@ */
   0,                                   /* @tp_base@ */
   0,                                   /* @tp_dict@ */
   0,                                   /* @tp_descr_get@ */
index 0e5c31c..26463b4 100644 (file)
@@ -658,10 +658,10 @@ class _tmp:
   def check(me, sz): return me.min <= sz <= me.max and sz%me.mod == 0
   def best(me, sz):
     if sz < me.min: raise ValueError('key too small')
-    elif sz > me.max: return me.max
+    elif me.max is not None and sz > me.max: return me.max
     else: return sz - sz%me.mod
   def pad(me, sz):
-    if sz > me.max: raise ValueError('key too large')
+    if me.max is not None and sz > me.max: raise ValueError('key too large')
     elif sz < me.min: return me.min
     else: sz += me.mod - 1; return sz - sz%me.mod
 _augment(KeySZRange, _tmp)
index 3301228..cff1ebd 100644 (file)
@@ -38,7 +38,7 @@ def bad_key_size(ksz):
   if isinstance(ksz, C.KeySZAny): return None
   elif isinstance(ksz, C.KeySZRange):
     if ksz.mod != 1: return ksz.min + 1
-    elif ksz.max != 0: return ksz.max + 1
+    elif ksz.max is not None: return ksz.max + 1
     elif ksz.min != 0: return ksz.min - 1
     else: return None
   elif isinstance(ksz, C.KeySZSet):
@@ -52,7 +52,7 @@ def different_key_size(ksz, sz):
   if isinstance(ksz, C.KeySZAny): return sz + 1
   elif isinstance(ksz, C.KeySZRange):
     if sz > ksz.min: return sz - ksz.mod
-    elif ksz.max == 0 or sz < ksz.max: return sz + ksz.mod
+    elif ksz.max is None or sz < ksz.max: return sz + ksz.mod
     else: return None
   elif isinstance(ksz, C.KeySZSet):
     for sz1 in sorted(ksz.set):
@@ -145,7 +145,7 @@ class TestKeysize (U.TestCase):
     me.assertEqual(type(ksz), C.KeySZAny)
     me.assertEqual(ksz.default, 20)
     me.assertEqual(ksz.min, 0)
-    me.assertEqual(ksz.max, 0)
+    me.assertEqual(ksz.max, None)
     for n in [0, 12, 20, 5000]:
       me.assertTrue(ksz.check(n))
       me.assertEqual(ksz.best(n), n)
@@ -157,7 +157,7 @@ class TestKeysize (U.TestCase):
     me.assertEqual(type(ksz), C.KeySZAny)
     me.assertEqual(ksz.default, 32)
     me.assertEqual(ksz.min, 0)
-    me.assertEqual(ksz.max, 0)
+    me.assertEqual(ksz.max, None)
     for n in [0, 12, 20, 5000]:
       me.assertTrue(ksz.check(n))
       me.assertEqual(ksz.best(n), n)
@@ -167,7 +167,7 @@ class TestKeysize (U.TestCase):
     ksz = C.KeySZAny(15)
     me.assertEqual(ksz.default, 15)
     me.assertEqual(ksz.min, 0)
-    me.assertEqual(ksz.max, 0)
+    me.assertEqual(ksz.max, None)
     me.assertRaises(ValueError, lambda: C.KeySZAny(-8))
     me.assertEqual(C.KeySZAny(0).default, 0)
 
@@ -230,6 +230,11 @@ class TestKeysize (U.TestCase):
     me.assertEqual(ksz.min, 21)
     me.assertEqual(ksz.max, 35)
     me.assertEqual(ksz.mod, 7)
+    ksz = C.KeySZRange(28, 21, None, 7)
+    me.assertEqual(ksz.min, 21)
+    me.assertEqual(ksz.max, None)
+    me.assertEqual(ksz.mod, 7)
+    me.assertEqual(ksz.pad(36), 42)
     me.assertRaises(ValueError, C.KeySZRange, 29, 21, 35, 7)
     me.assertRaises(ValueError, C.KeySZRange, 28, 20, 35, 7)
     me.assertRaises(ValueError, C.KeySZRange, 28, 21, 34, 7)