view configmix/_speedups.c @ 654:0d6673d06c2c

Add support for using "tomllib" (in Python's stdlib since 3.11) and "tomli" TOML packages. They are preferred if they are found to be installed. But note that the declared dependency for the "toml" extra nevertheless is the "toml" package. Because it is available for all supported Python versions. So use Python 3.11+ or install "tomli" manually if you want to use the alternate packages.
author Franz Glasner <fzglas.hg@dom66.de>
date Thu, 19 May 2022 22:10:59 +0200
parents 999cfca55d25
children 2b1c7a68f913
line wrap: on
line source

/* -*- coding: utf-8 -*- */
/*
 * Speedups for configmix.
 *
 * :Copyright: (c) 2021-2022, Franz Glasner. All rights reserved.
 * :License:   BSD-3-Clause. See LICENSE.txt for details.
 */

#define PY_SSIZE_T_CLEAN
#include "Python.h"

#include "_py_helper.h"


const char _configmix_speedups_id[] = "@(#)configmix._speedups $Header$";
static const char release[] = "|VCSRevision|";
static const char date[] = "|VCSJustDate|";


/*
 * Module state holds some pre-created objects
 */
struct speedups_state {
    PyObject *DOT;
    PyObject *QUOTE;
    PyObject *NS_SEPARATOR;
    PyObject *FILTER_SEPARATOR;
    PyObject *EMPTY_FILTER;
    PyObject *NONE_FILTER;
    PyObject *EMPTY_STR;
    PyObject *QUOTE_MAP;
    PyObject *MISSING;
    PyObject *MARKER;
    PyObject *STARTTOK;
    PyObject *ENDTOK;
    PyObject *REF_NAMESPACE;
    PyObject *DEL_VALUE;
};


static PyObject * _fast_getvar_s(PyObject *config, PyObject *varname, PyObject *default_, struct speedups_state *sstate, int *cacheable);


static
int
_hex2ucs4(PyObject *s, Py_ssize_t end, Py_UCS4 *result)
{
    Py_ssize_t i;
    Py_UCS4 c;
    Py_UCS4 r = 0;

    for (i=1; i < end; i++) {
        r *= 16;
        c = PyUnicode_ReadChar(s, i);
        if ((c >= 48) && (c <= 57)) {    /* 0 - 9 */
            r += (c - 48);
        }
        else {
            if ((c >= 97) && (c <= 102)) {    /* a - f */
                r += (c - 87);
            }
            else {
                if ((c >= 65) && (c <= 70)) {   /* A - F */
                    r += (c - 55);
                }
                else {
                    PyErr_Format(PyExc_ValueError, "invalid base-16 literal: %c", (int)c);
                    return -1;
                }
            }
        }
    }
    *result = r;
    return 0;  /* success */
}


#if defined(Py_LIMITED_API)

static
void
_raise_utf8_encode_error(PyObject *s,
                         Py_ssize_t start, Py_ssize_t end,
                         const char *reason)
{
    /*
     * See also: https://docs.python.org/3/c-api/exceptions.html#unicode-exception-objects
     */
    PyObject *errobj = PyObject_CallFunction(
        PyExc_UnicodeEncodeError,
        "sOnns",
        "utf-8",
        s,
        start,
        end,
        reason);

    if (errobj == NULL) {
        /* cannot do anything here */
        return;
    }
    /* Make PyExc_UnicodeEncodeError owned because PyErr_Restore steals */
    //Py_INCREF(PyExc_UnicodeEncodeError);
    //PyErr_Restore(PyExc_UnicodeEncodeError, errobj, NULL);

    PyErr_SetObject(PyExc_UnicodeEncodeError, errobj);
    Py_DECREF(errobj);
}


/*
 * Copyright 2001-2004 Unicode, Inc.
 *
 * Disclaimer
 *
 * This source code is provided as is by Unicode, Inc. No claims are
 * made as to fitness for any particular purpose. No warranties of any
 * kind are expressed or implied. The recipient agrees to determine
 * applicability of information provided. If this file has been
 * purchased on magnetic or optical media from Unicode, Inc., the
 * sole remedy for any claim will be exchange of defective media
 * within 90 days of receipt.
 *
 * Limitations on Rights to Redistribute This Code
 *
 * Unicode, Inc. hereby grants the right to freely use the information
 * supplied in this file in the creation of products supporting the
 * Unicode Standard, and to make copies of this file in any form
 * for internal or external distribution as long as this notice
 * remains attached.
 */

#define UNI_MAX_LEGAL_UTF32 (Py_UCS4)0x0010FFFF
#define UNI_SUR_HIGH_START  (Py_UCS4)0xD800
#define UNI_SUR_HIGH_END    (Py_UCS4)0xDBFF
#define UNI_SUR_LOW_START   (Py_UCS4)0xDC00
#define UNI_SUR_LOW_END     (Py_UCS4)0xDFFF


/*
 * Once the bits are split out into bytes of UTF-8, this is a mask OR-ed
 * into the first byte, depending on how many bytes follow.  There are
 * as many entries in this table as there are UTF-8 sequence types.
 * (I.e., one byte sequence, two byte... etc.). Remember that sequencs
 * for *legal* UTF-8 will be 4 or fewer bytes total.
 */
static const unsigned char firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };


static
Py_ssize_t
_convert_ucs4_to_utf8(
    Py_UCS4 ch,
    unsigned char *targetStart, unsigned char *targetEnd,
    int strict,
    PyObject *ch_obj,  /* for error messages: the string where ch comes from */
    Py_ssize_t ch_obj_end  /* effective length of ch_obj (error reporting) */
    )
{
    const Py_UCS4 byteMask = 0xBF;
    const Py_UCS4 byteMark = 0x80;

    Py_ssize_t bytesToWrite = 0;
    unsigned char *target = targetStart;

    if (strict) {
        /* UTF-16 surrogate values are illegal */
        if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) {
            _raise_utf8_encode_error(
                ch_obj,
                1, ch_obj_end,
                "surrogate values are illegal in UCS4");
            return -1;
        }
    }
    /*
     * Figure out how many bytes the result will require. Turn any
     * illegally large UTF32 things (> Plane 17) into errors (exceptions).
     */
    if (ch < (Py_UCS4)0x80) {
        bytesToWrite = 1;
    }
    else if (ch < (Py_UCS4)0x800) {
        bytesToWrite = 2;
    }
    else if (ch < (Py_UCS4)0x10000) {
        bytesToWrite = 3;
    }
    else if (ch <= UNI_MAX_LEGAL_UTF32) {
        bytesToWrite = 4;
    }
    else {
        _raise_utf8_encode_error(
            ch_obj,
            1, ch_obj_end,
            "max Unicode codepoint value exceeded");
        return -1;
    }

    target += bytesToWrite;
    if (target > targetEnd) {
        _raise_utf8_encode_error(
            ch_obj,
            1, ch_obj_end,
            "temporary target buffer exhausted");
        return -1;
    }
    switch (bytesToWrite) { /* note: everything falls through. */
    case 4: *--target = (unsigned char)((ch | byteMark) & byteMask); ch >>= 6;
    case 3: *--target = (unsigned char)((ch | byteMark) & byteMask); ch >>= 6;
    case 2: *--target = (unsigned char)((ch | byteMark) & byteMask); ch >>= 6;
    case 1: *--target = (unsigned char) (ch | firstByteMark[bytesToWrite]);
    }
    return bytesToWrite;
}


/*
 * End of Copyright 2001-2004 Unicode, Inc.
 */


static
PyObject *
_hex2string(PyObject *s, Py_ssize_t end)
{
    Py_UCS4 c;
    unsigned char buf[6];
    Py_ssize_t bytes_written;
    PyObject *u;

    if (_hex2ucs4(s, end, &c) != 0)
        return NULL;

    /* Replace the combination PyUniode_New/PyUnicode_WriteChar */
    bytes_written = _convert_ucs4_to_utf8(c, buf, &(buf[6]), 1, s, end+1);
    if (bytes_written < 0) {
        return NULL;
    }
    u = PyUnicode_FromStringAndSize((const char *)buf, bytes_written);
    if (u == NULL) {
        return NULL;
    }
    return u;
}

#else

static
PyObject *
_hex2string(PyObject *s, Py_ssize_t end)
{
    Py_UCS4 c;
    PyObject *u = NULL;

    if (_hex2ucs4(s, end, &c) != 0)
        return NULL;
    u = PyUnicode_New(1, c);    /* ARGH: not  in the stable API */
    if (u == NULL)
        return NULL;
    if (PyUnicode_WriteChar(u, 0, c) != 0) {
        Py_DECREF(u);
        return NULL;
    }
    return u;
}

#endif /* Py_LIMITED_API */


static
PyObject *
_fast_unquote(PyObject *s, Py_ssize_t s_len, PyObject *self, struct speedups_state *sstate)
{
    Py_ssize_t find;
    Py_ssize_t parts_len;
    PyObject *res;
    PyObject *res_parts = NULL;
    PyObject *parts = NULL;
    PyObject *o;
    PyObject *pb;
    Py_ssize_t pb_len;
    Py_ssize_t i;
    Py_UCS4 c;

    if (s_len < 0) {
        s_len = PyUnicode_GetLength(s);
        if (s_len < 0) {
            return NULL;
        }
    }
    if (s_len == 0) {
        return Py_NewRef(s);
    }
    find = PyUnicode_FindChar(s, '%', 0, s_len, 1);
    if (find == -2) {
        return NULL;
    }
    if (find == -1) {
        return Py_NewRef(s);
    }

    if (sstate == NULL) {
        sstate = PyModule_GetState(self);
        if (sstate == NULL) {
            PyErr_SetString(PyExc_RuntimeError, "no module state available");
            return NULL;
        }
    }
    parts = PyUnicode_Split(s, sstate->QUOTE, -1);
    if (parts == NULL) {
        goto error;
    }
    parts_len = PyList_Size(parts);
    if (parts_len < 0) {
        goto error;
    }
    res_parts = PyTuple_New((parts_len-1)*2 + 1);
    if (res_parts == NULL) {
        goto error;
    }

    o = PyList_GetItem(parts, 0);   /* borrowed */
    if (o == NULL) {
        goto error;
    }
    /*
     * The first item may be also the empty string if `s' starts with
     * a quoted character.
     */
    PyTuple_SetItem(res_parts, 0, Py_NewRef(o));

    for (i=1; i<parts_len; i++) {
        pb = PyList_GetItem(parts, i);   /* borrowed */
        pb_len = PyUnicode_GetLength(pb);
        if (pb_len < 1) {
            PyErr_Format(PyExc_ValueError, "quote syntax: length too small: %U", pb);
            goto error;
        }
        c = PyUnicode_ReadChar(pb, 0);
        switch (c) {
        case 0x55:   /* U */
            if (pb_len < 9) {
                PyErr_Format(PyExc_ValueError, "quote syntax: length too small: %U", pb);
                goto error;
            }
            o = _hex2string(pb, 9);
            if (o == NULL) {
                goto error;
            }
            PyTuple_SetItem(res_parts, (i-1)*2 + 1, o);   /* steals */
            o = PyUnicode_Substring(pb, 9, pb_len);
            if (o == NULL) {
                goto error;
            }
            PyTuple_SetItem(res_parts, i*2, o);    /* steals */
            break;
        case 0x75:   /* u */
            if (pb_len < 5) {
                PyErr_Format(PyExc_ValueError, "quote syntax: length too small: %U", pb);
                goto error;
            }
            o = _hex2string(pb, 5);
            if (o == NULL) {
                goto error;
            }
            PyTuple_SetItem(res_parts, (i-1)*2 + 1, o);  /* steals */
            o = PyUnicode_Substring(pb, 5, pb_len);
            if (o == NULL) {
                goto error;
            }
            PyTuple_SetItem(res_parts, i*2, o);    /* steals */
            break;
        case 0x78:   /* x */
            if (pb_len < 3) {
                PyErr_Format(PyExc_ValueError, "quote syntax: length too small: %U", pb);
                goto error;
            }
            o = _hex2string(pb, 3);
            if (o == NULL) {
                goto error;
            }
            PyTuple_SetItem(res_parts, (i-1)*2 + 1, o);  /* steals */
            o = PyUnicode_Substring(pb, 3, pb_len);
            if (o == NULL) {
                goto error;
            }
            PyTuple_SetItem(res_parts, i*2, o);    /* steals */
            break;

        default:
            PyErr_Format(PyExc_ValueError, "quote syntax: length too small: %U", pb);
            goto error;
        }
    }

    res = PyUnicode_Join(sstate->EMPTY_STR, res_parts);
    if (res == NULL) {
        goto error;
    }
    Py_DECREF(parts);
    Py_DECREF(res_parts);
    return res;

error:
    Py_XDECREF(res_parts);
    Py_XDECREF(parts);
    return NULL;
}


static
PyObject *
fast_unquote(PyObject *self, PyObject *s)
{
    return _fast_unquote(s, -1, self, NULL);
}


static
PyObject *
fast_quote(PyObject *self, PyObject *s)
{
    Py_ssize_t s_len;
    Py_ssize_t i;
    Py_UCS4 c;
    int need_quoting;
    struct speedups_state *sstate;

    s_len = PyUnicode_GetLength(s);
    if (s_len < 0) {
        return NULL;
    }
    if (s_len == 0) {
        return Py_NewRef(s);
    }
    need_quoting = 0;
    for (i=0; i<s_len; i++) {
        c = PyUnicode_ReadChar(s, i);   /* type already checked */
        switch (c) {
        case 0x25:
        case 0x2e:
        case 0x3a:
        case 0x23:
        case 0x7c:
        case 0x22:
        case 0x27:
        case 0x7b:
        case 0x7d:
        case 0x5b:
        case 0x5d:
            need_quoting = 1;
            i = s_len;   /* break the for-loop */
            break;
        default:
            /* VOID */
            ;
        }
    }
    if (!need_quoting) {
        return Py_NewRef(s);
    }
    sstate = PyModule_GetState(self);
    if (sstate == NULL) {
        PyErr_SetString(PyExc_RuntimeError, "no module state available");
        return NULL;
    }
    return PyUnicode_Translate(s, sstate->QUOTE_MAP, "strict");
}


static
PyObject *
_fast_pathstr2path(PyObject *varname, PyObject *self, struct speedups_state *sstate)
{
    Py_ssize_t varname_len;
    PyObject *parts = NULL;
    Py_ssize_t parts_len;
    PyObject *res = NULL;
    Py_ssize_t i;
    PyObject *o;
    PyObject *u;

    varname_len = PyUnicode_GetLength(varname);
    if (varname_len < 0) {
        return NULL;
    }
    if (varname_len == 0) {
        return PyTuple_New(0);
    }

    if (sstate == NULL) {
        sstate = PyModule_GetState(self);
        if (sstate == NULL) {
            PyErr_SetString(PyExc_RuntimeError, "no module state available");
            return NULL;
        }
    }
    parts = PyUnicode_Split(varname, sstate->DOT, -1);
    if (parts == NULL) {
        goto error;
    }
    parts_len = PyList_Size(parts);
    if (parts_len < 0) {
        goto error;
    }
    res = PyTuple_New(parts_len);
    if (res == NULL) {
        goto error;
    }
    for (i=0; i < parts_len; i++) {
        o = PyList_GetItem(parts, i);   /* borrowed */
        u = _fast_unquote(o, -1, NULL, sstate);
        if (u == NULL) {
            goto error;
        }
        PyTuple_SetItem(res, i, u);     /* steals */
    }

    Py_DECREF(parts);
    return res;

error:
    Py_XDECREF(parts);
    Py_XDECREF(res);
    return NULL;
}


static
PyObject *
fast_pathstr2path(PyObject *self, PyObject *varname)
{
    return _fast_pathstr2path(varname, self, NULL);
}


static
PyObject *
_fast_split_filters(PyObject *varname, PyObject *self, struct speedups_state *sstate)
{
    Py_ssize_t varname_len;
    Py_ssize_t sep;
    PyObject *res = NULL;
    PyObject *filters = NULL;
    PyObject *name = NULL;
    PyObject *tmp;

    varname_len = PyUnicode_GetLength(varname);
    if (varname_len < 0) {
        return NULL;
    }
    if (varname_len == 0) {
        sep = -1;
    }
    else {
        sep = PyUnicode_FindChar(varname, '|', 0, varname_len, 1);
        if (sep == -2) {
            return NULL;
        }
    }
    if (sep == -1) {
        res = PyTuple_New(2);
        if (res == NULL) {
            goto error;
        }
        PyTuple_SetItem(res, 0, Py_NewRef(varname));  /* steals */
        filters = PyList_New(0);
        if (filters == NULL) {
            goto error;
        }
        PyTuple_SetItem(res, 1, filters);  /* steals */
        return res;
    }
    name = PyUnicode_Substring(varname, 0, sep);
    if (name == NULL) {
        goto error;
    }

    filters = PyUnicode_Substring(varname, sep+1, varname_len);
    if (filters == NULL) {
        goto error;
    }
    tmp = PyObject_CallMethod(filters, "strip", NULL);
    if (tmp == NULL) {
        goto error;
    }
    py_transfer_owned(&filters, &tmp);

    if (PyObject_Not(filters)) {
        py_clear_ref(&filters);

        res = PyTuple_New(2);
        if (res == NULL) {
            goto error;
        }
        PyTuple_SetItem(res, 0, name);  /* steals */
        name = NULL;                    /* no ownership any more */
        filters = PyList_New(0);
        if (filters == NULL) {
            goto error;
        }
        PyTuple_SetItem(res, 1, filters);  /* steals */
        filters = NULL;
        return res;
    }

    if (sstate == NULL) {
        sstate = PyModule_GetState(self);
        if (sstate == NULL) {
            PyErr_SetString(PyExc_RuntimeError, "no module state available");
            goto error;
        }
    }

    tmp = PyUnicode_Split(filters, sstate->FILTER_SEPARATOR, -1);
    if (tmp == NULL) {
        goto error;
    }
    py_transfer_owned(&filters, &tmp);

    res = PyTuple_New(2);
    if (res == NULL) {
        goto error;
    }
    PyTuple_SetItem(res, 0, name);     /* steals -- ownership changed */
    PyTuple_SetItem(res, 1, filters);  /* steals -- ownership changed */
    return res;

error:
    Py_XDECREF(res);
    Py_XDECREF(filters);
    Py_XDECREF(name);
    return NULL;
}


static
PyObject *
fast_split_filters(PyObject *self, PyObject *varname)
{
    return _fast_split_filters(varname, self, NULL);
}


static
PyObject *
_fast_split_ns(PyObject *varname, PyObject *self, struct speedups_state *sstate)
{
    PyObject *res = NULL;
    Py_ssize_t ns_idx;
    Py_ssize_t varname_len;
    PyObject *o1;
    PyObject *o2;

    varname_len = PyUnicode_GetLength(varname);
    if (varname_len < 0) {
        return NULL;
    }
    ns_idx = PyUnicode_FindChar(varname, ':', 0, varname_len, 1);
    if (ns_idx == -2) {
        return NULL;
    }
    if (ns_idx == -1) {
        res = PyTuple_New(2);
        if (res == NULL) {
            return NULL;
        }
        PyTuple_SetItem(res, 0, Py_NewRef(Py_None));  /* steals */
        PyTuple_SetItem(res, 1, Py_NewRef(varname));  /* steals */
        return res;
    }

    res = PyTuple_New(2);
    if (res == NULL) {
        return NULL;
    }
    o1 = PyUnicode_Substring(varname, 0, ns_idx);
    if (o1 == NULL) {
        Py_DECREF(res);
        return NULL;
    }
    o2 = _fast_unquote(o1, ns_idx, self, sstate);
    if (o2 == NULL) {
        Py_DECREF(o1);
        Py_DECREF(res);
        return NULL;
    }
    Py_DECREF(o1);
    PyTuple_SetItem(res, 0, o2);    /* steals */
    o1 = PyUnicode_Substring(varname, ns_idx+1, varname_len);
    if (o1 == NULL) {
        Py_DECREF(res);
        return NULL;
    }
    PyTuple_SetItem(res, 1, o1);    /* steals */
    return res;
}


static
PyObject *
fast_split_ns(PyObject *self, PyObject *varname)
{
    return _fast_split_ns(varname, self, NULL);
}


static
PyObject *
fast_interpolate_variables_old(PyObject *self, PyObject *args)
{
    PyObject *config;
    PyObject *s;
    PyObject *cache;

    int cmp;
    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 *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();
        return Py_NewRef(s);
    }
    sstate = PyModule_GetState(self);
    if (sstate == NULL) {
        PyErr_SetString(PyExc_RuntimeError, "no module state available");
        return NULL;
    }

    cmp = PyUnicode_Compare(s, sstate->DEL_VALUE);
    if ((cmp < 0) && PyErr_Occurred()) {
        return NULL;
    }
    if (cmp == 0) {
        return Py_NewRef(s);
    }

    idx = PyUnicode_Find(s, sstate->STARTTOK, 0, s_len, 1);
    if (idx < 0) {
        PyErr_Clear();
        return Py_NewRef(s);
    }

    res = PyDict_GetItem(cache, s);      /* borrowed */
    if (res != NULL) {
        if (py_object_is(res, sstate->MISSING)) {
            return PyErr_Format(
                PyExc_KeyError,
                "Cannot interpolate variables in string %R (cached)",
                s);
        }
        else {
            return Py_NewRef(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);
    /* steals -- cannot fail here -- NOTE: tmp is currently borrowed */
    PyList_SetItem(res_parts, 0, Py_NewRef(tmp));
    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_clear_ref(&tmp);
            goto error;
        }
        py_clear_ref(&varname);
        /* Unpack the result tuple */
        /* borrowed -- cannot fail -- need ownership */
        varname = Py_NewRef(PyTuple_GetItem(tmp, 0));
        /* borrowed -- cannot fail -- want ownership */
        filters = Py_NewRef(PyTuple_GetItem(tmp, 1));
        py_clear_ref(&tmp);

        varvalue = _fast_getvar_s(config, varname, NULL, sstate, &cacheable);
        if (varvalue == NULL) {
            if (PyErr_ExceptionMatches(PyExc_KeyError)) {
                cacheable = 1;
                if (PySequence_Contains(filters, sstate->NONE_FILTER) == 1) {
                    PyErr_Clear();
                    varvalue = Py_NewRef(Py_None);
                }
                else {
                    if (PySequence_Contains(filters, sstate->EMPTY_FILTER) == 1) {
                        PyErr_Clear();
                        varvalue = Py_NewRef(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;
            }
        }

        if (!cacheable) {
            use_cache = 0;
        }

        py_clear_ref(&varname);

        tmp = PyObject_CallMethod(
            config, "_apply_filters", "OO", filters, varvalue);
        if (tmp == NULL) {
            goto error;
        }
        py_clear_ref(&varvalue);

        py_clear_ref(&filters);

        /*
         * 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 (py_object_isnot(varvalue, Py_None)) {
            tmp = PyObject_Str(varvalue);
            if (tmp == NULL) {
                goto error;
            }
            if (PyList_Append(res_parts, tmp) < 0) {
                py_clear_ref(&tmp);
                goto error;
            }
            py_clear_ref(&tmp);
        }
        py_clear_ref(&varvalue);
        /* 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_clear_ref(&tmp);
            goto error;
        }
        py_clear_ref(&tmp);
    }

    res = PyUnicode_Join(sstate->EMPTY_STR, res_parts);
    if (res == NULL) {
        goto error;
    }

success:
    Py_DECREF(parts);
    Py_DECREF(res_parts);

    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(filters);
    Py_XDECREF(parts);
    Py_XDECREF(res_parts);
    Py_XDECREF(res);
    return NULL;
}


static
PyObject *
fast_interpolate_variables(PyObject *self, PyObject *args)
{
    PyObject *config;
    PyObject *s;
    PyObject *cache = NULL;

    int cmp;
    Py_ssize_t s_len;
    Py_ssize_t start, rest, end;
    PyObject *tmp;
    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", 2, 3, &config, &s, &cache)) {
        return NULL;
    }
    /* Disable caching if the cache param is given as None */
    if ((cache != NULL) && py_object_is(cache, Py_None)) {
        cache = NULL;
    }
    s_len = PyUnicode_GetLength(s);   /* also an implicit type check */
    if (s_len < 0) {
        return NULL;
    }
    if (s_len < 4) {
        return Py_NewRef(s);
    }

    sstate = PyModule_GetState(self);
    if (sstate == NULL) {
        PyErr_SetString(PyExc_RuntimeError, "no module state available");
        return NULL;
    }

    cmp = PyUnicode_Compare(s, sstate->DEL_VALUE);
    if ((cmp < 0) && PyErr_Occurred()) {
        return NULL;
    }
    if (cmp == 0) {
        return Py_NewRef(s);
    }

    start = PyUnicode_Find(s, sstate->STARTTOK, 0, s_len, 1);
    if (start == -2) {
        return NULL;
    }
    if (start == -1) {
        return Py_NewRef(s);
    }

    if (cache != NULL) {
        result = PyDict_GetItem(cache, s);    /* borrowed */
        if (result != NULL) {
            if (py_object_is(result, sstate->MISSING)) {
                return PyErr_Format(
                    PyExc_KeyError,
                    "Cannot interpolate variables in string %R (cached)",
                    s);
            }
            else {
                return Py_NewRef(result);
            }
        }
    }

    result = PyList_New(0);
    if (result == NULL) {
        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_clear_ref(&tmp);
                goto error;
            }
            py_clear_ref(&tmp);
        }
        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_clear_ref(&tmp);
            goto error;
        }
        py_clear_ref(&varname);
        /* Unpack the result tuple */
        /* borrowed -- cannot fail -- need ownership */
        varname = Py_NewRef(PyTuple_GetItem(tmp, 0));
        /* borrowed -- cannot fail -- need ownership */
        filters = Py_NewRef(PyTuple_GetItem(tmp, 1));
        py_clear_ref(&tmp);

        varvalue = _fast_getvar_s(config, varname, NULL, sstate, &cacheable);

        if (varvalue == NULL) {
            if (PyErr_ExceptionMatches(PyExc_KeyError)) {
                cacheable = 1;
                if (PySequence_Contains(filters, sstate->NONE_FILTER) == 1) {
                    PyErr_Clear();
                    varvalue = Py_NewRef(Py_None);
                }
                else {
                    if (PySequence_Contains(filters, sstate->EMPTY_FILTER) == 1) {
                        PyErr_Clear();
                        varvalue = Py_NewRef(sstate->EMPTY_STR);
                    }
                    else {
                        if (cache != NULL) {
                            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;
            }
        }

        if (!cacheable) {
            use_cache = 0;
        }

        py_clear_ref(&varname);

        tmp = PyObject_CallMethod(
            config, "_apply_filters", "OO", filters, varvalue);
        if (tmp == NULL) {
            goto error;
        }
        py_transfer_owned(&varvalue, &tmp);

        py_clear_ref(&filters);

        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_transfer_owned(&result, &varvalue);
            goto success;     /* break out early */
        }

        /* Handle None like the empty string */
        if (py_object_isnot(varvalue, Py_None)) {
            tmp = PyObject_Str(varvalue);
            if (tmp == NULL) {
                goto error;
            }
            if (PyList_Append(result, tmp) < 0) {
                py_clear_ref(&tmp);
                goto error;
            }
            py_clear_ref(&tmp);
        }

        /* 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_clear_ref(&tmp);
            goto error;
        }
        py_clear_ref(&tmp);
    }

    tmp = PyUnicode_Join(sstate->EMPTY_STR, result);
    if (tmp == NULL) {
        goto error;
    }
    py_transfer_owned(&result, &tmp);

success:
    if (use_cache && (cache != NULL)) {
        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 *
_fast_getvarl(PyObject *config, PyObject *path, PyObject *namespace, PyObject *default_, struct speedups_state *sstate)
{
    PyObject *varvalue;
    PyObject *lookupfn = NULL;

    if ((namespace == NULL) || PyObject_Not(namespace)) {
        lookupfn = PyObject_GetAttrString(config, "_lookupvar");
        if (lookupfn == NULL) {
            goto error;
        }
    }
    else {
        int ns_equals_ref = PyUnicode_Compare(namespace, sstate->REF_NAMESPACE);
        if ((ns_equals_ref < 0) && PyErr_Occurred()) {
            return NULL;
        }
        if (ns_equals_ref == 0) {
            lookupfn = PyObject_GetAttrString(config, "_lookupref");
            if (lookupfn == NULL) {
                goto error;
            }
        }
        else {
            /* lookup_varns */
            /* this is borrowed */
            PyObject *cfg_vars_mod = PyImport_AddModule("configmix.variables");
            if (cfg_vars_mod == NULL) {
                goto error;
            }
            lookupfn = PyObject_CallMethod(cfg_vars_mod, "lookup_varns", "O", namespace);
            if (lookupfn == NULL) {
                goto handle_possible_keyerror;
            }
        }
    }
    varvalue = PyObject_CallObject(lookupfn, path);
    if (varvalue == NULL) {
        goto handle_possible_keyerror;
    }
    Py_DECREF(lookupfn);
    return varvalue;

handle_possible_keyerror:
    if (PyErr_ExceptionMatches(PyExc_KeyError)) {
        if ((default_ == NULL) || py_object_is(default_, sstate->MARKER)) {
            PyErr_Format(PyExc_KeyError, "Variable %R not found", path);
            /* fall through */
        }
        else {
            PyErr_Clear();
            Py_XDECREF(lookupfn);
            return Py_NewRef(default_);
        }
    }
    /* fall through */
error:
    Py_XDECREF(lookupfn);
    return NULL;
}


static
PyObject *
fast_getvarl(PyObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"config", "path", "namespace", "default", NULL};
    PyObject *path;
    PyObject *config;
    PyObject *namespace = NULL;
    PyObject *default_ = NULL;

    struct speedups_state *sstate;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO!|$OO", kwlist, &config, &PyTuple_Type, &path, &namespace, &default_)) {

        return NULL;
    }
    sstate = PyModule_GetState(self);
    if (sstate == NULL) {
        PyErr_SetString(PyExc_RuntimeError, "no module state available");
        return NULL;
    }
    return _fast_getvarl(config, path, namespace, default_, sstate);
}


static
PyObject *
fast_getvarl_s(PyObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"config", "path", "namespace", "default", NULL};

    PyObject *config;
    PyObject *path;
    PyObject *namespace = NULL;
    PyObject *default_ = NULL;

    PyObject *res = NULL;
    PyObject *tmp;

    struct speedups_state *sstate;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO!|$OO", kwlist, &config, &PyTuple_Type, &path, &namespace, &default_)) {

        return NULL;
    }
    sstate = PyModule_GetState(self);
    if (sstate == NULL) {
        PyErr_SetString(PyExc_RuntimeError, "no module state available");
        return NULL;
    }

    tmp = _fast_getvarl(config, path, namespace, NULL, sstate);
    if (tmp == NULL) {
        goto handle_possible_keyerror;
    }
    res = PyObject_CallMethod(config, "substitute_variables_in_obj", "O", tmp);
    if (res == NULL) {
        py_clear_ref(&tmp);
        goto handle_possible_keyerror;
    }
    py_clear_ref(&tmp);
    return res;

handle_possible_keyerror:
    if (PyErr_ExceptionMatches(PyExc_KeyError)) {
        if ((default_ == NULL) || py_object_is(default_, sstate->MARKER)) {
            /* fall through */
        }
        else {
            PyErr_Clear();
            return Py_NewRef(default_);
        }
    }
    /* fall through */

error:
    return NULL;
}


/**
 * Combination of py_getvar_s and _py_getvar_s_with_cache_info
 */
static
PyObject *
_fast_getvar_s(PyObject *config, PyObject *varname, PyObject *default_, struct speedups_state *sstate, int *cacheable)
{
    PyObject *varname_b;            /* always borrowed */
    PyObject *namespace_b;          /* always borrowed */
    PyObject *splitted = NULL;
    PyObject *res;
    PyObject *tmp1;
    PyObject *tmp2;

    splitted = _fast_split_ns(varname, NULL, sstate);
    if (splitted == NULL) {
        goto error;
    }
    namespace_b = PyTuple_GetItem(splitted, 0);   /* borrowed */
    varname_b = PyTuple_GetItem(splitted, 1);     /* borrowed */

    if (PyObject_Not(namespace_b)) {
        tmp1 = _fast_pathstr2path(varname_b, NULL, sstate);
        if (tmp1 == NULL) {
            goto error;
        }
        tmp2 = _fast_getvarl(config, tmp1, NULL, NULL, sstate);
        if (tmp2 == NULL) {
            py_clear_ref(&tmp1);
            goto handle_possible_keyerror;
        }
        py_clear_ref(&tmp1);
        res = PyObject_CallMethod(config, "substitute_variables_in_obj", "O", tmp2);
        if (res == NULL) {
            py_clear_ref(&tmp2);
            goto handle_possible_keyerror;
        }
        py_clear_ref(&tmp2);
        /* no namespace -> cacheable */
        *cacheable = 1;
    }
    else {
        tmp1 = PyTuple_New(1);
        if (tmp1 == NULL) {
            goto error;
        }
        PyTuple_SetItem(tmp1, 0, Py_NewRef(varname_b));
        tmp2 = _fast_getvarl(config, tmp1, namespace_b, NULL, sstate);
        if (tmp2 == NULL) {
            py_clear_ref(&tmp1);
            goto handle_possible_keyerror;
        }
        py_clear_ref(&tmp1);
        res = PyObject_CallMethod(config, "substitute_variables_in_obj", "O", tmp2);
        if (res == NULL) {
            py_clear_ref(&tmp2);
            goto handle_possible_keyerror;
        }
        py_clear_ref(&tmp2);
        /* results from namespaced lookups are currently not cacheable */
        *cacheable = 0;
    }

    /* free splitted last because of using borrowed refs from it */
    Py_DECREF(splitted);
    return res;

handle_possible_keyerror:
    if (PyErr_ExceptionMatches(PyExc_KeyError)) {
        if ((default_ == NULL) || py_object_is(default_, sstate->MARKER)) {
            /* fall through */
        }
        else {
            PyErr_Clear();
            Py_XDECREF(splitted);
            return Py_NewRef(default_);
        }
    }
    /* fall through */

error:
    Py_XDECREF(splitted);
    return NULL;
}


static
PyObject *
fast_getvar(PyObject *self, PyObject *args)
{
    PyObject *config;
    PyObject *varname;
    PyObject *default_;

    PyObject *varname_b;            /* always borrowed */
    PyObject *namespace_b;          /* always borrowed */
    PyObject *splitted = NULL;
    PyObject *res;
    PyObject *tmp1;
    struct speedups_state *sstate;

    if (!PyArg_UnpackTuple(args, "config", 3, 3, &config, &varname, &default_)) {
        return NULL;
    }

    sstate = PyModule_GetState(self);
    if (sstate == NULL) {
        PyErr_SetString(PyExc_RuntimeError, "no module state available");
        return NULL;
    }

    splitted = _fast_split_ns(varname, NULL, sstate);
    if (splitted == NULL) {
        goto error;
    }
    namespace_b = PyTuple_GetItem(splitted, 0);   /* borrowed */
    varname_b = PyTuple_GetItem(splitted, 1);     /* borrowed */

    if (PyObject_Not(namespace_b)) {
        tmp1 = _fast_pathstr2path(varname_b, NULL, sstate);
        if (tmp1 == NULL) {
            goto error;
        }
        res = _fast_getvarl(config, tmp1, NULL, default_, sstate);
        if (res == NULL) {
            py_clear_ref(&tmp1);
            goto error;
        }
        py_clear_ref(&tmp1);
    }
    else {
        tmp1 = PyTuple_New(1);
        if (tmp1 == NULL) {
            goto error;
        }
        PyTuple_SetItem(tmp1, 0, Py_NewRef(varname_b));
        res = _fast_getvarl(config, tmp1, namespace_b, default_, sstate);
        if (res == NULL) {tmp1 = _fast_pathstr2path(varname_b, NULL, sstate);
        if (tmp1 == NULL) {
            goto error;
        }
            py_clear_ref(&tmp1);
            goto error;
        }
        py_clear_ref(&tmp1);
    }
    Py_DECREF(splitted);
    return res;

error:
    Py_XDECREF(splitted);
    return NULL;
}


static
PyObject *
fast_getvar_s(PyObject *self, PyObject *args)
{
    PyObject *config;
    PyObject *varname;
    PyObject *default_;
    int cacheable;
    struct speedups_state *sstate;

    if (!PyArg_UnpackTuple(args, "config", 3, 3, &config, &varname, &default_)) {
        return NULL;
    }

    sstate = PyModule_GetState(self);
    if (sstate == NULL) {
        PyErr_SetString(PyExc_RuntimeError, "no module state available");
        return NULL;
    }
    return _fast_getvar_s(config, varname, default_, sstate, &cacheable);
}


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;
    }
    sstate->MISSING = Py_NewRef(missing);
    Py_RETURN_NONE;
}


static
PyObject *
sync_MARKER(PyObject *self, PyObject *marker)
{
    struct speedups_state *sstate;

    sstate = PyModule_GetState(self);
    if (sstate == NULL) {
        PyErr_SetString(PyExc_RuntimeError, "no module state available");
        return NULL;
    }
    if (sstate->MARKER != NULL) {
        PyErr_SetString(PyExc_RuntimeError, "_MARKER already set");
        return NULL;
    }
    sstate->MARKER = Py_NewRef(marker);
    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")},
    {"_fast_getvarl", (PyCFunction)fast_getvarl, METH_VARARGS | METH_KEYWORDS, PyDoc_STR("C-Implementation of configmix.config.Configuration.getvarl")},
    {"_fast_getvarl_s", (PyCFunction)fast_getvarl_s, METH_VARARGS | METH_KEYWORDS, PyDoc_STR("C-Implementation of configmix.config.Configuration.getvarl_s")},
    {"_fast_getvar", fast_getvar, METH_VARARGS, PyDoc_STR("C-Implementation of configmix.config.Configuration.getvar")},
    {"_fast_getvar_s", fast_getvar_s, METH_VARARGS, PyDoc_STR("C-Implementation of configmix.config.Configuration.getvar_s")},
    {"_sync_MISSING", sync_MISSING, METH_O, PyDoc_STR("Internal function to easily sync the _MISSING object with configmix.config")},
    {"_sync_MARKER", sync_MARKER, METH_O, PyDoc_STR("Internal function to easily sync the _MARKER object with configmix.config")},
    {NULL, NULL, 0, NULL}
};

#define STRINGIFY(s) #s
#define XSTRINGIFY(s) STRINGIFY(s)


static
int
speedups_exec(PyObject *module)
{
    struct speedups_state *sstate = PyModule_GetState(module);

    if (sstate == NULL) {
        PyErr_SetString(PyExc_ImportError, "no module state available yet");
        return -1;
    }

    PyModule_AddStringConstant(module, "__release__", release);
    PyModule_AddStringConstant(module, "__date__", date);
    PyModule_AddStringConstant(module, "__author__", "Franz Glasner");
#if defined(Py_LIMITED_API)
    PyModule_AddStringConstant(module, "Py_LIMITED_API", XSTRINGIFY(Py_LIMITED_API));
#endif

    sstate->DOT = PyUnicode_FromStringAndSize(".", 1);
    if (sstate->DOT == NULL) {
        return -1;
    }
    PyUnicode_InternInPlace(&(sstate->DOT));

    sstate->QUOTE = PyUnicode_FromStringAndSize("%", 1);
    if (sstate->QUOTE == NULL) {
        return -1;
    }
    PyUnicode_InternInPlace(&(sstate->QUOTE));

    sstate->NS_SEPARATOR = PyUnicode_FromStringAndSize(":", 1);
    if (sstate->NS_SEPARATOR == NULL) {
        return -1;
    }
    PyUnicode_InternInPlace(&(sstate->NS_SEPARATOR));

    sstate->FILTER_SEPARATOR = PyUnicode_FromStringAndSize("|", 1);
    if (sstate->FILTER_SEPARATOR == NULL) {
        return -1;
    }
    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;
    }
    PyUnicode_InternInPlace(&(sstate->EMPTY_STR));

    sstate->QUOTE_MAP = Py_BuildValue(
        "{IsIsIsIsIsIsIsIsIsIsIs}",
        0x25, "%x25",     /* QUOTE: % */
        0x2e, "%x2e",     /* DOT: . */
        0x3a, "%x3a",     /* NS_SEPARATOR: : */
        0x23, "%x23",     /* COMMENT/anchor: # */
        0x7c, "%x7c",     /* FILTER_SEPARATOR: | */
        0x22, "%x22",
        0x27, "%x27",
        0x7b, "%x7b",
        0x7d, "%x7d",
        0x5b, "%x5b",
        0x5d, "%x5d");
    if (sstate->QUOTE_MAP == NULL) {
        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));

    sstate->REF_NAMESPACE = PyUnicode_FromStringAndSize("ref", 3);
    if (sstate->REF_NAMESPACE == NULL) {
        return -1;
    }
    PyUnicode_InternInPlace(&(sstate->REF_NAMESPACE));

    sstate->DEL_VALUE = PyUnicode_FromStringAndSize("{{::DEL::}}", 11);
    if (sstate->DEL_VALUE == NULL) {
        return -1;
    }
    PyUnicode_InternInPlace(&(sstate->DEL_VALUE));

    return 0;
}


static
int
speeeupds_traverse(PyObject *module, visitproc visit, void *arg)
{
    struct speedups_state *sstate = PyModule_GetState(module);

    if (sstate != NULL) {
        Py_VISIT(sstate->DOT);
        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->MARKER);
        Py_VISIT(sstate->STARTTOK);
        Py_VISIT(sstate->ENDTOK);
        Py_VISIT(sstate->REF_NAMESPACE);
        Py_VISIT(sstate->DEL_VALUE);
    }
    return 0;
}


static
int
speedups_clear(PyObject *module)
{
    struct speedups_state *sstate = PyModule_GetState(module);

    if (sstate != NULL) {
        Py_CLEAR(sstate->DOT);
        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->MARKER);
        Py_CLEAR(sstate->STARTTOK);
        Py_CLEAR(sstate->ENDTOK);
        Py_CLEAR(sstate->REF_NAMESPACE);
        Py_CLEAR(sstate->DEL_VALUE);
    }
    return 0;
}


static struct PyModuleDef_Slot speedups_slots[] = {
    {Py_mod_exec, speedups_exec},
    {0, NULL}
};


static struct PyModuleDef speedups_def = {
    PyModuleDef_HEAD_INIT,                      /* m_base */
    "_speedups",                                /* m_name  (relative) */
    PyDoc_STR("Speedups for configmix"),        /* m_doc */
    sizeof(struct speedups_state),              /* m_size */
    speedups_methods,                           /* m_methods */
    speedups_slots,                             /* m_slots */
    speeeupds_traverse,                         /* m_traverse */
    speedups_clear,                             /* m_clear */
    NULL                                        /* m_free */
};


PyMODINIT_FUNC
PyInit__speedups(void)
{
    /*
     * Use multi-phase extension module initialization (PEP 489).
     * This is Python 3.5+.
     */
    return PyModuleDef_Init(&speedups_def);
}