changeset 558:7a3c311991d7

An alternate C-implementation of fast_interpolate_variables() that uses the algorithm of the pure-Python implementation
author Franz Glasner <fzglas.hg@dom66.de>
date Wed, 05 Jan 2022 16:02:07 +0100
parents ba8f1295e1e2
children bb160a1e67d7
files configmix/_speedups.c
diffstat 1 files changed, 243 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/configmix/_speedups.c	Tue Jan 04 21:33:09 2022 +0100
+++ b/configmix/_speedups.c	Wed Jan 05 16:02:07 2022 +0100
@@ -691,7 +691,7 @@
 
 static
 PyObject *
-fast_interpolate_variables(PyObject *self, PyObject *args)
+fast_interpolate_variables_old(PyObject *self, PyObject *args)
 {
     PyObject *config;
     PyObject *s;
@@ -960,6 +960,248 @@
 
 static
 PyObject *
+fast_interpolate_variables(PyObject *self, PyObject *args)
+{
+    PyObject *config;
+    PyObject *s;
+    PyObject *cache;
+
+    Py_ssize_t s_len;
+    Py_ssize_t start, rest, end;
+    PyObject *tmp;
+    PyObject *tmp2;
+    PyObject *result = NULL;
+    PyObject *varname = NULL;
+    PyObject *varvalue = NULL;
+    PyObject *filters = NULL;
+    PyObject *err_type;
+    PyObject *err_value;
+    PyObject *err_tb;
+    int use_cache, cacheable;
+    struct speedups_state *sstate;
+
+    if (!PyArg_UnpackTuple(args, "s", 3, 3, &config, &s, &cache)) {
+        return NULL;
+    }
+    s_len = PyUnicode_GetLength(s);   /* also an implicit type check */
+    if (s_len < 0) {
+        return NULL;
+    }
+    if (s_len < 4) {
+        Py_INCREF(s);
+        return s;
+    }
+
+    sstate = PyModule_GetState(self);
+    if (sstate == NULL) {
+        PyErr_SetString(PyExc_RuntimeError, "no module state available");
+        return NULL;
+    }
+
+    start = PyUnicode_Find(s, sstate->STARTTOK, 0, s_len, 1);
+    if (start == -2) {
+        return NULL;
+    }
+    if (start == -1) {
+        Py_INCREF(s);
+        return s;
+    }
+
+    result = PyDict_GetItem(cache, s);    /* borrowed */
+    if (result != NULL) {
+        if (result == sstate->MISSING) {
+            return PyErr_Format(
+                PyExc_KeyError,
+                "Cannot interpolate variables in string %R (cached)",
+                s);
+        }
+        else {
+            Py_INCREF(result);   /* need ownership */
+            return result;
+        }
+    }
+
+    result = PyList_New(0);
+    if (result == 0) {
+        return NULL;
+    }
+
+    rest = 0;
+    use_cache = 1;
+
+    while (start != -1) {
+        if (rest < start) {
+            tmp = PyUnicode_Substring(s, rest, start);
+            if (tmp == NULL) {
+                goto error;
+            }
+            if (PyList_Append(result, tmp) < 0) {
+                Py_DECREF(tmp);
+                goto error;
+            }
+            Py_DECREF(tmp); tmp = NULL;
+        }
+        end = PyUnicode_Find(s, sstate->ENDTOK, start+2, s_len, 1);
+        if (end == -2) {
+            goto error;
+        }
+        if (end == -1) {
+            rest = start;
+            break;
+        }
+
+        varname = PyUnicode_Substring(s, start+2, end); /* 2 == len(STARTTOK) */
+        if (varname == NULL) {
+            goto error;
+        }
+
+        tmp = _fast_split_filters(varname, NULL, sstate);
+        if (tmp == NULL) {
+            goto error;
+        }
+        if (PyTuple_Size(tmp) != 2) {
+            PyErr_SetString(PyExc_TypeError, "tuple of size 2 expected");
+            Py_DECREF(tmp);
+            goto error;
+        }
+        /* Unpack the result tuple */
+        tmp2 = PyTuple_GetItem(tmp, 0);   /* borrowed -- cannot fail */
+        Py_DECREF(varname);
+        Py_INCREF(tmp2);
+        varname = tmp2; tmp2 = NULL;
+        filters = PyTuple_GetItem(tmp, 1);   /* borrowed -- cannot fail */
+        Py_INCREF(filters);
+        Py_DECREF(tmp); tmp = NULL;
+
+        tmp = PyObject_CallMethod(
+            config, "_getvar_s_with_cache_info", "O", varname);
+        if (tmp == NULL) {
+            if (PyErr_ExceptionMatches(PyExc_KeyError)) {
+                cacheable = 1;
+                if (PySequence_Contains(filters, sstate->NONE_FILTER) == 1) {
+                    PyErr_Clear();
+                    Py_INCREF(Py_None);
+                    varvalue = Py_None;
+                }
+                else {
+                    if (PySequence_Contains(filters, sstate->EMPTY_FILTER) == 1) {
+                        PyErr_Clear();
+                        Py_INCREF(sstate->EMPTY_STR);
+                        varvalue = sstate->EMPTY_STR;
+                    }
+                    else {
+                        PyErr_Fetch(&err_type, &err_value, &err_tb);
+                        /* this does NOT steal */
+                        PyDict_SetItem(cache, s, sstate->MISSING);
+                        PyErr_Restore(err_type, err_value, err_tb);
+                        goto error;
+                    }
+                }
+            }
+            else {
+                /* other exception/error than KeyError */
+                goto error;
+            }
+        }
+        else {
+            if (PyTuple_Size(tmp) != 2) {
+                Py_DECREF(tmp);
+                PyErr_SetString(PyExc_TypeError, "tuple of size 2 expected");
+                goto error;
+            }
+            /* unpack the result */
+            varvalue = PyTuple_GetItem(tmp, 0); /* borrowed -- but want own */
+            Py_INCREF(varvalue);
+            cacheable = PyObject_IsTrue(PyTuple_GetItem(tmp, 1));
+            Py_DECREF(tmp); tmp = NULL;
+        }
+
+        if (!cacheable) {
+            use_cache = 0;
+        }
+
+        Py_DECREF(varname); varname = NULL;
+
+        tmp = PyObject_CallMethod(
+            config, "_apply_filters", "OO", filters, varvalue);
+        if (tmp == NULL) {
+            goto error;
+        }
+        Py_DECREF(varvalue);
+        varvalue = tmp; tmp = NULL;
+
+        Py_DECREF(filters); filters = NULL;
+
+        rest = end + 2;  /* 2 == len(ENDTOK) */
+
+        /*
+         * Dont apply and type conversions to the variable value if
+         * the whole `s` is just one expansion
+         */
+        if ((start == 0) && (rest == s_len)) {
+            Py_DECREF(result);
+            result = varvalue; varvalue = NULL;
+            goto success;     /* break out early */
+        }
+
+        /* Handle None like the empty string */
+        if (varvalue != Py_None) {
+            tmp = PyObject_Str(varvalue);
+            if (tmp == NULL) {
+                goto error;
+            }
+            if (PyList_Append(result, tmp) < 0) {
+                Py_DECREF(tmp);
+                goto error;
+            }
+            Py_DECREF(tmp); tmp = NULL;
+        }
+
+        /* don't re-evaluate because `self.getvar_s()` expands already */
+        start = PyUnicode_Find(s, sstate->STARTTOK, rest, s_len, 1);
+        if (start == -2) {
+            goto error;
+        }
+    }
+
+    if (rest < s_len) {
+        tmp = PyUnicode_Substring(s, rest, s_len);
+        if (tmp == NULL) {
+            goto error;
+        }
+        if (PyList_Append(result, tmp) < 0) {
+            Py_DECREF(tmp);
+            goto error;
+        }
+        Py_DECREF(tmp); tmp = NULL;
+    }
+
+    tmp = PyUnicode_Join(sstate->EMPTY_STR, result);
+    if (tmp == NULL) {
+        goto error;
+    }
+    Py_DECREF(result);
+    result = tmp; tmp = NULL;
+
+success:
+    if (use_cache) {
+        if (PyDict_SetItem(cache, s, result) < 0) {
+            PyErr_Clear();    /* clear any cache-related error */
+        }
+    }
+    return result;
+
+error:
+    Py_XDECREF(varname);
+    Py_XDECREF(varvalue);
+    Py_XDECREF(filters);
+    Py_XDECREF(result);
+    return NULL;
+}
+
+
+static
+PyObject *
 sync_MISSING(PyObject *self, PyObject *missing)
 {
     struct speedups_state *sstate;