# HG changeset patch # User Franz Glasner # Date 1641394927 -3600 # Node ID 7a3c311991d7bc2c2d027935509f6e72bd48fa0f # Parent ba8f1295e1e2ef9a1c6fa47e08bce5dff4efcace An alternate C-implementation of fast_interpolate_variables() that uses the algorithm of the pure-Python implementation diff -r ba8f1295e1e2 -r 7a3c311991d7 configmix/_speedups.c --- 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;