#ifndef C4CORE_SINGLE_HEADER
#include "c4/std/std.hpp"
#include "c4/substr.hpp"
#endif

#include <c4/test.hpp>

#include "c4/libtest/supprwarn_push.hpp"
#include <iostream>

namespace c4 {

static_assert(c4::is_string<char[2]>::value, "dump directly");
static_assert(c4::is_string<char(&)[2]>::value, "dump directly");
static_assert(c4::is_string<char(&&)[2]>::value, "dump directly");
static_assert(c4::is_string<const char[2]>::value, "dump directly");
static_assert(c4::is_string<const char(&&)[2]>::value, "dump directly");
static_assert(c4::is_string<substr>::value, "dump directly");
static_assert(c4::is_string<csubstr>::value, "dump directly");
static_assert(c4::is_string<std::string>::value, "dump directly");


TEST_CASE("substr.empty_ctor")
{
    {
        substr s;
        CHECK_EQ(s.str, nullptr);
        CHECK_EQ(s.len, 0u);
    }
    {
        csubstr s;
        CHECK_EQ(s.str, nullptr);
        CHECK_EQ(s.len, 0u);
    }
    {
        substr s = {};
        CHECK_EQ(s.str, nullptr);
        CHECK_EQ(s.len, 0u);
    }
    {
        csubstr s = {};
        CHECK_EQ(s.str, nullptr);
        CHECK_EQ(s.len, 0u);
    }
}

TEST_CASE("substr.ctor_from_char_arr")
{
    SUBCASE("substr from char")
    {
        char buf1[] = "01";
        char buf2[] = "012";
        substr s(buf1);
        CHECK_EQ(s, "01");
        CHECK_EQ(s.str, buf1);
        CHECK_EQ(s.len, strlen(buf1));
        s = buf2;
        CHECK_EQ(s, "012");
        CHECK_EQ(s.str, buf2);
        CHECK_EQ(s.len, strlen(buf2));
        s.assign(buf1);
        CHECK_EQ(s, "01");
        CHECK_EQ(s.str, buf1);
        CHECK_EQ(s.len, strlen(buf1));
        s = to_substr(buf2);
        CHECK_EQ(s, "012");
        CHECK_EQ(s.str, buf2);
        CHECK_EQ(s.len, strlen(buf2));
        //s = to_csubstr(buf1); // deliberate compile error
        //CHECK_EQ(s, "01");
        //CHECK_EQ(s.str, buf1);
        //CHECK_EQ(s.len, strlen(buf1));
    }
    SUBCASE("csubstr from char")
    {
        char buf1[] = "01";
        char buf2[] = "012";
        csubstr s(buf1);
        CHECK_EQ(s, "01");
        CHECK_EQ(s.str, buf1);
        CHECK_EQ(s.len, strlen(buf1));
        s = buf2;
        CHECK_EQ(s, "012");
        CHECK_EQ(s.str, buf2);
        CHECK_EQ(s.len, strlen(buf2));
        s.assign(buf1);
        CHECK_EQ(s, "01");
        CHECK_EQ(s.str, buf1);
        CHECK_EQ(s.len, strlen(buf1));
        s = to_substr(buf2);
        CHECK_EQ(s, "012");
        CHECK_EQ(s.str, buf2);
        CHECK_EQ(s.len, strlen(buf2));
        s = to_csubstr(buf1);
        CHECK_EQ(s, "01");
        CHECK_EQ(s.str, buf1);
        CHECK_EQ(s.len, strlen(buf1));
    }
    SUBCASE("csubstr from const char")
    {
        const char buf1[] = "01";
        const char buf2[] = "012";
        csubstr s(buf1);
        CHECK_EQ(s, "01");
        CHECK_EQ(s.str, buf1);
        CHECK_EQ(s.len, strlen(buf1));
        s = buf2;
        CHECK_EQ(s, "012");
        CHECK_EQ(s.str, buf2);
        CHECK_EQ(s.len, strlen(buf2));
        s.assign(buf1);
        CHECK_EQ(s, "01");
        CHECK_EQ(s.str, buf1);
        CHECK_EQ(s.len, strlen(buf1));
        // s = to_substr(buf2); // deliberate compile error
        s = to_csubstr(buf2);
        CHECK_EQ(s, "012");
        CHECK_EQ(s.str, buf2);
        CHECK_EQ(s.len, strlen(buf2));
    }
}

TEST_CASE("substr.ctor_from_char_ptr")
{
    {
        char buf1_[] = "01";
        char buf2_[] = "012";
        char *buf1 = buf1_;
        char *buf2 = buf2_;
        substr s(buf1);
        CHECK_EQ(s, "01");
        CHECK_EQ(s.str, buf1);
        CHECK_EQ(s.len, strlen(buf1));
        s = buf2;
        CHECK_EQ(s, "012");
        CHECK_EQ(s.str, buf2);
        CHECK_EQ(s.len, strlen(buf2));
        s.assign(buf1);
        CHECK_EQ(s, "01");
        CHECK_EQ(s.str, buf1);
        CHECK_EQ(s.len, strlen(buf1));
        s = to_substr(buf2);
        CHECK_EQ(s, "012");
        CHECK_EQ(s.str, buf2);
        CHECK_EQ(s.len, strlen(buf2));
        //s = to_csubstr(buf1);  // deliberate compile error
        //CHECK_EQ(s, "01");
        //CHECK_EQ(s.str, buf1);
        //CHECK_EQ(s.len, strlen(buf1));
    }
    {
        char buf1_[] = "01";
        char buf2_[] = "012";
        char *buf1 = buf1_;
        char *buf2 = buf2_;
        csubstr s(buf1);
        CHECK_EQ(s, "01");
        CHECK_EQ(s.str, buf1);
        CHECK_EQ(s.len, strlen(buf1));
        s = buf2;
        CHECK_EQ(s, "012");
        CHECK_EQ(s.str, buf2);
        CHECK_EQ(s.len, strlen(buf2));
        s.assign(buf1);
        CHECK_EQ(s, "01");
        CHECK_EQ(s.str, buf1);
        CHECK_EQ(s.len, strlen(buf1));
        s = to_substr(buf2);
        CHECK_EQ(s, "012");
        CHECK_EQ(s.str, buf2);
        CHECK_EQ(s.len, strlen(buf2));
        s = to_csubstr(buf1);
        CHECK_EQ(s, "01");
        CHECK_EQ(s.str, buf1);
        CHECK_EQ(s.len, strlen(buf1));
    }
    {
        const char buf1_[] = "01";
        const char buf2_[] = "012";
        const char *buf1 = buf1_;
        const char *buf2 = buf2_;
        csubstr s(buf1);
        CHECK_EQ(s, "01");
        CHECK_EQ(s.str, buf1);
        CHECK_EQ(s.len, strlen(buf1));
        s = buf2;
        CHECK_EQ(s, "012");
        CHECK_EQ(s.str, buf2);
        CHECK_EQ(s.len, strlen(buf2));
        s.assign(buf1);
        CHECK_EQ(s, "01");
        CHECK_EQ(s.str, buf1);
        CHECK_EQ(s.len, strlen(buf1));
        //s = to_substr(buf2);  // deliberate compile error
        s = to_csubstr(buf2);
        CHECK_EQ(s, "012");
        CHECK_EQ(s.str, buf2);
        CHECK_EQ(s.len, strlen(buf2));
    }
}

TEST_CASE("substr.ctor_from_two_ptrs")
{
    {
        char buf1_[] = "0123456789";
        char *buf1 = buf1_;
        substr s(buf1 + 1, buf1_ + 5);
        CHECK_EQ(s, "1234");
        CHECK_EQ(s.str, buf1 + 1);
        CHECK_EQ(s.len, 4);
        s.assign(buf1, buf1 + 2);
        CHECK_EQ(s, "01");
        CHECK_EQ(s.str, buf1);
        CHECK_EQ(s.len, 2);
    }
    {
        char buf1_[] = "0123456789";
        char *buf1 = buf1_;
        csubstr s(buf1 + 1, buf1_ + 5);
        CHECK_EQ(s, "1234");
        CHECK_EQ(s.str, buf1 + 1);
        CHECK_EQ(s.len, 4);
        s.assign(buf1, buf1 + 2);
        CHECK_EQ(s, "01");
        CHECK_EQ(s.str, buf1);
        CHECK_EQ(s.len, 2);
    }
    {
        const char buf1_[] = "0123456789";
        const char *buf1 = buf1_;
        csubstr s(buf1 + 1, buf1_ + 5);
        CHECK_EQ(s, "1234");
        CHECK_EQ(s.str, buf1 + 1);
        CHECK_EQ(s.len, 4);
        s.assign(buf1, buf1 + 2);
        CHECK_EQ(s, "01");
        CHECK_EQ(s.str, buf1);
        CHECK_EQ(s.len, 2);
    }
}

TEST_CASE("substr.ctor_from_ptr_len")
{
    {
        char buf1_[] = "0123456789";
        char *buf1 = buf1_;
        substr s(buf1 + 1, 4);
        CHECK_EQ(s, "1234");
        CHECK_EQ(s.str, buf1 + 1);
        CHECK_EQ(s.len, 4);
        s.assign(buf1, 2);
        CHECK_EQ(s, "01");
        CHECK_EQ(s.str, buf1);
        CHECK_EQ(s.len, 2);
    }
    {
        char buf1_[] = "0123456789";
        char *buf1 = buf1_;
        csubstr s(buf1 + 1, 4);
        CHECK_EQ(s, "1234");
        CHECK_EQ(s.str, buf1 + 1);
        CHECK_EQ(s.len, 4);
        s.assign(buf1, 2);
        CHECK_EQ(s, "01");
        CHECK_EQ(s.str, buf1);
        CHECK_EQ(s.len, 2);
    }
    {
        const char buf1_[] = "0123456789";
        const char *buf1 = buf1_;
        csubstr s(buf1 + 1, 4);
        CHECK_EQ(s, "1234");
        CHECK_EQ(s.str, buf1 + 1);
        CHECK_EQ(s.len, 4);
        s.assign(buf1, 2);
        CHECK_EQ(s, "01");
        CHECK_EQ(s.str, buf1);
        CHECK_EQ(s.len, 2);
    }
}

TEST_CASE("csubstr.empty_vs_null")
{
    csubstr s;
    CHECK_UNARY(s.empty());
    CHECK_UNARY(s.len == 0);
    CHECK_UNARY(s.str == nullptr);
    CHECK_UNARY(s == nullptr);

    s = "";
    CHECK_UNARY(s.empty());
    CHECK_UNARY(s.len == 0);
    CHECK_UNARY(s.str != nullptr);
    CHECK_UNARY(s != nullptr);

    s = nullptr;
    CHECK_UNARY(s.empty());
    CHECK_UNARY(s.len == 0);
    CHECK_UNARY(s.str == nullptr);
    CHECK_UNARY(s == nullptr);

    s = "";
    CHECK_UNARY(s.empty());
    CHECK_UNARY(s.len == 0);
    CHECK_UNARY(s.str != nullptr);
    CHECK_UNARY(s != nullptr);

    s = {};
    CHECK_UNARY(s.empty());
    CHECK_UNARY(s.len == 0);
    CHECK_UNARY(s.str == nullptr);
    CHECK_UNARY(s == nullptr);

    csubstr pp(nullptr);
    CHECK_UNARY(pp.empty());
    CHECK_UNARY(pp.len == 0);
    CHECK_UNARY(pp.str == nullptr);
    CHECK_UNARY(pp == nullptr);
}

TEST_CASE("substr.is_sub")
{
    csubstr buf = "0123456789";

    // ref
    csubstr s;
    csubstr ref = buf.select("345");
    CHECK_EQ(ref, "345");
    CHECK_UNARY(buf.is_super(ref));
    CHECK_UNARY(ref.is_sub(buf));
    CHECK_FALSE(ref.is_super(buf));
    CHECK_FALSE(buf.is_sub(ref));

    buf.clear();
    ref.clear();
    CHECK_FALSE(buf.is_super(ref));
    CHECK_FALSE(ref.is_super(buf));
    CHECK_FALSE(ref.is_sub(buf));
    CHECK_FALSE(buf.is_sub(ref));

    buf = "";
    ref = buf;
    CHECK_FALSE(buf.is_super("a"));
    CHECK_UNARY(buf.is_super(ref));
}

TEST_CASE("substr.overlaps")
{
    csubstr buf = "0123456789";

    // ref
    csubstr s;
    csubstr ref = buf.select("345");
    CHECK_EQ(ref.len, 3);
    CHECK_EQ(ref, "345");

    // all_left
    s = buf.sub(0, 2);
    CHECK_EQ(s, "01");
    CHECK_FALSE(ref.overlaps(s));
    CHECK_FALSE(s.overlaps(ref));

    // all_left_tight
    s = buf.sub(0, 3);
    CHECK_EQ(s, "012");
    CHECK_FALSE(ref.overlaps(s));
    CHECK_FALSE(s.overlaps(ref));

    // overlap_left
    s = buf.sub(0, 4);
    CHECK_EQ(s, "0123");
    CHECK_UNARY(ref.overlaps(s));
    CHECK_UNARY(s.overlaps(ref));

    // inside_tight_left
    s = buf.sub(3, 1);
    CHECK_EQ(s, "3");
    CHECK_UNARY(ref.overlaps(s));
    CHECK_UNARY(s.overlaps(ref));
    s = buf.sub(3, 2);
    CHECK_EQ(s, "34");
    CHECK_UNARY(ref.overlaps(s));
    CHECK_UNARY(s.overlaps(ref));

    // all_inside_tight
    s = buf.sub(4, 1);
    CHECK_EQ(s, "4");
    CHECK_UNARY(ref.overlaps(s));
    CHECK_UNARY(s.overlaps(ref));
    s = buf.sub(3, 3);
    CHECK_EQ(s, "345");
    CHECK_UNARY(ref.overlaps(s));
    CHECK_UNARY(s.overlaps(ref));

    // inside_tight_right
    s = buf.sub(4, 2);
    CHECK_EQ(s, "45");
    CHECK_UNARY(ref.overlaps(s));
    CHECK_UNARY(s.overlaps(ref));
    s = buf.sub(5, 1);
    CHECK_EQ(s, "5");
    CHECK_UNARY(ref.overlaps(s));
    CHECK_UNARY(s.overlaps(ref));

    // overlap_right
    s = buf.sub(5, 2);
    CHECK_EQ(s, "56");
    CHECK_UNARY(ref.overlaps(s));
    CHECK_UNARY(s.overlaps(ref));
    s = buf.sub(5, 3);
    CHECK_EQ(s, "567");
    CHECK_UNARY(ref.overlaps(s));
    CHECK_UNARY(s.overlaps(ref));

    // all_right_tight
    s = buf.sub(6, 1);
    CHECK_EQ(s, "6");
    CHECK_FALSE(ref.overlaps(s));
    CHECK_FALSE(s.overlaps(ref));
    s = buf.sub(6, 2);
    CHECK_EQ(s, "67");
    CHECK_FALSE(ref.overlaps(s));
    CHECK_FALSE(s.overlaps(ref));

    // all_right
    s = buf.sub(7, 1);
    CHECK_EQ(s, "7");
    CHECK_FALSE(ref.overlaps(s));
    CHECK_FALSE(s.overlaps(ref));
    s = buf.sub(7, 2);
    CHECK_EQ(s, "78");
    CHECK_FALSE(ref.overlaps(s));
    CHECK_FALSE(s.overlaps(ref));

    // null vs null
    csubstr n1, n2;
    CHECK_EQ(n1.str, nullptr);
    CHECK_EQ(n2.str, nullptr);
    CHECK_EQ(n1.len, 0);
    CHECK_EQ(n2.len, 0);
    CHECK_FALSE(n1.overlaps(n2));
    CHECK_FALSE(n2.overlaps(n1));
}

TEST_CASE("substr.sub")
{
    CHECK_EQ(csubstr("10]").sub(0, 2), "10");
}

TEST_CASE("substr.range")
{
    csubstr s = "0123456789";
    CHECK_EQ(s.range(0, 10), "0123456789");
    CHECK_EQ(s.range(0    ), "0123456789");
    CHECK_EQ(s.range(1, 10), "123456789");
    CHECK_EQ(s.range(1    ), "123456789");
    CHECK_EQ(s.range(2, 10), "23456789");
    CHECK_EQ(s.range(2    ), "23456789");
    CHECK_EQ(s.range(3, 10), "3456789");
    CHECK_EQ(s.range(3    ), "3456789");
    CHECK_EQ(s.range(4, 10), "456789");
    CHECK_EQ(s.range(4    ), "456789");
    CHECK_EQ(s.range(5, 10), "56789");
    CHECK_EQ(s.range(5    ), "56789");
    CHECK_EQ(s.range(6, 10), "6789");
    CHECK_EQ(s.range(6    ), "6789");
    CHECK_EQ(s.range(7, 10), "789");
    CHECK_EQ(s.range(7    ), "789");
    CHECK_EQ(s.range(8, 10), "89");
    CHECK_EQ(s.range(8    ), "89");
    CHECK_EQ(s.range(9, 10), "9");
    CHECK_EQ(s.range(9    ), "9");
    CHECK_EQ(s.range(10, 10), "");
    CHECK_EQ(s.range(10    ), "");

    CHECK_EQ(s.range(0 , 9), "012345678");
    CHECK_EQ(s.range(1 , 9), "12345678");
    CHECK_EQ(s.range(2 , 9), "2345678");
    CHECK_EQ(s.range(3 , 9), "345678");
    CHECK_EQ(s.range(4 , 9), "45678");
    CHECK_EQ(s.range(5 , 9), "5678");
    CHECK_EQ(s.range(6 , 9), "678");
    CHECK_EQ(s.range(7 , 9), "78");
    CHECK_EQ(s.range(8 , 9), "8");
    CHECK_EQ(s.range(9 , 9), "");

    CHECK_EQ(s.range(0 , 7), "0123456");
    CHECK_EQ(s.range(1 , 7), "123456");
    CHECK_EQ(s.range(2 , 7), "23456");
    CHECK_EQ(s.range(3 , 7), "3456");
    CHECK_EQ(s.range(4 , 7), "456");
    CHECK_EQ(s.range(5 , 7), "56");
    CHECK_EQ(s.range(6 , 7), "6");
    CHECK_EQ(s.range(7 , 7), "");

    CHECK_EQ(s.range(0 , 5), "01234");
    CHECK_EQ(s.range(1 , 5), "1234");
    CHECK_EQ(s.range(2 , 5), "234");
    CHECK_EQ(s.range(3 , 5), "34");
    CHECK_EQ(s.range(4 , 5), "4");
    CHECK_EQ(s.range(5 , 5), "");

    CHECK_EQ(s.range(0 , 3), "012");
    CHECK_EQ(s.range(1 , 3), "12");
    CHECK_EQ(s.range(2 , 3), "2");
    CHECK_EQ(s.range(3 , 3), "");

    CHECK_EQ(s.range(0 , 2), "01");
    CHECK_EQ(s.range(1 , 2), "1");
    CHECK_EQ(s.range(2 , 2), "");

    CHECK_EQ(s.range(0 , 1), "0");
    CHECK_EQ(s.range(1 , 1), "");
}

TEST_CASE("substr.first")
{
    csubstr s = "0123456789";

    CHECK_EQ(s.first(csubstr::npos), s);

    CHECK_EQ(s.first(10), "0123456789");
    CHECK_EQ(s.first(9), "012345678");
    CHECK_EQ(s.first(8), "01234567");
    CHECK_EQ(s.first(7), "0123456");
    CHECK_EQ(s.first(6), "012345");
    CHECK_EQ(s.first(5), "01234");
    CHECK_EQ(s.first(4), "0123");
    CHECK_EQ(s.first(3), "012");
    CHECK_EQ(s.first(2), "01");
    CHECK_EQ(s.first(1), "0");
    CHECK_EQ(s.first(0), "");
}

TEST_CASE("substr.last")
{
    csubstr s = "0123456789";

    CHECK_EQ(s.last(csubstr::npos), s);

    CHECK_EQ(s.last(10), "0123456789");
    CHECK_EQ(s.last(9), "123456789");
    CHECK_EQ(s.last(8), "23456789");
    CHECK_EQ(s.last(7), "3456789");
    CHECK_EQ(s.last(6), "456789");
    CHECK_EQ(s.last(5), "56789");
    CHECK_EQ(s.last(4), "6789");
    CHECK_EQ(s.last(3), "789");
    CHECK_EQ(s.last(2), "89");
    CHECK_EQ(s.last(1), "9");
    CHECK_EQ(s.last(0), "");
}

TEST_CASE("substr.offs")
{
    csubstr s = "0123456789";

    CHECK_EQ(s.offs(0, 0), s);

    CHECK_EQ(s.offs(1, 0), "123456789");
    CHECK_EQ(s.offs(0, 1), "012345678");
    CHECK_EQ(s.offs(1, 1), "12345678");

    CHECK_EQ(s.offs(1, 2), "1234567");
    CHECK_EQ(s.offs(2, 1), "2345678");
    CHECK_EQ(s.offs(2, 2), "234567");

    CHECK_EQ(s.offs(2, 3), "23456");
    CHECK_EQ(s.offs(3, 2), "34567");
    CHECK_EQ(s.offs(3, 3), "3456");

    CHECK_EQ(s.offs(3, 4), "345");
    CHECK_EQ(s.offs(4, 3), "456");
    CHECK_EQ(s.offs(4, 4), "45");

    CHECK_EQ(s.offs(4, 5), "4");
    CHECK_EQ(s.offs(5, 4), "5");
    CHECK_EQ(s.offs(5, 5), "");
}

TEST_CASE("substr.count")
{
    csubstr buf = "0123456789";

    CHECK_EQ(buf.count('0'), 1);
    CHECK_EQ(buf.count('0', 0), 1);
    CHECK_EQ(buf.count('0', 1), 0);
    CHECK_EQ(buf.count('0', buf.len), 0);

    CHECK_EQ(buf.count("01"), 1);
    CHECK_EQ(buf.count("01", 0), 1);
    CHECK_EQ(buf.count("01", 1), 0);
    CHECK_EQ(buf.count("01", buf.len), 0);

    CHECK_EQ(buf.count('1'), 1);
    CHECK_EQ(buf.count('1', 0), 1);
    CHECK_EQ(buf.count('1', 1), 1);
    CHECK_EQ(buf.count('1', 2), 0);
    CHECK_EQ(buf.count('1', buf.len), 0);

    CHECK_EQ(buf.count("12"), 1);
    CHECK_EQ(buf.count("12", 0), 1);
    CHECK_EQ(buf.count("12", 1), 1);
    CHECK_EQ(buf.count("12", 2), 0);
    CHECK_EQ(buf.count("12", buf.len), 0);

    CHECK_EQ(buf.count('2'), 1);
    CHECK_EQ(buf.count('2', 0), 1);
    CHECK_EQ(buf.count('2', 1), 1);
    CHECK_EQ(buf.count('2', 2), 1);
    CHECK_EQ(buf.count('2', 3), 0);
    CHECK_EQ(buf.count('2', buf.len), 0);

    CHECK_EQ(buf.count("23"), 1);
    CHECK_EQ(buf.count("23", 0), 1);
    CHECK_EQ(buf.count("23", 1), 1);
    CHECK_EQ(buf.count("23", 2), 1);
    CHECK_EQ(buf.count("23", 3), 0);
    CHECK_EQ(buf.count("23", buf.len), 0);

    CHECK_EQ(buf.count('3'), 1);
    CHECK_EQ(buf.count('3', 0), 1);
    CHECK_EQ(buf.count('3', 1), 1);
    CHECK_EQ(buf.count('3', 2), 1);
    CHECK_EQ(buf.count('3', 3), 1);
    CHECK_EQ(buf.count('3', 4), 0);
    CHECK_EQ(buf.count('3', buf.len), 0);

    CHECK_EQ(buf.count("34"), 1);
    CHECK_EQ(buf.count("34", 0), 1);
    CHECK_EQ(buf.count("34", 1), 1);
    CHECK_EQ(buf.count("34", 2), 1);
    CHECK_EQ(buf.count("34", 3), 1);
    CHECK_EQ(buf.count("34", 4), 0);
    CHECK_EQ(buf.count("34", buf.len), 0);

    CHECK_EQ(buf.count('4'), 1);
    CHECK_EQ(buf.count('4', 0), 1);
    CHECK_EQ(buf.count('4', 1), 1);
    CHECK_EQ(buf.count('4', 2), 1);
    CHECK_EQ(buf.count('4', 3), 1);
    CHECK_EQ(buf.count('4', 4), 1);
    CHECK_EQ(buf.count('4', 5), 0);
    CHECK_EQ(buf.count('4', buf.len), 0);

    CHECK_EQ(buf.count("45"), 1);
    CHECK_EQ(buf.count("45", 0), 1);
    CHECK_EQ(buf.count("45", 1), 1);
    CHECK_EQ(buf.count("45", 2), 1);
    CHECK_EQ(buf.count("45", 3), 1);
    CHECK_EQ(buf.count("45", 4), 1);
    CHECK_EQ(buf.count("45", 5), 0);
    CHECK_EQ(buf.count("45", buf.len), 0);

    CHECK_EQ(buf.count('5'), 1);
    CHECK_EQ(buf.count('5', 0), 1);
    CHECK_EQ(buf.count('5', 1), 1);
    CHECK_EQ(buf.count('5', 2), 1);
    CHECK_EQ(buf.count('5', 3), 1);
    CHECK_EQ(buf.count('5', 4), 1);
    CHECK_EQ(buf.count('5', 5), 1);
    CHECK_EQ(buf.count('5', 6), 0);
    CHECK_EQ(buf.count('5', buf.len), 0);

    CHECK_EQ(buf.count("56"), 1);
    CHECK_EQ(buf.count("56", 0), 1);
    CHECK_EQ(buf.count("56", 1), 1);
    CHECK_EQ(buf.count("56", 2), 1);
    CHECK_EQ(buf.count("56", 3), 1);
    CHECK_EQ(buf.count("56", 4), 1);
    CHECK_EQ(buf.count("56", 5), 1);
    CHECK_EQ(buf.count("56", 6), 0);
    CHECK_EQ(buf.count("56", buf.len), 0);

    CHECK_EQ(buf.count('a'), 0);
    CHECK_EQ(buf.count('a', 0), 0);
    CHECK_EQ(buf.count('a', 1), 0);
    CHECK_EQ(buf.count('a', 2), 0);
    CHECK_EQ(buf.count('a', 3), 0);
    CHECK_EQ(buf.count('a', 4), 0);
    CHECK_EQ(buf.count('a', 5), 0);
    CHECK_EQ(buf.count('a', 6), 0);
    CHECK_EQ(buf.count('a', buf.len), 0);

    CHECK_EQ(buf.count("ab"), 0);
    CHECK_EQ(buf.count("ab", 0), 0);
    CHECK_EQ(buf.count("ab", 1), 0);
    CHECK_EQ(buf.count("ab", 2), 0);
    CHECK_EQ(buf.count("ab", 3), 0);
    CHECK_EQ(buf.count("ab", 4), 0);
    CHECK_EQ(buf.count("ab", 5), 0);
    CHECK_EQ(buf.count("ab", 6), 0);
    CHECK_EQ(buf.count("ab", buf.len), 0);

    buf = "00110022003300440055";
    CHECK_EQ(buf.count('0', 0), 10);
    CHECK_EQ(buf.count('0', 1), 9);
    CHECK_EQ(buf.count('0', 2), 8);
    CHECK_EQ(buf.count('0', 3), 8);
    CHECK_EQ(buf.count('0', 4), 8);
    CHECK_EQ(buf.count('0', 5), 7);
    CHECK_EQ(buf.count('0', 6), 6);
    CHECK_EQ(buf.count('0', 7), 6);
    CHECK_EQ(buf.count('0', 8), 6);
    CHECK_EQ(buf.count('0', 9), 5);
    CHECK_EQ(buf.count('0', 10), 4);
    CHECK_EQ(buf.count('0', 11), 4);
    CHECK_EQ(buf.count('0', 12), 4);
    CHECK_EQ(buf.count('0', 13), 3);
    CHECK_EQ(buf.count('0', 14), 2);
    CHECK_EQ(buf.count('0', 15), 2);
    CHECK_EQ(buf.count('0', 16), 2);
    CHECK_EQ(buf.count('0', 17), 1);
    CHECK_EQ(buf.count('0', 18), 0);
    CHECK_EQ(buf.count('0', 19), 0);
    CHECK_EQ(buf.count('0', 20), 0);

    CHECK_EQ(buf.count('1', 0), 2);
    CHECK_EQ(buf.count('1', 1), 2);
    CHECK_EQ(buf.count('1', 2), 2);
    CHECK_EQ(buf.count('1', 3), 1);
    CHECK_EQ(buf.count('1', 4), 0);
    CHECK_EQ(buf.count('1', 5), 0);

    CHECK_EQ(buf.count("01"    ), 1);
    CHECK_EQ(buf.count("01",  2), 0);
    CHECK_EQ(buf.count("10"    ), 1);
    CHECK_EQ(buf.count("10",  4), 0);
    CHECK_EQ(buf.count("00",  0), 5);
    CHECK_EQ(buf.count("00",  1), 4);
    CHECK_EQ(buf.count("00",  2), 4);
    CHECK_EQ(buf.count("00",  3), 4);
    CHECK_EQ(buf.count("00",  4), 4);
    CHECK_EQ(buf.count("00",  5), 3);
    CHECK_EQ(buf.count("00",  6), 3);
    CHECK_EQ(buf.count("00",  7), 3);
    CHECK_EQ(buf.count("00",  8), 3);
    CHECK_EQ(buf.count("00",  9), 2);
    CHECK_EQ(buf.count("00", 10), 2);
    CHECK_EQ(buf.count("00", 11), 2);
    CHECK_EQ(buf.count("00", 12), 2);
    CHECK_EQ(buf.count("00", 13), 1);
    CHECK_EQ(buf.count("00", 14), 1);
    CHECK_EQ(buf.count("00", 15), 1);
    CHECK_EQ(buf.count("00", 16), 1);
    CHECK_EQ(buf.count("00", 17), 0);
    CHECK_EQ(buf.count("00", 18), 0);
    CHECK_EQ(buf.count("00", 19), 0);
    CHECK_EQ(buf.count("00", 20), 0);
}

TEST_CASE("substr.select")
{
    csubstr buf = "0123456789";

    CHECK_EQ(buf.select('0'), "0");
    CHECK_EQ(buf.select('1'), "1");
    CHECK_EQ(buf.select('2'), "2");
    CHECK_EQ(buf.select('8'), "8");
    CHECK_EQ(buf.select('9'), "9");

    CHECK_EQ(buf.select('a').str, nullptr);
    CHECK_EQ(buf.select('a').len, 0);
    CHECK_EQ(buf.select('a'), "");

    CHECK_EQ(buf.select("a").str, nullptr);
    CHECK_EQ(buf.select("a").len, 0);
    CHECK_EQ(buf.select("a"), "");

    CHECK_EQ(buf.select("0"), "0");
    CHECK_EQ(buf.select("0").str, buf.str+0);
    CHECK_EQ(buf.select("0").len, 1);

    CHECK_EQ(buf.select("1"), "1");
    CHECK_EQ(buf.select("1").str, buf.str+1);
    CHECK_EQ(buf.select("1").len, 1);

    CHECK_EQ(buf.select("2"), "2");
    CHECK_EQ(buf.select("2").str, buf.str+2);
    CHECK_EQ(buf.select("2").len, 1);

    CHECK_EQ(buf.select("9"), "9");
    CHECK_EQ(buf.select("9").str, buf.str+9);
    CHECK_EQ(buf.select("9").len, 1);

    CHECK_EQ(buf.select("012"), "012");
    CHECK_EQ(buf.select("012").str, buf.str+0);
    CHECK_EQ(buf.select("012").len, 3);

    CHECK_EQ(buf.select("345"), "345");
    CHECK_EQ(buf.select("345").str, buf.str+3);
    CHECK_EQ(buf.select("345").len, 3);

    CHECK_EQ(buf.select("789"), "789");
    CHECK_EQ(buf.select("789").str, buf.str+7);
    CHECK_EQ(buf.select("789").len, 3);

    CHECK_EQ(buf.select("89a"), "");
    CHECK_EQ(buf.select("89a").str, nullptr);
    CHECK_EQ(buf.select("89a").len, 0);
}

TEST_CASE("substr.begins_with")
{
    CHECK (csubstr(": ").begins_with(":" ));
    CHECK (csubstr(": ").begins_with(':' ));
    CHECK_FALSE(csubstr(":") .begins_with(": "));

    CHECK (csubstr(    "1234").begins_with('0', 0));
    CHECK (csubstr(   "01234").begins_with('0', 1));
    CHECK_FALSE(csubstr(   "01234").begins_with('0', 2));
    CHECK (csubstr(  "001234").begins_with('0', 1));
    CHECK (csubstr(  "001234").begins_with('0', 2));
    CHECK_FALSE(csubstr(  "001234").begins_with('0', 3));
    CHECK (csubstr( "0001234").begins_with('0', 1));
    CHECK (csubstr( "0001234").begins_with('0', 2));
    CHECK (csubstr( "0001234").begins_with('0', 3));
    CHECK_FALSE(csubstr( "0001234").begins_with('0', 4));
    CHECK (csubstr("00001234").begins_with('0', 1));
    CHECK (csubstr("00001234").begins_with('0', 2));
    CHECK (csubstr("00001234").begins_with('0', 3));
    CHECK (csubstr("00001234").begins_with('0', 4));
    CHECK_FALSE(csubstr("00001234").begins_with('0', 5));
}

TEST_CASE("substr.ends_with")
{
    CHECK_UNARY(csubstr("{% if foo %}bar{% endif %}").ends_with("{% endif %}"));

    CHECK (csubstr("1234"    ).ends_with('0', 0));
    CHECK (csubstr("12340"   ).ends_with('0', 1));
    CHECK_FALSE(csubstr("12340"   ).ends_with('0', 2));
    CHECK (csubstr("123400"  ).ends_with('0', 1));
    CHECK (csubstr("123400"  ).ends_with('0', 2));
    CHECK_FALSE(csubstr("123400"  ).ends_with('0', 3));
    CHECK (csubstr("1234000" ).ends_with('0', 1));
    CHECK (csubstr("1234000" ).ends_with('0', 2));
    CHECK (csubstr("1234000" ).ends_with('0', 3));
    CHECK_FALSE(csubstr("1234000" ).ends_with('0', 4));
    CHECK (csubstr("12340000").ends_with('0', 1));
    CHECK (csubstr("12340000").ends_with('0', 2));
    CHECK (csubstr("12340000").ends_with('0', 3));
    CHECK (csubstr("12340000").ends_with('0', 4));
    CHECK_FALSE(csubstr("12340000").ends_with('0', 5));
}

TEST_CASE("substr.find")
{
    csubstr s012345 = "012345";
    CHECK(s012345.find('a') == csubstr::npos);
    CHECK(s012345.find('0'    ) == 0u);
    CHECK(s012345.find('0', 1u) == csubstr::npos);
    CHECK(s012345.find('1'    ) == 1u);
    CHECK(s012345.find('1', 2u) == csubstr::npos);
    CHECK(s012345.find('2'    ) == 2u);
    CHECK(s012345.find('2', 3u) == csubstr::npos);
    CHECK(s012345.find('3'    ) == 3u);
    CHECK(s012345.find('3', 4u) == csubstr::npos);
    CHECK(s012345.find("ab"    ) == csubstr::npos);
    CHECK(s012345.find("01"    ) == 0u);
    CHECK(s012345.find("01", 1u) == csubstr::npos);
    CHECK(s012345.find("12"    ) == 1u);
    CHECK(s012345.find("12", 2u) == csubstr::npos);
    CHECK(s012345.find("23"    ) == 2u);
    CHECK(s012345.find("23", 3u) == csubstr::npos);
}

TEST_CASE("substr.first_of")
{
    size_t npos = csubstr::npos;

    CHECK_EQ(csubstr("012345").first_of('a'), npos);
    CHECK_EQ(csubstr("012345").first_of("ab"), npos);

    CHECK_EQ(csubstr("012345").first_of('0'), 0u);
    CHECK_EQ(csubstr("012345").first_of("0"), 0u);
    CHECK_EQ(csubstr("012345").first_of("01"), 0u);
    CHECK_EQ(csubstr("012345").first_of("10"), 0u);
    CHECK_EQ(csubstr("012345").first_of("012"), 0u);
    CHECK_EQ(csubstr("012345").first_of("210"), 0u);
    CHECK_EQ(csubstr("012345").first_of("0123"), 0u);
    CHECK_EQ(csubstr("012345").first_of("3210"), 0u);
    CHECK_EQ(csubstr("012345").first_of("01234"), 0u);
    CHECK_EQ(csubstr("012345").first_of("43210"), 0u);
    CHECK_EQ(csubstr("012345").first_of("012345"), 0u);
    CHECK_EQ(csubstr("012345").first_of("543210"), 0u);

    CHECK_EQ(csubstr("012345").first_of('0', 2u), npos);
    CHECK_EQ(csubstr("012345").first_of("0", 2u), npos);
    CHECK_EQ(csubstr("012345").first_of("01", 2u), npos);
    CHECK_EQ(csubstr("012345").first_of("10", 2u), npos);
    CHECK_EQ(csubstr("012345").first_of("012", 2u), 2u);
    CHECK_EQ(csubstr("012345").first_of("210", 2u), 2u);
    CHECK_EQ(csubstr("012345").first_of("0123", 2u), 2u);
    CHECK_EQ(csubstr("012345").first_of("3210", 2u), 2u);
    CHECK_EQ(csubstr("012345").first_of("01234", 2u), 2u);
    CHECK_EQ(csubstr("012345").first_of("43210", 2u), 2u);
    CHECK_EQ(csubstr("012345").first_of("012345", 2u), 2u);
    CHECK_EQ(csubstr("012345").first_of("543210", 2u), 2u);

    CHECK_EQ(csubstr("012345").first_of('5'), 5u);
    CHECK_EQ(csubstr("012345").first_of("5"), 5u);
    CHECK_EQ(csubstr("012345").first_of("45"), 4u);
    CHECK_EQ(csubstr("012345").first_of("54"), 4u);
    CHECK_EQ(csubstr("012345").first_of("345"), 3u);
    CHECK_EQ(csubstr("012345").first_of("543"), 3u);
    CHECK_EQ(csubstr("012345").first_of("2345"), 2u);
    CHECK_EQ(csubstr("012345").first_of("5432"), 2u);
    CHECK_EQ(csubstr("012345").first_of("12345"), 1u);
    CHECK_EQ(csubstr("012345").first_of("54321"), 1u);
    CHECK_EQ(csubstr("012345").first_of("012345"), 0u);
    CHECK_EQ(csubstr("012345").first_of("543210"), 0u);

    CHECK_EQ(csubstr("012345").first_of('5', 2u), 5u);
    CHECK_EQ(csubstr("012345").first_of("5", 2u), 5u);
    CHECK_EQ(csubstr("012345").first_of("45", 2u), 4u);
    CHECK_EQ(csubstr("012345").first_of("54", 2u), 4u);
    CHECK_EQ(csubstr("012345").first_of("345", 2u), 3u);
    CHECK_EQ(csubstr("012345").first_of("543", 2u), 3u);
    CHECK_EQ(csubstr("012345").first_of("2345", 2u), 2u);
    CHECK_EQ(csubstr("012345").first_of("5432", 2u), 2u);
    CHECK_EQ(csubstr("012345").first_of("12345", 2u), 2u);
    CHECK_EQ(csubstr("012345").first_of("54321", 2u), 2u);
    CHECK_EQ(csubstr("012345").first_of("012345", 2u), 2u);
    CHECK_EQ(csubstr("012345").first_of("543210", 2u), 2u);

    CHECK_EQ(csubstr{}.first_of('0'), npos);
    CHECK_EQ(csubstr{}.first_of('0', 0u), npos);
    CHECK_EQ(csubstr("012345").first_of('0', 6u), npos);
    CHECK_EQ(csubstr("012345").first_of('5', 6u), npos);
    CHECK_EQ(csubstr("012345").first_of("012345", 6u), npos);
}

TEST_CASE("substr.last_of")
{
    size_t npos = csubstr::npos;

    CHECK_EQ(csubstr("012345").last_of('a'), npos);
    CHECK_EQ(csubstr("012345").last_of("ab"), npos);

    CHECK_EQ(csubstr("012345").last_of('0'), 0u);
    CHECK_EQ(csubstr("012345").last_of("0"), 0u);
    CHECK_EQ(csubstr("012345").last_of("01"), 1u);
    CHECK_EQ(csubstr("012345").last_of("10"), 1u);
    CHECK_EQ(csubstr("012345").last_of("012"), 2u);
    CHECK_EQ(csubstr("012345").last_of("210"), 2u);
    CHECK_EQ(csubstr("012345").last_of("0123"), 3u);
    CHECK_EQ(csubstr("012345").last_of("3210"), 3u);
    CHECK_EQ(csubstr("012345").last_of("01234"), 4u);
    CHECK_EQ(csubstr("012345").last_of("43210"), 4u);
    CHECK_EQ(csubstr("012345").last_of("012345"), 5u);
    CHECK_EQ(csubstr("012345").last_of("543210"), 5u);

    CHECK_EQ(csubstr("012345").last_of('0', 2u), 0u);
    CHECK_EQ(csubstr("012345").last_of("0", 2u), 0u);
    CHECK_EQ(csubstr("012345").last_of("01", 2u), 1u);
    CHECK_EQ(csubstr("012345").last_of("10", 2u), 1u);
    CHECK_EQ(csubstr("012345").last_of("012", 2u), 1u);
    CHECK_EQ(csubstr("012345").last_of("210", 2u), 1u);
    CHECK_EQ(csubstr("012345").last_of("0123", 2u), 1u);
    CHECK_EQ(csubstr("012345").last_of("3210", 2u), 1u);
    CHECK_EQ(csubstr("012345").last_of("01234", 2u), 1u);
    CHECK_EQ(csubstr("012345").last_of("43210", 2u), 1u);
    CHECK_EQ(csubstr("012345").last_of("012345", 2u), 1u);
    CHECK_EQ(csubstr("012345").last_of("543210", 2u), 1u);

    CHECK_EQ(csubstr("012345").last_of('5'), 5u);
    CHECK_EQ(csubstr("012345").last_of("5"), 5u);
    CHECK_EQ(csubstr("012345").last_of("45"), 5u);
    CHECK_EQ(csubstr("012345").last_of("54"), 5u);
    CHECK_EQ(csubstr("012345").last_of("345"), 5u);
    CHECK_EQ(csubstr("012345").last_of("543"), 5u);
    CHECK_EQ(csubstr("012345").last_of("2345"), 5u);
    CHECK_EQ(csubstr("012345").last_of("5432"), 5u);
    CHECK_EQ(csubstr("012345").last_of("12345"), 5u);
    CHECK_EQ(csubstr("012345").last_of("54321"), 5u);
    CHECK_EQ(csubstr("012345").last_of("012345"), 5u);
    CHECK_EQ(csubstr("012345").last_of("543210"), 5u);

    CHECK_EQ(csubstr("012345").last_of('5', 2u), npos);
    CHECK_EQ(csubstr("012345").last_of("5", 2u), npos);
    CHECK_EQ(csubstr("012345").last_of("45", 2u), npos);
    CHECK_EQ(csubstr("012345").last_of("54", 2u), npos);
    CHECK_EQ(csubstr("012345").last_of("345", 2u), npos);
    CHECK_EQ(csubstr("012345").last_of("543", 2u), npos);
    CHECK_EQ(csubstr("012345").last_of("2345", 2u), npos);
    CHECK_EQ(csubstr("012345").last_of("5432", 2u), npos);
    CHECK_EQ(csubstr("012345").last_of("12345", 2u), 1u);
    CHECK_EQ(csubstr("012345").last_of("54321", 2u), 1u);
    CHECK_EQ(csubstr("012345").last_of("012345", 2u), 1u);
    CHECK_EQ(csubstr("012345").last_of("543210", 2u), 1u);

    CHECK_EQ(csubstr{}.last_of('?'), npos);
    CHECK_EQ(csubstr("012345").last_of('0', 6u), 0u);
    CHECK_EQ(csubstr("012345").last_of('5', 6u), 5u);
    CHECK_EQ(csubstr("012345").last_of("012345", 6u), 5u);
}

TEST_CASE("substr.first_not_of")
{
    size_t npos = csubstr::npos;

    CHECK_EQ(csubstr("012345").first_not_of('a'), 0u);
    CHECK_EQ(csubstr("012345").first_not_of("ab"), 0u);

    CHECK_EQ(csubstr("012345").first_not_of('0'), 1u);
    CHECK_EQ(csubstr("012345").first_not_of("0"), 1u);
    CHECK_EQ(csubstr("012345").first_not_of("01"), 2u);
    CHECK_EQ(csubstr("012345").first_not_of("10"), 2u);
    CHECK_EQ(csubstr("012345").first_not_of("012"), 3u);
    CHECK_EQ(csubstr("012345").first_not_of("210"), 3u);
    CHECK_EQ(csubstr("012345").first_not_of("0123"), 4u);
    CHECK_EQ(csubstr("012345").first_not_of("3210"), 4u);
    CHECK_EQ(csubstr("012345").first_not_of("01234"), 5u);
    CHECK_EQ(csubstr("012345").first_not_of("43210"), 5u);
    CHECK_EQ(csubstr("012345").first_not_of("012345"), npos);
    CHECK_EQ(csubstr("012345").first_not_of("543210"), npos);

    CHECK_EQ(csubstr("012345").first_not_of('0', 2u), 2u);
    CHECK_EQ(csubstr("012345").first_not_of("0", 2u), 2u);
    CHECK_EQ(csubstr("012345").first_not_of("01", 2u), 2u);
    CHECK_EQ(csubstr("012345").first_not_of("10", 2u), 2u);
    CHECK_EQ(csubstr("012345").first_not_of("012", 2u), 3u);
    CHECK_EQ(csubstr("012345").first_not_of("210", 2u), 3u);
    CHECK_EQ(csubstr("012345").first_not_of("0123", 2u), 4u);
    CHECK_EQ(csubstr("012345").first_not_of("3210", 2u), 4u);
    CHECK_EQ(csubstr("012345").first_not_of("01234", 2u), 5u);
    CHECK_EQ(csubstr("012345").first_not_of("43210", 2u), 5u);
    CHECK_EQ(csubstr("012345").first_not_of("012345", 2u), npos);
    CHECK_EQ(csubstr("012345").first_not_of("543210", 2u), npos);

    CHECK_EQ(csubstr("012345").first_not_of('5'), 0u);
    CHECK_EQ(csubstr("012345").first_not_of("5"), 0u);
    CHECK_EQ(csubstr("012345").first_not_of("45"), 0u);
    CHECK_EQ(csubstr("012345").first_not_of("54"), 0u);
    CHECK_EQ(csubstr("012345").first_not_of("345"), 0u);
    CHECK_EQ(csubstr("012345").first_not_of("543"), 0u);
    CHECK_EQ(csubstr("012345").first_not_of("2345"), 0u);
    CHECK_EQ(csubstr("012345").first_not_of("5432"), 0u);
    CHECK_EQ(csubstr("012345").first_not_of("12345"), 0u);
    CHECK_EQ(csubstr("012345").first_not_of("54321"), 0u);
    CHECK_EQ(csubstr("012345").first_not_of("012345"), npos);
    CHECK_EQ(csubstr("012345").first_not_of("543210"), npos);

    CHECK_EQ(csubstr("012345").first_not_of('5', 2u), 2u);
    CHECK_EQ(csubstr("012345").first_not_of("5", 2u), 2u);
    CHECK_EQ(csubstr("012345").first_not_of("45", 2u), 2u);
    CHECK_EQ(csubstr("012345").first_not_of("54", 2u), 2u);
    CHECK_EQ(csubstr("012345").first_not_of("345", 2u), 2u);
    CHECK_EQ(csubstr("012345").first_not_of("543", 2u), 2u);
    CHECK_EQ(csubstr("012345").first_not_of("2345", 2u), npos);
    CHECK_EQ(csubstr("012345").first_not_of("5432", 2u), npos);
    CHECK_EQ(csubstr("012345").first_not_of("12345", 2u), npos);
    CHECK_EQ(csubstr("012345").first_not_of("54321", 2u), npos);
    CHECK_EQ(csubstr("012345").first_not_of("012345", 2u), npos);
    CHECK_EQ(csubstr("012345").first_not_of("543210", 2u), npos);

    CHECK_EQ(csubstr("").first_not_of('0', 0u), npos);
    CHECK_EQ(csubstr("012345").first_not_of('5', 6u), npos);
    CHECK_EQ(csubstr("012345").first_not_of("012345", 6u), npos);
}

TEST_CASE("substr.last_not_of")
{
    size_t npos = csubstr::npos;

    CHECK_EQ(csubstr("012345").last_not_of('a'), 5u);
    CHECK_EQ(csubstr("012345").last_not_of("ab"), 5u);

    CHECK_EQ(csubstr("012345").last_not_of('5'), 4u);
    CHECK_EQ(csubstr("012345").last_not_of("5"), 4u);
    CHECK_EQ(csubstr("012345").last_not_of("45"), 3u);
    CHECK_EQ(csubstr("012345").last_not_of("54"), 3u);
    CHECK_EQ(csubstr("012345").last_not_of("345"), 2u);
    CHECK_EQ(csubstr("012345").last_not_of("543"), 2u);
    CHECK_EQ(csubstr("012345").last_not_of("2345"), 1u);
    CHECK_EQ(csubstr("012345").last_not_of("5432"), 1u);
    CHECK_EQ(csubstr("012345").last_not_of("12345"), 0u);
    CHECK_EQ(csubstr("012345").last_not_of("54321"), 0u);
    CHECK_EQ(csubstr("012345").last_not_of("012345"), npos);
    CHECK_EQ(csubstr("012345").last_not_of("543210"), npos);

    CHECK_EQ(csubstr("012345").last_not_of('5', 2u), 1u);
    CHECK_EQ(csubstr("012345").last_not_of("5", 2u), 1u);
    CHECK_EQ(csubstr("012345").last_not_of("45", 2u), 1u);
    CHECK_EQ(csubstr("012345").last_not_of("54", 2u), 1u);
    CHECK_EQ(csubstr("012345").last_not_of("345", 2u), 1u);
    CHECK_EQ(csubstr("012345").last_not_of("543", 2u), 1u);
    CHECK_EQ(csubstr("012345").last_not_of("2345", 2u), 1u);
    CHECK_EQ(csubstr("012345").last_not_of("5432", 2u), 1u);
    CHECK_EQ(csubstr("012345").last_not_of("12345", 2u), 0u);
    CHECK_EQ(csubstr("012345").last_not_of("54321", 2u), 0u);
    CHECK_EQ(csubstr("012345").last_not_of("012345", 2u), npos);
    CHECK_EQ(csubstr("012345").last_not_of("543210", 2u), npos);

    CHECK_EQ(csubstr("012345").last_not_of('0'), 5u);
    CHECK_EQ(csubstr("012345").last_not_of("0"), 5u);
    CHECK_EQ(csubstr("012345").last_not_of("01"), 5u);
    CHECK_EQ(csubstr("012345").last_not_of("10"), 5u);
    CHECK_EQ(csubstr("012345").last_not_of("012"), 5u);
    CHECK_EQ(csubstr("012345").last_not_of("210"), 5u);
    CHECK_EQ(csubstr("012345").last_not_of("0123"), 5u);
    CHECK_EQ(csubstr("012345").last_not_of("3210"), 5u);
    CHECK_EQ(csubstr("012345").last_not_of("01234"), 5u);
    CHECK_EQ(csubstr("012345").last_not_of("43210"), 5u);
    CHECK_EQ(csubstr("012345").last_not_of("012345"), npos);
    CHECK_EQ(csubstr("012345").last_not_of("543210"), npos);

    CHECK_EQ(csubstr("012345").last_not_of('0', 2u), 1u);
    CHECK_EQ(csubstr("012345").last_not_of("0", 2u), 1u);
    CHECK_EQ(csubstr("012345").last_not_of("01", 2u), npos);
    CHECK_EQ(csubstr("012345").last_not_of("10", 2u), npos);
    CHECK_EQ(csubstr("012345").last_not_of("012", 2u), npos);
    CHECK_EQ(csubstr("012345").last_not_of("210", 2u), npos);
    CHECK_EQ(csubstr("012345").last_not_of("0123", 2u), npos);
    CHECK_EQ(csubstr("012345").last_not_of("3210", 2u), npos);
    CHECK_EQ(csubstr("012345").last_not_of("01234", 2u), npos);
    CHECK_EQ(csubstr("012345").last_not_of("43210", 2u), npos);
    CHECK_EQ(csubstr("012345").last_not_of("012345", 2u), npos);
    CHECK_EQ(csubstr("012345").last_not_of("543210", 2u), npos);

    CHECK_EQ(csubstr("").last_not_of('0', 0u), npos);
    CHECK_EQ(csubstr("012345").last_not_of('5', 6u), 4u);
}

TEST_CASE("substr.left_of")
{
    csubstr s = "012345";

    CHECK_EQ(s.left_of(csubstr::npos), s);
    CHECK_EQ(s.left_of(csubstr::npos, /*include_pos*/false), s);
    CHECK_EQ(s.left_of(csubstr::npos, /*include_pos*/true), s);


    CHECK_EQ(s.left_of(0), "");
    CHECK_EQ(s.left_of(1), "0");
    CHECK_EQ(s.left_of(2), "01");
    CHECK_EQ(s.left_of(3), "012");
    CHECK_EQ(s.left_of(4), "0123");
    CHECK_EQ(s.left_of(5), "01234");
    CHECK_EQ(s.left_of(6), "012345");

    CHECK_EQ(s.left_of(0, /*include_pos*/false), "");
    CHECK_EQ(s.left_of(1, /*include_pos*/false), "0");
    CHECK_EQ(s.left_of(2, /*include_pos*/false), "01");
    CHECK_EQ(s.left_of(3, /*include_pos*/false), "012");
    CHECK_EQ(s.left_of(4, /*include_pos*/false), "0123");
    CHECK_EQ(s.left_of(5, /*include_pos*/false), "01234");
    CHECK_EQ(s.left_of(6, /*include_pos*/false), "012345");

    CHECK_UNARY(s.is_super(s.left_of(0, /*include_pos*/false)));
    CHECK_UNARY(s.is_super(s.left_of(1, /*include_pos*/false)));
    CHECK_UNARY(s.is_super(s.left_of(2, /*include_pos*/false)));
    CHECK_UNARY(s.is_super(s.left_of(3, /*include_pos*/false)));
    CHECK_UNARY(s.is_super(s.left_of(4, /*include_pos*/false)));


    CHECK_EQ(s.left_of(0, /*include_pos*/true), "0");
    CHECK_EQ(s.left_of(1, /*include_pos*/true), "01");
    CHECK_EQ(s.left_of(2, /*include_pos*/true), "012");
    CHECK_EQ(s.left_of(3, /*include_pos*/true), "0123");
    CHECK_EQ(s.left_of(4, /*include_pos*/true), "01234");
    CHECK_EQ(s.left_of(5, /*include_pos*/true), "012345");

    CHECK_UNARY(s.is_super(s.left_of(0, /*include_pos*/true)));
    CHECK_UNARY(s.is_super(s.left_of(1, /*include_pos*/true)));
    CHECK_UNARY(s.is_super(s.left_of(2, /*include_pos*/true)));
    CHECK_UNARY(s.is_super(s.left_of(3, /*include_pos*/true)));
    CHECK_UNARY(s.is_super(s.left_of(4, /*include_pos*/true)));


    CHECK_EQ(s.sub(5), "5");
    CHECK_EQ(s.sub(4), "45");
    CHECK_EQ(s.sub(3), "345");
    CHECK_EQ(s.sub(2), "2345");
    CHECK_EQ(s.sub(1), "12345");
    CHECK_EQ(s.sub(0), "012345");

    CHECK_EQ(s.left_of(s.sub(5)), "01234");
    CHECK_EQ(s.left_of(s.sub(4)), "0123");
    CHECK_EQ(s.left_of(s.sub(3)), "012");
    CHECK_EQ(s.left_of(s.sub(2)), "01");
    CHECK_EQ(s.left_of(s.sub(1)), "0");
    CHECK_EQ(s.left_of(s.sub(0)), "");

    CHECK_UNARY(s.is_super(s.left_of(s.sub(5))));
    CHECK_UNARY(s.is_super(s.left_of(s.sub(4))));
    CHECK_UNARY(s.is_super(s.left_of(s.sub(3))));
    CHECK_UNARY(s.is_super(s.left_of(s.sub(2))));
    CHECK_UNARY(s.is_super(s.left_of(s.sub(1))));
    CHECK_UNARY(s.is_super(s.left_of(s.sub(0))));
}

TEST_CASE("substr.right_of")
{
    csubstr s = "012345";

    CHECK_EQ(s.right_of(csubstr::npos), "");
    CHECK_EQ(s.right_of(csubstr::npos), "");

    CHECK_EQ(s.right_of(csubstr::npos, /*include_pos*/false), "");
    CHECK_EQ(s.right_of(csubstr::npos, /*include_pos*/true), "");


    CHECK_EQ(s.right_of(0), "12345");
    CHECK_EQ(s.right_of(1), "2345");
    CHECK_EQ(s.right_of(2), "345");
    CHECK_EQ(s.right_of(3), "45");
    CHECK_EQ(s.right_of(4), "5");
    CHECK_EQ(s.right_of(5), "");

    CHECK_EQ(s.right_of(0, /*include_pos*/false), "12345");
    CHECK_EQ(s.right_of(1, /*include_pos*/false), "2345");
    CHECK_EQ(s.right_of(2, /*include_pos*/false), "345");
    CHECK_EQ(s.right_of(3, /*include_pos*/false), "45");
    CHECK_EQ(s.right_of(4, /*include_pos*/false), "5");
    CHECK_EQ(s.right_of(5, /*include_pos*/false), "");

    CHECK_UNARY(s.is_super(s.right_of(0)));
    CHECK_UNARY(s.is_super(s.right_of(1)));
    CHECK_UNARY(s.is_super(s.right_of(2)));
    CHECK_UNARY(s.is_super(s.right_of(3)));
    CHECK_UNARY(s.is_super(s.right_of(4)));
    CHECK_UNARY(s.is_super(s.right_of(5)));

    CHECK_UNARY(s.is_super(s.right_of(0, /*include_pos*/false)));
    CHECK_UNARY(s.is_super(s.right_of(1, /*include_pos*/false)));
    CHECK_UNARY(s.is_super(s.right_of(2, /*include_pos*/false)));
    CHECK_UNARY(s.is_super(s.right_of(3, /*include_pos*/false)));
    CHECK_UNARY(s.is_super(s.right_of(4, /*include_pos*/false)));
    CHECK_UNARY(s.is_super(s.right_of(5, /*include_pos*/false)));


    CHECK_EQ(s.right_of(0, /*include_pos*/true), "012345");
    CHECK_EQ(s.right_of(1, /*include_pos*/true), "12345");
    CHECK_EQ(s.right_of(2, /*include_pos*/true), "2345");
    CHECK_EQ(s.right_of(3, /*include_pos*/true), "345");
    CHECK_EQ(s.right_of(4, /*include_pos*/true), "45");
    CHECK_EQ(s.right_of(5, /*include_pos*/true), "5");
    CHECK_EQ(s.right_of(6, /*include_pos*/true), "");

    CHECK_UNARY(s.is_super(s.right_of(0, /*include_pos*/true)));
    CHECK_UNARY(s.is_super(s.right_of(1, /*include_pos*/true)));
    CHECK_UNARY(s.is_super(s.right_of(2, /*include_pos*/true)));
    CHECK_UNARY(s.is_super(s.right_of(3, /*include_pos*/true)));
    CHECK_UNARY(s.is_super(s.right_of(4, /*include_pos*/true)));
    CHECK_UNARY(s.is_super(s.right_of(5, /*include_pos*/true)));
    CHECK_UNARY(s.is_super(s.right_of(6, /*include_pos*/true)));


    CHECK_EQ(s.sub(0, 0), "");
    CHECK_EQ(s.sub(0, 1), "0");
    CHECK_EQ(s.sub(0, 2), "01");
    CHECK_EQ(s.sub(0, 3), "012");
    CHECK_EQ(s.sub(0, 4), "0123");
    CHECK_EQ(s.sub(0, 5), "01234");
    CHECK_EQ(s.sub(0, 6), "012345");

    CHECK_EQ(s.right_of(s.sub(0, 0)), "012345");
    CHECK_EQ(s.right_of(s.sub(0, 1)), "12345");
    CHECK_EQ(s.right_of(s.sub(0, 2)), "2345");
    CHECK_EQ(s.right_of(s.sub(0, 3)), "345");
    CHECK_EQ(s.right_of(s.sub(0, 4)), "45");
    CHECK_EQ(s.right_of(s.sub(0, 5)), "5");
    CHECK_EQ(s.right_of(s.sub(0, 6)), "");

    CHECK_UNARY(s.is_super(s.right_of(s.sub(0, 0))));
    CHECK_UNARY(s.is_super(s.right_of(s.sub(0, 1))));
    CHECK_UNARY(s.is_super(s.right_of(s.sub(0, 2))));
    CHECK_UNARY(s.is_super(s.right_of(s.sub(0, 3))));
    CHECK_UNARY(s.is_super(s.right_of(s.sub(0, 4))));
    CHECK_UNARY(s.is_super(s.right_of(s.sub(0, 5))));
    CHECK_UNARY(s.is_super(s.right_of(s.sub(0, 6))));
}

TEST_CASE_TEMPLATE("substr.compare_different_length", SS, csubstr, substr)
{
    typename SS::char_type s1[] = "one empty doc";
    typename SS::char_type s2[] = "one empty doc, explicit termination";
    SS c1(s1), c2(s2);
    CHECK_NE(c1, c2);
    CHECK_NE(c1, s2);
    CHECK_NE(s1, c2);
    CHECK_LT(c1, c2);
    CHECK_LT(c1, s2);
    CHECK_LT(s1, c2);
    CHECK_GT(c2, c1);
    CHECK_GT(c2, s1);
    CHECK_GT(s2, c1);
    CHECK_NE((c1 > c2), (c1 < c2));
    CHECK_NE((c1 > s2), (c1 < s2));
    CHECK_NE((s1 > c2), (s1 < c2));
    CHECK_NE((c2 > c1), (c2 < c1));
    CHECK_NE((c2 > s1), (c2 < s1));
    CHECK_NE((s2 > c1), (s2 < c1));
    CHECK_NE((c1 == c2), (c1 != c2));
    CHECK_NE((c1 == s2), (c1 != s2));
    CHECK_NE((s1 == c2), (s1 != c2));
    CHECK_NE((c2 == c1), (c2 != c1));
    CHECK_NE((c2 == s1), (c2 != s1));
    CHECK_NE((s2 == c1), (s2 != c1));
}

TEST_CASE_TEMPLATE("substr.compare_null", SS, csubstr, substr)
{
    typename SS::char_type spbuf[] = " ";
    SS s1, s2, sp(spbuf);
    CHECK_EQ(s1, "");
    CHECK_EQ(s1, s2);
    CHECK(!(s1 > s2));
    CHECK(!(s1 < s2));
    CHECK((s1 <= s2));
    CHECK((s1 >= s2));
    CHECK(!(s1 != s2));
    CHECK_EQ(s1.compare('-'), -1);
    CHECK_EQ(sp.compare(' '), 0);
    CHECK_EQ(s1.compare("-", 1u), -1);
    CHECK_EQ(s1.compare("-", 0u), 0);
    CHECK_EQ(s1.compare((const char*)0, 0u), 0);
    CHECK_EQ(sp.compare((const char*)0, 0u), 1);
    CHECK_EQ(sp.compare(" ", 0u), 1);
    CHECK_EQ(sp.compare(" ", 1u), 0);
}

TEST_CASE_TEMPLATE("substr.compare_vs_char", SS, csubstr, substr)
{
    CHECK_EQ(SS().compare('1'), -1); // str==null, len==0
    typename SS::char_type numbuf[] = "0123"; SS num(numbuf);
    CHECK_EQ(num.first(0).compare('1'), -1); // str!=null, len==0
    CHECK_EQ(num.first(1).compare('1'), -1);

    typename SS::char_type dashbuf[] = "-"; SS dash(dashbuf);
    CHECK_EQ(dash, '-');
    CHECK_EQ(dash, "-");
    CHECK_EQ('-', dash);
    CHECK_EQ("-", dash);

    typename SS::char_type plusbuf[] = "+"; SS plus(plusbuf);
    CHECK_NE(plus, '-');
    CHECK_NE(plus, "-");
    CHECK_NE('-', plus);
    CHECK_NE("-", plus);

    typename SS::char_type dash3buf[] = "---"; SS dash3(dash3buf);
    CHECK_NE(dash3, '-');
    CHECK_NE(dash3, "-");
    CHECK_NE('-', dash3);
    CHECK_NE("-", dash3);

    typename SS::char_type aaabuf[] = "aaa"; SS aaa(aaabuf);
    CHECK_NE(aaa, 'a');
    CHECK_NE(aaa, "a");
    CHECK_NE('a', aaa);
    CHECK_NE("a", aaa);

    CHECK_NE(aaa, 'b');
    CHECK_NE(aaa, "b");
    CHECK_NE('b', aaa);
    CHECK_NE("b", aaa);

    CHECK_LT(aaa, 'b');
    CHECK_LT(aaa, "b");
    CHECK_GT('b', aaa);
    CHECK_GT("b", aaa);

    CHECK_LE(aaa, 'b');
    CHECK_LE(aaa, "b");
    CHECK_GE('b', aaa);
    CHECK_GE("b", aaa);

    typename SS::char_type bbbbuf[] = "bbb"; SS bbb(bbbbuf);
    CHECK_NE(bbb, 'a');
    CHECK_NE(bbb, "a");
    CHECK_NE('a', bbb);
    CHECK_NE("a", bbb);

    CHECK_GT(bbb, 'a');
    CHECK_GT(bbb, "a");
    CHECK_LT('a', bbb);
    CHECK_LT("a", bbb);

    CHECK_GE(bbb, 'a');
    CHECK_GE(bbb, "a");
    CHECK_LE('a', bbb);
    CHECK_LE("a", bbb);
}

TEST_CASE("substr.mixed_cmp")
{
    // c++20 introduced new comparison rules and clang10 fails:
    //
    // error: ISO C++20 considers use of overloaded operator '==' (with operand
    // types 'const c4::basic_substring<char>' and 'const
    // c4::basic_substring<char>') to be ambiguous despite there being a unique
    // best viable function [-Werror,-Wambiguous-reversed-operator]

    char sa_[] = "a";
    char sb_[] = "b";
    csubstr csa = "a"; substr sa = sa_;
    csubstr csb = "b"; substr sb = sb_;

    CHECK_EQ(csa, csa);
    CHECK_EQ(sa, sa); // this fails
    CHECK_EQ(csa, sa);
    CHECK_EQ(sa, csa);

    CHECK_NE(sa, sb);
    CHECK_NE(csa, csb);
    CHECK_NE(csa, sb);
    CHECK_NE(sa, csb);

    CHECK_LT(sa,  sb);
    CHECK_LT(csa, csb);
    CHECK_LT(csa,  sb);
    CHECK_LT(sa, csb);

    CHECK_LE(sa, sb);
    CHECK_LE(csa, csb);
    CHECK_LE(csa,  sb);
    CHECK_LE(sa, csb);

    CHECK_LE(sa, sa);
    CHECK_LE(csa, csa);
    CHECK_LE(csa, sa);
    CHECK_LE(sa, csa);

    CHECK_GT(sb, sa);
    CHECK_GT(csb, csa);
    CHECK_GT(csb,  sa);
    CHECK_GT( sb, csa);

    CHECK_GE(sb,  sa);
    CHECK_GE(csb, csa);
    CHECK_GE(csb,  sa);
    CHECK_GE( sb, csa);

    CHECK_GE(sb,  sb);
    CHECK_GE(csb, csb);
    CHECK_GE(csb,  sb);
    CHECK_GE( sb, csb);
}

TEST_CASE_TEMPLATE("substr.eqne", SS, csubstr, substr)
{
    char buf[128];
    for(size_t i = 0; i < 5; ++i) buf[i] = (char)('0' + i);
    SS cmp(buf, 5);

    typename SS::char_type buf2[] = "0123456789";
    SS ref(buf2);

    CHECK_EQ(ref.first(5), cmp);
    CHECK_EQ("01234", cmp);
    CHECK_EQ(cmp, "01234");

    CHECK_NE(ref.first(4), cmp);
    CHECK_NE("0123", cmp);
    CHECK_NE(cmp, "0123");

    CHECK_NE(ref.first(6), cmp);
    CHECK_NE("012345", cmp);
    CHECK_NE(cmp, "012345");
}

TEST_CASE("substr.substr2csubstr")
{
    char b[] = "some string";
    substr s(b);
    csubstr sc = s;
    CHECK_EQ(sc, s);
    const substr cs(b);
    const csubstr csc(b);
}

template <class ...Args>
void test_first_of_any(csubstr input, bool true_or_false, size_t which, size_t pos, Args... args)
{
    csubstr::first_of_any_result r = input.first_of_any(to_csubstr(args)...);
    //std::cout << input << ": " << (bool(r) ? "true" : "false") << "/which:" << r.which << "/pos:" << r.pos << "\n";
    CHECK_EQ(r, true_or_false);
    if(true_or_false)
    {
        CHECK_UNARY(r);
    }
    else
    {
        CHECK_FALSE(r);
    }
    CHECK_EQ(r.which, which);
    CHECK_EQ(r.pos, pos);
}

TEST_CASE("substr.first_of_any")
{
    size_t NONE = csubstr::NONE;
    size_t npos = csubstr::npos;

    test_first_of_any("foobar"               , true , 0u  ,   3u, "bar", "barbell", "bark", "barff");
    test_first_of_any("foobar"               , false, NONE, npos,        "barbell", "bark", "barff");
    test_first_of_any("foobart"              , false, NONE, npos,        "barbell", "bark", "barff");

    test_first_of_any("10"                   , false, NONE, npos, "0x", "0X", "-0x", "-0X");
    test_first_of_any("10]"                  , false, NONE, npos, "0x", "0X", "-0x", "-0X");
    test_first_of_any(csubstr("10]").first(2), false, NONE, npos, "0x", "0X", "-0x", "-0X");


    test_first_of_any("baz{% endif %}", true, 0u, 3u, "{% endif %}", "{% if "         , "{% elif bar %}" , "{% else %}" );
    test_first_of_any("baz{% endif %}", true, 1u, 3u, "{% if "     , "{% endif %}"    , "{% elif bar %}" , "{% else %}" );
    test_first_of_any("baz{% endif %}", true, 2u, 3u, "{% if "     , "{% elif bar %}" , "{% endif %}"    , "{% else %}" );
    test_first_of_any("baz{% endif %}", true, 3u, 3u, "{% if "     , "{% elif bar %}" , "{% else %}"     , "{% endif %}");

    test_first_of_any("baz{% e..if %}", false, NONE, npos, "{% endif %}", "{% if "         , "{% elif bar %}" , "{% else %}" );
    test_first_of_any("baz{% e..if %}", false, NONE, npos, "{% if "     , "{% endif %}"    , "{% elif bar %}" , "{% else %}" );
    test_first_of_any("baz{% e..if %}", false, NONE, npos, "{% if "     , "{% elif bar %}" , "{% endif %}"    , "{% else %}" );
    test_first_of_any("baz{% e..if %}", false, NONE, npos, "{% if "     , "{% elif bar %}" , "{% else %}"     , "{% endif %}");


    test_first_of_any("bar{% else %}baz{% endif %}", true, 0u, 3u, "{% else %}" , "{% if "         , "{% elif bar %}" , "{% endif %}");
    test_first_of_any("bar{% else %}baz{% endif %}", true, 1u, 3u, "{% if "     , "{% else %}"     , "{% elif bar %}" , "{% endif %}");
    test_first_of_any("bar{% else %}baz{% endif %}", true, 2u, 3u, "{% if "     , "{% elif bar %}" , "{% else %}"     , "{% endif %}");
    test_first_of_any("bar{% else %}baz{% endif %}", true, 3u, 3u, "{% if "     , "{% elif bar %}" , "{% endif %}"    , "{% else %}" );

    test_first_of_any("bar{% e..e %}baz{% e..if %}", false, NONE, npos, "{% else %}" , "{% if "         , "{% elif bar %}" , "{% endif %}");
    test_first_of_any("bar{% e..e %}baz{% e..if %}", false, NONE, npos, "{% if "     , "{% else %}"     , "{% elif bar %}" , "{% endif %}");
    test_first_of_any("bar{% e..e %}baz{% e..if %}", false, NONE, npos, "{% if "     , "{% elif bar %}" , "{% else %}"     , "{% endif %}");
    test_first_of_any("bar{% e..e %}baz{% e..if %}", false, NONE, npos, "{% if "     , "{% elif bar %}" , "{% endif %}"    , "{% else %}" );


    test_first_of_any("foo{% elif bar %}bar{% else %}baz{% endif %}", true, 0u, 3u, "{% elif bar %}" , "{% if "         , "{% else %}"     , "{% endif %}"   );
    test_first_of_any("foo{% elif bar %}bar{% else %}baz{% endif %}", true, 1u, 3u, "{% if "         , "{% elif bar %}" , "{% else %}"     , "{% endif %}"   );
    test_first_of_any("foo{% elif bar %}bar{% else %}baz{% endif %}", true, 2u, 3u, "{% if "         , "{% else %}"     , "{% elif bar %}" , "{% endif %}"   );
    test_first_of_any("foo{% elif bar %}bar{% else %}baz{% endif %}", true, 3u, 3u, "{% if "         , "{% else %}"     , "{% endif %}"    , "{% elif bar %}");

    test_first_of_any("foo{% e..f bar %}bar{% e..e %}baz{% e..if %}", false, NONE, npos, "{% elif bar %}" , "{% if "         , "{% else %}"     , "{% endif %}"   );
    test_first_of_any("foo{% e..f bar %}bar{% e..e %}baz{% e..if %}", false, NONE, npos, "{% if "         , "{% elif bar %}" , "{% else %}"     , "{% endif %}"   );
    test_first_of_any("foo{% e..f bar %}bar{% e..e %}baz{% e..if %}", false, NONE, npos, "{% if "         , "{% else %}"     , "{% elif bar %}" , "{% endif %}"   );
    test_first_of_any("foo{% e..f bar %}bar{% e..e %}baz{% e..if %}", false, NONE, npos, "{% if "         , "{% else %}"     , "{% endif %}"    , "{% elif bar %}");


    test_first_of_any("{% if foo %}foo{% elif bar %}bar{% else %}baz{% endif %}", true, 0u, 0u, "{% if "         , "{% elif bar %}" , "{% else %}" , "{% endif %}" );
    test_first_of_any("{% if foo %}foo{% elif bar %}bar{% else %}baz{% endif %}", true, 1u, 0u, "{% elif bar %}" , "{% if "         , "{% else %}" , "{% endif %}" );
    test_first_of_any("{% if foo %}foo{% elif bar %}bar{% else %}baz{% endif %}", true, 2u, 0u, "{% elif bar %}" , "{% else %}"     , "{% if "     , "{% endif %}" );
    test_first_of_any("{% if foo %}foo{% elif bar %}bar{% else %}baz{% endif %}", true, 3u, 0u, "{% elif bar %}" , "{% else %}"     , "{% endif %}", "{% if "      );

    test_first_of_any("{% .. foo %}foo{% e..f bar %}bar{% e..e %}baz{% e..if %}", false, NONE, npos, "{% if "         , "{% elif bar %}" , "{% else %}" , "{% endif %}" );
    test_first_of_any("{% .. foo %}foo{% e..f bar %}bar{% e..e %}baz{% e..if %}", false, NONE, npos, "{% elif bar %}" , "{% if "         , "{% else %}" , "{% endif %}" );
    test_first_of_any("{% .. foo %}foo{% e..f bar %}bar{% e..e %}baz{% e..if %}", false, NONE, npos, "{% elif bar %}" , "{% else %}"     , "{% if "     , "{% endif %}" );
    test_first_of_any("{% .. foo %}foo{% e..f bar %}bar{% e..e %}baz{% e..if %}", false, NONE, npos, "{% elif bar %}" , "{% else %}"     , "{% endif %}", "{% if "      );
}


TEST_CASE("substr.pair_range_esc")
{
    const char q = '\'';
    CHECK_EQ(csubstr("").pair_range_esc(q), "");
    CHECK_EQ(csubstr("'").pair_range_esc(q), "");
    CHECK_EQ(csubstr("''").pair_range_esc(q), "''");
    CHECK_EQ(csubstr("'\\'\\''").pair_range_esc(q), "'\\'\\''");
    CHECK_EQ(csubstr("asdasdasd''asdasd").pair_range_esc(q), "''");
    CHECK_EQ(csubstr("asdasdasd'abc'asdasda").pair_range_esc(q), "'abc'");
}

TEST_CASE("substr.pair_range")
{
    CHECK_EQ(csubstr("").pair_range('{', '}'), "");
    CHECK_EQ(csubstr("{").pair_range('{', '}'), "");
    CHECK_EQ(csubstr("}").pair_range('{', '}'), "");
    CHECK_EQ(csubstr("{}").pair_range('{', '}'), "{}");
    CHECK_EQ(csubstr("{abc}").pair_range('{', '}'), "{abc}");
    CHECK_EQ(csubstr("123{abc}456").pair_range('{', '}'), "{abc}");
}

TEST_CASE("substr.pair_range_nested")
{
    CHECK_EQ(csubstr("").pair_range_nested('{', '}'), "");
    CHECK_EQ(csubstr("{").pair_range_nested('{', '}'), "");
    CHECK_EQ(csubstr("}").pair_range_nested('{', '}'), "");
    CHECK_EQ(csubstr("{}").pair_range_nested('{', '}'), "{}");
    CHECK_EQ(csubstr("{abc}").pair_range_nested('{', '}'), "{abc}");
    CHECK_EQ(csubstr("123{abc}456").pair_range_nested('{', '}'), "{abc}");
    CHECK_EQ(csubstr("123{abc}456{def}").pair_range_nested('{', '}'), "{abc}");
    CHECK_EQ(csubstr(   "{{}}").pair_range_nested('{', '}'), "{{}}");
    CHECK_EQ(csubstr("123{{}}456").pair_range_nested('{', '}'), "{{}}");
    CHECK_EQ(csubstr(   "{a{}b{}c}").pair_range_nested('{', '}'), "{a{}b{}c}");
    CHECK_EQ(csubstr("123{a{}b{}c}456").pair_range_nested('{', '}'), "{a{}b{}c}");
    CHECK_EQ(csubstr(    "{a{{}}b{{}}c}").pair_range_nested('{', '}'), "{a{{}}b{{}}c}");
    CHECK_EQ(csubstr("123{a{{}}b{{}}c}456").pair_range_nested('{', '}'), "{a{{}}b{{}}c}");
    CHECK_EQ(csubstr(   "{{{}}a{{}}b{{}}c{{}}}").pair_range_nested('{', '}'), "{{{}}a{{}}b{{}}c{{}}}");
    CHECK_EQ(csubstr("123{{{}}a{{}}b{{}}c{{}}}456").pair_range_nested('{', '}'), "{{{}}a{{}}b{{}}c{{}}}");
}

TEST_CASE("substr.unquoted")
{
    CHECK_EQ(csubstr("").unquoted(), "");

    CHECK_EQ(csubstr("''").unquoted(), "");
    CHECK_EQ(csubstr("\"\"").unquoted(), "");

    CHECK_EQ(csubstr("'\''").unquoted(), "'");

    CHECK_EQ(csubstr("aa").unquoted(), "aa");
    CHECK_EQ(csubstr("'aa'").unquoted(), "aa");
    CHECK_EQ(csubstr("\"aa\"").unquoted(), "aa");
    CHECK_EQ(csubstr("'aa\''").unquoted(), "aa'");
}


TEST_CASE("substr.first_non_empty_span")
{
    CHECK_EQ(csubstr("foo bar").first_non_empty_span(), "foo");
    CHECK_EQ(csubstr("       foo bar").first_non_empty_span(), "foo");
    CHECK_EQ(csubstr("\n   \r  \t  foo bar").first_non_empty_span(), "foo");
    CHECK_EQ(csubstr("\n   \r  \t  foo\n\r\t bar").first_non_empty_span(), "foo");
    CHECK_EQ(csubstr("\n   \r  \t  foo\n\r\t bar").first_non_empty_span(), "foo");
    CHECK_EQ(csubstr(",\n   \r  \t  foo\n\r\t bar").first_non_empty_span(), ",");
}

TEST_CASE("substr.first_uint_span")
{
    CHECK_EQ(csubstr("1234").first_uint_span(), "1234");
    CHECK_EQ(csubstr("+1234").first_uint_span(), "+1234");
    CHECK_EQ(csubstr("-1234").first_uint_span(), "");
    CHECK_EQ(csubstr("1234 asdkjh").first_uint_span(), "1234");
    CHECK_EQ(csubstr("1234\rasdkjh").first_uint_span(), "1234");
    CHECK_EQ(csubstr("1234\tasdkjh").first_uint_span(), "1234");
    CHECK_EQ(csubstr("1234\nasdkjh").first_uint_span(), "1234");
    CHECK_EQ(csubstr("1234]asdkjh").first_uint_span(), "1234");
    CHECK_EQ(csubstr("1234)asdkjh").first_uint_span(), "1234");
    CHECK_EQ(csubstr("1234gasdkjh").first_uint_span(), "");
    CHECK_EQ(csubstr("1").first_uint_span(), "1");
    CHECK_EQ(csubstr("+1").first_uint_span(), "+1");
    CHECK_EQ(csubstr("-1").first_uint_span(), "");
    CHECK_EQ(csubstr("-0").first_uint_span(), "");
    CHECK_EQ(csubstr("0").first_uint_span(), "0");
    CHECK_EQ(csubstr("+0").first_uint_span(), "+0");
    CHECK_EQ(csubstr("-0").first_uint_span(), "");
    CHECK_EQ(csubstr("1234 abc").first_uint_span(), "1234");
    CHECK_EQ(csubstr("abc 1234 abc").first_uint_span(), "");
    CHECK_EQ(csubstr("+0x1234 abc").first_uint_span(), "+0x1234");
    CHECK_EQ(csubstr("-0x1234 abc").first_uint_span(), "");
    CHECK_EQ(csubstr("0x1234 abc").first_uint_span(), "0x1234");
    CHECK_EQ(csubstr("0x1234\rabc").first_uint_span(), "0x1234");
    CHECK_EQ(csubstr("0x1234\nabc").first_uint_span(), "0x1234");
    CHECK_EQ(csubstr("0x1234\tabc").first_uint_span(), "0x1234");
    CHECK_EQ(csubstr("0x1234]abc").first_uint_span(), "0x1234");
    CHECK_EQ(csubstr("0x1234)abc").first_uint_span(), "0x1234");
    CHECK_EQ(csubstr("0x1234g").first_uint_span(), "");
    CHECK_EQ(csubstr("0b01").first_uint_span(), "0b01");
    CHECK_EQ(csubstr("+0b01").first_uint_span(), "+0b01");
    CHECK_EQ(csubstr("-0b01").first_uint_span(), "");
    CHECK_EQ(csubstr("0b01 asdasd").first_uint_span(), "0b01");
    CHECK_EQ(csubstr("0b01\rasdasd").first_uint_span(), "0b01");
    CHECK_EQ(csubstr("0b01\tasdasd").first_uint_span(), "0b01");
    CHECK_EQ(csubstr("0b01\nasdasd").first_uint_span(), "0b01");
    CHECK_EQ(csubstr("0b01]asdasd").first_uint_span(), "0b01");
    CHECK_EQ(csubstr("0b01)asdasd").first_uint_span(), "0b01");
    CHECK_EQ(csubstr("0b01hasdasd").first_uint_span(), "");
    CHECK_EQ(csubstr("+").first_uint_span(), "");
    CHECK_EQ(csubstr("-").first_uint_span(), "");
}

TEST_CASE("substr.first_int_span")
{
    CHECK_EQ(csubstr("1234").first_int_span(), "1234");
    CHECK_EQ(csubstr("+1234").first_int_span(), "+1234");
    CHECK_EQ(csubstr("-1234").first_int_span(), "-1234");
    CHECK_EQ(csubstr("-1234 asdkjh").first_int_span(), "-1234");
    CHECK_EQ(csubstr("-1234\rasdkjh").first_int_span(), "-1234");
    CHECK_EQ(csubstr("-1234\tasdkjh").first_int_span(), "-1234");
    CHECK_EQ(csubstr("-1234\nasdkjh").first_int_span(), "-1234");
    CHECK_EQ(csubstr("-1234]asdkjh").first_int_span(), "-1234");
    CHECK_EQ(csubstr("-1234)asdkjh").first_int_span(), "-1234");
    CHECK_EQ(csubstr("-1234gasdkjh").first_int_span(), "");
    CHECK_EQ(csubstr("1").first_int_span(), "1");
    CHECK_EQ(csubstr("+1").first_int_span(), "+1");
    CHECK_EQ(csubstr("-1").first_int_span(), "-1");
    CHECK_EQ(csubstr("-0").first_int_span(), "-0");
    CHECK_EQ(csubstr("0").first_int_span(), "0");
    CHECK_EQ(csubstr("+0").first_int_span(), "+0");
    CHECK_EQ(csubstr("-0").first_int_span(), "-0");
    CHECK_EQ(csubstr("1234 abc").first_int_span(), "1234");
    CHECK_EQ(csubstr("abc 1234 abc").first_int_span(), "");
    CHECK_EQ(csubstr("+0x1234 abc").first_int_span(), "+0x1234");
    CHECK_EQ(csubstr("-0x1234 abc").first_int_span(), "-0x1234");
    CHECK_EQ(csubstr("0x1234 abc").first_int_span(), "0x1234");
    CHECK_EQ(csubstr("0x1234\rabc").first_int_span(), "0x1234");
    CHECK_EQ(csubstr("0x1234\nabc").first_int_span(), "0x1234");
    CHECK_EQ(csubstr("0x1234\tabc").first_int_span(), "0x1234");
    CHECK_EQ(csubstr("0x1234]abc").first_int_span(), "0x1234");
    CHECK_EQ(csubstr("0x1234)abc").first_int_span(), "0x1234");
    CHECK_EQ(csubstr("0x1234gabc").first_int_span(), "");
    CHECK_EQ(csubstr("0b01").first_int_span(), "0b01");
    CHECK_EQ(csubstr("+0b01").first_int_span(), "+0b01");
    CHECK_EQ(csubstr("-0b01").first_int_span(), "-0b01");
    CHECK_EQ(csubstr("0b01 asdasd").first_int_span(), "0b01");
    CHECK_EQ(csubstr("0b01\rasdasd").first_int_span(), "0b01");
    CHECK_EQ(csubstr("0b01\tasdasd").first_int_span(), "0b01");
    CHECK_EQ(csubstr("0b01\nasdasd").first_int_span(), "0b01");
    CHECK_EQ(csubstr("0b01]asdasd").first_int_span(), "0b01");
    CHECK_EQ(csubstr("0b01)asdasd").first_int_span(), "0b01");
    CHECK_EQ(csubstr("0b01gasdasd").first_int_span(), "");
}

TEST_CASE("substr.first_real_span")
{
    // all integers are reals
    CHECK_EQ(csubstr("1234").first_real_span(), "1234");
    CHECK_EQ(csubstr("+1234").first_real_span(), "+1234");
    CHECK_EQ(csubstr("-1234").first_real_span(), "-1234");
    CHECK_EQ(csubstr("-1234 asdkjh").first_real_span(), "-1234");
    CHECK_EQ(csubstr("-1234\rasdkjh").first_real_span(), "-1234");
    CHECK_EQ(csubstr("-1234\tasdkjh").first_real_span(), "-1234");
    CHECK_EQ(csubstr("-1234\nasdkjh").first_real_span(), "-1234");
    CHECK_EQ(csubstr("-1234]asdkjh").first_real_span(), "-1234");
    CHECK_EQ(csubstr("-1234)asdkjh").first_real_span(), "-1234");
    CHECK_EQ(csubstr("-1234gasdkjh").first_real_span(), "");
    CHECK_EQ(csubstr("1").first_real_span(), "1");
    CHECK_EQ(csubstr("+1").first_real_span(), "+1");
    CHECK_EQ(csubstr("-1").first_real_span(), "-1");
    CHECK_EQ(csubstr("-0").first_real_span(), "-0");
    CHECK_EQ(csubstr("0").first_real_span(), "0");
    CHECK_EQ(csubstr("+0").first_real_span(), "+0");
    CHECK_EQ(csubstr("-0").first_real_span(), "-0");
    CHECK_EQ(csubstr("1.").first_real_span(), "1.");
    CHECK_EQ(csubstr("+1.").first_real_span(), "+1.");
    CHECK_EQ(csubstr("-1.").first_real_span(), "-1.");
    CHECK_EQ(csubstr("-0.").first_real_span(), "-0.");
    CHECK_EQ(csubstr("0.").first_real_span(), "0.");
    CHECK_EQ(csubstr("+0.").first_real_span(), "+0.");
    CHECK_EQ(csubstr("-0.").first_real_span(), "-0.");
    CHECK_EQ(csubstr("1.2").first_real_span(), "1.2");
    CHECK_EQ(csubstr("+1.2").first_real_span(), "+1.2");
    CHECK_EQ(csubstr("-1.2").first_real_span(), "-1.2");
    CHECK_EQ(csubstr("-0.2").first_real_span(), "-0.2");
    CHECK_EQ(csubstr("0.2").first_real_span(), "0.2");
    CHECK_EQ(csubstr("+0.2").first_real_span(), "+0.2");
    CHECK_EQ(csubstr("-0.2").first_real_span(), "-0.2");
    CHECK_EQ(csubstr("1234 abc").first_real_span(), "1234");
    CHECK_EQ(csubstr("abc 1234 abc").first_real_span(), "");
    CHECK_EQ(csubstr("+0x1234 abc").first_real_span(), "+0x1234");
    CHECK_EQ(csubstr("-0x1234 abc").first_real_span(), "-0x1234");
    CHECK_EQ(csubstr("0x1234 abc").first_real_span(), "0x1234");
    CHECK_EQ(csubstr("0x1234\rabc").first_real_span(), "0x1234");
    CHECK_EQ(csubstr("0x1234\nabc").first_real_span(), "0x1234");
    CHECK_EQ(csubstr("0x1234\tabc").first_real_span(), "0x1234");
    CHECK_EQ(csubstr("0x1234]abc").first_real_span(), "0x1234");
    CHECK_EQ(csubstr("0x1234)abc").first_real_span(), "0x1234");
    CHECK_EQ(csubstr("0x1234gabc").first_real_span(), "");
    CHECK_EQ(csubstr("0b01").first_real_span(), "0b01");
    CHECK_EQ(csubstr("+0b01").first_real_span(), "+0b01");
    CHECK_EQ(csubstr("-0b01").first_real_span(), "-0b01");
    CHECK_EQ(csubstr("0b01 asdasd").first_real_span(), "0b01");
    CHECK_EQ(csubstr("0b01\rasdasd").first_real_span(), "0b01");
    CHECK_EQ(csubstr("0b01\tasdasd").first_real_span(), "0b01");
    CHECK_EQ(csubstr("0b01\nasdasd").first_real_span(), "0b01");
    CHECK_EQ(csubstr("0b01]asdasd").first_real_span(), "0b01");
    CHECK_EQ(csubstr("0b01)asdasd").first_real_span(), "0b01");
    CHECK_EQ(csubstr("0b01gasdasd").first_real_span(), "");
    CHECK_EQ(csubstr("0b1.01 asdasd").first_real_span(), "0b1.01");
    CHECK_EQ(csubstr("0b1.01\rasdasd").first_real_span(), "0b1.01");
    CHECK_EQ(csubstr("0b1.01\tasdasd").first_real_span(), "0b1.01");
    CHECK_EQ(csubstr("0b1.01\nasdasd").first_real_span(), "0b1.01");
    CHECK_EQ(csubstr("0b1.01]asdasd").first_real_span(), "0b1.01");
    CHECK_EQ(csubstr("0b1.01)asdasd").first_real_span(), "0b1.01");
    CHECK_EQ(csubstr("0b1.01gasdasd").first_real_span(), "");
    CHECK_EQ(csubstr("0b1.02 asdasd").first_real_span(), "");
    CHECK_EQ(csubstr("0b1.02\rasdasd").first_real_span(), "");
    CHECK_EQ(csubstr("0b1.02\tasdasd").first_real_span(), "");
    CHECK_EQ(csubstr("0b1.02\nasdasd").first_real_span(), "");
    CHECK_EQ(csubstr("0b1.02]asdasd").first_real_span(), "");
    CHECK_EQ(csubstr("0b1.02)asdasd").first_real_span(), "");
    CHECK_EQ(csubstr("0b1.02gasdasd").first_real_span(), "");
    CHECK_EQ(csubstr("+").first_real_span(), "");
    CHECK_EQ(csubstr("-").first_real_span(), "");
    CHECK_EQ(csubstr("+0x").first_real_span(), "");
    CHECK_EQ(csubstr("-0x").first_real_span(), "");
    CHECK_EQ(csubstr("+0b").first_real_span(), "");
    CHECK_EQ(csubstr("-0b").first_real_span(), "");
    CHECK_EQ(csubstr("+0o").first_real_span(), "");
    CHECK_EQ(csubstr("-0o").first_real_span(), "");
    CHECK_EQ(csubstr("-1.234 asdkjh").first_real_span(), "-1.234");
    CHECK_EQ(csubstr("-1.234e5 asdkjh").first_real_span(), "-1.234e5");
    CHECK_EQ(csubstr("-1.234E5 asdkjh").first_real_span(), "-1.234E5");
    CHECK_EQ(csubstr("-1.234e+5 asdkjh").first_real_span(), "-1.234e+5");
    CHECK_EQ(csubstr("-1.234E+5 asdkjh").first_real_span(), "-1.234E+5");
    CHECK_EQ(csubstr("-1.234e-5 asdkjh").first_real_span(), "-1.234e-5");
    CHECK_EQ(csubstr("-1.234E-5 asdkjh").first_real_span(), "-1.234E-5");
    CHECK_EQ(csubstr("9.5e7 asdkjh").first_real_span(), "9.5e7");
    CHECK_EQ(csubstr("9.5E7 asdkjh").first_real_span(), "9.5E7");
    CHECK_EQ(csubstr("9.5e+7 asdkjh").first_real_span(), "9.5e+7");
    CHECK_EQ(csubstr("9.5E+7 asdkjh").first_real_span(), "9.5E+7");
    CHECK_EQ(csubstr("9.5e-7 asdkjh").first_real_span(), "9.5e-7");
    CHECK_EQ(csubstr("9.5E-7 asdkjh").first_real_span(), "9.5E-7");
    CHECK_EQ(csubstr("-9.5e7 asdkjh").first_real_span(), "-9.5e7");
    CHECK_EQ(csubstr("-9.5E7 asdkjh").first_real_span(), "-9.5E7");
    CHECK_EQ(csubstr("-9.5e+7 asdkjh").first_real_span(), "-9.5e+7");
    CHECK_EQ(csubstr("-9.5E+7 asdkjh").first_real_span(), "-9.5E+7");
    CHECK_EQ(csubstr("-9.5e-7 asdkjh").first_real_span(), "-9.5e-7");
    CHECK_EQ(csubstr("-9.5E-7 asdkjh").first_real_span(), "-9.5E-7");
    CHECK_EQ(csubstr("+9.5e7 asdkjh").first_real_span(), "+9.5e7");
    CHECK_EQ(csubstr("+9.5E7 asdkjh").first_real_span(), "+9.5E7");
    CHECK_EQ(csubstr("+9.5e+7 asdkjh").first_real_span(), "+9.5e+7");
    CHECK_EQ(csubstr("+9.5E+7 asdkjh").first_real_span(), "+9.5E+7");
    CHECK_EQ(csubstr("+9.5e-7 asdkjh").first_real_span(), "+9.5e-7");
    CHECK_EQ(csubstr("+9.5E-7 asdkjh").first_real_span(), "+9.5E-7");
    CHECK_EQ(csubstr("9.5e17 asdkjh").first_real_span(), "9.5e17");
    CHECK_EQ(csubstr("9.5E17 asdkjh").first_real_span(), "9.5E17");
    CHECK_EQ(csubstr("9.5e+17 asdkjh").first_real_span(), "9.5e+17");
    CHECK_EQ(csubstr("9.5E+17 asdkjh").first_real_span(), "9.5E+17");
    CHECK_EQ(csubstr("9.5e-17 asdkjh").first_real_span(), "9.5e-17");
    CHECK_EQ(csubstr("9.5E-17 asdkjh").first_real_span(), "9.5E-17");
    CHECK_EQ(csubstr("-9.5e17 asdkjh").first_real_span(), "-9.5e17");
    CHECK_EQ(csubstr("-9.5E17 asdkjh").first_real_span(), "-9.5E17");
    CHECK_EQ(csubstr("-9.5e+17 asdkjh").first_real_span(), "-9.5e+17");
    CHECK_EQ(csubstr("-9.5E+17 asdkjh").first_real_span(), "-9.5E+17");
    CHECK_EQ(csubstr("-9.5e-17 asdkjh").first_real_span(), "-9.5e-17");
    CHECK_EQ(csubstr("-9.5E-17 asdkjh").first_real_span(), "-9.5E-17");
    CHECK_EQ(csubstr("+9.5e17 asdkjh").first_real_span(), "+9.5e17");
    CHECK_EQ(csubstr("+9.5E17 asdkjh").first_real_span(), "+9.5E17");
    CHECK_EQ(csubstr("+9.5e+17 asdkjh").first_real_span(), "+9.5e+17");
    CHECK_EQ(csubstr("+9.5E+17 asdkjh").first_real_span(), "+9.5E+17");
    CHECK_EQ(csubstr("+9.5e-17 asdkjh").first_real_span(), "+9.5e-17");
    CHECK_EQ(csubstr("+9.5E-17 asdkjh").first_real_span(), "+9.5E-17");
    CHECK_EQ(csubstr("129.532e7 asdkjh").first_real_span(), "129.532e7");
    CHECK_EQ(csubstr("129.532E7 asdkjh").first_real_span(), "129.532E7");
    CHECK_EQ(csubstr("129.532e+7 asdkjh").first_real_span(), "129.532e+7");
    CHECK_EQ(csubstr("129.532E+7 asdkjh").first_real_span(), "129.532E+7");
    CHECK_EQ(csubstr("129.532e-7 asdkjh").first_real_span(), "129.532e-7");
    CHECK_EQ(csubstr("129.532E-7 asdkjh").first_real_span(), "129.532E-7");
    CHECK_EQ(csubstr("-129.532e7 asdkjh").first_real_span(), "-129.532e7");
    CHECK_EQ(csubstr("-129.532E7 asdkjh").first_real_span(), "-129.532E7");
    CHECK_EQ(csubstr("-129.532e+7 asdkjh").first_real_span(), "-129.532e+7");
    CHECK_EQ(csubstr("-129.532E+7 asdkjh").first_real_span(), "-129.532E+7");
    CHECK_EQ(csubstr("-129.532e-7 asdkjh").first_real_span(), "-129.532e-7");
    CHECK_EQ(csubstr("-129.532E-7 asdkjh").first_real_span(), "-129.532E-7");
    CHECK_EQ(csubstr("+129.532e7 asdkjh").first_real_span(), "+129.532e7");
    CHECK_EQ(csubstr("+129.532E7 asdkjh").first_real_span(), "+129.532E7");
    CHECK_EQ(csubstr("+129.532e+7 asdkjh").first_real_span(), "+129.532e+7");
    CHECK_EQ(csubstr("+129.532E+7 asdkjh").first_real_span(), "+129.532E+7");
    CHECK_EQ(csubstr("+129.532e-7 asdkjh").first_real_span(), "+129.532e-7");
    CHECK_EQ(csubstr("+129.532E-7 asdkjh").first_real_span(), "+129.532E-7");
    CHECK_EQ(csubstr("129.532e17 asdkjh").first_real_span(), "129.532e17");
    CHECK_EQ(csubstr("129.532E17 asdkjh").first_real_span(), "129.532E17");
    CHECK_EQ(csubstr("129.532e+17 asdkjh").first_real_span(), "129.532e+17");
    CHECK_EQ(csubstr("129.532E+17 asdkjh").first_real_span(), "129.532E+17");
    CHECK_EQ(csubstr("129.532e-17 asdkjh").first_real_span(), "129.532e-17");
    CHECK_EQ(csubstr("129.532E-17 asdkjh").first_real_span(), "129.532E-17");
    CHECK_EQ(csubstr("-129.532e17 asdkjh").first_real_span(), "-129.532e17");
    CHECK_EQ(csubstr("-129.532E17 asdkjh").first_real_span(), "-129.532E17");
    CHECK_EQ(csubstr("-129.532e+17 asdkjh").first_real_span(), "-129.532e+17");
    CHECK_EQ(csubstr("-129.532E+17 asdkjh").first_real_span(), "-129.532E+17");
    CHECK_EQ(csubstr("-129.532e-17 asdkjh").first_real_span(), "-129.532e-17");
    CHECK_EQ(csubstr("-129.532E-17 asdkjh").first_real_span(), "-129.532E-17");
    CHECK_EQ(csubstr("+129.532e17 asdkjh").first_real_span(), "+129.532e17");
    CHECK_EQ(csubstr("+129.532E17 asdkjh").first_real_span(), "+129.532E17");
    CHECK_EQ(csubstr("+129.532e+17 asdkjh").first_real_span(), "+129.532e+17");
    CHECK_EQ(csubstr("+129.532E+17 asdkjh").first_real_span(), "+129.532E+17");
    CHECK_EQ(csubstr("+129.532e-17 asdkjh").first_real_span(), "+129.532e-17");
    CHECK_EQ(csubstr("+129.532E-17 asdkjh").first_real_span(), "+129.532E-17");
    CHECK_EQ(csubstr("129.532e asdkjh").first_real_span(), "");
    CHECK_EQ(csubstr("129.532E asdkjh").first_real_span(), "");
    CHECK_EQ(csubstr("129.532e+ asdkjh").first_real_span(), "");
    CHECK_EQ(csubstr("129.532E+ asdkjh").first_real_span(), "");
    CHECK_EQ(csubstr("129.532e- asdkjh").first_real_span(), "");
    CHECK_EQ(csubstr("129.532E- asdkjh").first_real_span(), "");
    CHECK_EQ(csubstr("-129.532e asdkjh").first_real_span(), "");
    CHECK_EQ(csubstr("-129.532E asdkjh").first_real_span(), "");
    CHECK_EQ(csubstr("-129.532e+ asdkjh").first_real_span(), "");
    CHECK_EQ(csubstr("-129.532E+ asdkjh").first_real_span(), "");
    CHECK_EQ(csubstr("-129.532e- asdkjh").first_real_span(), "");
    CHECK_EQ(csubstr("-129.532E- asdkjh").first_real_span(), "");
    CHECK_EQ(csubstr("+129.532e asdkjh").first_real_span(), "");
    CHECK_EQ(csubstr("+129.532E asdkjh").first_real_span(), "");
    CHECK_EQ(csubstr("+129.532e+ asdkjh").first_real_span(), "");
    CHECK_EQ(csubstr("+129.532E+ asdkjh").first_real_span(), "");
    CHECK_EQ(csubstr("+129.532e- asdkjh").first_real_span(), "");
    CHECK_EQ(csubstr("+129.532E- asdkjh").first_real_span(), "");
    CHECK_EQ(csubstr("0x1.e8480p+19 asdkjh").first_real_span(), "0x1.e8480p+19");
    CHECK_EQ(csubstr("0x1.e8480p-19 asdkjh").first_real_span(), "0x1.e8480p-19");
    CHECK_EQ(csubstr("-0x1.e8480p+19 asdkjh").first_real_span(), "-0x1.e8480p+19");
    CHECK_EQ(csubstr("-0x1.e8480p-19 asdkjh").first_real_span(), "-0x1.e8480p-19");
    CHECK_EQ(csubstr("+0x1.e8480p+19 asdkjh").first_real_span(), "+0x1.e8480p+19");
    CHECK_EQ(csubstr("+0x1.e8480p-19 asdkjh").first_real_span(), "+0x1.e8480p-19");
    CHECK_EQ(csubstr("0x1.e8480p+1 asdkjh").first_real_span(), "0x1.e8480p+1");
    CHECK_EQ(csubstr("0x1.e8480p-1 asdkjh").first_real_span(), "0x1.e8480p-1");
    CHECK_EQ(csubstr("-0x1.e8480p+1 asdkjh").first_real_span(), "-0x1.e8480p+1");
    CHECK_EQ(csubstr("-0x1.e8480p-1 asdkjh").first_real_span(), "-0x1.e8480p-1");
    CHECK_EQ(csubstr("+0x1.e8480p+1 asdkjh").first_real_span(), "+0x1.e8480p+1");
    CHECK_EQ(csubstr("+0x1.e8480p-1 asdkjh").first_real_span(), "+0x1.e8480p-1");
    CHECK_EQ(csubstr("0x1.e8480p+ asdkjh").first_real_span(), "");
    CHECK_EQ(csubstr("0x1.e8480p- asdkjh").first_real_span(), "");
    CHECK_EQ(csubstr("-0x1.e8480p+ asdkjh").first_real_span(), "");
    CHECK_EQ(csubstr("-0x1.e8480p- asdkjh").first_real_span(), "");
    CHECK_EQ(csubstr("+0x1.e8480p+ asdkjh").first_real_span(), "");
    CHECK_EQ(csubstr("+0x1.e8480p- asdkjh").first_real_span(), "");
    CHECK_EQ(csubstr("infinity").first_real_span(), "infinity");
    CHECK_EQ(csubstr(" infinity").first_real_span(), "infinity");
    CHECK_EQ(csubstr("-infinity").first_real_span(), "-infinity");
    CHECK_EQ(csubstr(" -infinity").first_real_span(), "-infinity");
    CHECK_EQ(csubstr("+infinity").first_real_span(), "+infinity");
    CHECK_EQ(csubstr(" +infinity").first_real_span(), "+infinity");
    CHECK_EQ(csubstr("infinity ").first_real_span(), "infinity");
    CHECK_EQ(csubstr(" infinity ").first_real_span(), "infinity");
    CHECK_EQ(csubstr("-infinity ").first_real_span(), "-infinity");
    CHECK_EQ(csubstr(" -infinity ").first_real_span(), "-infinity");
    CHECK_EQ(csubstr("+infinity ").first_real_span(), "+infinity");
    CHECK_EQ(csubstr(" +infinity ").first_real_span(), "+infinity");
    CHECK_EQ(csubstr("infinity1").first_real_span(), "");
    CHECK_EQ(csubstr(" infinity1").first_real_span(), "");
    CHECK_EQ(csubstr("-infinity1").first_real_span(), "");
    CHECK_EQ(csubstr(" -infinity1").first_real_span(), "");
    CHECK_EQ(csubstr("+infinity1").first_real_span(), "");
    CHECK_EQ(csubstr(" +infinity1").first_real_span(), "");
    CHECK_EQ(csubstr("infin").first_real_span(), "");
    CHECK_EQ(csubstr(" infin").first_real_span(), "");
    CHECK_EQ(csubstr("-infin").first_real_span(), "");
    CHECK_EQ(csubstr(" -infin").first_real_span(), "");
    CHECK_EQ(csubstr("+infin").first_real_span(), "");
    CHECK_EQ(csubstr(" +infin").first_real_span(), "");
    CHECK_EQ(csubstr("inflated").first_real_span(), "");
    CHECK_EQ(csubstr(" inflated").first_real_span(), "");
    CHECK_EQ(csubstr("-inflated").first_real_span(), "");
    CHECK_EQ(csubstr(" -inflated").first_real_span(), "");
    CHECK_EQ(csubstr("+inflated").first_real_span(), "");
    CHECK_EQ(csubstr(" +inflated").first_real_span(), "");
    CHECK_EQ(csubstr("inf").first_real_span(), "inf");
    CHECK_EQ(csubstr(" inf").first_real_span(), "inf");
    CHECK_EQ(csubstr("-inf").first_real_span(), "-inf");
    CHECK_EQ(csubstr(" -inf").first_real_span(), "-inf");
    CHECK_EQ(csubstr("+inf").first_real_span(), "+inf");
    CHECK_EQ(csubstr(" +inf").first_real_span(), "+inf");
    CHECK_EQ(csubstr("inf ").first_real_span(), "inf");
    CHECK_EQ(csubstr(" inf ").first_real_span(), "inf");
    CHECK_EQ(csubstr("-inf ").first_real_span(), "-inf");
    CHECK_EQ(csubstr(" -inf ").first_real_span(), "-inf");
    CHECK_EQ(csubstr("+inf ").first_real_span(), "+inf");
    CHECK_EQ(csubstr(" +inf ").first_real_span(), "+inf");
    CHECK_EQ(csubstr("inf1").first_real_span(), "");
    CHECK_EQ(csubstr(" inf1").first_real_span(), "");
    CHECK_EQ(csubstr("-inf1").first_real_span(), "");
    CHECK_EQ(csubstr(" -inf1").first_real_span(), "");
    CHECK_EQ(csubstr("+inf1").first_real_span(), "");
    CHECK_EQ(csubstr(" +inf1").first_real_span(), "");
    CHECK_EQ(csubstr("nan").first_real_span(), "nan");
    CHECK_EQ(csubstr(" nan").first_real_span(), "nan");
    CHECK_EQ(csubstr("-nan").first_real_span(), "-nan");
    CHECK_EQ(csubstr(" -nan").first_real_span(), "-nan");
    CHECK_EQ(csubstr("+nan").first_real_span(), "+nan");
    CHECK_EQ(csubstr(" +nan").first_real_span(), "+nan");
    CHECK_EQ(csubstr("nan ").first_real_span(), "nan");
    CHECK_EQ(csubstr(" nan ").first_real_span(), "nan");
    CHECK_EQ(csubstr("-nan ").first_real_span(), "-nan");
    CHECK_EQ(csubstr(" -nan ").first_real_span(), "-nan");
    CHECK_EQ(csubstr("+nan ").first_real_span(), "+nan");
    CHECK_EQ(csubstr(" +nan ").first_real_span(), "+nan");
    CHECK_EQ(csubstr("nan1").first_real_span(), "");
    CHECK_EQ(csubstr(" nan1").first_real_span(), "");
    CHECK_EQ(csubstr("-nan1").first_real_span(), "");
    CHECK_EQ(csubstr(" -nan1").first_real_span(), "");
    CHECK_EQ(csubstr("+nan1").first_real_span(), "");
    CHECK_EQ(csubstr(" +nan1").first_real_span(), "");
}

// start with some obvious direct tests
TEST_CASE("substr.is_unsigned_integer")
{
    SUBCASE("empty_string")
    {
        CHECK_FALSE(csubstr().is_unsigned_integer());
        CHECK_FALSE(csubstr("").is_unsigned_integer());
    }
    SUBCASE("signs")
    {
        CHECK_FALSE(csubstr("-").is_unsigned_integer());
        CHECK_FALSE(csubstr("+").is_unsigned_integer());
        CHECK_FALSE(csubstr("-1").is_unsigned_integer());
        CHECK_UNARY(csubstr("+1").is_unsigned_integer());
    }
    SUBCASE("whitespace_before")
    {
        CHECK_FALSE(csubstr(" 0").is_unsigned_integer());
        CHECK_FALSE(csubstr(" 1").is_unsigned_integer());
        CHECK_FALSE(csubstr(" -1").is_unsigned_integer());
        CHECK_FALSE(csubstr(" 0.1").is_unsigned_integer());
        CHECK_FALSE(csubstr(" -0.1").is_unsigned_integer());
    }
    SUBCASE("whitespace_after")
    {
        CHECK_FALSE(csubstr("0 ").is_unsigned_integer());
        CHECK_FALSE(csubstr("1 ").is_unsigned_integer());
        CHECK_FALSE(csubstr("-1 ").is_unsigned_integer());
        CHECK_FALSE(csubstr("0.1 ").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0.1 ").is_unsigned_integer());
    }
    SUBCASE("whitespace_only")
    {
        CHECK_FALSE(csubstr("    ").is_unsigned_integer());
        CHECK_FALSE(csubstr("\t\t\t\t").is_unsigned_integer());
        CHECK_FALSE(csubstr("\n\n\n\n").is_unsigned_integer());
        CHECK_FALSE(csubstr("\r\r\r\r").is_unsigned_integer());
    }
    SUBCASE("decimal")
    {
        CHECK_UNARY(csubstr("0").is_unsigned_integer());
        CHECK_UNARY(csubstr("1").is_unsigned_integer());
        CHECK_FALSE(csubstr("-1").is_unsigned_integer());
        CHECK_FALSE(csubstr("0.1").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0.1").is_unsigned_integer());
    }
    SUBCASE("hexadecimal")
    {
        CHECK_FALSE(csubstr("0x").is_unsigned_integer());
        CHECK_FALSE(csubstr("0X").is_unsigned_integer());
        CHECK_UNARY(csubstr("0x1").is_unsigned_integer());
        CHECK_UNARY(csubstr("0X1").is_unsigned_integer());
        CHECK_UNARY(csubstr("0x0").is_unsigned_integer());
        CHECK_UNARY(csubstr("0X0").is_unsigned_integer());
        CHECK_UNARY(csubstr("0xa").is_unsigned_integer());
        CHECK_UNARY(csubstr("0Xa").is_unsigned_integer());
        CHECK_UNARY(csubstr("0xb").is_unsigned_integer());
        CHECK_UNARY(csubstr("0Xb").is_unsigned_integer());
        CHECK_UNARY(csubstr("0xc").is_unsigned_integer());
        CHECK_UNARY(csubstr("0Xc").is_unsigned_integer());
        CHECK_UNARY(csubstr("0xd").is_unsigned_integer());
        CHECK_UNARY(csubstr("0Xd").is_unsigned_integer());
        CHECK_UNARY(csubstr("0xe").is_unsigned_integer());
        CHECK_UNARY(csubstr("0Xe").is_unsigned_integer());
        CHECK_UNARY(csubstr("0xf").is_unsigned_integer());
        CHECK_UNARY(csubstr("0Xf").is_unsigned_integer());
        CHECK_UNARY(csubstr("0xA").is_unsigned_integer());
        CHECK_UNARY(csubstr("0XA").is_unsigned_integer());
        CHECK_UNARY(csubstr("0xB").is_unsigned_integer());
        CHECK_UNARY(csubstr("0XB").is_unsigned_integer());
        CHECK_UNARY(csubstr("0xC").is_unsigned_integer());
        CHECK_UNARY(csubstr("0XC").is_unsigned_integer());
        CHECK_UNARY(csubstr("0xD").is_unsigned_integer());
        CHECK_UNARY(csubstr("0XD").is_unsigned_integer());
        CHECK_UNARY(csubstr("0xE").is_unsigned_integer());
        CHECK_UNARY(csubstr("0XE").is_unsigned_integer());
        CHECK_UNARY(csubstr("0xF").is_unsigned_integer());
        CHECK_UNARY(csubstr("0XF").is_unsigned_integer());
        CHECK_FALSE(csubstr("0xg").is_unsigned_integer());
        CHECK_FALSE(csubstr("0Xg").is_unsigned_integer());
        CHECK_FALSE(csubstr("0xG").is_unsigned_integer());
        CHECK_FALSE(csubstr("0XG").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0x1").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0X1").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0x0").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0X0").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0xa").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0Xa").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0xb").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0Xb").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0xc").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0Xc").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0xd").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0Xd").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0xe").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0Xe").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0xf").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0Xf").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0xA").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0XA").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0xB").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0XB").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0xC").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0XC").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0xD").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0XD").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0xE").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0XE").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0xF").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0XF").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0xg").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0Xg").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0xG").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0XG").is_unsigned_integer());
    }
    SUBCASE("binary")
    {
        CHECK_FALSE(csubstr("0b").is_unsigned_integer());
        CHECK_FALSE(csubstr("0B").is_unsigned_integer());
        CHECK_UNARY(csubstr("0b0").is_unsigned_integer());
        CHECK_UNARY(csubstr("0B0").is_unsigned_integer());
        CHECK_UNARY(csubstr("0b1").is_unsigned_integer());
        CHECK_UNARY(csubstr("0B1").is_unsigned_integer());
        CHECK_FALSE(csubstr("0b2").is_unsigned_integer());
        CHECK_FALSE(csubstr("0B2").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0b0").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0B0").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0b1").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0B1").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0b2").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0B2").is_unsigned_integer());
    }
    SUBCASE("octal")
    {
        CHECK_FALSE(csubstr("0o").is_unsigned_integer());
        CHECK_FALSE(csubstr("0O").is_unsigned_integer());
        CHECK_UNARY(csubstr("0o0").is_unsigned_integer());
        CHECK_UNARY(csubstr("0O0").is_unsigned_integer());
        CHECK_UNARY(csubstr("0o1").is_unsigned_integer());
        CHECK_UNARY(csubstr("0O1").is_unsigned_integer());
        CHECK_UNARY(csubstr("0o6").is_unsigned_integer());
        CHECK_UNARY(csubstr("0O6").is_unsigned_integer());
        CHECK_UNARY(csubstr("0o6").is_unsigned_integer());
        CHECK_UNARY(csubstr("0O6").is_unsigned_integer());
        CHECK_UNARY(csubstr("0o7").is_unsigned_integer());
        CHECK_UNARY(csubstr("0O7").is_unsigned_integer());
        CHECK_FALSE(csubstr("0o8").is_unsigned_integer());
        CHECK_FALSE(csubstr("0O8").is_unsigned_integer());
        CHECK_FALSE(csubstr("0o9").is_unsigned_integer());
        CHECK_FALSE(csubstr("0O9").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0o0").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0O0").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0o1").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0O1").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0o6").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0O6").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0o6").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0O6").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0o7").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0O7").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0o8").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0O8").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0o9").is_unsigned_integer());
        CHECK_FALSE(csubstr("-0O9").is_unsigned_integer());
    }
}

TEST_CASE("substr.is_integer")
{
    SUBCASE("empty_string")
    {
        CHECK_FALSE(csubstr().is_integer());
        CHECK_FALSE(csubstr("").is_integer());
    }
    SUBCASE("signs")
    {
        CHECK_FALSE(csubstr("-").is_integer());
        CHECK_FALSE(csubstr("+").is_integer());
        CHECK_UNARY(csubstr("-1").is_integer());
        CHECK_UNARY(csubstr("+1").is_integer());
    }
    SUBCASE("whitespace_before")
    {
        CHECK_FALSE(csubstr(" 0").is_integer());
        CHECK_FALSE(csubstr(" 1").is_integer());
        CHECK_FALSE(csubstr(" -1").is_integer());
        CHECK_FALSE(csubstr(" 0.1").is_integer());
        CHECK_FALSE(csubstr(" -0.1").is_integer());
    }
    SUBCASE("whitespace_after")
    {
        CHECK_FALSE(csubstr("0 ").is_integer());
        CHECK_FALSE(csubstr("1 ").is_integer());
        CHECK_FALSE(csubstr("-1 ").is_integer());
        CHECK_FALSE(csubstr("0.1 ").is_integer());
        CHECK_FALSE(csubstr("-0.1 ").is_integer());
    }
    SUBCASE("whitespace_only")
    {
        CHECK_FALSE(csubstr("    ").is_integer());
        CHECK_FALSE(csubstr("\t\t\t\t").is_integer());
        CHECK_FALSE(csubstr("\n\n\n\n").is_integer());
        CHECK_FALSE(csubstr("\r\r\r\r").is_integer());
    }
    SUBCASE("decimal")
    {
        CHECK_UNARY(csubstr("0").is_integer());
        CHECK_UNARY(csubstr("1").is_integer());
        CHECK_UNARY(csubstr("-1").is_integer());
        CHECK_FALSE(csubstr("0.1").is_integer());
        CHECK_FALSE(csubstr("-0.1").is_integer());
    }
    SUBCASE("hexadecimal")
    {
        CHECK_FALSE(csubstr("0x").is_integer());
        CHECK_FALSE(csubstr("0X").is_integer());
        CHECK_UNARY(csubstr("0x1").is_integer());
        CHECK_UNARY(csubstr("0X1").is_integer());
        CHECK_UNARY(csubstr("0x0").is_integer());
        CHECK_UNARY(csubstr("0X0").is_integer());
        CHECK_UNARY(csubstr("0xa").is_integer());
        CHECK_UNARY(csubstr("0Xa").is_integer());
        CHECK_UNARY(csubstr("0xb").is_integer());
        CHECK_UNARY(csubstr("0Xb").is_integer());
        CHECK_UNARY(csubstr("0xc").is_integer());
        CHECK_UNARY(csubstr("0Xc").is_integer());
        CHECK_UNARY(csubstr("0xd").is_integer());
        CHECK_UNARY(csubstr("0Xd").is_integer());
        CHECK_UNARY(csubstr("0xe").is_integer());
        CHECK_UNARY(csubstr("0Xe").is_integer());
        CHECK_UNARY(csubstr("0xf").is_integer());
        CHECK_UNARY(csubstr("0Xf").is_integer());
        CHECK_UNARY(csubstr("0xA").is_integer());
        CHECK_UNARY(csubstr("0XA").is_integer());
        CHECK_UNARY(csubstr("0xB").is_integer());
        CHECK_UNARY(csubstr("0XB").is_integer());
        CHECK_UNARY(csubstr("0xC").is_integer());
        CHECK_UNARY(csubstr("0XC").is_integer());
        CHECK_UNARY(csubstr("0xD").is_integer());
        CHECK_UNARY(csubstr("0XD").is_integer());
        CHECK_UNARY(csubstr("0xE").is_integer());
        CHECK_UNARY(csubstr("0XE").is_integer());
        CHECK_UNARY(csubstr("0xF").is_integer());
        CHECK_UNARY(csubstr("0XF").is_integer());
        CHECK_FALSE(csubstr("0xg").is_integer());
        CHECK_FALSE(csubstr("0Xg").is_integer());
        CHECK_FALSE(csubstr("0xG").is_integer());
        CHECK_FALSE(csubstr("0XG").is_integer());
        CHECK_UNARY(csubstr("-0x1").is_integer());
        CHECK_UNARY(csubstr("-0X1").is_integer());
        CHECK_UNARY(csubstr("-0x0").is_integer());
        CHECK_UNARY(csubstr("-0X0").is_integer());
        CHECK_UNARY(csubstr("-0xa").is_integer());
        CHECK_UNARY(csubstr("-0Xa").is_integer());
        CHECK_UNARY(csubstr("-0xb").is_integer());
        CHECK_UNARY(csubstr("-0Xb").is_integer());
        CHECK_UNARY(csubstr("-0xc").is_integer());
        CHECK_UNARY(csubstr("-0Xc").is_integer());
        CHECK_UNARY(csubstr("-0xd").is_integer());
        CHECK_UNARY(csubstr("-0Xd").is_integer());
        CHECK_UNARY(csubstr("-0xe").is_integer());
        CHECK_UNARY(csubstr("-0Xe").is_integer());
        CHECK_UNARY(csubstr("-0xf").is_integer());
        CHECK_UNARY(csubstr("-0Xf").is_integer());
        CHECK_UNARY(csubstr("-0xA").is_integer());
        CHECK_UNARY(csubstr("-0XA").is_integer());
        CHECK_UNARY(csubstr("-0xB").is_integer());
        CHECK_UNARY(csubstr("-0XB").is_integer());
        CHECK_UNARY(csubstr("-0xC").is_integer());
        CHECK_UNARY(csubstr("-0XC").is_integer());
        CHECK_UNARY(csubstr("-0xD").is_integer());
        CHECK_UNARY(csubstr("-0XD").is_integer());
        CHECK_UNARY(csubstr("-0xE").is_integer());
        CHECK_UNARY(csubstr("-0XE").is_integer());
        CHECK_UNARY(csubstr("-0xF").is_integer());
        CHECK_UNARY(csubstr("-0XF").is_integer());
        CHECK_FALSE(csubstr("-0xg").is_integer());
        CHECK_FALSE(csubstr("-0Xg").is_integer());
        CHECK_FALSE(csubstr("-0xG").is_integer());
        CHECK_FALSE(csubstr("-0XG").is_integer());
    }
    SUBCASE("binary")
    {
        CHECK_FALSE(csubstr("0b").is_integer());
        CHECK_FALSE(csubstr("0B").is_integer());
        CHECK_UNARY(csubstr("0b0").is_integer());
        CHECK_UNARY(csubstr("0B0").is_integer());
        CHECK_UNARY(csubstr("0b1").is_integer());
        CHECK_UNARY(csubstr("0B1").is_integer());
        CHECK_FALSE(csubstr("0b2").is_integer());
        CHECK_FALSE(csubstr("0B2").is_integer());
        CHECK_UNARY(csubstr("-0b0").is_integer());
        CHECK_UNARY(csubstr("-0B0").is_integer());
        CHECK_UNARY(csubstr("-0b1").is_integer());
        CHECK_UNARY(csubstr("-0B1").is_integer());
        CHECK_FALSE(csubstr("-0b2").is_integer());
        CHECK_FALSE(csubstr("-0B2").is_integer());
    }
    SUBCASE("octal")
    {
        CHECK_FALSE(csubstr("0o").is_integer());
        CHECK_FALSE(csubstr("0O").is_integer());
        CHECK_UNARY(csubstr("0o0").is_integer());
        CHECK_UNARY(csubstr("0O0").is_integer());
        CHECK_UNARY(csubstr("0o1").is_integer());
        CHECK_UNARY(csubstr("0O1").is_integer());
        CHECK_UNARY(csubstr("0o6").is_integer());
        CHECK_UNARY(csubstr("0O6").is_integer());
        CHECK_UNARY(csubstr("0o6").is_integer());
        CHECK_UNARY(csubstr("0O6").is_integer());
        CHECK_UNARY(csubstr("0o7").is_integer());
        CHECK_UNARY(csubstr("0O7").is_integer());
        CHECK_FALSE(csubstr("0o8").is_integer());
        CHECK_FALSE(csubstr("0O8").is_integer());
        CHECK_FALSE(csubstr("0o9").is_integer());
        CHECK_FALSE(csubstr("0O9").is_integer());
        CHECK_UNARY(csubstr("-0o0").is_integer());
        CHECK_UNARY(csubstr("-0O0").is_integer());
        CHECK_UNARY(csubstr("-0o1").is_integer());
        CHECK_UNARY(csubstr("-0O1").is_integer());
        CHECK_UNARY(csubstr("-0o6").is_integer());
        CHECK_UNARY(csubstr("-0O6").is_integer());
        CHECK_UNARY(csubstr("-0o6").is_integer());
        CHECK_UNARY(csubstr("-0O6").is_integer());
        CHECK_UNARY(csubstr("-0o7").is_integer());
        CHECK_UNARY(csubstr("-0O7").is_integer());
        CHECK_FALSE(csubstr("-0o8").is_integer());
        CHECK_FALSE(csubstr("-0O8").is_integer());
        CHECK_FALSE(csubstr("-0o9").is_integer());
        CHECK_FALSE(csubstr("-0O9").is_integer());
    }
}

TEST_CASE("substr.is_real")
{
    SUBCASE("empty_string")
    {
        CHECK_FALSE(csubstr().is_real());
        CHECK_FALSE(csubstr("").is_real());
    }
    SUBCASE("signs")
    {
        CHECK_FALSE(csubstr("-").is_real());
        CHECK_FALSE(csubstr("+").is_real());
        CHECK_UNARY(csubstr("-1").is_real());
        CHECK_UNARY(csubstr("+1").is_real());
    }
    SUBCASE("whitespace_before")
    {
        CHECK_FALSE(csubstr(" 0").is_real());
        CHECK_FALSE(csubstr(" 1").is_real());
        CHECK_FALSE(csubstr(" -1").is_real());
        CHECK_FALSE(csubstr(" 0.1").is_real());
        CHECK_FALSE(csubstr(" -0.1").is_real());
    }
    SUBCASE("whitespace_after")
    {
        CHECK_FALSE(csubstr("0 ").is_real());
        CHECK_FALSE(csubstr("1 ").is_real());
        CHECK_FALSE(csubstr("-1 ").is_real());
        CHECK_FALSE(csubstr("0.1 ").is_real());
        CHECK_FALSE(csubstr("-0.1 ").is_real());
    }
    SUBCASE("whitespace_only")
    {
        CHECK_FALSE(csubstr("    ").is_real());
        CHECK_FALSE(csubstr("\t\t\t\t").is_real());
        CHECK_FALSE(csubstr("\n\n\n\n").is_real());
        CHECK_FALSE(csubstr("\r\r\r\r").is_real());
    }
    SUBCASE("decimal")
    {
        CHECK_UNARY(csubstr("0").is_real());
        CHECK_UNARY(csubstr("1").is_real());
        CHECK_UNARY(csubstr("-1").is_real());
        CHECK_UNARY(csubstr("0.1").is_real());
        CHECK_UNARY(csubstr("-0.1").is_real());
    }
    SUBCASE("scientific")
    {
        CHECK_UNARY(csubstr("-1.234").is_real());
        CHECK_UNARY(csubstr("-1.234e5").is_real());
        CHECK_UNARY(csubstr("-1.234E5").is_real());
        CHECK_UNARY(csubstr("-1.234e+5").is_real());
        CHECK_UNARY(csubstr("-1.234E+5").is_real());
        CHECK_UNARY(csubstr("-1.234e-5").is_real());
        CHECK_UNARY(csubstr("-1.234E-5").is_real());
        CHECK_UNARY(csubstr("9.5e7").is_real());
        CHECK_UNARY(csubstr("9.5E7").is_real());
        CHECK_UNARY(csubstr("9.5e+7").is_real());
        CHECK_UNARY(csubstr("9.5E+7").is_real());
        CHECK_UNARY(csubstr("9.5e-7").is_real());
        CHECK_UNARY(csubstr("9.5E-7").is_real());
        CHECK_UNARY(csubstr("-9.5e7").is_real());
        CHECK_UNARY(csubstr("-9.5E7").is_real());
        CHECK_UNARY(csubstr("-9.5e+7").is_real());
        CHECK_UNARY(csubstr("-9.5E+7").is_real());
        CHECK_UNARY(csubstr("-9.5e-7").is_real());
        CHECK_UNARY(csubstr("-9.5E-7").is_real());
        CHECK_UNARY(csubstr("+9.5e7").is_real());
        CHECK_UNARY(csubstr("+9.5E7").is_real());
        CHECK_UNARY(csubstr("+9.5e+7").is_real());
        CHECK_UNARY(csubstr("+9.5E+7").is_real());
        CHECK_UNARY(csubstr("+9.5e-7").is_real());
        CHECK_UNARY(csubstr("+9.5E-7").is_real());
        CHECK_UNARY(csubstr("9.5e17").is_real());
        CHECK_UNARY(csubstr("9.5E17").is_real());
        CHECK_UNARY(csubstr("9.5e+17").is_real());
        CHECK_UNARY(csubstr("9.5E+17").is_real());
        CHECK_UNARY(csubstr("9.5e-17").is_real());
        CHECK_UNARY(csubstr("9.5E-17").is_real());
        CHECK_UNARY(csubstr("-9.5e17").is_real());
        CHECK_UNARY(csubstr("-9.5E17").is_real());
        CHECK_UNARY(csubstr("-9.5e+17").is_real());
        CHECK_UNARY(csubstr("-9.5E+17").is_real());
        CHECK_UNARY(csubstr("-9.5e-17").is_real());
        CHECK_UNARY(csubstr("-9.5E-17").is_real());
        CHECK_UNARY(csubstr("+9.5e17").is_real());
        CHECK_UNARY(csubstr("+9.5E17").is_real());
        CHECK_UNARY(csubstr("+9.5e+17").is_real());
        CHECK_UNARY(csubstr("+9.5E+17").is_real());
        CHECK_UNARY(csubstr("+9.5e-17").is_real());
        CHECK_UNARY(csubstr("+9.5E-17").is_real());
        CHECK_UNARY(csubstr("129.532e7").is_real());
        CHECK_UNARY(csubstr("129.532E7").is_real());
        CHECK_UNARY(csubstr("129.532e+7").is_real());
        CHECK_UNARY(csubstr("129.532E+7").is_real());
        CHECK_UNARY(csubstr("129.532e-7").is_real());
        CHECK_UNARY(csubstr("129.532E-7").is_real());
        CHECK_UNARY(csubstr("-129.532e7").is_real());
        CHECK_UNARY(csubstr("-129.532E7").is_real());
        CHECK_UNARY(csubstr("-129.532e+7").is_real());
        CHECK_UNARY(csubstr("-129.532E+7").is_real());
        CHECK_UNARY(csubstr("-129.532e-7").is_real());
        CHECK_UNARY(csubstr("-129.532E-7").is_real());
        CHECK_UNARY(csubstr("+129.532e7").is_real());
        CHECK_UNARY(csubstr("+129.532E7").is_real());
        CHECK_UNARY(csubstr("+129.532e+7").is_real());
        CHECK_UNARY(csubstr("+129.532E+7").is_real());
        CHECK_UNARY(csubstr("+129.532e-7").is_real());
        CHECK_UNARY(csubstr("+129.532E-7").is_real());
        CHECK_UNARY(csubstr("129.532e17").is_real());
        CHECK_UNARY(csubstr("129.532E17").is_real());
        CHECK_UNARY(csubstr("129.532e+17").is_real());
        CHECK_UNARY(csubstr("129.532E+17").is_real());
        CHECK_UNARY(csubstr("129.532e-17").is_real());
        CHECK_UNARY(csubstr("129.532E-17").is_real());
        CHECK_UNARY(csubstr("-129.532e17").is_real());
        CHECK_UNARY(csubstr("-129.532E17").is_real());
        CHECK_UNARY(csubstr("-129.532e+17").is_real());
        CHECK_UNARY(csubstr("-129.532E+17").is_real());
        CHECK_UNARY(csubstr("-129.532e-17").is_real());
        CHECK_UNARY(csubstr("-129.532E-17").is_real());
        CHECK_UNARY(csubstr("+129.532e17").is_real());
        CHECK_UNARY(csubstr("+129.532E17").is_real());
        CHECK_UNARY(csubstr("+129.532e+17").is_real());
        CHECK_UNARY(csubstr("+129.532E+17").is_real());
        CHECK_UNARY(csubstr("+129.532e-17").is_real());
        CHECK_UNARY(csubstr("+129.532E-17").is_real());
    }
    SUBCASE("hexadecimal")
    {
        CHECK_FALSE(csubstr("0x").is_real());
        CHECK_FALSE(csubstr("0X").is_real());
        CHECK_UNARY(csubstr("0x1").is_real());
        CHECK_UNARY(csubstr("0X1").is_real());
        CHECK_UNARY(csubstr("0x0").is_real());
        CHECK_UNARY(csubstr("0X0").is_real());
        CHECK_UNARY(csubstr("0xa").is_real());
        CHECK_UNARY(csubstr("0Xa").is_real());
        CHECK_UNARY(csubstr("0xb").is_real());
        CHECK_UNARY(csubstr("0Xb").is_real());
        CHECK_UNARY(csubstr("0xc").is_real());
        CHECK_UNARY(csubstr("0Xc").is_real());
        CHECK_UNARY(csubstr("0xd").is_real());
        CHECK_UNARY(csubstr("0Xd").is_real());
        CHECK_UNARY(csubstr("0xe").is_real());
        CHECK_UNARY(csubstr("0Xe").is_real());
        CHECK_UNARY(csubstr("0xf").is_real());
        CHECK_UNARY(csubstr("0Xf").is_real());
        CHECK_UNARY(csubstr("0xA").is_real());
        CHECK_UNARY(csubstr("0XA").is_real());
        CHECK_UNARY(csubstr("0xB").is_real());
        CHECK_UNARY(csubstr("0XB").is_real());
        CHECK_UNARY(csubstr("0xC").is_real());
        CHECK_UNARY(csubstr("0XC").is_real());
        CHECK_UNARY(csubstr("0xD").is_real());
        CHECK_UNARY(csubstr("0XD").is_real());
        CHECK_UNARY(csubstr("0xE").is_real());
        CHECK_UNARY(csubstr("0XE").is_real());
        CHECK_UNARY(csubstr("0xF").is_real());
        CHECK_UNARY(csubstr("0XF").is_real());
        CHECK_FALSE(csubstr("0xg").is_real());
        CHECK_FALSE(csubstr("0Xg").is_real());
        CHECK_FALSE(csubstr("0xG").is_real());
        CHECK_FALSE(csubstr("0XG").is_real());
        CHECK_UNARY(csubstr("-0x1").is_real());
        CHECK_UNARY(csubstr("-0X1").is_real());
        CHECK_UNARY(csubstr("-0x0").is_real());
        CHECK_UNARY(csubstr("-0X0").is_real());
        CHECK_UNARY(csubstr("-0xa").is_real());
        CHECK_UNARY(csubstr("-0Xa").is_real());
        CHECK_UNARY(csubstr("-0xb").is_real());
        CHECK_UNARY(csubstr("-0Xb").is_real());
        CHECK_UNARY(csubstr("-0xc").is_real());
        CHECK_UNARY(csubstr("-0Xc").is_real());
        CHECK_UNARY(csubstr("-0xd").is_real());
        CHECK_UNARY(csubstr("-0Xd").is_real());
        CHECK_UNARY(csubstr("-0xe").is_real());
        CHECK_UNARY(csubstr("-0Xe").is_real());
        CHECK_UNARY(csubstr("-0xf").is_real());
        CHECK_UNARY(csubstr("-0Xf").is_real());
        CHECK_UNARY(csubstr("-0xA").is_real());
        CHECK_UNARY(csubstr("-0XA").is_real());
        CHECK_UNARY(csubstr("-0xB").is_real());
        CHECK_UNARY(csubstr("-0XB").is_real());
        CHECK_UNARY(csubstr("-0xC").is_real());
        CHECK_UNARY(csubstr("-0XC").is_real());
        CHECK_UNARY(csubstr("-0xD").is_real());
        CHECK_UNARY(csubstr("-0XD").is_real());
        CHECK_UNARY(csubstr("-0xE").is_real());
        CHECK_UNARY(csubstr("-0XE").is_real());
        CHECK_UNARY(csubstr("-0xF").is_real());
        CHECK_UNARY(csubstr("-0XF").is_real());
        CHECK_FALSE(csubstr("-0xg").is_real());
        CHECK_FALSE(csubstr("-0Xg").is_real());
        CHECK_FALSE(csubstr("-0xG").is_real());
        CHECK_FALSE(csubstr("-0XG").is_real());
        CHECK_UNARY(csubstr("0x1.e8480p+19").is_real());
        CHECK_UNARY(csubstr("0X1.e8480P+19").is_real());
        CHECK_UNARY(csubstr("0x1.e8480P+19").is_real());
        CHECK_UNARY(csubstr("0X1.e8480p+19").is_real());
        CHECK_UNARY(csubstr("-0x1.e8480p+19").is_real());
        CHECK_UNARY(csubstr("-0X1.e8480P+19").is_real());
        CHECK_UNARY(csubstr("-0x1.e8480P+19").is_real());
        CHECK_UNARY(csubstr("-0X1.e8480p+19").is_real());
    }
    SUBCASE("binary")
    {
        CHECK_FALSE(csubstr("0b").is_real());
        CHECK_FALSE(csubstr("0B").is_real());
        CHECK_UNARY(csubstr("0b0").is_real());
        CHECK_UNARY(csubstr("0B0").is_real());
        CHECK_UNARY(csubstr("0b1").is_real());
        CHECK_UNARY(csubstr("0B1").is_real());
        CHECK_FALSE(csubstr("0b2").is_real());
        CHECK_FALSE(csubstr("0B2").is_real());
        CHECK_UNARY(csubstr("-0b0").is_real());
        CHECK_UNARY(csubstr("-0B0").is_real());
        CHECK_UNARY(csubstr("-0b1").is_real());
        CHECK_UNARY(csubstr("-0B1").is_real());
        CHECK_FALSE(csubstr("-0b2").is_real());
        CHECK_FALSE(csubstr("-0B2").is_real());
    }
    SUBCASE("octal")
    {
        CHECK_FALSE(csubstr("0o").is_real());
        CHECK_FALSE(csubstr("0O").is_real());
        CHECK_UNARY(csubstr("0o0").is_real());
        CHECK_UNARY(csubstr("0O0").is_real());
        CHECK_UNARY(csubstr("0o1").is_real());
        CHECK_UNARY(csubstr("0O1").is_real());
        CHECK_UNARY(csubstr("0o6").is_real());
        CHECK_UNARY(csubstr("0O6").is_real());
        CHECK_UNARY(csubstr("0o6").is_real());
        CHECK_UNARY(csubstr("0O6").is_real());
        CHECK_UNARY(csubstr("0o7").is_real());
        CHECK_UNARY(csubstr("0O7").is_real());
        CHECK_FALSE(csubstr("0o8").is_real());
        CHECK_FALSE(csubstr("0O8").is_real());
        CHECK_FALSE(csubstr("0o9").is_real());
        CHECK_FALSE(csubstr("0O9").is_real());
        CHECK_UNARY(csubstr("-0o0").is_real());
        CHECK_UNARY(csubstr("-0O0").is_real());
        CHECK_UNARY(csubstr("-0o1").is_real());
        CHECK_UNARY(csubstr("-0O1").is_real());
        CHECK_UNARY(csubstr("-0o6").is_real());
        CHECK_UNARY(csubstr("-0O6").is_real());
        CHECK_UNARY(csubstr("-0o6").is_real());
        CHECK_UNARY(csubstr("-0O6").is_real());
        CHECK_UNARY(csubstr("-0o7").is_real());
        CHECK_UNARY(csubstr("-0O7").is_real());
        CHECK_FALSE(csubstr("-0o8").is_real());
        CHECK_FALSE(csubstr("-0O8").is_real());
        CHECK_FALSE(csubstr("-0o9").is_real());
        CHECK_FALSE(csubstr("-0O9").is_real());
    }
    SUBCASE("infinity")
    {
        CHECK_UNARY(csubstr("infinity").is_real());
        CHECK_FALSE(csubstr(" infinity").is_real());
        CHECK_UNARY(csubstr("-infinity").is_real());
        CHECK_FALSE(csubstr(" -infinity").is_real());
        CHECK_UNARY(csubstr("+infinity").is_real());
        CHECK_FALSE(csubstr(" +infinity").is_real());
        CHECK_FALSE(csubstr("infinity ").is_real());
        CHECK_FALSE(csubstr(" infinity ").is_real());
        CHECK_FALSE(csubstr("-infinity ").is_real());
        CHECK_FALSE(csubstr(" -infinity ").is_real());
        CHECK_FALSE(csubstr("+infinity ").is_real());
        CHECK_FALSE(csubstr(" +infinity ").is_real());
        CHECK_FALSE(csubstr("infinity1").is_real());
        CHECK_FALSE(csubstr(" infinity1").is_real());
        CHECK_FALSE(csubstr("-infinity1").is_real());
        CHECK_FALSE(csubstr(" -infinity1").is_real());
        CHECK_FALSE(csubstr("+infinity1").is_real());
        CHECK_FALSE(csubstr(" +infinity1").is_real());
        CHECK_FALSE(csubstr("infin").is_real());
        CHECK_FALSE(csubstr(" infin").is_real());
        CHECK_FALSE(csubstr("-infin").is_real());
        CHECK_FALSE(csubstr(" -infin").is_real());
        CHECK_FALSE(csubstr("+infin").is_real());
        CHECK_FALSE(csubstr(" +infin").is_real());
        CHECK_FALSE(csubstr("inflated").is_real());
        CHECK_FALSE(csubstr(" inflated").is_real());
        CHECK_FALSE(csubstr("-inflated").is_real());
        CHECK_FALSE(csubstr(" -inflated").is_real());
        CHECK_FALSE(csubstr("+inflated").is_real());
        CHECK_FALSE(csubstr(" +inflated").is_real());
    }
    SUBCASE("inf")
    {
        CHECK_UNARY(csubstr("inf").is_real());
        CHECK_FALSE(csubstr(" inf").is_real());
        CHECK_UNARY(csubstr("-inf").is_real());
        CHECK_FALSE(csubstr(" -inf").is_real());
        CHECK_UNARY(csubstr("+inf").is_real());
        CHECK_FALSE(csubstr(" +inf").is_real());
        CHECK_FALSE(csubstr("inf ").is_real());
        CHECK_FALSE(csubstr(" inf ").is_real());
        CHECK_FALSE(csubstr("-inf ").is_real());
        CHECK_FALSE(csubstr(" -inf ").is_real());
        CHECK_FALSE(csubstr("+inf ").is_real());
        CHECK_FALSE(csubstr(" +inf ").is_real());
        CHECK_FALSE(csubstr("inf1").is_real());
        CHECK_FALSE(csubstr(" inf1").is_real());
        CHECK_FALSE(csubstr("-inf1").is_real());
        CHECK_FALSE(csubstr(" -inf1").is_real());
        CHECK_FALSE(csubstr("+inf1").is_real());
        CHECK_FALSE(csubstr(" +inf1").is_real());
    }
    SUBCASE("nan")
    {
        CHECK_UNARY(csubstr("nan").is_real());
        CHECK_FALSE(csubstr(" nan").is_real());
        CHECK_UNARY(csubstr("-nan").is_real());
        CHECK_FALSE(csubstr(" -nan").is_real());
        CHECK_UNARY(csubstr("+nan").is_real());
        CHECK_FALSE(csubstr(" +nan").is_real());
        CHECK_FALSE(csubstr("nan ").is_real());
        CHECK_FALSE(csubstr(" nan ").is_real());
        CHECK_FALSE(csubstr("-nan ").is_real());
        CHECK_FALSE(csubstr(" -nan ").is_real());
        CHECK_FALSE(csubstr("+nan ").is_real());
        CHECK_FALSE(csubstr(" +nan ").is_real());
        CHECK_FALSE(csubstr("nan1").is_real());
        CHECK_FALSE(csubstr(" nan1").is_real());
        CHECK_FALSE(csubstr("-nan1").is_real());
        CHECK_FALSE(csubstr(" -nan1").is_real());
        CHECK_FALSE(csubstr("+nan1").is_real());
        CHECK_FALSE(csubstr(" +nan1").is_real());
    }
}

typedef enum : uint8_t { kIsNone = 0, kIsUint = 1, kIsInt = 3, kIsReal = 7 } NumberClass;
struct number
{
    csubstr num;
    NumberClass cls;

    template<size_t N>
    number(const char (&n)[N], NumberClass c) : num(n), cls(c) {}
    number(csubstr n, NumberClass c) : num(n), cls(c) {}

    void test(csubstr ref={})
    {
        if(ref.empty())
            ref = num;
        INFO("num=" << num);
        INFO("ref=" << ref);
        switch(cls)
        {
        case kIsUint:
        {
            INFO("uint");
            CHECK_EQ(num.first_uint_span(), ref);
            CHECK_EQ(num.first_int_span(), ref);
            CHECK_EQ(num.first_real_span(), ref);
            CHECK_UNARY(num.first_uint_span().is_unsigned_integer());
            CHECK_UNARY(num.first_uint_span().is_integer());
            CHECK_UNARY(num.first_uint_span().is_number());
            break;
        }
        case kIsInt:
        {
            INFO("int");
            CHECK_EQ(num.first_uint_span(), "");
            CHECK_EQ(num.first_int_span(), ref);
            CHECK_EQ(num.first_real_span(), ref);
            CHECK_FALSE(num.first_int_span().is_unsigned_integer());
            CHECK_UNARY(num.first_int_span().is_integer());
            CHECK_UNARY(num.first_int_span().is_number());
            break;
        }
        case kIsReal:
        {
            INFO("real");
            CHECK_EQ(num.first_uint_span(), "");
            CHECK_EQ(num.first_int_span(), "");
            CHECK_EQ(num.first_real_span(), ref);
            CHECK_FALSE(num.first_real_span().is_unsigned_integer());
            CHECK_FALSE(num.first_real_span().is_integer());
            CHECK_UNARY(num .first_real_span().is_number());
            break;
        }
        case kIsNone:
        {
            INFO("none");
            CHECK_EQ(num.first_uint_span(), "");
            CHECK_EQ(num.first_int_span(), "");
            CHECK_EQ(num.first_real_span(), "");
            CHECK_FALSE(num.is_unsigned_integer());
            CHECK_FALSE(num.is_integer());
            CHECK_FALSE(num.is_number());
            break;
        }
        default:
        {
            CHECK_UNARY(false);//FAIL();
            break;
        }
        }
    }
};

const number numbers[] = {
    {"", kIsNone},
    {"0x", kIsNone},
    {"0b", kIsNone},
    {"0o", kIsNone},
    {".", kIsNone},
    {"0x.", kIsNone},
    {"0b.", kIsNone},
    {"0o.", kIsNone},
    {"-", kIsNone},
    {"0x-", kIsNone},
    {"0b-", kIsNone},
    {"0o-", kIsNone},
    {"+", kIsNone},
    {"0x+", kIsNone},
    {"0b+", kIsNone},
    {"0o+", kIsNone},
    {".e", kIsNone},
    {".e+", kIsNone},
    {".e-", kIsNone},
    {"0x.p", kIsNone},
    {"0x.p+", kIsNone},
    {"0x.p-", kIsNone},
    {"0b.p", kIsNone},
    {"0b.p+", kIsNone},
    {"0b.p-", kIsNone},
    {"0o.p", kIsNone},
    {"0o.p+", kIsNone},
    {"0o.p-", kIsNone},
    {"0x.p+", kIsNone},
    {"0x0.p+", kIsNone},
    {"0x.0p+", kIsNone},
    {"0x.p+0", kIsNone},
    {"0x.p+00", kIsNone},
    {"0x0.0p+", kIsNone},
    {"0x.0p+0", kIsReal},
    {"0x0.p+0", kIsReal},
    {"0x0.0p+0", kIsReal},
    {"0x0.0p+00", kIsReal},
    {"0x00.00p+00", kIsReal},
    {"0x0p0.00p+00", kIsNone},
    {"0x00.0p0p+00", kIsNone},
    {"0x00.00p+0p0", kIsNone},
    {"0x00.00p+00p", kIsNone},
    {"0b.p+", kIsNone},
    {"0b0.p+", kIsNone},
    {"0b.0p+", kIsNone},
    {"0b.p+0", kIsNone},
    {"0b.p+00", kIsNone},
    {"0b0.0p+", kIsNone},
    {"0b.0p+0", kIsReal},
    {"0b0.p+0", kIsReal},
    {"0b0.0p+0", kIsReal},
    {"0b0.0p+00", kIsReal},
    {"0b00.00p+00", kIsReal},
    {"0b0p0.00p+00", kIsNone},
    {"0b00.0p0p+00", kIsNone},
    {"0b00.00p+0p0", kIsNone},
    {"0b00.00p+00p", kIsNone},
    {"0o.p+", kIsNone},
    {"0o0.p+", kIsNone},
    {"0o.0p+", kIsNone},
    {"0o.p+0", kIsNone},
    {"0o.p+00", kIsNone},
    {"0o0.0p+", kIsNone},
    {"0o.0p+0", kIsReal},
    {"0o0.p+0", kIsReal},
    {"0o0.0p+0", kIsReal},
    {"0o0.0p+00", kIsReal},
    {"0o00.00p+00", kIsReal},
    {"0o0p0.00p+00", kIsNone},
    {"0o00.0p0p+00", kIsNone},
    {"0o00.00p+0p0", kIsNone},
    {"0o00.00p+00p", kIsNone},
    {".e+", kIsNone},
    {"0.e+", kIsNone},
    {".0e+", kIsNone},
    {".e+0", kIsNone},
    {".e+00", kIsNone},
    {"0.0e+", kIsNone},
    {".0e+0", kIsReal},
    {"0.e+0", kIsReal},
    {"0.0e+0", kIsReal},
    {"0.0e+00", kIsReal},
    {"00.00e+00", kIsReal},
    {"0e0.00e+00", kIsNone},
    {"00.0e0e+00", kIsNone},
    {"00.00e+0e0", kIsNone},
    {"00.00e+00e", kIsNone},
    {"0x1234.", kIsReal},
    {"+0x1234.", kIsReal},
    {"-0x1234.", kIsReal},
    {"0x.1234", kIsReal},
    {"0x0.1.2.3", kIsNone},
    {"0o0.1.2.3", kIsNone},
    {"0b0.1.0.1", kIsNone},
    {"a", kIsNone},
    {"b", kIsNone},
    {"?", kIsNone},
    {"0.0.1", kIsNone},
    {"0.0.9", kIsNone},
    {"0.0.10", kIsNone},
    {"0.1.0", kIsNone},
    {"0.9.0", kIsNone},
    {"0.10.0", kIsNone},
    {"1.0.0", kIsNone},
    {"9.0.0", kIsNone},
    {"10.0.0", kIsNone},
    {".0", kIsReal},
    {"0.", kIsReal},
    {"0.0", kIsReal},
    {"1234", kIsUint},
    {"+1234", kIsUint},
    {"-1234", kIsInt},
    {"1234.0", kIsReal},
    {"+1234.0", kIsReal},
    {"-1234.0", kIsReal},
    {"0000", kIsUint},
    {"0123", kIsUint},
    {"0", kIsUint},
    {"1", kIsUint},
    {"1.", kIsReal},
    {".1", kIsReal},
    {"0x1234", kIsUint},
    {"+0x1234", kIsUint},
    {"-0x1234", kIsInt},
    {"0b01", kIsUint},
    {"1e+1", kIsReal},
    {"1e-1", kIsReal},
    {"1.e-1", kIsReal},
    {"1.e+1", kIsReal},
    {"1.0e-1", kIsReal},
    {"1.0e+1", kIsReal},
    {"1e+123", kIsReal},
    {"1e-123", kIsReal},
    {"1.e-123", kIsReal},
    {"1.e+123", kIsReal},
    {"1.0e123", kIsReal},
    {"1.0e-123", kIsReal},
    {"1.0e+123", kIsReal},
    {"0x1.e8480p+19", kIsReal},
    {"0x1.g8480p+19", kIsNone},
    {"0xg.e8480p+19", kIsNone},
    {"0b101.011p+19", kIsReal},
    {"0b101.012p+19", kIsNone},
    {"0b102.011p+19", kIsNone},
    {"0o173.045p+19", kIsReal},
    {"0o173.048p+19", kIsNone},
    {"0o178.045p+19", kIsNone},
    {"infinity", kIsReal},
    {"inf", kIsReal},
    {"nan", kIsReal},
        {"-1.234", kIsReal},
        {"-1.234e5", kIsReal},
        {"-1.234E5", kIsReal},
        {"-1.234e+5", kIsReal},
        {"-1.234E+5", kIsReal},
        {"-1.234e-5", kIsReal},
        {"-1.234E-5", kIsReal},
        {"9.5e7", kIsReal},
        {"9.5E7", kIsReal},
        {"9.5e+7", kIsReal},
        {"9.5E+7", kIsReal},
        {"9.5e-7", kIsReal},
        {"9.5E-7", kIsReal},
        {"-9.5e7", kIsReal},
        {"-9.5E7", kIsReal},
        {"-9.5e+7", kIsReal},
        {"-9.5E+7", kIsReal},
        {"-9.5e-7", kIsReal},
        {"-9.5E-7", kIsReal},
        {"+9.5e7", kIsReal},
        {"+9.5E7", kIsReal},
        {"+9.5e+7", kIsReal},
        {"+9.5E+7", kIsReal},
        {"+9.5e-7", kIsReal},
        {"+9.5E-7", kIsReal},
        {"9.5e17", kIsReal},
        {"9.5E17", kIsReal},
        {"9.5e+17", kIsReal},
        {"9.5E+17", kIsReal},
        {"9.5e-17", kIsReal},
        {"9.5E-17", kIsReal},
        {"-9.5e17", kIsReal},
        {"-9.5E17", kIsReal},
        {"-9.5e+17", kIsReal},
        {"-9.5E+17", kIsReal},
        {"-9.5e-17", kIsReal},
        {"-9.5E-17", kIsReal},
        {"+9.5e17", kIsReal},
        {"+9.5E17", kIsReal},
        {"+9.5e+17", kIsReal},
        {"+9.5E+17", kIsReal},
        {"+9.5e-17", kIsReal},
        {"+9.5E-17", kIsReal},
        {"129.532e7", kIsReal},
        {"129.532E7", kIsReal},
        {"129.532e+7", kIsReal},
        {"129.532E+7", kIsReal},
        {"129.532e-7", kIsReal},
        {"129.532E-7", kIsReal},
        {"-129.532e7", kIsReal},
        {"-129.532E7", kIsReal},
        {"-129.532e+7", kIsReal},
        {"-129.532E+7", kIsReal},
        {"-129.532e-7", kIsReal},
        {"-129.532E-7", kIsReal},
        {"+129.532e7", kIsReal},
        {"+129.532E7", kIsReal},
        {"+129.532e+7", kIsReal},
        {"+129.532E+7", kIsReal},
        {"+129.532e-7", kIsReal},
        {"+129.532E-7", kIsReal},
        {"129.532e17", kIsReal},
        {"129.532E17", kIsReal},
        {"129.532e+17", kIsReal},
        {"129.532E+17", kIsReal},
        {"129.532e-17", kIsReal},
        {"129.532E-17", kIsReal},
        {"-129.532e17", kIsReal},
        {"-129.532E17", kIsReal},
        {"-129.532e+17", kIsReal},
        {"-129.532E+17", kIsReal},
        {"-129.532e-17", kIsReal},
        {"-129.532E-17", kIsReal},
        {"+129.532e17", kIsReal},
        {"+129.532E17", kIsReal},
        {"+129.532e+17", kIsReal},
        {"+129.532E+17", kIsReal},
        {"+129.532e-17", kIsReal},
        {"+129.532E-17", kIsReal},
};

TEST_CASE("substr.is_number")
{
    SUBCASE("basic.hex")
    {
        CHECK_EQ(csubstr("0x.0p+0").first_real_span(), csubstr("0x.0p+0"));
        CHECK_EQ(csubstr("0x)sdkjhsdfkju").first_int_span(), csubstr{});
        CHECK_EQ(csubstr("0x)sdkjhsdfkju").first_uint_span(), csubstr{});
        CHECK_EQ(csubstr("0x)sdkjhsdfkju").first_real_span(), csubstr{});
        CHECK_EQ(csubstr("0x0)sdkjhsdfkju").first_int_span(), csubstr("0x0"));
        CHECK_EQ(csubstr("0x0)sdkjhsdfkju").first_uint_span(), csubstr("0x0"));
        CHECK_EQ(csubstr("0x0)sdkjhsdfkju").first_real_span(), csubstr("0x0"));
    }
    SUBCASE("basic.dec")
    {
        CHECK_EQ(csubstr("+infinity").first_real_span(), csubstr("+infinity"));
        CHECK_EQ(csubstr("-infinity").first_real_span(), csubstr("-infinity"));
        CHECK_EQ(csubstr("+inf").first_real_span(), csubstr("+inf"));
        CHECK_EQ(csubstr("-inf").first_real_span(), csubstr("-inf"));
        CHECK_EQ(csubstr("0.e+0").first_real_span(), csubstr("0.e+0"));
        CHECK_EQ(csubstr("0.e-0").first_real_span(), csubstr("0.e-0"));
    }
    SUBCASE("plain")
    {
        for(number n : numbers)
        {
            n.test();
        }
    }
    char buf[128];
    SUBCASE("leading+")
    {
        for(number n : numbers)
        {
            INFO("orig=" << n.num);
            substr withplus = cat_sub(buf, '+', n.num);
            NumberClass cls = n.cls;
            if(withplus.begins_with("+-") || withplus.begins_with("++"))
                cls = kIsNone;
            number cp(withplus, cls);
            cp.test();
        }
    }
    SUBCASE("leading-")
    {
        for(number n : numbers)
        {
            INFO("orig=" << n.num);
            substr withminus = cat_sub(buf, '-', n.num);
            NumberClass cls = n.cls;
            if(cls == kIsUint)
                cls = kIsInt;
            if(withminus.begins_with("--") || withminus.begins_with("-+"))
                cls = kIsNone;
            number cp(withminus, cls);
            cp.test();
        }
    }
    SUBCASE("capital_e")
    {
        for(number n : numbers)
        {
            INFO("orig=" << n.num);
            substr replaced = cat_sub(buf, n.num);
            replaced.replace('e', 'E');
            number cp(replaced, n.cls);
            cp.test();
        }
    }
    SUBCASE("capital_p")
    {
        for(number n : numbers)
        {
            INFO("orig=" << n.num);
            substr replaced = cat_sub(buf, n.num);
            replaced.replace('p', 'P');
            number cp(replaced, n.cls);
            cp.test();
        }
    }
    SUBCASE("capital_0x")
    {
        for(number n : numbers)
        {
            INFO("orig=" << n.num);
            substr replaced = cat_sub(buf, n.num);
            replaced.replace('x', 'X');
            number cp(replaced, n.cls);
            cp.test();
        }
    }
    SUBCASE("capital_0b")
    {
        for(number n : numbers)
        {
            INFO("orig=" << n.num);
            substr replaced = cat_sub(buf, n.num);
            replaced.replace('b', 'B');
            number cp(replaced, n.cls);
            cp.test();
        }
    }
    SUBCASE("capital_0o")
    {
        for(number n : numbers)
        {
            INFO("orig=" << n.num);
            substr replaced = cat_sub(buf, n.num);
            replaced.replace('o', 'O');
            number cp(replaced, n.cls);
            cp.test();
        }
    }
    SUBCASE("numbers before")
    {
        char numbuf_[16] = {};
        substr numbuf = numbuf_;
        for(number n : numbers)
        {
            INFO("orig=" << n.num);
            for(char c : {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'})
            {
                numbuf.fill(c);
                substr result = cat_sub(buf, numbuf, n.num);
                number cp(result.sub(numbuf.len), n.cls);
                cp.test();
            }
        }
    }
    SUBCASE("numbers after")
    {
        char numbuf_[16] = {};
        substr numbuf = numbuf_;
        for(number n : numbers)
        {
            INFO("orig=" << n.num);
            for(char c : {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'})
            {
                numbuf.fill(c);
                substr result = cat_sub(buf, n.num, numbuf);
                number cp(result.first(n.num.len), n.cls);
                cp.test();
            }
        }
    }
    SUBCASE("delimiter after")
    {
        for(number n : numbers)
        {
            INFO("orig=" << n.num);
            for(char c : {' ', '\n', ']', ')', '}', ',', ';', '\r', '\t','\0'})
            {
                INFO("delimiter='" << c << "'");
                substr result = cat_sub(buf, n.num, c);
                number cp(result, n.cls);
                cp.test(n.num);
            }
        }
    }
    csubstr garbage = "sdkjhsdfkju";
    SUBCASE("prepend")
    {
        // adding anything before the number will make it not be a number
        for(number n : numbers)
        {
            if(n.num.empty())
                continue;
            INFO("orig=" << n.num);
            for(int i = 0; i < 127; ++i)
            {
                char c = (char)i;
                csubstr fmtd = cat_sub(buf, garbage, c, n.num);
                number cp(fmtd, kIsNone);
                cp.test();
            }
        }
    }
    SUBCASE("append")
    {
        // adding after may or may not make it a number
        for(number const& n : numbers)
        {
            INFO("orig=" << n.num);
            for(int i = 0; i < 127; ++i)
            {
                number cp = n;
                char c = (char)i;
                cp.num = cat_sub(buf, n.num, c, garbage);
                if(!csubstr::_is_delim_char(c))
                {
                    cp.cls = kIsNone;
                }
                cp.test(n.num);
            }
        }
    }
}

TEST_CASE("substr.triml")
{
    using S = csubstr;

    CHECK_EQ(S("aaabbb"   ).triml('a' ), "bbb");
    CHECK_EQ(S("aaabbb"   ).triml('b' ), "aaabbb");
    CHECK_EQ(S("aaabbb"   ).triml('c' ), "aaabbb");
    CHECK_EQ(S("aaabbb"   ).triml("ab"), "");
    CHECK_EQ(S("aaabbb"   ).triml("ba"), "");
    CHECK_EQ(S("aaabbb"   ).triml("cd"), "aaabbb");
    CHECK_EQ(S("aaa...bbb").triml('a' ), "...bbb");
    CHECK_EQ(S("aaa...bbb").triml('b' ), "aaa...bbb");
    CHECK_EQ(S("aaa...bbb").triml('c' ), "aaa...bbb");
    CHECK_EQ(S("aaa...bbb").triml("ab"), "...bbb");
    CHECK_EQ(S("aaa...bbb").triml("ba"), "...bbb");
    CHECK_EQ(S("aaa...bbb").triml("ab."), "");
    CHECK_EQ(S("aaa...bbb").triml("a."), "bbb");
    CHECK_EQ(S("aaa...bbb").triml(".a"), "bbb");
    CHECK_EQ(S("aaa...bbb").triml("b."), "aaa...bbb");
    CHECK_EQ(S("aaa...bbb").triml(".b"), "aaa...bbb");
    CHECK_EQ(S("aaa...bbb").triml("cd"), "aaa...bbb");

    CHECK_EQ(S("ab"   ).triml('a' ), "b");
    CHECK_EQ(S("ab"   ).triml('b' ), "ab");
    CHECK_EQ(S("ab"   ).triml('c' ), "ab");
    CHECK_EQ(S("ab"   ).triml("ab"), "");
    CHECK_EQ(S("ab"   ).triml("ba"), "");
    CHECK_EQ(S("ab"   ).triml("cd"), "ab");
    CHECK_EQ(S("a...b").triml('a' ), "...b");
    CHECK_EQ(S("a...b").triml('b' ), "a...b");
    CHECK_EQ(S("a...b").triml('c' ), "a...b");
    CHECK_EQ(S("a...b").triml("ab"), "...b");
    CHECK_EQ(S("a...b").triml("ba"), "...b");
    CHECK_EQ(S("a...b").triml("ab."), "");
    CHECK_EQ(S("a...b").triml("a."), "b");
    CHECK_EQ(S("a...b").triml(".a"), "b");
    CHECK_EQ(S("a...b").triml("b."), "a...b");
    CHECK_EQ(S("a...b").triml(".b"), "a...b");
    CHECK_EQ(S("a...b").triml("cd"), "a...b");
}

TEST_CASE("substr.trimr")
{
    using S = csubstr;

    CHECK_EQ(S("aaabbb"   ).trimr('a' ), "aaabbb");
    CHECK_EQ(S("aaabbb"   ).trimr('b' ), "aaa");
    CHECK_EQ(S("aaabbb"   ).trimr('c' ), "aaabbb");
    CHECK_EQ(S("aaabbb"   ).trimr("ab"), "");
    CHECK_EQ(S("aaabbb"   ).trimr("ba"), "");
    CHECK_EQ(S("aaabbb"   ).trimr("cd"), "aaabbb");
    CHECK_EQ(S("aaa...bbb").trimr('a' ), "aaa...bbb");
    CHECK_EQ(S("aaa...bbb").trimr('b' ), "aaa...");
    CHECK_EQ(S("aaa...bbb").trimr('c' ), "aaa...bbb");
    CHECK_EQ(S("aaa...bbb").trimr("ab"), "aaa...");
    CHECK_EQ(S("aaa...bbb").trimr("ba"), "aaa...");
    CHECK_EQ(S("aaa...bbb").trimr("ab."), "");
    CHECK_EQ(S("aaa...bbb").trimr("a."), "aaa...bbb");
    CHECK_EQ(S("aaa...bbb").trimr(".a"), "aaa...bbb");
    CHECK_EQ(S("aaa...bbb").trimr("b."), "aaa");
    CHECK_EQ(S("aaa...bbb").trimr(".b"), "aaa");
    CHECK_EQ(S("aaa...bbb").trimr("cd"), "aaa...bbb");

    CHECK_EQ(S("ab"   ).trimr('a' ), "ab");
    CHECK_EQ(S("ab"   ).trimr('b' ), "a");
    CHECK_EQ(S("ab"   ).trimr('c' ), "ab");
    CHECK_EQ(S("ab"   ).trimr("ab"), "");
    CHECK_EQ(S("ab"   ).trimr("ba"), "");
    CHECK_EQ(S("ab"   ).trimr("cd"), "ab");
    CHECK_EQ(S("a...b").trimr('a' ), "a...b");
    CHECK_EQ(S("a...b").trimr('b' ), "a...");
    CHECK_EQ(S("a...b").trimr('c' ), "a...b");
    CHECK_EQ(S("a...b").trimr("ab"), "a...");
    CHECK_EQ(S("a...b").trimr("ba"), "a...");
    CHECK_EQ(S("a...b").trimr("ab."), "");
    CHECK_EQ(S("a...b").trimr("a."), "a...b");
    CHECK_EQ(S("a...b").trimr(".a"), "a...b");
    CHECK_EQ(S("a...b").trimr("b."), "a");
    CHECK_EQ(S("a...b").trimr(".b"), "a");
    CHECK_EQ(S("a...b").trimr("cd"), "a...b");
}

TEST_CASE("substr.trim")
{
    using S = csubstr;

    CHECK_EQ(S("aaabbb"   ).trim('a' ), "bbb");
    CHECK_EQ(S("aaabbb"   ).trim('b' ), "aaa");
    CHECK_EQ(S("aaabbb"   ).trim('c' ), "aaabbb");
    CHECK_EQ(S("aaabbb"   ).trim("ab"), "");
    CHECK_EQ(S("aaabbb"   ).trim("ba"), "");
    CHECK_EQ(S("aaabbb"   ).trim("cd"), "aaabbb");
    CHECK_EQ(S("aaa...bbb").trim('a' ), "...bbb");
    CHECK_EQ(S("aaa...bbb").trim('b' ), "aaa...");
    CHECK_EQ(S("aaa...bbb").trim('c' ), "aaa...bbb");
    CHECK_EQ(S("aaa...bbb").trim("ab"), "...");
    CHECK_EQ(S("aaa...bbb").trim("ba"), "...");
    CHECK_EQ(S("aaa...bbb").trim('c' ), "aaa...bbb");
    CHECK_EQ(S("aaa...bbb").trim("ab."), "");
    CHECK_EQ(S("aaa...bbb").trim("." ), "aaa...bbb");
    CHECK_EQ(S("aaa...bbb").trim("a."), "bbb");
    CHECK_EQ(S("aaa...bbb").trim(".a"), "bbb");
    CHECK_EQ(S("aaa...bbb").trim("b."), "aaa");
    CHECK_EQ(S("aaa...bbb").trim(".b"), "aaa");
    CHECK_EQ(S("aaa...bbb").trim("cd"), "aaa...bbb");

    CHECK_EQ(S("ab"   ).trim('a' ), "b");
    CHECK_EQ(S("ab"   ).trim('b' ), "a");
    CHECK_EQ(S("ab"   ).trim('c' ), "ab");
    CHECK_EQ(S("ab"   ).trim("ab"), "");
    CHECK_EQ(S("ab"   ).trim("ba"), "");
    CHECK_EQ(S("ab"   ).trim("cd"), "ab");
    CHECK_EQ(S("a...b").trim('a' ), "...b");
    CHECK_EQ(S("a...b").trim('b' ), "a...");
    CHECK_EQ(S("a...b").trim('c' ), "a...b");
    CHECK_EQ(S("a...b").trim("ab"), "...");
    CHECK_EQ(S("a...b").trim("ba"), "...");
    CHECK_EQ(S("a...b").trim('c' ), "a...b");
    CHECK_EQ(S("a...b").trim("ab."), "");
    CHECK_EQ(S("a...b").trim("." ), "a...b");
    CHECK_EQ(S("a...b").trim("a."), "b");
    CHECK_EQ(S("a...b").trim(".a"), "b");
    CHECK_EQ(S("a...b").trim("b."), "a");
    CHECK_EQ(S("a...b").trim(".b"), "a");
    CHECK_EQ(S("a...b").trim("cd"), "a...b");
}

TEST_CASE("substr.pop_right")
{
    using S = csubstr;

    CHECK_EQ(S("0/1/2"    ).pop_right('/'      ), "2");
    CHECK_EQ(S("0/1/2"    ).pop_right('/', true), "2");
    CHECK_EQ(S("0/1/2/"   ).pop_right('/'      ), "");
    CHECK_EQ(S("0/1/2/"   ).pop_right('/', true), "2/");
    CHECK_EQ(S("0/1/2///" ).pop_right('/'      ), "");
    CHECK_EQ(S("0/1/2///" ).pop_right('/', true), "2///");

    CHECK_EQ(S("0/1//2"    ).pop_right('/'      ), "2");
    CHECK_EQ(S("0/1//2"    ).pop_right('/', true), "2");
    CHECK_EQ(S("0/1//2/"   ).pop_right('/'      ), "");
    CHECK_EQ(S("0/1//2/"   ).pop_right('/', true), "2/");
    CHECK_EQ(S("0/1//2///" ).pop_right('/'      ), "");
    CHECK_EQ(S("0/1//2///" ).pop_right('/', true), "2///");

    CHECK_EQ(S("0/1///2"    ).pop_right('/'      ), "2");
    CHECK_EQ(S("0/1///2"    ).pop_right('/', true), "2");
    CHECK_EQ(S("0/1///2/"   ).pop_right('/'      ), "");
    CHECK_EQ(S("0/1///2/"   ).pop_right('/', true), "2/");
    CHECK_EQ(S("0/1///2///" ).pop_right('/'      ), "");
    CHECK_EQ(S("0/1///2///" ).pop_right('/', true), "2///");

    CHECK_EQ(S("/0/1/2"   ).pop_right('/'      ), "2");
    CHECK_EQ(S("/0/1/2"   ).pop_right('/', true), "2");
    CHECK_EQ(S("/0/1/2/"  ).pop_right('/'      ), "");
    CHECK_EQ(S("/0/1/2/"  ).pop_right('/', true), "2/");
    CHECK_EQ(S("/0/1/2///").pop_right('/'      ), "");
    CHECK_EQ(S("/0/1/2///").pop_right('/', true), "2///");

    CHECK_EQ(S("0"        ).pop_right('/'      ), "0");
    CHECK_EQ(S("0"        ).pop_right('/', true), "0");
    CHECK_EQ(S("0/"       ).pop_right('/'      ), "");
    CHECK_EQ(S("0/"       ).pop_right('/', true), "0/");
    CHECK_EQ(S("0///"     ).pop_right('/'      ), "");
    CHECK_EQ(S("0///"     ).pop_right('/', true), "0///");

    CHECK_EQ(S("/0"       ).pop_right('/'      ), "0");
    CHECK_EQ(S("/0"       ).pop_right('/', true), "0");
    CHECK_EQ(S("/0/"      ).pop_right('/'      ), "");
    CHECK_EQ(S("/0/"      ).pop_right('/', true), "0/");
    CHECK_EQ(S("/0///"    ).pop_right('/'      ), "");
    CHECK_EQ(S("/0///"    ).pop_right('/', true), "0///");

    CHECK_EQ(S("/"        ).pop_right('/'      ), "");
    CHECK_EQ(S("/"        ).pop_right('/', true), "");
    CHECK_EQ(S("///"      ).pop_right('/'      ), "");
    CHECK_EQ(S("///"      ).pop_right('/', true), "");

    CHECK_EQ(S(""         ).pop_right('/'      ), "");
    CHECK_EQ(S(""         ).pop_right('/', true), "");

    CHECK_EQ(S("0-1-2"    ).pop_right('-'      ), "2");
    CHECK_EQ(S("0-1-2"    ).pop_right('-', true), "2");
    CHECK_EQ(S("0-1-2-"   ).pop_right('-'      ), "");
    CHECK_EQ(S("0-1-2-"   ).pop_right('-', true), "2-");
    CHECK_EQ(S("0-1-2---" ).pop_right('-'      ), "");
    CHECK_EQ(S("0-1-2---" ).pop_right('-', true), "2---");

    CHECK_EQ(S("0-1--2"    ).pop_right('-'      ), "2");
    CHECK_EQ(S("0-1--2"    ).pop_right('-', true), "2");
    CHECK_EQ(S("0-1--2-"   ).pop_right('-'      ), "");
    CHECK_EQ(S("0-1--2-"   ).pop_right('-', true), "2-");
    CHECK_EQ(S("0-1--2---" ).pop_right('-'      ), "");
    CHECK_EQ(S("0-1--2---" ).pop_right('-', true), "2---");

    CHECK_EQ(S("0-1---2"    ).pop_right('-'      ), "2");
    CHECK_EQ(S("0-1---2"    ).pop_right('-', true), "2");
    CHECK_EQ(S("0-1---2-"   ).pop_right('-'      ), "");
    CHECK_EQ(S("0-1---2-"   ).pop_right('-', true), "2-");
    CHECK_EQ(S("0-1---2---" ).pop_right('-'      ), "");
    CHECK_EQ(S("0-1---2---" ).pop_right('-', true), "2---");

    CHECK_EQ(S("-0-1-2"   ).pop_right('-'      ), "2");
    CHECK_EQ(S("-0-1-2"   ).pop_right('-', true), "2");
    CHECK_EQ(S("-0-1-2-"  ).pop_right('-'      ), "");
    CHECK_EQ(S("-0-1-2-"  ).pop_right('-', true), "2-");
    CHECK_EQ(S("-0-1-2---").pop_right('-'      ), "");
    CHECK_EQ(S("-0-1-2---").pop_right('-', true), "2---");

    CHECK_EQ(S("0"        ).pop_right('-'      ), "0");
    CHECK_EQ(S("0"        ).pop_right('-', true), "0");
    CHECK_EQ(S("0-"       ).pop_right('-'      ), "");
    CHECK_EQ(S("0-"       ).pop_right('-', true), "0-");
    CHECK_EQ(S("0---"     ).pop_right('-'      ), "");
    CHECK_EQ(S("0---"     ).pop_right('-', true), "0---");

    CHECK_EQ(S("-0"       ).pop_right('-'      ), "0");
    CHECK_EQ(S("-0"       ).pop_right('-', true), "0");
    CHECK_EQ(S("-0-"      ).pop_right('-'      ), "");
    CHECK_EQ(S("-0-"      ).pop_right('-', true), "0-");
    CHECK_EQ(S("-0---"    ).pop_right('-'      ), "");
    CHECK_EQ(S("-0---"    ).pop_right('-', true), "0---");

    CHECK_EQ(S("-"        ).pop_right('-'      ), "");
    CHECK_EQ(S("-"        ).pop_right('-', true), "");
    CHECK_EQ(S("---"      ).pop_right('-'      ), "");
    CHECK_EQ(S("---"      ).pop_right('-', true), "");

    CHECK_EQ(S(""         ).pop_right('-'      ), "");
    CHECK_EQ(S(""         ).pop_right('-', true), "");
}

TEST_CASE("substr.pop_left")
{
    using S = csubstr;

    CHECK_EQ(S("0/1/2"    ).pop_left('/'      ), "0");
    CHECK_EQ(S("0/1/2"    ).pop_left('/', true), "0");
    CHECK_EQ(S("0/1/2/"   ).pop_left('/'      ), "0");
    CHECK_EQ(S("0/1/2/"   ).pop_left('/', true), "0");
    CHECK_EQ(S("0/1/2///" ).pop_left('/'      ), "0");
    CHECK_EQ(S("0/1/2///" ).pop_left('/', true), "0");

    CHECK_EQ(S("0//1/2"    ).pop_left('/'      ), "0");
    CHECK_EQ(S("0//1/2"    ).pop_left('/', true), "0");
    CHECK_EQ(S("0//1/2/"   ).pop_left('/'      ), "0");
    CHECK_EQ(S("0//1/2/"   ).pop_left('/', true), "0");
    CHECK_EQ(S("0//1/2///" ).pop_left('/'      ), "0");
    CHECK_EQ(S("0//1/2///" ).pop_left('/', true), "0");

    CHECK_EQ(S("0///1/2"    ).pop_left('/'      ), "0");
    CHECK_EQ(S("0///1/2"    ).pop_left('/', true), "0");
    CHECK_EQ(S("0///1/2/"   ).pop_left('/'      ), "0");
    CHECK_EQ(S("0///1/2/"   ).pop_left('/', true), "0");
    CHECK_EQ(S("0///1/2///" ).pop_left('/'      ), "0");
    CHECK_EQ(S("0///1/2///" ).pop_left('/', true), "0");

    CHECK_EQ(S("/0/1/2"   ).pop_left('/'      ), "");
    CHECK_EQ(S("/0/1/2"   ).pop_left('/', true), "/0");
    CHECK_EQ(S("/0/1/2/"  ).pop_left('/'      ), "");
    CHECK_EQ(S("/0/1/2/"  ).pop_left('/', true), "/0");
    CHECK_EQ(S("/0/1/2///").pop_left('/'      ), "");
    CHECK_EQ(S("/0/1/2///").pop_left('/', true), "/0");
    CHECK_EQ(S("///0/1/2" ).pop_left('/'      ), "");
    CHECK_EQ(S("///0/1/2" ).pop_left('/', true), "///0");
    CHECK_EQ(S("///0/1/2/").pop_left('/'      ), "");
    CHECK_EQ(S("///0/1/2/").pop_left('/', true), "///0");
    CHECK_EQ(S("///0/1/2/").pop_left('/'      ), "");
    CHECK_EQ(S("///0/1/2/").pop_left('/', true), "///0");

    CHECK_EQ(S("0"        ).pop_left('/'      ), "0");
    CHECK_EQ(S("0"        ).pop_left('/', true), "0");
    CHECK_EQ(S("0/"       ).pop_left('/'      ), "0");
    CHECK_EQ(S("0/"       ).pop_left('/', true), "0");
    CHECK_EQ(S("0///"     ).pop_left('/'      ), "0");
    CHECK_EQ(S("0///"     ).pop_left('/', true), "0");

    CHECK_EQ(S("/0"       ).pop_left('/'      ), "");
    CHECK_EQ(S("/0"       ).pop_left('/', true), "/0");
    CHECK_EQ(S("/0/"      ).pop_left('/'      ), "");
    CHECK_EQ(S("/0/"      ).pop_left('/', true), "/0");
    CHECK_EQ(S("/0///"    ).pop_left('/'      ), "");
    CHECK_EQ(S("/0///"    ).pop_left('/', true), "/0");
    CHECK_EQ(S("///0///"  ).pop_left('/'      ), "");
    CHECK_EQ(S("///0///"  ).pop_left('/', true), "///0");

    CHECK_EQ(S("/"        ).pop_left('/'      ), "");
    CHECK_EQ(S("/"        ).pop_left('/', true), "");
    CHECK_EQ(S("///"      ).pop_left('/'      ), "");
    CHECK_EQ(S("///"      ).pop_left('/', true), "");

    CHECK_EQ(S(""         ).pop_left('/'      ), "");
    CHECK_EQ(S(""         ).pop_left('/', true), "");

    CHECK_EQ(S("0-1-2"    ).pop_left('-'      ), "0");
    CHECK_EQ(S("0-1-2"    ).pop_left('-', true), "0");
    CHECK_EQ(S("0-1-2-"   ).pop_left('-'      ), "0");
    CHECK_EQ(S("0-1-2-"   ).pop_left('-', true), "0");
    CHECK_EQ(S("0-1-2---" ).pop_left('-'      ), "0");
    CHECK_EQ(S("0-1-2---" ).pop_left('-', true), "0");

    CHECK_EQ(S("0--1-2"    ).pop_left('-'      ), "0");
    CHECK_EQ(S("0--1-2"    ).pop_left('-', true), "0");
    CHECK_EQ(S("0--1-2-"   ).pop_left('-'      ), "0");
    CHECK_EQ(S("0--1-2-"   ).pop_left('-', true), "0");
    CHECK_EQ(S("0--1-2---" ).pop_left('-'      ), "0");
    CHECK_EQ(S("0--1-2---" ).pop_left('-', true), "0");

    CHECK_EQ(S("0---1-2"    ).pop_left('-'      ), "0");
    CHECK_EQ(S("0---1-2"    ).pop_left('-', true), "0");
    CHECK_EQ(S("0---1-2-"   ).pop_left('-'      ), "0");
    CHECK_EQ(S("0---1-2-"   ).pop_left('-', true), "0");
    CHECK_EQ(S("0---1-2---" ).pop_left('-'      ), "0");
    CHECK_EQ(S("0---1-2---" ).pop_left('-', true), "0");

    CHECK_EQ(S("-0-1-2"   ).pop_left('-'      ), "");
    CHECK_EQ(S("-0-1-2"   ).pop_left('-', true), "-0");
    CHECK_EQ(S("-0-1-2-"  ).pop_left('-'      ), "");
    CHECK_EQ(S("-0-1-2-"  ).pop_left('-', true), "-0");
    CHECK_EQ(S("-0-1-2---").pop_left('-'      ), "");
    CHECK_EQ(S("-0-1-2---").pop_left('-', true), "-0");
    CHECK_EQ(S("---0-1-2" ).pop_left('-'      ), "");
    CHECK_EQ(S("---0-1-2" ).pop_left('-', true), "---0");
    CHECK_EQ(S("---0-1-2-").pop_left('-'      ), "");
    CHECK_EQ(S("---0-1-2-").pop_left('-', true), "---0");
    CHECK_EQ(S("---0-1-2-").pop_left('-'      ), "");
    CHECK_EQ(S("---0-1-2-").pop_left('-', true), "---0");

    CHECK_EQ(S("0"        ).pop_left('-'      ), "0");
    CHECK_EQ(S("0"        ).pop_left('-', true), "0");
    CHECK_EQ(S("0-"       ).pop_left('-'      ), "0");
    CHECK_EQ(S("0-"       ).pop_left('-', true), "0");
    CHECK_EQ(S("0---"     ).pop_left('-'      ), "0");
    CHECK_EQ(S("0---"     ).pop_left('-', true), "0");

    CHECK_EQ(S("-0"       ).pop_left('-'      ), "");
    CHECK_EQ(S("-0"       ).pop_left('-', true), "-0");
    CHECK_EQ(S("-0-"      ).pop_left('-'      ), "");
    CHECK_EQ(S("-0-"      ).pop_left('-', true), "-0");
    CHECK_EQ(S("-0---"    ).pop_left('-'      ), "");
    CHECK_EQ(S("-0---"    ).pop_left('-', true), "-0");
    CHECK_EQ(S("---0---"  ).pop_left('-'      ), "");
    CHECK_EQ(S("---0---"  ).pop_left('-', true), "---0");

    CHECK_EQ(S("-"        ).pop_left('-'      ), "");
    CHECK_EQ(S("-"        ).pop_left('-', true), "");
    CHECK_EQ(S("---"      ).pop_left('-'      ), "");
    CHECK_EQ(S("---"      ).pop_left('-', true), "");

    CHECK_EQ(S(""         ).pop_left('-'      ), "");
    CHECK_EQ(S(""         ).pop_left('-', true), "");
}

TEST_CASE("substr.gpop_left")
{
    using S = csubstr;

    CHECK_EQ(S("0/1/2"      ).gpop_left('/'      ), "0/1");
    CHECK_EQ(S("0/1/2"      ).gpop_left('/', true), "0/1");
    CHECK_EQ(S("0/1/2/"     ).gpop_left('/'      ), "0/1/2");
    CHECK_EQ(S("0/1/2/"     ).gpop_left('/', true), "0/1");
    CHECK_EQ(S("0/1/2//"    ).gpop_left('/'      ), "0/1/2/");
    CHECK_EQ(S("0/1/2//"    ).gpop_left('/', true), "0/1");
    CHECK_EQ(S("0/1/2///"   ).gpop_left('/'      ), "0/1/2//");
    CHECK_EQ(S("0/1/2///"   ).gpop_left('/', true), "0/1");

    CHECK_EQ(S("0/1//2"     ).gpop_left('/'      ), "0/1/");
    CHECK_EQ(S("0/1//2"     ).gpop_left('/', true), "0/1");
    CHECK_EQ(S("0/1//2/"    ).gpop_left('/'      ), "0/1//2");
    CHECK_EQ(S("0/1//2/"    ).gpop_left('/', true), "0/1");
    CHECK_EQ(S("0/1//2//"   ).gpop_left('/'      ), "0/1//2/");
    CHECK_EQ(S("0/1//2//"   ).gpop_left('/', true), "0/1");
    CHECK_EQ(S("0/1//2///"  ).gpop_left('/'      ), "0/1//2//");
    CHECK_EQ(S("0/1//2///"  ).gpop_left('/', true), "0/1");

    CHECK_EQ(S("0/1///2"    ).gpop_left('/'      ), "0/1//");
    CHECK_EQ(S("0/1///2"    ).gpop_left('/', true), "0/1");
    CHECK_EQ(S("0/1///2/"   ).gpop_left('/'      ), "0/1///2");
    CHECK_EQ(S("0/1///2/"   ).gpop_left('/', true), "0/1");
    CHECK_EQ(S("0/1///2//"  ).gpop_left('/'      ), "0/1///2/");
    CHECK_EQ(S("0/1///2//"  ).gpop_left('/', true), "0/1");
    CHECK_EQ(S("0/1///2///" ).gpop_left('/'      ), "0/1///2//");
    CHECK_EQ(S("0/1///2///" ).gpop_left('/', true), "0/1");

    CHECK_EQ(S("/0/1/2"     ).gpop_left('/'      ), "/0/1");
    CHECK_EQ(S("/0/1/2"     ).gpop_left('/', true), "/0/1");
    CHECK_EQ(S("/0/1/2/"    ).gpop_left('/'      ), "/0/1/2");
    CHECK_EQ(S("/0/1/2/"    ).gpop_left('/', true), "/0/1");
    CHECK_EQ(S("/0/1/2//"   ).gpop_left('/'      ), "/0/1/2/");
    CHECK_EQ(S("/0/1/2//"   ).gpop_left('/', true), "/0/1");
    CHECK_EQ(S("/0/1/2///"  ).gpop_left('/'      ), "/0/1/2//");
    CHECK_EQ(S("/0/1/2///"  ).gpop_left('/', true), "/0/1");

    CHECK_EQ(S("//0/1/2"    ).gpop_left('/'      ), "//0/1");
    CHECK_EQ(S("//0/1/2"    ).gpop_left('/', true), "//0/1");
    CHECK_EQ(S("//0/1/2/"   ).gpop_left('/'      ), "//0/1/2");
    CHECK_EQ(S("//0/1/2/"   ).gpop_left('/', true), "//0/1");
    CHECK_EQ(S("//0/1/2//"  ).gpop_left('/'      ), "//0/1/2/");
    CHECK_EQ(S("//0/1/2//"  ).gpop_left('/', true), "//0/1");
    CHECK_EQ(S("//0/1/2///" ).gpop_left('/'      ), "//0/1/2//");
    CHECK_EQ(S("//0/1/2///" ).gpop_left('/', true), "//0/1");

    CHECK_EQ(S("///0/1/2"   ).gpop_left('/'      ), "///0/1");
    CHECK_EQ(S("///0/1/2"   ).gpop_left('/', true), "///0/1");
    CHECK_EQ(S("///0/1/2/"  ).gpop_left('/'      ), "///0/1/2");
    CHECK_EQ(S("///0/1/2/"  ).gpop_left('/', true), "///0/1");
    CHECK_EQ(S("///0/1/2//" ).gpop_left('/'      ), "///0/1/2/");
    CHECK_EQ(S("///0/1/2//" ).gpop_left('/', true), "///0/1");
    CHECK_EQ(S("///0/1/2///").gpop_left('/'      ), "///0/1/2//");
    CHECK_EQ(S("///0/1/2///").gpop_left('/', true), "///0/1");


    CHECK_EQ(S("0/1"      ).gpop_left('/'      ), "0");
    CHECK_EQ(S("0/1"      ).gpop_left('/', true), "0");
    CHECK_EQ(S("0/1/"     ).gpop_left('/'      ), "0/1");
    CHECK_EQ(S("0/1/"     ).gpop_left('/', true), "0");
    CHECK_EQ(S("0/1//"    ).gpop_left('/'      ), "0/1/");
    CHECK_EQ(S("0/1//"    ).gpop_left('/', true), "0");
    CHECK_EQ(S("0/1///"   ).gpop_left('/'      ), "0/1//");
    CHECK_EQ(S("0/1///"   ).gpop_left('/', true), "0");

    CHECK_EQ(S("0//1"     ).gpop_left('/'      ), "0/");
    CHECK_EQ(S("0//1"     ).gpop_left('/', true), "0");
    CHECK_EQ(S("0//1/"    ).gpop_left('/'      ), "0//1");
    CHECK_EQ(S("0//1/"    ).gpop_left('/', true), "0");
    CHECK_EQ(S("0//1//"   ).gpop_left('/'      ), "0//1/");
    CHECK_EQ(S("0//1//"   ).gpop_left('/', true), "0");
    CHECK_EQ(S("0//1///"  ).gpop_left('/'      ), "0//1//");
    CHECK_EQ(S("0//1///"  ).gpop_left('/', true), "0");

    CHECK_EQ(S("0///1"    ).gpop_left('/'      ), "0//");
    CHECK_EQ(S("0///1"    ).gpop_left('/', true), "0");
    CHECK_EQ(S("0///1/"   ).gpop_left('/'      ), "0///1");
    CHECK_EQ(S("0///1/"   ).gpop_left('/', true), "0");
    CHECK_EQ(S("0///1//"  ).gpop_left('/'      ), "0///1/");
    CHECK_EQ(S("0///1//"  ).gpop_left('/', true), "0");
    CHECK_EQ(S("0///1///" ).gpop_left('/'      ), "0///1//");
    CHECK_EQ(S("0///1///" ).gpop_left('/', true), "0");

    CHECK_EQ(S("/0/1"      ).gpop_left('/'      ), "/0");
    CHECK_EQ(S("/0/1"      ).gpop_left('/', true), "/0");
    CHECK_EQ(S("/0/1/"     ).gpop_left('/'      ), "/0/1");
    CHECK_EQ(S("/0/1/"     ).gpop_left('/', true), "/0");
    CHECK_EQ(S("/0/1//"    ).gpop_left('/'      ), "/0/1/");
    CHECK_EQ(S("/0/1//"    ).gpop_left('/', true), "/0");
    CHECK_EQ(S("/0/1///"   ).gpop_left('/'      ), "/0/1//");
    CHECK_EQ(S("/0/1///"   ).gpop_left('/', true), "/0");

    CHECK_EQ(S("/0//1"     ).gpop_left('/'      ), "/0/");
    CHECK_EQ(S("/0//1"     ).gpop_left('/', true), "/0");
    CHECK_EQ(S("/0//1/"    ).gpop_left('/'      ), "/0//1");
    CHECK_EQ(S("/0//1/"    ).gpop_left('/', true), "/0");
    CHECK_EQ(S("/0//1//"   ).gpop_left('/'      ), "/0//1/");
    CHECK_EQ(S("/0//1//"   ).gpop_left('/', true), "/0");
    CHECK_EQ(S("/0//1///"  ).gpop_left('/'      ), "/0//1//");
    CHECK_EQ(S("/0//1///"  ).gpop_left('/', true), "/0");

    CHECK_EQ(S("/0///1"    ).gpop_left('/'      ), "/0//");
    CHECK_EQ(S("/0///1"    ).gpop_left('/', true), "/0");
    CHECK_EQ(S("/0///1/"   ).gpop_left('/'      ), "/0///1");
    CHECK_EQ(S("/0///1/"   ).gpop_left('/', true), "/0");
    CHECK_EQ(S("/0///1//"  ).gpop_left('/'      ), "/0///1/");
    CHECK_EQ(S("/0///1//"  ).gpop_left('/', true), "/0");
    CHECK_EQ(S("/0///1///" ).gpop_left('/'      ), "/0///1//");
    CHECK_EQ(S("/0///1///" ).gpop_left('/', true), "/0");

    CHECK_EQ(S("//0/1"      ).gpop_left('/'      ), "//0");
    CHECK_EQ(S("//0/1"      ).gpop_left('/', true), "//0");
    CHECK_EQ(S("//0/1/"     ).gpop_left('/'      ), "//0/1");
    CHECK_EQ(S("//0/1/"     ).gpop_left('/', true), "//0");
    CHECK_EQ(S("//0/1//"    ).gpop_left('/'      ), "//0/1/");
    CHECK_EQ(S("//0/1//"    ).gpop_left('/', true), "//0");
    CHECK_EQ(S("//0/1///"   ).gpop_left('/'      ), "//0/1//");
    CHECK_EQ(S("//0/1///"   ).gpop_left('/', true), "//0");

    CHECK_EQ(S("//0//1"     ).gpop_left('/'      ), "//0/");
    CHECK_EQ(S("//0//1"     ).gpop_left('/', true), "//0");
    CHECK_EQ(S("//0//1/"    ).gpop_left('/'      ), "//0//1");
    CHECK_EQ(S("//0//1/"    ).gpop_left('/', true), "//0");
    CHECK_EQ(S("//0//1//"   ).gpop_left('/'      ), "//0//1/");
    CHECK_EQ(S("//0//1//"   ).gpop_left('/', true), "//0");
    CHECK_EQ(S("//0//1///"  ).gpop_left('/'      ), "//0//1//");
    CHECK_EQ(S("//0//1///"  ).gpop_left('/', true), "//0");

    CHECK_EQ(S("//0///1"    ).gpop_left('/'      ), "//0//");
    CHECK_EQ(S("//0///1"    ).gpop_left('/', true), "//0");
    CHECK_EQ(S("//0///1/"   ).gpop_left('/'      ), "//0///1");
    CHECK_EQ(S("//0///1/"   ).gpop_left('/', true), "//0");
    CHECK_EQ(S("//0///1//"  ).gpop_left('/'      ), "//0///1/");
    CHECK_EQ(S("//0///1//"  ).gpop_left('/', true), "//0");
    CHECK_EQ(S("//0///1///" ).gpop_left('/'      ), "//0///1//");
    CHECK_EQ(S("//0///1///" ).gpop_left('/', true), "//0");

    CHECK_EQ(S("0"      ).gpop_left('/'      ), "");
    CHECK_EQ(S("0"      ).gpop_left('/', true), "");
    CHECK_EQ(S("0/"     ).gpop_left('/'      ), "0");
    CHECK_EQ(S("0/"     ).gpop_left('/', true), "");
    CHECK_EQ(S("0//"    ).gpop_left('/'      ), "0/");
    CHECK_EQ(S("0//"    ).gpop_left('/', true), "");
    CHECK_EQ(S("0///"   ).gpop_left('/'      ), "0//");
    CHECK_EQ(S("0///"   ).gpop_left('/', true), "");

    CHECK_EQ(S("/0"      ).gpop_left('/'      ), "");
    CHECK_EQ(S("/0"      ).gpop_left('/', true), "");
    CHECK_EQ(S("/0/"     ).gpop_left('/'      ), "/0");
    CHECK_EQ(S("/0/"     ).gpop_left('/', true), "");
    CHECK_EQ(S("/0//"    ).gpop_left('/'      ), "/0/");
    CHECK_EQ(S("/0//"    ).gpop_left('/', true), "");
    CHECK_EQ(S("/0///"   ).gpop_left('/'      ), "/0//");
    CHECK_EQ(S("/0///"   ).gpop_left('/', true), "");

    CHECK_EQ(S("//0"      ).gpop_left('/'      ), "/");
    CHECK_EQ(S("//0"      ).gpop_left('/', true), "");
    CHECK_EQ(S("//0/"     ).gpop_left('/'      ), "//0");
    CHECK_EQ(S("//0/"     ).gpop_left('/', true), "");
    CHECK_EQ(S("//0//"    ).gpop_left('/'      ), "//0/");
    CHECK_EQ(S("//0//"    ).gpop_left('/', true), "");
    CHECK_EQ(S("//0///"   ).gpop_left('/'      ), "//0//");
    CHECK_EQ(S("//0///"   ).gpop_left('/', true), "");

    CHECK_EQ(S("///0"      ).gpop_left('/'      ), "//");
    CHECK_EQ(S("///0"      ).gpop_left('/', true), "");
    CHECK_EQ(S("///0/"     ).gpop_left('/'      ), "///0");
    CHECK_EQ(S("///0/"     ).gpop_left('/', true), "");
    CHECK_EQ(S("///0//"    ).gpop_left('/'      ), "///0/");
    CHECK_EQ(S("///0//"    ).gpop_left('/', true), "");
    CHECK_EQ(S("///0///"   ).gpop_left('/'      ), "///0//");
    CHECK_EQ(S("///0///"   ).gpop_left('/', true), "");

    CHECK_EQ(S("/"        ).gpop_left('/'      ), "");
    CHECK_EQ(S("/"        ).gpop_left('/', true), "");
    CHECK_EQ(S("//"       ).gpop_left('/'      ), "/");
    CHECK_EQ(S("//"       ).gpop_left('/', true), "");
    CHECK_EQ(S("///"      ).gpop_left('/'      ), "//");
    CHECK_EQ(S("///"      ).gpop_left('/', true), "");

    CHECK_EQ(S(""         ).gpop_left('/'      ), "");
    CHECK_EQ(S(""         ).gpop_left('/', true), "");
}

TEST_CASE("substr.gpop_right")
{
    using S = csubstr;

    CHECK_EQ(S("0/1/2"      ).gpop_right('/'      ), "1/2");
    CHECK_EQ(S("0/1/2"      ).gpop_right('/', true), "1/2");
    CHECK_EQ(S("0/1/2/"     ).gpop_right('/'      ), "1/2/");
    CHECK_EQ(S("0/1/2/"     ).gpop_right('/', true), "1/2/");
    CHECK_EQ(S("0/1/2//"    ).gpop_right('/'      ), "1/2//");
    CHECK_EQ(S("0/1/2//"    ).gpop_right('/', true), "1/2//");
    CHECK_EQ(S("0/1/2///"   ).gpop_right('/'      ), "1/2///");
    CHECK_EQ(S("0/1/2///"   ).gpop_right('/', true), "1/2///");

    CHECK_EQ(S("0//1/2"     ).gpop_right('/'      ), "/1/2");
    CHECK_EQ(S("0//1/2"     ).gpop_right('/', true),  "1/2");
    CHECK_EQ(S("0//1/2/"    ).gpop_right('/'      ), "/1/2/");
    CHECK_EQ(S("0//1/2/"    ).gpop_right('/', true),  "1/2/");
    CHECK_EQ(S("0//1/2//"   ).gpop_right('/'      ), "/1/2//");
    CHECK_EQ(S("0//1/2//"   ).gpop_right('/', true),  "1/2//");
    CHECK_EQ(S("0//1/2///"  ).gpop_right('/'      ), "/1/2///");
    CHECK_EQ(S("0//1/2///"  ).gpop_right('/', true),  "1/2///");

    CHECK_EQ(S("0///1/2"     ).gpop_right('/'      ), "//1/2");
    CHECK_EQ(S("0///1/2"     ).gpop_right('/', true),   "1/2");
    CHECK_EQ(S("0///1/2/"    ).gpop_right('/'      ), "//1/2/");
    CHECK_EQ(S("0///1/2/"    ).gpop_right('/', true),   "1/2/");
    CHECK_EQ(S("0///1/2//"   ).gpop_right('/'      ), "//1/2//");
    CHECK_EQ(S("0///1/2//"   ).gpop_right('/', true),   "1/2//");
    CHECK_EQ(S("0///1/2///"  ).gpop_right('/'      ), "//1/2///");
    CHECK_EQ(S("0///1/2///"  ).gpop_right('/', true),   "1/2///");


    CHECK_EQ(S("/0/1/2"      ).gpop_right('/'      ), "0/1/2");
    CHECK_EQ(S("/0/1/2"      ).gpop_right('/', true),   "1/2");
    CHECK_EQ(S("/0/1/2/"     ).gpop_right('/'      ), "0/1/2/");
    CHECK_EQ(S("/0/1/2/"     ).gpop_right('/', true),   "1/2/");
    CHECK_EQ(S("/0/1/2//"    ).gpop_right('/'      ), "0/1/2//");
    CHECK_EQ(S("/0/1/2//"    ).gpop_right('/', true),   "1/2//");
    CHECK_EQ(S("/0/1/2///"   ).gpop_right('/'      ), "0/1/2///");
    CHECK_EQ(S("/0/1/2///"   ).gpop_right('/', true),   "1/2///");

    CHECK_EQ(S("/0//1/2"     ).gpop_right('/'      ), "0//1/2");
    CHECK_EQ(S("/0//1/2"     ).gpop_right('/', true),    "1/2");
    CHECK_EQ(S("/0//1/2/"    ).gpop_right('/'      ), "0//1/2/");
    CHECK_EQ(S("/0//1/2/"    ).gpop_right('/', true),    "1/2/");
    CHECK_EQ(S("/0//1/2//"   ).gpop_right('/'      ), "0//1/2//");
    CHECK_EQ(S("/0//1/2//"   ).gpop_right('/', true),    "1/2//");
    CHECK_EQ(S("/0//1/2///"  ).gpop_right('/'      ), "0//1/2///");
    CHECK_EQ(S("/0//1/2///"  ).gpop_right('/', true),    "1/2///");

    CHECK_EQ(S("/0///1/2"     ).gpop_right('/'      ), "0///1/2");
    CHECK_EQ(S("/0///1/2"     ).gpop_right('/', true),     "1/2");
    CHECK_EQ(S("/0///1/2/"    ).gpop_right('/'      ), "0///1/2/");
    CHECK_EQ(S("/0///1/2/"    ).gpop_right('/', true),     "1/2/");
    CHECK_EQ(S("/0///1/2//"   ).gpop_right('/'      ), "0///1/2//");
    CHECK_EQ(S("/0///1/2//"   ).gpop_right('/', true),     "1/2//");
    CHECK_EQ(S("/0///1/2///"  ).gpop_right('/'      ), "0///1/2///");
    CHECK_EQ(S("/0///1/2///"  ).gpop_right('/', true),     "1/2///");


    CHECK_EQ(S("//0/1/2"      ).gpop_right('/'      ), "/0/1/2");
    CHECK_EQ(S("//0/1/2"      ).gpop_right('/', true),    "1/2");
    CHECK_EQ(S("//0/1/2/"     ).gpop_right('/'      ), "/0/1/2/");
    CHECK_EQ(S("//0/1/2/"     ).gpop_right('/', true),    "1/2/");
    CHECK_EQ(S("//0/1/2//"    ).gpop_right('/'      ), "/0/1/2//");
    CHECK_EQ(S("//0/1/2//"    ).gpop_right('/', true),    "1/2//");
    CHECK_EQ(S("//0/1/2///"   ).gpop_right('/'      ), "/0/1/2///");
    CHECK_EQ(S("//0/1/2///"   ).gpop_right('/', true),    "1/2///");

    CHECK_EQ(S("//0//1/2"     ).gpop_right('/'      ), "/0//1/2");
    CHECK_EQ(S("//0//1/2"     ).gpop_right('/', true),     "1/2");
    CHECK_EQ(S("//0//1/2/"    ).gpop_right('/'      ), "/0//1/2/");
    CHECK_EQ(S("//0//1/2/"    ).gpop_right('/', true),     "1/2/");
    CHECK_EQ(S("//0//1/2//"   ).gpop_right('/'      ), "/0//1/2//");
    CHECK_EQ(S("//0//1/2//"   ).gpop_right('/', true),     "1/2//");
    CHECK_EQ(S("//0//1/2///"  ).gpop_right('/'      ), "/0//1/2///");
    CHECK_EQ(S("//0//1/2///"  ).gpop_right('/', true),     "1/2///");

    CHECK_EQ(S("//0///1/2"     ).gpop_right('/'      ), "/0///1/2");
    CHECK_EQ(S("//0///1/2"     ).gpop_right('/', true),     "1/2");
    CHECK_EQ(S("//0///1/2/"    ).gpop_right('/'      ), "/0///1/2/");
    CHECK_EQ(S("//0///1/2/"    ).gpop_right('/', true),     "1/2/");
    CHECK_EQ(S("//0///1/2//"   ).gpop_right('/'      ), "/0///1/2//");
    CHECK_EQ(S("//0///1/2//"   ).gpop_right('/', true),     "1/2//");
    CHECK_EQ(S("//0///1/2///"  ).gpop_right('/'      ), "/0///1/2///");
    CHECK_EQ(S("//0///1/2///"  ).gpop_right('/', true),      "1/2///");


    CHECK_EQ(S("0/1"      ).gpop_right('/'      ), "1");
    CHECK_EQ(S("0/1"      ).gpop_right('/', true), "1");
    CHECK_EQ(S("0/1/"     ).gpop_right('/'      ), "1/");
    CHECK_EQ(S("0/1/"     ).gpop_right('/', true), "1/");
    CHECK_EQ(S("0/1//"    ).gpop_right('/'      ), "1//");
    CHECK_EQ(S("0/1//"    ).gpop_right('/', true), "1//");
    CHECK_EQ(S("0/1///"   ).gpop_right('/'      ), "1///");
    CHECK_EQ(S("0/1///"   ).gpop_right('/', true), "1///");

    CHECK_EQ(S("0//1"     ).gpop_right('/'      ), "/1");
    CHECK_EQ(S("0//1"     ).gpop_right('/', true),  "1");
    CHECK_EQ(S("0//1/"    ).gpop_right('/'      ), "/1/");
    CHECK_EQ(S("0//1/"    ).gpop_right('/', true),  "1/");
    CHECK_EQ(S("0//1//"   ).gpop_right('/'      ), "/1//");
    CHECK_EQ(S("0//1//"   ).gpop_right('/', true),  "1//");
    CHECK_EQ(S("0//1///"  ).gpop_right('/'      ), "/1///");
    CHECK_EQ(S("0//1///"  ).gpop_right('/', true),  "1///");

    CHECK_EQ(S("0///1"    ).gpop_right('/'      ), "//1");
    CHECK_EQ(S("0///1"    ).gpop_right('/', true),   "1");
    CHECK_EQ(S("0///1/"   ).gpop_right('/'      ), "//1/");
    CHECK_EQ(S("0///1/"   ).gpop_right('/', true),   "1/");
    CHECK_EQ(S("0///1//"  ).gpop_right('/'      ), "//1//");
    CHECK_EQ(S("0///1//"  ).gpop_right('/', true),   "1//");
    CHECK_EQ(S("0///1///" ).gpop_right('/'      ), "//1///");
    CHECK_EQ(S("0///1///" ).gpop_right('/', true),   "1///");


    CHECK_EQ(S("/0/1"      ).gpop_right('/'      ), "0/1");
    CHECK_EQ(S("/0/1"      ).gpop_right('/', true),   "1");
    CHECK_EQ(S("/0/1/"     ).gpop_right('/'      ), "0/1/");
    CHECK_EQ(S("/0/1/"     ).gpop_right('/', true),   "1/");
    CHECK_EQ(S("/0/1//"    ).gpop_right('/'      ), "0/1//");
    CHECK_EQ(S("/0/1//"    ).gpop_right('/', true),   "1//");
    CHECK_EQ(S("/0/1///"   ).gpop_right('/'      ), "0/1///");
    CHECK_EQ(S("/0/1///"   ).gpop_right('/', true),   "1///");

    CHECK_EQ(S("/0//1"     ).gpop_right('/'      ), "0//1");
    CHECK_EQ(S("/0//1"     ).gpop_right('/', true),    "1");
    CHECK_EQ(S("/0//1/"    ).gpop_right('/'      ), "0//1/");
    CHECK_EQ(S("/0//1/"    ).gpop_right('/', true),    "1/");
    CHECK_EQ(S("/0//1//"   ).gpop_right('/'      ), "0//1//");
    CHECK_EQ(S("/0//1//"   ).gpop_right('/', true),    "1//");
    CHECK_EQ(S("/0//1///"  ).gpop_right('/'      ), "0//1///");
    CHECK_EQ(S("/0//1///"  ).gpop_right('/', true),    "1///");

    CHECK_EQ(S("/0///1"    ).gpop_right('/'      ), "0///1");
    CHECK_EQ(S("/0///1"    ).gpop_right('/', true),     "1");
    CHECK_EQ(S("/0///1/"   ).gpop_right('/'      ), "0///1/");
    CHECK_EQ(S("/0///1/"   ).gpop_right('/', true),     "1/");
    CHECK_EQ(S("/0///1//"  ).gpop_right('/'      ), "0///1//");
    CHECK_EQ(S("/0///1//"  ).gpop_right('/', true),     "1//");
    CHECK_EQ(S("/0///1///" ).gpop_right('/'      ), "0///1///");
    CHECK_EQ(S("/0///1///" ).gpop_right('/', true),     "1///");


    CHECK_EQ(S("//0/1"      ).gpop_right('/'      ), "/0/1");
    CHECK_EQ(S("//0/1"      ).gpop_right('/', true),    "1");
    CHECK_EQ(S("//0/1/"     ).gpop_right('/'      ), "/0/1/");
    CHECK_EQ(S("//0/1/"     ).gpop_right('/', true),    "1/");
    CHECK_EQ(S("//0/1//"    ).gpop_right('/'      ), "/0/1//");
    CHECK_EQ(S("//0/1//"    ).gpop_right('/', true),    "1//");
    CHECK_EQ(S("//0/1///"   ).gpop_right('/'      ), "/0/1///");
    CHECK_EQ(S("//0/1///"   ).gpop_right('/', true),    "1///");

    CHECK_EQ(S("//0//1"     ).gpop_right('/'      ), "/0//1");
    CHECK_EQ(S("//0//1"     ).gpop_right('/', true),     "1");
    CHECK_EQ(S("//0//1/"    ).gpop_right('/'      ), "/0//1/");
    CHECK_EQ(S("//0//1/"    ).gpop_right('/', true),     "1/");
    CHECK_EQ(S("//0//1//"   ).gpop_right('/'      ), "/0//1//");
    CHECK_EQ(S("//0//1//"   ).gpop_right('/', true),     "1//");
    CHECK_EQ(S("//0//1///"  ).gpop_right('/'      ), "/0//1///");
    CHECK_EQ(S("//0//1///"  ).gpop_right('/', true),     "1///");

    CHECK_EQ(S("//0///1"    ).gpop_right('/'      ), "/0///1");
    CHECK_EQ(S("//0///1"    ).gpop_right('/', true),      "1");
    CHECK_EQ(S("//0///1/"   ).gpop_right('/'      ), "/0///1/");
    CHECK_EQ(S("//0///1/"   ).gpop_right('/', true),      "1/");
    CHECK_EQ(S("//0///1//"  ).gpop_right('/'      ), "/0///1//");
    CHECK_EQ(S("//0///1//"  ).gpop_right('/', true),      "1//");
    CHECK_EQ(S("//0///1///" ).gpop_right('/'      ), "/0///1///");
    CHECK_EQ(S("//0///1///" ).gpop_right('/', true),      "1///");


    CHECK_EQ(S("0"      ).gpop_right('/'      ), "");
    CHECK_EQ(S("0"      ).gpop_right('/', true), "");
    CHECK_EQ(S("0/"     ).gpop_right('/'      ), "");
    CHECK_EQ(S("0/"     ).gpop_right('/', true), "");
    CHECK_EQ(S("0//"    ).gpop_right('/'      ), "/");
    CHECK_EQ(S("0//"    ).gpop_right('/', true), "");
    CHECK_EQ(S("0///"   ).gpop_right('/'      ), "//");
    CHECK_EQ(S("0///"   ).gpop_right('/', true), "");

    CHECK_EQ(S("/0"      ).gpop_right('/'      ), "0");
    CHECK_EQ(S("/0"      ).gpop_right('/', true), "");
    CHECK_EQ(S("/0/"     ).gpop_right('/'      ), "0/");
    CHECK_EQ(S("/0/"     ).gpop_right('/', true), "");
    CHECK_EQ(S("/0//"    ).gpop_right('/'      ), "0//");
    CHECK_EQ(S("/0//"    ).gpop_right('/', true), "");
    CHECK_EQ(S("/0///"   ).gpop_right('/'      ), "0///");
    CHECK_EQ(S("/0///"   ).gpop_right('/', true), "");

    CHECK_EQ(S("//0"      ).gpop_right('/'      ), "/0");
    CHECK_EQ(S("//0"      ).gpop_right('/', true), "");
    CHECK_EQ(S("//0/"     ).gpop_right('/'      ), "/0/");
    CHECK_EQ(S("//0/"     ).gpop_right('/', true), "");
    CHECK_EQ(S("//0//"    ).gpop_right('/'      ), "/0//");
    CHECK_EQ(S("//0//"    ).gpop_right('/', true), "");
    CHECK_EQ(S("//0///"   ).gpop_right('/'      ), "/0///");
    CHECK_EQ(S("//0///"   ).gpop_right('/', true), "");

    CHECK_EQ(S("///0"      ).gpop_right('/'      ), "//0");
    CHECK_EQ(S("///0"      ).gpop_right('/', true), "");
    CHECK_EQ(S("///0/"     ).gpop_right('/'      ), "//0/");
    CHECK_EQ(S("///0/"     ).gpop_right('/', true), "");
    CHECK_EQ(S("///0//"    ).gpop_right('/'      ), "//0//");
    CHECK_EQ(S("///0//"    ).gpop_right('/', true), "");
    CHECK_EQ(S("///0///"   ).gpop_right('/'      ), "//0///");
    CHECK_EQ(S("///0///"   ).gpop_right('/', true), "");

    CHECK_EQ(S("/"        ).gpop_right('/'      ), "");
    CHECK_EQ(S("/"        ).gpop_right('/', true), "");
    CHECK_EQ(S("//"       ).gpop_right('/'      ), "/");
    CHECK_EQ(S("//"       ).gpop_right('/', true), "");
    CHECK_EQ(S("///"      ).gpop_right('/'      ), "//");
    CHECK_EQ(S("///"      ).gpop_right('/', true), "");

    CHECK_EQ(S(""         ).gpop_right('/'      ), "");
    CHECK_EQ(S(""         ).gpop_right('/', true), "");
}

TEST_CASE("substr.basename")
{
    using S = csubstr;
    CHECK_EQ(S("0/1/2").basename(), "2");
    CHECK_EQ(S("0/1/2/").basename(), "2");
    CHECK_EQ(S("0/1/2///").basename(), "2");
    CHECK_EQ(S("/0/1/2").basename(), "2");
    CHECK_EQ(S("/0/1/2/").basename(), "2");
    CHECK_EQ(S("/0/1/2///").basename(), "2");
    CHECK_EQ(S("///0/1/2").basename(), "2");
    CHECK_EQ(S("///0/1/2/").basename(), "2");
    CHECK_EQ(S("///0/1/2///").basename(), "2");
    CHECK_EQ(S("/").basename(), "");
    CHECK_EQ(S("//").basename(), "");
    CHECK_EQ(S("///").basename(), "");
    CHECK_EQ(S("////").basename(), "");
    CHECK_EQ(S("").basename(), "");
}

TEST_CASE("substr.dirname")
{
    using S = csubstr;
    CHECK_EQ(S("0/1/2").dirname(), "0/1/");
    CHECK_EQ(S("0/1/2/").dirname(), "0/1/");
    CHECK_EQ(S("/0/1/2").dirname(), "/0/1/");
    CHECK_EQ(S("/0/1/2/").dirname(), "/0/1/");
    CHECK_EQ(S("///0/1/2").dirname(), "///0/1/");
    CHECK_EQ(S("///0/1/2/").dirname(), "///0/1/");
    CHECK_EQ(S("/0").dirname(), "/");
    CHECK_EQ(S("/").dirname(), "/");
    CHECK_EQ(S("//").dirname(), "//");
    CHECK_EQ(S("///").dirname(), "///");
    CHECK_EQ(S("////").dirname(), "////");
    CHECK_EQ(S("").dirname(), "");
}

TEST_CASE("substr.extshort")
{
    using S = csubstr;
    CHECK_EQ(S("filename.with.ext").extshort(), "ext");
    CHECK_EQ(S("filename.with.ext.").extshort(), "");
    CHECK_EQ(S(".a.b").extshort(), "b");
    CHECK_EQ(S(".a.b.").extshort(), "");
    CHECK_EQ(S(".b..").extshort(), "");
    CHECK_EQ(S("..b.").extshort(), "");
}

TEST_CASE("substr.extlong")
{
    using S = csubstr;
    CHECK_EQ(S("filename.with.ext").extlong(), "with.ext");
    CHECK_EQ(S("filename.with.ext.").extlong(), "with.ext.");
    CHECK_EQ(S(".a.b").extlong(), "a.b");
    CHECK_EQ(S(".a.b.").extlong(), "a.b.");
    CHECK_EQ(S(".b..").extlong(), "b..");
    CHECK_EQ(S("..b.").extlong(), ".b.");
}

TEST_CASE("substr.next_split")
{
#ifndef C4_UBSAN // the tests succeed, so UBSAN can take a hike.
    using S = csubstr;

    {
        S const n;
        typename S::size_type pos = 0;
        S ss;
        CHECK_EQ(n.next_split(':', &pos, &ss), false);
        CHECK_EQ(ss.empty(), true);
        CHECK_EQ(n.next_split(':', &pos, &ss), false);
        CHECK_EQ(ss.empty(), true);
        pos = 0;
        CHECK_EQ(n.next_split(',', &pos, &ss), false);
        CHECK_EQ(ss.empty(), true);
        CHECK_EQ(n.next_split(',', &pos, &ss), false);
        CHECK_EQ(ss.empty(), true);
    }

    {
        S const n("0");
        typename S::size_type pos = 0;
        S ss;
        CHECK_EQ(n.next_split(':', &pos, &ss), true);
        CHECK_EQ(ss.empty(), false);
        CHECK_EQ(n.next_split(':', &pos, &ss), false);
        CHECK_EQ(ss.empty(), true);
        CHECK_EQ(n.next_split(':', &pos, &ss), false);
        CHECK_EQ(ss.empty(), true);
        pos = 0;
        CHECK_EQ(n.next_split(',', &pos, &ss), true);
        CHECK_EQ(ss.empty(), false);
        CHECK_EQ(n.next_split(',', &pos, &ss), false);
        CHECK_EQ(ss.empty(), true);
        CHECK_EQ(n.next_split(',', &pos, &ss), false);
        CHECK_EQ(ss.empty(), true);
    }

    {
        S const n;
        typename S::size_type pos = 0;
        typename S::size_type count = 0;
        S ss;
        while(n.next_split(':', &pos, &ss))
        {
            ++count;
        }
        CHECK_EQ(count, 0);
    }

    {
        S const n("0123456");
        typename S::size_type pos = 0;
        typename S::size_type count = 0;
        S ss;
        while(n.next_split(':', &pos, &ss))
        {
            switch(count)
            {
            case 0:
                CHECK_EQ(ss.size(), n.size());
                CHECK_EQ(ss.empty(), false);
                break;
            default:
                CHECK_UNARY(false);//GTEST_FAIL();
                break;
            }
            ++count;
        }
        CHECK_EQ(count, 1);
    }

    {
        S const n("0123456:");
        typename S::size_type pos = 0;
        typename S::size_type count = 0;
        S ss;
        while(n.next_split(':', &pos, &ss))
        {
            switch(count)
            {
            case 0:
                CHECK_EQ(ss.size(), n.size()-1);
                CHECK_EQ(ss.empty(), false);
                break;
            case 1:
                CHECK_EQ(ss.size(), 0);
                CHECK_EQ(ss.empty(), true);
                break;
            default:
                CHECK_UNARY(false);//GTEST_FAIL();
                break;
            }
            ++count;
        }
        CHECK_EQ(count, 2);
    }

    {
        S const n(":0123456:");
        typename S::size_type pos = 0;
        typename S::size_type count = 0;
        S ss;
        while(n.next_split(':', &pos, &ss))
        {
            switch(count)
            {
            case 0:
                CHECK_EQ(ss.size(), 0);
                CHECK_EQ(ss.empty(), true);
                break;
            case 1:
                CHECK_EQ(ss.size(), n.size()-2);
                CHECK_EQ(ss.empty(), false);
                break;
            case 2:
                CHECK_EQ(ss.size(), 0);
                CHECK_EQ(ss.empty(), true);
                break;
            default:
                CHECK_UNARY(false);//GTEST_FAIL();
                break;
            }
            ++count;
        }
        CHECK_EQ(count, 3);
    }

    {
        S const n(":");
        typename S::size_type pos = 0;
        typename S::size_type count = 0;
        S ss;
        while(n.next_split(':', &pos, &ss))
        {
            switch(count)
            {
            case 0:
                CHECK_EQ(ss.size(), 0);
                CHECK_EQ(ss.empty(), true);
                break;
            case 1:
                CHECK_EQ(ss.size(), 0);
                CHECK_EQ(ss.empty(), true);
                break;
            default:
                CHECK_UNARY(false);//GTEST_FAIL();
                break;
            }
            ++count;
        }
        CHECK_EQ(count, 2);
    }

    {
        S const n("01:23:45:67");
        typename S::size_type pos = 0;
        typename S::size_type count = 0;
        S ss;
        while(n.next_split(':', &pos, &ss))
        {
            switch(count)
            {
            case 0:
                CHECK_EQ(ss.size(), 2);
                CHECK_EQ(ss, "01");
                CHECK_NE(ss, "01:");
                CHECK_NE(ss, ":01:");
                CHECK_NE(ss, ":01");
                break;
            case 1:
                CHECK_EQ(ss.size(), 2);
                CHECK_EQ(ss, "23");
                CHECK_NE(ss, "23:");
                CHECK_NE(ss, ":23:");
                CHECK_NE(ss, ":23");
                break;
            case 2:
                CHECK_EQ(ss.size(), 2);
                CHECK_EQ(ss, "45");
                CHECK_NE(ss, "45:");
                CHECK_NE(ss, ":45:");
                CHECK_NE(ss, ":45");
                break;
            case 3:
                CHECK_EQ(ss.size(), 2);
                CHECK_EQ(ss, "67");
                CHECK_NE(ss, "67:");
                CHECK_NE(ss, ":67:");
                CHECK_NE(ss, ":67");
                break;
            default:
                CHECK_UNARY(false);//GTEST_FAIL();
                break;
            }
            count++;
        }
        CHECK_EQ(count, 4);
    }

    {
        const S n(":01:23:45:67:");
        typename S::size_type pos = 0;
        typename S::size_type count = 0;
        S ss;
        while(n.next_split(':', &pos, &ss))
        {
            switch(count)
            {
            case 0:
                CHECK_EQ(ss.size(), 0);
                CHECK_EQ(ss.empty(), true);
                break;
            case 1:
                CHECK_EQ(ss.size(), 2);
                CHECK_EQ(ss, "01");
                CHECK_NE(ss, "01:");
                CHECK_NE(ss, ":01:");
                CHECK_NE(ss, ":01");
                break;
            case 2:
                CHECK_EQ(ss.size(), 2);
                CHECK_EQ(ss, "23");
                CHECK_NE(ss, "23:");
                CHECK_NE(ss, ":23:");
                CHECK_NE(ss, ":23");
                break;
            case 3:
                CHECK_EQ(ss.size(), 2);
                CHECK_EQ(ss, "45");
                CHECK_NE(ss, "45:");
                CHECK_NE(ss, ":45:");
                CHECK_NE(ss, ":45");
                break;
            case 4:
                CHECK_EQ(ss.size(), 2);
                CHECK_EQ(ss, "67");
                CHECK_NE(ss, "67:");
                CHECK_NE(ss, ":67:");
                CHECK_NE(ss, ":67");
                break;
            case 5:
                CHECK_EQ(ss.size(), 0);
                CHECK_EQ(ss.empty(), true);
                break;
            default:
                CHECK_UNARY(false);//GTEST_FAIL();
                break;
            }
            count++;
        }
        CHECK_EQ(count, 6);
    }

    {
        const S n("::::01:23:45:67::::");
        typename S::size_type pos = 0;
        typename S::size_type count = 0;
        S ss;
        while(n.next_split(':', &pos, &ss))
        {
            switch(count)
            {
            case 0:
                CHECK_EQ(ss.size(), 0);
                CHECK_EQ(ss.empty(), true);
                CHECK_NE(ss, "::");
                break;
            case 1:
                CHECK_EQ(ss.size(), 0);
                CHECK_EQ(ss.empty(), true);
                CHECK_NE(ss, "::");
                break;
            case 2:
                CHECK_EQ(ss.size(), 0);
                CHECK_EQ(ss.empty(), true);
                CHECK_NE(ss, "::");
                break;
            case 3:
                CHECK_EQ(ss.size(), 0);
                CHECK_EQ(ss.empty(), true);
                CHECK_NE(ss, "::");
                break;
            case 4:
                CHECK_EQ(ss.size(), 2);
                CHECK_EQ(ss, "01");
                CHECK_NE(ss, "01:");
                CHECK_NE(ss, ":01:");
                CHECK_NE(ss, ":01");
                break;
            case 5:
                CHECK_EQ(ss.size(), 2);
                CHECK_EQ(ss, "23");
                CHECK_NE(ss, "23:");
                CHECK_NE(ss, ":23:");
                CHECK_NE(ss, ":23");
                break;
            case 6:
                CHECK_EQ(ss.size(), 2);
                CHECK_EQ(ss, "45");
                CHECK_NE(ss, "45:");
                CHECK_NE(ss, ":45:");
                CHECK_NE(ss, ":45");
                break;
            case 7:
                CHECK_EQ(ss.size(), 2);
                CHECK_EQ(ss, "67");
                CHECK_NE(ss, "67:");
                CHECK_NE(ss, ":67:");
                CHECK_NE(ss, ":67");
                break;
            case 8:
                CHECK_EQ(ss.size(), 0);
                CHECK_EQ(ss.empty(), true);
                CHECK_NE(ss, "::");
                break;
            case 9:
                CHECK_EQ(ss.size(), 0);
                CHECK_EQ(ss.empty(), true);
                CHECK_NE(ss, "::");
                break;
            case 10:
                CHECK_EQ(ss.size(), 0);
                CHECK_EQ(ss.empty(), true);
                CHECK_NE(ss, "::");
                break;
            case 11:
                CHECK_EQ(ss.size(), 0);
                CHECK_EQ(ss.empty(), true);
                CHECK_NE(ss, "::");
                break;
            default:
                CHECK_UNARY(false);//GTEST_FAIL();
                break;
            }
            count++;
        }
        CHECK_EQ(count, 12);
    }
#endif
}

TEST_CASE("substr.split")
{
#ifndef C4_UBSAN // the tests succeed, so UBSAN can take a hike.
    using S = csubstr;

    {
        S const n;
        {
            auto spl = n.split(':');
            auto beg = spl.begin();
            auto end = spl.end();
            CHECK_UNARY(beg == end);
        }
    }

    {
        S const n("foo:bar:baz");
        auto spl = n.split(':');
        auto beg = spl.begin();
        auto end = spl.end();
        CHECK_EQ(beg->size(), 3);
        CHECK_EQ(end->size(), 0);
        CHECK_EQ(*beg, "foo");
        CHECK_UNARY(beg != end);
        auto it = beg;
        CHECK_EQ(it->size(), 3);
        CHECK_EQ(*it, "foo");
        CHECK_UNARY(it != end);
        CHECK_UNARY(it == beg);
        CHECK_EQ(beg->size(), 3);
        CHECK_EQ(*beg, "foo");
        CHECK_UNARY(beg != end);
        ++it;
        CHECK_EQ(it->size(), 3);
        CHECK_EQ(*it, "bar");
        CHECK_UNARY(it != end);
        CHECK_UNARY(it != beg);
        CHECK_EQ(beg->size(), 3);
        CHECK_EQ(*beg, "foo");
        CHECK_UNARY(beg != end);
        ++it;
        CHECK_EQ(it->size(), 3);
        CHECK_EQ(*it, "baz");
        CHECK_UNARY(it != end);
        CHECK_UNARY(it != beg);
        CHECK_EQ(beg->size(), 3);
        CHECK_EQ(*beg, "foo");
        CHECK_UNARY(beg != end);
        ++it;
        CHECK_EQ(it->size(), 0);
        CHECK_UNARY(it == end);
        CHECK_UNARY(it != beg);
        CHECK_EQ(beg->size(), 3);
        CHECK_EQ(*beg, "foo");
        CHECK_UNARY(beg != end);
        it = beg;
        CHECK_EQ(it->size(), 3);
        CHECK_EQ(*it, "foo");
        CHECK_UNARY(it != end);
        CHECK_UNARY(it == beg);
        CHECK_EQ(beg->size(), 3);
        CHECK_EQ(*beg, "foo");
        CHECK_UNARY(beg != end);
        it++;
        CHECK_EQ(it->size(), 3);
        CHECK_EQ(*it, "bar");
        CHECK_UNARY(it != end);
        CHECK_UNARY(it != beg);
        CHECK_EQ(beg->size(), 3);
        CHECK_EQ(*beg, "foo");
        CHECK_UNARY(beg != end);
        it++;
        CHECK_EQ(it->size(), 3);
        CHECK_EQ(*it, "baz");
        CHECK_UNARY(it != end);
        CHECK_UNARY(it != beg);
        CHECK_EQ(beg->size(), 3);
        CHECK_EQ(*beg, "foo");
        CHECK_UNARY(beg != end);
        it++;
        CHECK_EQ(it->size(), 0);
        CHECK_UNARY(it == end);
        CHECK_UNARY(it != beg);
        CHECK_EQ(beg->size(), 3);
        CHECK_EQ(*beg, "foo");
        CHECK_UNARY(beg != end);
    }

    {
        S const n("foo:bar:baz:");
        auto spl = n.split(':');
        auto beg = spl.begin();
        auto end = spl.end();
        CHECK_EQ(beg->size(), 3);
        CHECK_EQ(end->size(), 0);
        CHECK_EQ(*beg, "foo");
        CHECK_UNARY(beg != end);
        auto it = beg;
        CHECK_EQ(it->size(), 3);
        CHECK_EQ(*it, "foo");
        CHECK_UNARY(it != end);
        CHECK_UNARY(it == beg);
        CHECK_EQ(beg->size(), 3);
        CHECK_EQ(*beg, "foo");
        CHECK_UNARY(beg != end);
        ++it;
        CHECK_EQ(it->size(), 3);
        CHECK_EQ(*it, "bar");
        CHECK_UNARY(it != end);
        CHECK_UNARY(it != beg);
        CHECK_EQ(beg->size(), 3);
        CHECK_EQ(*beg, "foo");
        CHECK_UNARY(beg != end);
        ++it;
        CHECK_EQ(it->size(), 3);
        CHECK_EQ(*it, "baz");
        CHECK_UNARY(it != end);
        CHECK_UNARY(it != beg);
        CHECK_EQ(beg->size(), 3);
        CHECK_EQ(*beg, "foo");
        CHECK_UNARY(beg != end);
        ++it;
        CHECK_EQ(it->size(), 0);
        CHECK_EQ(*it, "");
        CHECK_UNARY(it != end);
        CHECK_UNARY(it != beg);
        CHECK_EQ(beg->size(), 3);
        CHECK_EQ(*beg, "foo");
        CHECK_UNARY(beg != end);
        ++it;
        CHECK_EQ(it->size(), 0);
        CHECK_UNARY(it == end);
        CHECK_UNARY(it != beg);
        CHECK_EQ(beg->size(), 3);
        CHECK_EQ(*beg, "foo");
        CHECK_UNARY(beg != end);
        //--------------------------
        it = beg;
        CHECK_EQ(it->size(), 3);
        CHECK_EQ(*it, "foo");
        CHECK_UNARY(it != end);
        CHECK_UNARY(it == beg);
        CHECK_EQ(beg->size(), 3);
        CHECK_EQ(*beg, "foo");
        CHECK_UNARY(beg != end);
        it++;
        CHECK_EQ(it->size(), 3);
        CHECK_EQ(*it, "bar");
        CHECK_UNARY(it != end);
        CHECK_UNARY(it != beg);
        CHECK_EQ(beg->size(), 3);
        CHECK_EQ(*beg, "foo");
        CHECK_UNARY(beg != end);
        it++;
        CHECK_EQ(it->size(), 3);
        CHECK_EQ(*it, "baz");
        CHECK_UNARY(it != end);
        CHECK_UNARY(it != beg);
        CHECK_EQ(beg->size(), 3);
        CHECK_EQ(*beg, "foo");
        CHECK_UNARY(beg != end);
        it++;
        CHECK_EQ(it->size(), 0);
        CHECK_EQ(*it, "");
        CHECK_UNARY(it != end);
        CHECK_UNARY(it != beg);
        CHECK_EQ(beg->size(), 3);
        CHECK_EQ(*beg, "foo");
        CHECK_UNARY(beg != end);
        it++;
        CHECK_EQ(it->size(), 0);
        CHECK_UNARY(it == end);
        CHECK_UNARY(it != beg);
        CHECK_EQ(beg->size(), 3);
        CHECK_EQ(*beg, "foo");
        CHECK_UNARY(beg != end);
    }

    {
        S const n;
        auto s = n.split(':');
        // check that multiple calls to begin() always yield the same result
        CHECK_EQ(*s.begin(), "");
        CHECK_EQ(*s.begin(), "");
        CHECK_EQ(*s.begin(), "");
        // check that multiple calls to end() always yield the same result
        auto e = s.end();
        CHECK_UNARY(s.end() == e);
        CHECK_UNARY(s.end() == e);
        //
        auto it = s.begin();
        CHECK_EQ(*it, "");
        CHECK_EQ(it->empty(), true);
        CHECK_EQ(it->size(), 0);
        ++it;
        CHECK_UNARY(it == e);
    }

    {
        S const n("01:23:45:67");
        auto s = n.split(':');
        // check that multiple calls to begin() always yield the same result
        CHECK_EQ(*s.begin(), "01");
        CHECK_EQ(*s.begin(), "01");
        CHECK_EQ(*s.begin(), "01");
        // check that multiple calls to end() always yield the same result
        auto e = s.end();
        CHECK_UNARY(s.end() == e);
        CHECK_UNARY(s.end() == e);
        CHECK_UNARY(s.end() == e);
        //
        auto it = s.begin();
        CHECK_EQ(*it, "01");
        CHECK_EQ(it->size(), 2);
        ++it;
        CHECK_EQ(*it, "23");
        CHECK_EQ(it->size(), 2);
        ++it;
        CHECK_EQ(*it, "45");
        CHECK_EQ(it->size(), 2);
        ++it;
        CHECK_EQ(*it, "67");
        CHECK_EQ(it->size(), 2);
        ++it;
        CHECK_UNARY(it == s.end());
    }

    {
        S const n;
        typename S::size_type count = 0;
        for(auto &ss : n.split(':'))
        {
            ++count;
        }
        CHECK_EQ(count, 0);
    }

    {
        S const n("0123456");
        {
            auto spl = n.split(':');
            auto beg = spl.begin();
            auto end = spl.end();
            CHECK_EQ(beg->size(), n.size());
            CHECK_EQ(end->size(), 0);
        }
        typename S::size_type count = 0;
        for(auto &ss : n.split(':'))
        {
            switch(count)
            {
            case 0:
                CHECK_EQ(ss.size(), n.size());
                CHECK_EQ(ss.empty(), false);
                break;
            }
            ++count;
        }
        CHECK_EQ(count, 1);
    }

    {
        S const n("foo:bar");
        typename S::size_type count = 0;
        for(auto &ss : n.split(':'))
        {
            switch(count)
            {
            case 0:
                CHECK_EQ(ss.size(), 3);
                CHECK_EQ(ss.empty(), false);
                CHECK_EQ(ss, "foo");
                break;
            case 1:
                CHECK_EQ(ss.size(), 3);
                CHECK_EQ(ss.empty(), false);
                CHECK_EQ(ss, "bar");
                break;
            }
            ++count;
        }
        CHECK_EQ(count, 2);
    }

    {
        S const n("0123456:");
        typename S::size_type count = 0;
        for(auto &ss : n.split(':'))
        {
            switch(count)
            {
            case 0:
                CHECK_EQ(ss.size(), n.size()-1);
                CHECK_EQ(ss.empty(), false);
                break;
            case 1:
                CHECK_EQ(ss.size(), 0);
                CHECK_EQ(ss.empty(), true);
                break;
            }
            ++count;
        }
        CHECK_EQ(count, 2);
    }

    {
        S const n(":0123456:");
        typename S::size_type count = 0;
        for(auto &ss : n.split(':'))
        {
            switch(count)
            {
            case 0:
                CHECK_EQ(ss.size(), 0);
                CHECK_EQ(ss.empty(), true);
                break;
            case 1:
                CHECK_EQ(ss.size(), n.size()-2);
                CHECK_EQ(ss.empty(), false);
                break;
            case 2:
                CHECK_EQ(ss.size(), 0);
                CHECK_EQ(ss.empty(), true);
                break;
            }
            ++count;
        }
        CHECK_EQ(count, 3);
    }

    {
        S const n(":");
        typename S::size_type count = 0;
        for(auto &ss : n.split(':'))
        {
            switch(count)
            {
            case 0:
                CHECK_EQ(ss.size(), 0);
                CHECK_EQ(ss.empty(), true);
                break;
            case 1:
                CHECK_EQ(ss.size(), 0);
                CHECK_EQ(ss.empty(), true);
                break;
            }
            ++count;
        }
        CHECK_EQ(count, 2);
    }

    {
        S const n("01:23:45:67");
        typename S::size_type count = 0;
        for(auto &ss : n.split(':'))
        {
            switch(count)
            {
            case 0:
                CHECK_EQ(ss, "01");
                CHECK_NE(ss, "01:");
                CHECK_NE(ss, ":01:");
                CHECK_NE(ss, ":01");
                break;
            case 1:
                CHECK_EQ(ss, "23");
                CHECK_NE(ss, "23:");
                CHECK_NE(ss, ":23:");
                CHECK_NE(ss, ":23");
                break;
            case 2:
                CHECK_EQ(ss, "45");
                CHECK_NE(ss, "45:");
                CHECK_NE(ss, ":45:");
                CHECK_NE(ss, ":45");
                break;
            case 3:
                CHECK_EQ(ss, "67");
                CHECK_NE(ss, "67:");
                CHECK_NE(ss, ":67:");
                CHECK_NE(ss, ":67");
                break;
            }
            count++;
        }
        CHECK_EQ(count, 4);
    }

    {
        const S n(":01:23:45:67:");
        typename S::size_type count = 0;
        for(auto &ss : n.split(':'))
        {
            switch(count)
            {
            case 0:
                CHECK_EQ(ss.size(), 0);
                CHECK_EQ(ss.empty(), true);
                break;
            case 1:
                CHECK_EQ(ss, "01");
                CHECK_NE(ss, "01:");
                CHECK_NE(ss, ":01:");
                CHECK_NE(ss, ":01");
                break;
            case 2:
                CHECK_EQ(ss, "23");
                CHECK_NE(ss, "23:");
                CHECK_NE(ss, ":23:");
                CHECK_NE(ss, ":23");
                break;
            case 3:
                CHECK_EQ(ss, "45");
                CHECK_NE(ss, "45:");
                CHECK_NE(ss, ":45:");
                CHECK_NE(ss, ":45");
                break;
            case 4:
                CHECK_EQ(ss, "67");
                CHECK_NE(ss, "67:");
                CHECK_NE(ss, ":67:");
                CHECK_NE(ss, ":67");
                break;
            case 5:
                CHECK_EQ(ss.size(), 0);
                CHECK_EQ(ss.empty(), true);
                break;
            }
            count++;
        }
        CHECK_EQ(count, 6);
    }

    {
        const S n("::::01:23:45:67::::");
        typename S::size_type count = 0;
        for(auto &ss : n.split(':'))
        {
            switch(count)
            {
            case 0:
                CHECK_EQ(ss.size(), 0);
                CHECK_EQ(ss.empty(), true);
                CHECK_NE(ss, "::");
                break;
            case 1:
                CHECK_EQ(ss.size(), 0);
                CHECK_EQ(ss.empty(), true);
                CHECK_NE(ss, "::");
                break;
            case 2:
                CHECK_EQ(ss.size(), 0);
                CHECK_EQ(ss.empty(), true);
                CHECK_NE(ss, "::");
                break;
            case 3:
                CHECK_EQ(ss.size(), 0);
                CHECK_EQ(ss.empty(), true);
                CHECK_NE(ss, "::");
                break;
            case 4:
                CHECK_EQ(ss, "01");
                CHECK_NE(ss, "01:");
                CHECK_NE(ss, ":01:");
                CHECK_NE(ss, ":01");
                break;
            case 5:
                CHECK_EQ(ss, "23");
                CHECK_NE(ss, "23:");
                CHECK_NE(ss, ":23:");
                CHECK_NE(ss, ":23");
                break;
            case 6:
                CHECK_EQ(ss, "45");
                CHECK_NE(ss, "45:");
                CHECK_NE(ss, ":45:");
                CHECK_NE(ss, ":45");
                break;
            case 7:
                CHECK_EQ(ss, "67");
                CHECK_NE(ss, "67:");
                CHECK_NE(ss, ":67:");
                CHECK_NE(ss, ":67");
                break;
            case 8:
                CHECK_EQ(ss.size(), 0);
                CHECK_EQ(ss.empty(), true);
                CHECK_NE(ss, "::");
                break;
            case 9:
                CHECK_EQ(ss.size(), 0);
                CHECK_EQ(ss.empty(), true);
                CHECK_NE(ss, "::");
                break;
            case 10:
                CHECK_EQ(ss.size(), 0);
                CHECK_EQ(ss.empty(), true);
                CHECK_NE(ss, "::");
                break;
            case 11:
                CHECK_EQ(ss.size(), 0);
                CHECK_EQ(ss.empty(), true);
                CHECK_NE(ss, "::");
                break;
            }
            count++;
        }
        CHECK_EQ(count, 12);
    }
#endif
}


//-----------------------------------------------------------------------------
TEST_CASE("substr.copy_from")
{
    char buf[128] = {0};
    substr s = buf;
    CHECK_EQ(s.size(), sizeof(buf)-1);
    CHECK_NE(s.first(3), "123");
    s.copy_from("123");
    CHECK_EQ(s.first(3), "123");
    CHECK_EQ(s.first(6), "123\0\0\0");
    s.copy_from("+++", 3);
    CHECK_EQ(s.first(6), "123+++");
    CHECK_EQ(s.first(9), "123+++\0\0\0");
    s.copy_from("456", 6);
    CHECK_EQ(s.first(9), "123+++456");
    CHECK_EQ(s.first(12), "123+++456\0\0\0");
    s.copy_from("***", 3);
    CHECK_EQ(s.first(9), "123***456");
    CHECK_EQ(s.first(12), "123***456\0\0\0");

    // make sure that it's safe to pass source strings that don't fit
    // in the remaining destination space
    substr ss = s.first(9);
    ss.copy_from("987654321", 9); // should be a no-op
    CHECK_EQ(s.first(12), "123***456\0\0\0");
    ss.copy_from("987654321", 6);
    CHECK_EQ(s.first(12), "123***987\0\0\0");
    ss.copy_from("987654321", 3);
    CHECK_EQ(s.first(12), "123987654\0\0\0");
    ss.first(3).copy_from("987654321");
    CHECK_EQ(s.first(12), "987987654\0\0\0");
}


//-----------------------------------------------------------------------------
void do_test_reverse(substr s, csubstr orig, csubstr expected)
{
    CHECK_EQ(s, orig);
    s.reverse();
    CHECK_EQ(s, expected);
    s.reverse();
    CHECK_EQ(s, orig);
    //
    CHECK_EQ(s, orig);
    s.reverse_sub(0, s.len);
    CHECK_EQ(s, expected);
    s.reverse_sub(0, s.len);
    CHECK_EQ(s, orig);
    //
    CHECK_EQ(s, orig);
    s.reverse_range(0, s.len);
    CHECK_EQ(s, expected);
    s.reverse_range(0, s.len);
    CHECK_EQ(s, orig);
}

TEST_CASE("substr.reverse")
{
    char buf[] = "0123456789";
    do_test_reverse(buf, "0123456789", "9876543210");
    do_test_reverse(buf, "0123456789", "9876543210");

    // in the middle
    substr s = buf;
    s.sub(2, 2).reverse();
    CHECK_EQ(s, "0132456789");
    s.sub(2, 2).reverse();
    CHECK_EQ(s, "0123456789");

    s.sub(4, 2).reverse();
    CHECK_EQ(s, "0123546789");
    s.sub(4, 2).reverse();
    CHECK_EQ(s, "0123456789");

    // at the beginning
    s.first(3).reverse();
    CHECK_EQ(s, "2103456789");
    s.first(3).reverse();
    CHECK_EQ(s, "0123456789");

    // at the end
    s.last(3).reverse();
    CHECK_EQ(s, "0123456987");
    s.last(3).reverse();
    CHECK_EQ(s, "0123456789");
}


TEST_CASE("substr.reverse_sub")
{
    char buf[] = "0123456789";
    substr s = buf;

    s.reverse_sub(0, s.len);
    CHECK_EQ(s, "9876543210");
    s.reverse_sub(0, s.len);
    CHECK_EQ(s, "0123456789");

    s.reverse_sub(0, 0);
    CHECK_EQ(s, "0123456789");
    s.reverse_sub(s.len, 0);
    CHECK_EQ(s, "0123456789");

    s.reverse_sub(1, 3);
    CHECK_EQ(s, "0321456789");
    s.reverse_sub(1, 3);
    CHECK_EQ(s, "0123456789");
}

TEST_CASE("substr.reverse_range")
{
    char buf[] = "0123456789";
    substr s = buf;

    s.reverse_range(0, s.len);
    CHECK_EQ(s, "9876543210");
    s.reverse_range(0, s.len);
    CHECK_EQ(s, "0123456789");

    s.reverse_range(0, 0);
    CHECK_EQ(s, "0123456789");
    s.reverse_range(s.len, s.len);
    CHECK_EQ(s, "0123456789");

    s.reverse_range(1, 3);
    CHECK_EQ(s, "0213456789");
    s.reverse_range(1, 3);
    CHECK_EQ(s, "0123456789");
}


//-----------------------------------------------------------------------------
TEST_CASE("substr.erase")
{
    char buf[] = "0123456789";

    substr s = buf;
    CHECK_EQ(s.len, s.size());
    CHECK_EQ(s.len, 10);
    CHECK_EQ(s, "0123456789");

    substr ss = s.first(6);
    CHECK_EQ(ss.len, 6);
    for(size_t i = 0; i <= ss.len; ++i)
    {
        ss.erase(i, 0); // must be a no-op
        CHECK_EQ(s, "0123456789");
        ss.erase_range(i, i); // must be a no-op
        CHECK_EQ(s, "0123456789");
        ss.erase(ss.len-i, i); // must be a no-op
        CHECK_EQ(s, "0123456789");
    }

    substr r;
    ss = ss.erase(0, 1);
    CHECK_EQ(ss.len, 5);
    CHECK_EQ(ss, "12345");
    CHECK_EQ(s, "1234556789");
    ss = ss.erase(0, 2);
    CHECK_EQ(ss.len, 3);
    CHECK_EQ(ss, "345");
    CHECK_EQ(s, "3454556789");

    csubstr s55 = s.sub(4, 2);
    ss = s.erase(s55);
    CHECK_EQ(s, "3454678989");
}


//-----------------------------------------------------------------------------
TEST_CASE("substr.replace")
{
    char buf[] = "0.1.2.3.4.5.6.7.8.9";

    substr s = buf;

    auto ret = s.replace('+', '.');
    CHECK_EQ(ret, 0);

    ret = s.replace('.', '.', s.len);
    CHECK_EQ(s, "0.1.2.3.4.5.6.7.8.9");
    CHECK_EQ(ret, 0);
    ret = s.replace('.', '.');
    CHECK_EQ(ret, 9);
    CHECK_EQ(s, "0.1.2.3.4.5.6.7.8.9");

    ret = s.replace('.', '+', s.len);
    CHECK_EQ(s, "0.1.2.3.4.5.6.7.8.9");
    CHECK_EQ(ret, 0);
    ret = s.replace('.', '+');
    CHECK_EQ(ret, 9);
    CHECK_EQ(s, "0+1+2+3+4+5+6+7+8+9");

    ret = s.replace("16", '.', s.len);
    CHECK_EQ(s, "0+1+2+3+4+5+6+7+8+9");
    CHECK_EQ(ret, 0);
    ret = s.replace("16", '.');
    CHECK_EQ(ret, 2);
    CHECK_EQ(s, "0+.+2+3+4+5+.+7+8+9");
    ret = s.replace("3+2", '_');
    CHECK_EQ(ret, 11);
    CHECK_EQ(s, "0_._____4_5_._7_8_9");

    // must accept empty string
    ret = s.sub(0, 0).replace('0', '1');
    CHECK_EQ(ret, 0);
    CHECK_EQ(s, "0_._____4_5_._7_8_9");
    ret = s.sub(0, 0).replace("0", '1');
    CHECK_EQ(ret, 0);
    CHECK_EQ(s, "0_._____4_5_._7_8_9");
}

TEST_CASE("substr.replace_all")
{
    char buf[] = "0.1.2.3.4.5.6.7.8.9";
    std::string tmp, out("0+1+2+3+4+5+6+7+8+9");

    // must accept empty string
    substr(buf).sub(0, 0).replace_all(to_substr(tmp), "0", "X");
    CHECK_EQ(csubstr(buf), "0.1.2.3.4.5.6.7.8.9");

    substr r;
    auto replall = [&](csubstr pattern, csubstr repl) -> substr {
                       tmp = out;
                       csubstr rtmp = to_csubstr(tmp);
                       out.resize(128);
                       substr dst = to_substr(out);
                       size_t sz = rtmp.replace_all(dst, pattern, repl);
                       CHECK_LE(sz, out.size());
                       out.resize(sz);
                       return dst.first(sz);
                   };
    r = replall("0+1", "0+++++1");
    // the result must be a view of out
    CHECK_FALSE(r.empty());
    CHECK_FALSE(out.empty());
    CHECK_EQ(r.size(), out.size());
    CHECK_EQ(r.front(), out.front());
    CHECK_EQ(r.back(), out.back());
    CHECK_EQ(r, "0+++++1+2+3+4+5+6+7+8+9");

    r = replall("+", "");
    CHECK_EQ(r, "0123456789");

    r = replall("+", "");
    CHECK_EQ(r, "0123456789"); // must not change

    r = replall("0123456789", "9876543210");
    CHECK_EQ(r, "9876543210");

    r = replall("987", ".");
    CHECK_EQ(r, ".6543210");

    r = replall("210", ".");
    CHECK_EQ(r, ".6543.");

    r = replall("6543", ":");
    CHECK_EQ(r, ".:.");

    r = replall(".:.", "");
    CHECK_EQ(r, "");
}

TEST_CASE("substr.short_integer")
{
    char buf[] = "-";
    CHECK_FALSE(substr(buf).is_integer());
    CHECK_FALSE(csubstr("-").is_integer());
    CHECK_FALSE(csubstr("+").is_integer());
}

} // namespace c4

#include "c4/libtest/supprwarn_pop.hpp"
