Mercurial > hgrepos > Python > libs > ConfigMix
diff configmix/_speedups.c @ 554:36d7aa000435
Implement a C-version of Configuration.interpolate_variables
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 03 Jan 2022 00:11:41 +0100 |
| parents | 9d2bd411f5c5 |
| children | b7434a34a1f4 |
line wrap: on
line diff
--- a/configmix/_speedups.c Sun Jan 02 20:43:24 2022 +0100 +++ b/configmix/_speedups.c Mon Jan 03 00:11:41 2022 +0100 @@ -23,8 +23,13 @@ PyObject *QUOTE; PyObject *NS_SEPARATOR; PyObject *FILTER_SEPARATOR; + PyObject *EMPTY_FILTER; + PyObject *NONE_FILTER; PyObject *EMPTY_STR; PyObject *QUOTE_MAP; + PyObject *MISSING; + PyObject *STARTTOK; + PyObject *ENDTOK; }; @@ -684,12 +689,304 @@ } +static +PyObject * +fast_interpolate_variables(PyObject *self, PyObject *args) +{ + PyObject *config; + PyObject *s; + PyObject *cache; + + Py_ssize_t s_len; + Py_ssize_t idx; + Py_ssize_t i, j; + PyObject *parts = NULL; + Py_ssize_t parts_len; + PyObject *res_parts = NULL; + PyObject *res = NULL; + PyObject *tmp; + PyObject *tmp2; + PyObject *pb; + Py_ssize_t pb_len; + PyObject *varname = NULL; + PyObject *varvalue = NULL; + PyObject *filters = NULL; + int cacheable; + int use_cache = 1; + int first_part_is_empty; + PyObject *err_type; + PyObject *err_value; + PyObject *err_tb; + 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) { + PyErr_Clear(); + Py_INCREF(s); + return s; + } + sstate = PyModule_GetState(self); + if (sstate == NULL) { + PyErr_SetString(PyExc_RuntimeError, "no module state available"); + return NULL; + } + + idx = PyUnicode_Find(s, sstate->STARTTOK, 0, s_len, 1); + if (idx < 0) { + PyErr_Clear(); + Py_INCREF(s); + return s; + } + + res = PyDict_GetItem(cache, s); /* borrowed */ + if (res != NULL) { + if (res == sstate->MISSING) { + return PyErr_Format( + PyExc_KeyError, + "Cannot interpolate variables in string %R (cached)", + s); + } + else { + Py_INCREF(res); + return res; + } + } + + parts = PyUnicode_Split(s, sstate->STARTTOK, -1); + if (parts == NULL) { + goto error; + } + parts_len = PyList_Size(parts); + if (parts_len < 0) { + goto error; + } + res_parts = PyList_New(1); + if (res_parts == NULL) { + goto error; + } + + tmp = PyList_GetItem(parts, 0); /* borrowed */ + if (tmp == NULL) { + goto error; + } + /* + * The first item may be also the empty string if `s' starts with + * an interpolation token. + */ + first_part_is_empty = PyObject_Not(tmp); + Py_INCREF(tmp); /* because PyList_SetItem steals -- and o is borrowed */ + PyList_SetItem(res_parts, 0, tmp); /* steals -- cannot fail */ + tmp = NULL; + + for (i=1; i<parts_len; i++) { + pb = PyList_GetItem(parts, i); /* borrowed */ + pb_len = PyUnicode_GetLength(pb); + if (pb_len < 0) { + goto error; + } + idx = PyUnicode_Find(pb, sstate->ENDTOK, 0, pb_len, 1); + if (idx < 0) { + /* + * Behave similar to the pure-Python version: copy the complete + * rest as-is. Also include the start tokens! + */ + if (PyList_Append(res_parts, sstate->STARTTOK) < 0) { + goto error; + } + if (PyList_Append(res_parts, pb) < 0) { + goto error; + } + for (j=i+1; j<parts_len; j++) { + if (PyList_Append(res_parts, sstate->STARTTOK) < 0) { + goto error; + } + pb = PyList_GetItem(parts, j); /* borrowed */ + if (PyList_Append(res_parts, pb) < 0) { + goto error; + } + } + break; /* the for-loop */ + } + + varname = PyUnicode_Substring(pb, 0, idx); + 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 = PyTuple_GetItem(tmp, 1); /* borrowed -- cannot fail */ + Py_INCREF(tmp2); + filters = tmp2; + Py_DECREF(tmp); + tmp = tmp2 = NULL; + + tmp = PyObject_CallMethod( + config, "_getvar_s_with_cache_info", "O", varname); + if (tmp == NULL) { + if (PyErr_ExceptionMatches(PyExc_KeyError)) { + /* XXX TBD handle KeyError (None and Empty-filter) */ + 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; + + /* + * Dont apply and type conversions to the variable value if + * the whole `s` is just one expansion + */ + if (first_part_is_empty && (i == 1) && (pb_len == s_len - 2) && (idx == pb_len - 2)) { + res = varvalue; varvalue = NULL; + goto success; /* break out early */ + } + if (varvalue != Py_None) { + tmp = PyObject_Str(varvalue); + if (tmp == NULL) { + goto error; + } + if (PyList_Append(res_parts, tmp) < 0) { + Py_DECREF(tmp); + goto error; + } + Py_DECREF(tmp); + } + Py_DECREF(varvalue); varvalue = NULL; + /* append the rest of the string */ + tmp = PyUnicode_Substring(pb, idx+2, pb_len); + if (tmp == NULL) { + goto error; + } + if (PyList_Append(res_parts, tmp) < 0) { + Py_DECREF(tmp); + goto error; + } + Py_DECREF(tmp); tmp = NULL; + } + + res = PyUnicode_Join(sstate->EMPTY_STR, res_parts); + if (res == NULL) { + goto error; + } + +success: + Py_DECREF(parts); + Py_DECREF(res_parts); + Py_XDECREF(filters); + + if (use_cache) { + PyDict_SetItem(cache, s, res); + PyErr_Clear(); /* clear any possible cache-related error */ + } + return res; + +error: + Py_XDECREF(varname); + Py_XDECREF(varvalue); + Py_XDECREF(parts); + Py_XDECREF(res_parts); + Py_XDECREF(res); + Py_XDECREF(filters); + return NULL; +} + + +static +PyObject * +sync_MISSING(PyObject *self, PyObject *missing) +{ + struct speedups_state *sstate; + + sstate = PyModule_GetState(self); + if (sstate == NULL) { + PyErr_SetString(PyExc_RuntimeError, "no module state available"); + return NULL; + } + if (sstate->MISSING != NULL) { + PyErr_SetString(PyExc_RuntimeError, "_MISSING already set"); + return NULL; + } + Py_INCREF(missing); + sstate->MISSING = missing; + Py_RETURN_NONE; +} + + static struct PyMethodDef speedups_methods[] = { {"fast_unquote", fast_unquote, METH_O, PyDoc_STR("C-implementation of configmix.unquote")}, {"fast_quote", fast_quote, METH_O, PyDoc_STR("C-implementation of configmix.quote")}, {"fast_pathstr2path", fast_pathstr2path, METH_O, PyDoc_STR("C-implementation of configmix.pathstr2path")}, {"_fast_split_filters", fast_split_filters, METH_O, PyDoc_STR("C-implementation of configmix.config._split_filters")}, {"_fast_split_ns", fast_split_ns, METH_O, PyDoc_STR("C-implementation of configmix.config._split_ns")}, + {"_fast_interpolate_variables", fast_interpolate_variables, METH_VARARGS, PyDoc_STR("C-implementation of configmix.config.Configuration.interpolate_variables")}, + {"_sync_MISSING", sync_MISSING, METH_O, PyDoc_STR("Internal function to easily sync the _MISSING object with configmix.config")}, + {NULL, NULL, 0, NULL} }; @@ -739,6 +1036,18 @@ } PyUnicode_InternInPlace(&(sstate->FILTER_SEPARATOR)); + sstate->EMPTY_FILTER = PyUnicode_FromStringAndSize("Empty", 5); + if (sstate->EMPTY_FILTER == NULL) { + return -1; + } + PyUnicode_InternInPlace(&(sstate->EMPTY_FILTER)); + + sstate->NONE_FILTER = PyUnicode_FromStringAndSize("None", 4); + if (sstate->NONE_FILTER == NULL) { + return -1; + } + PyUnicode_InternInPlace(&(sstate->NONE_FILTER)); + sstate->EMPTY_STR = PyUnicode_FromStringAndSize("", 0); if (sstate->EMPTY_STR == NULL) { return -1; @@ -762,6 +1071,18 @@ return -1; } + sstate->STARTTOK = PyUnicode_FromStringAndSize("{{", 2); + if (sstate->STARTTOK == NULL) { + return -1; + } + PyUnicode_InternInPlace(&(sstate->STARTTOK)); + + sstate->ENDTOK = PyUnicode_FromStringAndSize("}}", 2); + if (sstate->ENDTOK == NULL) { + return -1; + } + PyUnicode_InternInPlace(&(sstate->ENDTOK)); + return 0; } @@ -777,8 +1098,13 @@ Py_VISIT(sstate->QUOTE); Py_VISIT(sstate->NS_SEPARATOR); Py_VISIT(sstate->FILTER_SEPARATOR); + Py_VISIT(sstate->EMPTY_FILTER); + Py_VISIT(sstate->NONE_FILTER); Py_VISIT(sstate->EMPTY_STR); Py_VISIT(sstate->QUOTE_MAP); + Py_VISIT(sstate->MISSING); + Py_VISIT(sstate->STARTTOK); + Py_VISIT(sstate->ENDTOK); } return 0; } @@ -795,8 +1121,13 @@ Py_CLEAR(sstate->QUOTE); Py_CLEAR(sstate->NS_SEPARATOR); Py_CLEAR(sstate->FILTER_SEPARATOR); + Py_CLEAR(sstate->EMPTY_FILTER); + Py_CLEAR(sstate->NONE_FILTER); Py_CLEAR(sstate->EMPTY_STR); Py_CLEAR(sstate->QUOTE_MAP); + Py_CLEAR(sstate->MISSING); + Py_CLEAR(sstate->STARTTOK); + Py_CLEAR(sstate->ENDTOK); } return 0; }
