view configmix/_speedups.c @ 748:c27d0812b30d

FIX: Ref to PEP 491 instead of old PEP 427
author Franz Glasner <fzglas.hg@dom66.de>
date Tue, 31 Oct 2023 08:28:54 +0100
parents c17a4e30ebbf
children
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 *FILTER_SEPARATOR_2;
    PyObject *EMPTY_FILTER;
    PyObject *NONE_FILTER;
    PyObject *EMPTY_STR;
    PyObject *QUOTE_MAP;
    PyObject *MISSING;
    PyObject *MARKER;
    PyObject *STARTTOK;
    PyObject *ENDTOK;
    PyObject *ENDTOK_FILTER;
    PyObject *REF_NAMESPACE;
    PyObject *DEL_VALUE;
};


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


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 */
}


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

    for (i=start; i<=end; i++) {
        /* Overflow error check */
        if (r > 3275) {
            PyErr_SetString(PyExc_OverflowError, "index too large");
            return -1;
        }
        r *= 10;
        c = PyUnicode_ReadChar(s, i);
        if ((c >= 48) && (c <= 57)) {    /* 0 - 9 */
            r += (c - 48);
        }
        else {
            if (i == start) {
                /* check for number sign (but only at the first index) */
                if (c == 0x2d) {
                    sign = -1;
                    continue;
                }
                else {
                    if (c == 0x2b) {
                        sign = 1;
                        continue;
                    }
                }
            }
            PyErr_Format(PyExc_ValueError, "invalid base-10 literal: %c", (int)c);
            return -1;
        }
    }
    if (sign >= 0) {
        *result = r;
    }
    else {
        *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);
    }
    if (s_len > 2) {
        /* Check for ~NNN~ syntax */
        c = PyUnicode_ReadChar(s, 0);
        if (c == 0x7e) {
            c = PyUnicode_ReadChar(s, s_len - 1);
            if (c == 0x7e) {
                if (_dec2num(s, 1, s_len - 2, &i) == 0) {
                    return PyLong_FromSsize_t(i);
                }
                PyErr_Clear();
            }
        }
    }
    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) {
        if (PyObject_IsInstance(s, (PyObject *)&PyLong_Type)) {
            PyErr_Clear();
            return PyUnicode_FromFormat("~%S~", s);
        }
        PyErr_SetString(PyExc_TypeError, "given object has no len()");
        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:
        case 0x7e:
            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, Py_ssize_t direction, struct speedups_state *sstate)
{
    Py_ssize_t varname_len;
    Py_ssize_t sep;
    PyObject *res = NULL;
    PyObject *filters = NULL;
    Py_ssize_t filters_len;
    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, (int)direction);
        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);

    filters_len = PyUnicode_GetLength(filters);
    if (filters_len < 0) {
        goto error;
    }
    if (filters_len == 0) {
        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;
        }
    }

    if (direction == 1) {
        if (PyUnicode_FindChar(filters, ',', 0, filters_len, 1) >= 0) {
            tmp = PyUnicode_Split(filters, sstate->FILTER_SEPARATOR_2, -1);
        }
        else {
            tmp = PyUnicode_Split(filters, sstate->FILTER_SEPARATOR, -1);
        }
    }
    else {
        tmp = PyUnicode_Split(filters, sstate->FILTER_SEPARATOR_2, -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 *args)
{
    PyObject *varname;
    PyObject *direction_obj;
    Py_ssize_t direction;

    if (!PyArg_UnpackTuple(args, "fast_split_filters", 2, 2, &varname, &direction_obj)) {
        return NULL;
    }
    direction = PyNumber_AsSsize_t(direction_obj, PyExc_OverflowError);
    if (direction == -1) {
        if (PyErr_Occurred()) {
            return NULL;
        }
    }
    if ((direction == 1) || (direction == -1)) {
        return _fast_split_filters(varname, self, direction, NULL);
    }
    else {
        PyErr_SetString(PyExc_ValueError, "`direction' must be -1 or +1");
        return 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, 1, sstate);
        if (tmp == NULL) {
            goto error;
        }
        if (PyTuple_Size(tmp) != 2) {
            py_clear_ref(&tmp);
            PyErr_SetString(PyExc_TypeError, "tuple of size 2 expected");
            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;

    if (!PyArg_UnpackTuple(args, "s", 2, 3, &config, &s, &cache)) {
        return NULL;
    }
    return _fast_interpolate_variables(self, config, s, cache);
}


static
PyObject *
_fast_interpolate_variables(PyObject *self, PyObject *config, PyObject *s, PyObject *cache)
{
    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;

    /* 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);
            }
        }
    }

    /* Check for {{| ... |}} */
    if ((s_len >= 6)
            && (start == 0)
            && (PyUnicode_ReadChar(s, 2) == 0x7c  /* `|' */)) {
        end = PyUnicode_Find(s, sstate->ENDTOK_FILTER, start+3, s_len, 1);
        if (end == -2) {
            return NULL;
        }
        if (end != (s_len - 3)) {
            PyErr_SetString(PyExc_ValueError, "`{{|' nested filter interpolation must end with `|}}'");
            return NULL;
        }
        /*
         * Handle {{| ... |filters, ... |}} :
         * split and recurse and apply filters
         */
        tmp = PyUnicode_Substring(s, 3, s_len-3);
        if (tmp == NULL) {
            return NULL;
        }
        filters = _fast_split_filters(tmp, NULL, -1, sstate);
        if (filters == NULL) {
            py_clear_ref(&tmp);
            return NULL;
        }
        if (PyTuple_Size(filters) != 2) {
            py_clear_ref(&tmp);
            PyErr_SetString(PyExc_TypeError, "tuple of size 2 expected");
            goto error;
        }
        py_transfer_owned(&tmp, &filters);
        /* 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_interpolate_variables(self, config, varname, cache);
        if (varvalue == NULL) {
            if (PyErr_ExceptionMatches(PyExc_KeyError)) {
                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 {
                        goto error;
                    }
                }
            }
            else {
                /* other exception/error than KeyError */
                goto error;
            }
        }
        py_clear_ref(&varname);
        result = PyObject_CallMethod(
            config, "_apply_filters", "OO", filters, varvalue);
        if (result == NULL) {
            goto error;
        }
        py_clear_ref(&varvalue);
        py_clear_ref(&filters);
        return 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, 1, 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_VARARGS, 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->FILTER_SEPARATOR_2 = PyUnicode_FromStringAndSize(",", 1);
    if (sstate->FILTER_SEPARATOR_2 == NULL) {
        return -1;
    }
    PyUnicode_InternInPlace(&(sstate->FILTER_SEPARATOR_2));

    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(
        "{IsIsIsIsIsIsIsIsIsIsIsIs}",
        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",
        0x7e, "%x7e");    /* tilde ~ */
    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->ENDTOK_FILTER = PyUnicode_FromStringAndSize("|}}", 3);
    if (sstate->ENDTOK_FILTER == NULL) {
        return -1;
    }
    PyUnicode_InternInPlace(&(sstate->ENDTOK_FILTER));

    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->FILTER_SEPARATOR_2);
        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->ENDTOK_FILTER);
        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->FILTER_SEPARATOR_2);
        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->ENDTOK_FILTER);
        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);
}