duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

substr.hpp (74879B)


      1 #ifndef _C4_SUBSTR_HPP_
      2 #define _C4_SUBSTR_HPP_
      3 
      4 /** @file substr.hpp read+write string views */
      5 
      6 #include <string.h>
      7 #include <ctype.h>
      8 #include <type_traits>
      9 
     10 #include "c4/config.hpp"
     11 #include "c4/error.hpp"
     12 #include "c4/substr_fwd.hpp"
     13 
     14 #ifdef __clang__
     15 #   pragma clang diagnostic push
     16 #   pragma clang diagnostic ignored "-Wold-style-cast"
     17 #elif defined(__GNUC__)
     18 #   pragma GCC diagnostic push
     19 #   pragma GCC diagnostic ignored "-Wtype-limits" // disable warnings on size_t>=0, used heavily in assertions below. These assertions are a preparation step for providing the index type as a template parameter.
     20 #   pragma GCC diagnostic ignored "-Wuseless-cast"
     21 #   pragma GCC diagnostic ignored "-Wold-style-cast"
     22 #endif
     23 
     24 
     25 namespace c4 {
     26 
     27 
     28 //-----------------------------------------------------------------------------
     29 //-----------------------------------------------------------------------------
     30 //-----------------------------------------------------------------------------
     31 
     32 namespace detail {
     33 
     34 template<typename C>
     35 static inline void _do_reverse(C *C4_RESTRICT first, C *C4_RESTRICT last)
     36 {
     37     while(last > first)
     38     {
     39         C tmp = *last;
     40         *last-- = *first;
     41         *first++ = tmp;
     42     }
     43 }
     44 
     45 } // namespace detail
     46 
     47 
     48 //-----------------------------------------------------------------------------
     49 //-----------------------------------------------------------------------------
     50 //-----------------------------------------------------------------------------
     51 
     52 // utility macros to deuglify SFINAE code; undefined after the class.
     53 // https://stackoverflow.com/questions/43051882/how-to-disable-a-class-member-funrtion-for-certain-template-types
     54 #define C4_REQUIRE_RW(ret_type) \
     55     template <typename U=C> \
     56     typename std::enable_if< ! std::is_const<U>::value, ret_type>::type
     57 
     58 
     59 /** a non-owning string-view, consisting of a character pointer
     60  * and a length.
     61  *
     62  * @note The pointer is explicitly restricted.
     63  *
     64  * @see to_substr()
     65  * @see to_csubstr()
     66  */
     67 template<class C>
     68 struct C4CORE_EXPORT basic_substring
     69 {
     70 public:
     71 
     72     /** a restricted pointer to the first character of the substring */
     73     C * C4_RESTRICT str;
     74     /** the length of the substring */
     75     size_t          len;
     76 
     77 public:
     78 
     79     /** @name Types */
     80     /** @{ */
     81 
     82     using  CC  = typename std::add_const<C>::type;     //!< CC=const char
     83     using NCC_ = typename std::remove_const<C>::type; //!< NCC_=non const char
     84 
     85     using ro_substr = basic_substring<CC>;
     86     using rw_substr = basic_substring<NCC_>;
     87 
     88     using char_type = C;
     89     using size_type = size_t;
     90 
     91     using iterator = C*;
     92     using const_iterator = CC*;
     93 
     94     enum : size_t { npos = (size_t)-1, NONE = (size_t)-1 };
     95 
     96     /// convert automatically to substring of const C
     97     template<class U=C>
     98     C4_ALWAYS_INLINE operator typename std::enable_if<!std::is_const<U>::value, ro_substr const&>::type () const noexcept
     99     {
    100         return *(ro_substr const*)this; // don't call the str+len ctor because it does a check
    101     }
    102 
    103     /** @} */
    104 
    105 public:
    106 
    107     /** @name Default construction and assignment */
    108     /** @{ */
    109 
    110     C4_ALWAYS_INLINE constexpr basic_substring() noexcept : str(), len() {}
    111 
    112     C4_ALWAYS_INLINE basic_substring(basic_substring const&) noexcept = default;
    113     C4_ALWAYS_INLINE basic_substring(basic_substring     &&) noexcept = default;
    114     C4_ALWAYS_INLINE basic_substring(std::nullptr_t) noexcept : str(nullptr), len(0) {}
    115 
    116     C4_ALWAYS_INLINE basic_substring& operator= (basic_substring const&) noexcept = default;
    117     C4_ALWAYS_INLINE basic_substring& operator= (basic_substring     &&) noexcept = default;
    118     C4_ALWAYS_INLINE basic_substring& operator= (std::nullptr_t) noexcept { str = nullptr; len = 0; return *this; }
    119 
    120     C4_ALWAYS_INLINE void clear() noexcept { str = nullptr; len = 0; }
    121 
    122     /** @} */
    123 
    124 public:
    125 
    126     /** @name Construction and assignment from characters with the same type */
    127     /** @{ */
    128 
    129     /** Construct from an array.
    130      * @warning the input string need not be zero terminated, but the
    131      * length is taken as if the string was zero terminated */
    132     template<size_t N>
    133     C4_ALWAYS_INLINE constexpr basic_substring(C (&s_)[N]) noexcept : str(s_), len(N-1) {}
    134     /** Construct from a pointer and length.
    135      * @warning the input string need not be zero terminated. */
    136     C4_ALWAYS_INLINE basic_substring(C *s_, size_t len_) noexcept : str(s_), len(len_) { C4_ASSERT(str || !len_); }
    137     /** Construct from two pointers.
    138      * @warning the end pointer MUST BE larger than or equal to the begin pointer
    139      * @warning the input string need not be zero terminated */
    140     C4_ALWAYS_INLINE basic_substring(C *beg_, C *end_) noexcept : str(beg_), len(static_cast<size_t>(end_ - beg_)) { C4_ASSERT(end_ >= beg_); }
    141     /** Construct from a C-string (zero-terminated string)
    142      * @warning the input string MUST BE zero terminated.
    143      * @warning will call strlen()
    144      * @note this overload uses SFINAE to prevent it from overriding the array ctor
    145      * @see For a more detailed explanation on why the plain overloads cannot
    146      * coexist, see http://cplusplus.bordoon.com/specializeForCharacterArrays.html */
    147     template<class U, typename std::enable_if<std::is_same<U, C*>::value || std::is_same<U, NCC_*>::value, int>::type=0>
    148     C4_ALWAYS_INLINE basic_substring(U s_) noexcept : str(s_), len(s_ ? strlen(s_) : 0) {}
    149 
    150     /** Assign from an array.
    151      * @warning the input string need not be zero terminated, but the
    152      * length is taken as if the string was zero terminated */
    153     template<size_t N>
    154     C4_ALWAYS_INLINE void assign(C (&s_)[N]) noexcept { str = (s_); len = (N-1); }
    155     /** Assign from a pointer and length.
    156      * @warning the input string need not be zero terminated. */
    157     C4_ALWAYS_INLINE void assign(C *s_, size_t len_) noexcept { str = s_; len = len_; C4_ASSERT(str || !len_); }
    158     /** Assign from two pointers.
    159      * @warning the end pointer MUST BE larger than or equal to the begin pointer
    160      * @warning the input string need not be zero terminated. */
    161     C4_ALWAYS_INLINE void assign(C *beg_, C *end_) noexcept { C4_ASSERT(end_ >= beg_); str = (beg_); len = static_cast<size_t>(end_ - beg_); }
    162     /** Assign from a C-string (zero-terminated string)
    163      * @warning the input string must be zero terminated.
    164      * @warning will call strlen()
    165      * @note this overload uses SFINAE to prevent it from overriding the array ctor
    166      * @see For a more detailed explanation on why the plain overloads cannot
    167      * coexist, see http://cplusplus.bordoon.com/specializeForCharacterArrays.html */
    168     template<class U, typename std::enable_if<std::is_same<U, C*>::value || std::is_same<U, NCC_*>::value, int>::type=0>
    169     C4_ALWAYS_INLINE void assign(U s_) noexcept { str = (s_); len = (s_ ? strlen(s_) : 0); }
    170 
    171     /** Assign from an array.
    172      * @warning the input string need not be zero terminated. */
    173     template<size_t N>
    174     C4_ALWAYS_INLINE basic_substring& operator= (C (&s_)[N]) noexcept { str = (s_); len = (N-1); return *this; }
    175     /** Assign from a C-string (zero-terminated string)
    176      * @warning the input string MUST BE zero terminated.
    177      * @warning will call strlen()
    178      * @note this overload uses SFINAE to prevent it from overriding the array ctor
    179      * @see For a more detailed explanation on why the plain overloads cannot
    180      * coexist, see http://cplusplus.bordoon.com/specializeForCharacterArrays.html */
    181     template<class U, typename std::enable_if<std::is_same<U, C*>::value || std::is_same<U, NCC_*>::value, int>::type=0>
    182     C4_ALWAYS_INLINE basic_substring& operator= (U s_) noexcept { str = s_; len = s_ ? strlen(s_) : 0; return *this; }
    183 
    184     /** @} */
    185 
    186 public:
    187 
    188     /** @name Standard accessor methods */
    189     /** @{ */
    190 
    191     C4_ALWAYS_INLINE C4_PURE bool   has_str()   const noexcept { return ! empty() && str[0] != C(0); }
    192     C4_ALWAYS_INLINE C4_PURE bool   empty()     const noexcept { return (len == 0 || str == nullptr); }
    193     C4_ALWAYS_INLINE C4_PURE bool   not_empty() const noexcept { return (len != 0 && str != nullptr); }
    194     C4_ALWAYS_INLINE C4_PURE size_t size()      const noexcept { return len; }
    195 
    196     C4_ALWAYS_INLINE C4_PURE iterator begin() noexcept { return str; }
    197     C4_ALWAYS_INLINE C4_PURE iterator end  () noexcept { return str + len; }
    198 
    199     C4_ALWAYS_INLINE C4_PURE const_iterator begin() const noexcept { return str; }
    200     C4_ALWAYS_INLINE C4_PURE const_iterator end  () const noexcept { return str + len; }
    201 
    202     C4_ALWAYS_INLINE C4_PURE C      * data()       noexcept { return str; }
    203     C4_ALWAYS_INLINE C4_PURE C const* data() const noexcept { return str; }
    204 
    205     C4_ALWAYS_INLINE C4_PURE C      & operator[] (size_t i)       noexcept { C4_ASSERT(i >= 0 && i < len); return str[i]; }
    206     C4_ALWAYS_INLINE C4_PURE C const& operator[] (size_t i) const noexcept { C4_ASSERT(i >= 0 && i < len); return str[i]; }
    207 
    208     C4_ALWAYS_INLINE C4_PURE C      & front()       noexcept { C4_ASSERT(len > 0 && str != nullptr); return *str; }
    209     C4_ALWAYS_INLINE C4_PURE C const& front() const noexcept { C4_ASSERT(len > 0 && str != nullptr); return *str; }
    210 
    211     C4_ALWAYS_INLINE C4_PURE C      & back()       noexcept { C4_ASSERT(len > 0 && str != nullptr); return *(str + len - 1); }
    212     C4_ALWAYS_INLINE C4_PURE C const& back() const noexcept { C4_ASSERT(len > 0 && str != nullptr); return *(str + len - 1); }
    213 
    214     /** @} */
    215 
    216 public:
    217 
    218     /** @name Comparison methods */
    219     /** @{ */
    220 
    221     C4_PURE int compare(C const c) const noexcept
    222     {
    223         C4_XASSERT((str != nullptr) || len == 0);
    224         if(C4_LIKELY(str != nullptr && len > 0))
    225             return (*str != c) ? *str - c : (static_cast<int>(len) - 1);
    226         else
    227             return -1;
    228     }
    229 
    230     C4_PURE int compare(const char *C4_RESTRICT that, size_t sz) const noexcept
    231     {
    232         C4_XASSERT(that || sz  == 0);
    233         C4_XASSERT(str  || len == 0);
    234         if(C4_LIKELY(str && that))
    235         {
    236             {
    237                 const size_t min = len < sz ? len : sz;
    238                 for(size_t i = 0; i < min; ++i)
    239                     if(str[i] != that[i])
    240                         return str[i] < that[i] ? -1 : 1;
    241             }
    242             if(len < sz)
    243                 return -1;
    244             else if(len == sz)
    245                 return 0;
    246             else
    247                 return 1;
    248         }
    249         else if(len == sz)
    250         {
    251             C4_XASSERT(len == 0 && sz == 0);
    252             return 0;
    253         }
    254         return len < sz ? -1 : 1;
    255     }
    256 
    257     C4_ALWAYS_INLINE C4_PURE int compare(ro_substr const that) const noexcept { return this->compare(that.str, that.len); }
    258 
    259     C4_ALWAYS_INLINE C4_PURE bool operator== (std::nullptr_t) const noexcept { return str == nullptr; }
    260     C4_ALWAYS_INLINE C4_PURE bool operator!= (std::nullptr_t) const noexcept { return str != nullptr; }
    261 
    262     C4_ALWAYS_INLINE C4_PURE bool operator== (C const c) const noexcept { return this->compare(c) == 0; }
    263     C4_ALWAYS_INLINE C4_PURE bool operator!= (C const c) const noexcept { return this->compare(c) != 0; }
    264     C4_ALWAYS_INLINE C4_PURE bool operator<  (C const c) const noexcept { return this->compare(c) <  0; }
    265     C4_ALWAYS_INLINE C4_PURE bool operator>  (C const c) const noexcept { return this->compare(c) >  0; }
    266     C4_ALWAYS_INLINE C4_PURE bool operator<= (C const c) const noexcept { return this->compare(c) <= 0; }
    267     C4_ALWAYS_INLINE C4_PURE bool operator>= (C const c) const noexcept { return this->compare(c) >= 0; }
    268 
    269     template<class U> C4_ALWAYS_INLINE C4_PURE bool operator== (basic_substring<U> const that) const noexcept { return this->compare(that) == 0; }
    270     template<class U> C4_ALWAYS_INLINE C4_PURE bool operator!= (basic_substring<U> const that) const noexcept { return this->compare(that) != 0; }
    271     template<class U> C4_ALWAYS_INLINE C4_PURE bool operator<  (basic_substring<U> const that) const noexcept { return this->compare(that) <  0; }
    272     template<class U> C4_ALWAYS_INLINE C4_PURE bool operator>  (basic_substring<U> const that) const noexcept { return this->compare(that) >  0; }
    273     template<class U> C4_ALWAYS_INLINE C4_PURE bool operator<= (basic_substring<U> const that) const noexcept { return this->compare(that) <= 0; }
    274     template<class U> C4_ALWAYS_INLINE C4_PURE bool operator>= (basic_substring<U> const that) const noexcept { return this->compare(that) >= 0; }
    275 
    276     template<size_t N> C4_ALWAYS_INLINE C4_PURE bool operator== (const char (&that)[N]) const noexcept { return this->compare(that, N-1) == 0; }
    277     template<size_t N> C4_ALWAYS_INLINE C4_PURE bool operator!= (const char (&that)[N]) const noexcept { return this->compare(that, N-1) != 0; }
    278     template<size_t N> C4_ALWAYS_INLINE C4_PURE bool operator<  (const char (&that)[N]) const noexcept { return this->compare(that, N-1) <  0; }
    279     template<size_t N> C4_ALWAYS_INLINE C4_PURE bool operator>  (const char (&that)[N]) const noexcept { return this->compare(that, N-1) >  0; }
    280     template<size_t N> C4_ALWAYS_INLINE C4_PURE bool operator<= (const char (&that)[N]) const noexcept { return this->compare(that, N-1) <= 0; }
    281     template<size_t N> C4_ALWAYS_INLINE C4_PURE bool operator>= (const char (&that)[N]) const noexcept { return this->compare(that, N-1) >= 0; }
    282 
    283     /** @} */
    284 
    285 public:
    286 
    287     /** @name Sub-selection methods */
    288     /** @{ */
    289 
    290     /** true if *this is a substring of that (ie, from the same buffer) */
    291     C4_ALWAYS_INLINE C4_PURE bool is_sub(ro_substr const that) const noexcept
    292     {
    293         return that.is_super(*this);
    294     }
    295 
    296     /** true if that is a substring of *this (ie, from the same buffer) */
    297     C4_ALWAYS_INLINE C4_PURE bool is_super(ro_substr const that) const noexcept
    298     {
    299         if(C4_LIKELY(len > 0))
    300             return that.str >= str && that.str+that.len <= str+len;
    301         else
    302             return that.len == 0 && that.str == str && str != nullptr;
    303     }
    304 
    305     /** true if there is overlap of at least one element between that and *this */
    306     C4_ALWAYS_INLINE C4_PURE bool overlaps(ro_substr const that) const noexcept
    307     {
    308         // thanks @timwynants
    309         return that.str+that.len > str && that.str < str+len;
    310     }
    311 
    312 public:
    313 
    314     /** return [first,len[ */
    315     C4_ALWAYS_INLINE C4_PURE basic_substring sub(size_t first) const noexcept
    316     {
    317         C4_ASSERT(first >= 0 && first <= len);
    318         return basic_substring(str + first, len - first);
    319     }
    320 
    321     /** return [first,first+num[. If num==npos, return [first,len[ */
    322     C4_ALWAYS_INLINE C4_PURE basic_substring sub(size_t first, size_t num) const noexcept
    323     {
    324         C4_ASSERT(first >= 0 && first <= len);
    325         C4_ASSERT((num >= 0 && num <= len) || (num == npos));
    326         size_t rnum = num != npos ? num : len - first;
    327         C4_ASSERT((first >= 0 && first + rnum <= len) || (num == 0));
    328         return basic_substring(str + first, rnum);
    329     }
    330 
    331     /** return [first,last[. If last==npos, return [first,len[ */
    332     C4_ALWAYS_INLINE C4_PURE basic_substring range(size_t first, size_t last=npos) const noexcept
    333     {
    334         C4_ASSERT(first >= 0 && first <= len);
    335         last = last != npos ? last : len;
    336         C4_ASSERT(first <= last);
    337         C4_ASSERT(last  >= 0 && last  <= len);
    338         return basic_substring(str + first, last - first);
    339     }
    340 
    341     /** return the first @p num elements: [0,num[*/
    342     C4_ALWAYS_INLINE C4_PURE basic_substring first(size_t num) const noexcept
    343     {
    344         C4_ASSERT(num <= len || num == npos);
    345         return basic_substring(str, num != npos ? num : len);
    346     }
    347 
    348     /** return the last @num elements: [len-num,len[*/
    349     C4_ALWAYS_INLINE C4_PURE basic_substring last(size_t num) const noexcept
    350     {
    351         C4_ASSERT(num <= len || num == npos);
    352         return num != npos ?
    353             basic_substring(str + len - num, num) :
    354             *this;
    355     }
    356 
    357     /** offset from the ends: return [left,len-right[ ; ie, trim a
    358         number of characters from the left and right. This is
    359         equivalent to python's negative list indices. */
    360     C4_ALWAYS_INLINE C4_PURE basic_substring offs(size_t left, size_t right) const noexcept
    361     {
    362         C4_ASSERT(left  >= 0 && left  <= len);
    363         C4_ASSERT(right >= 0 && right <= len);
    364         C4_ASSERT(left  <= len - right + 1);
    365         return basic_substring(str + left, len - right - left);
    366     }
    367 
    368     /** return [0, pos[ . Same as .first(pos), but provided for compatibility with .right_of() */
    369     C4_ALWAYS_INLINE C4_PURE basic_substring left_of(size_t pos) const noexcept
    370     {
    371         C4_ASSERT(pos <= len || pos == npos);
    372         return (pos != npos) ?
    373             basic_substring(str, pos) :
    374             *this;
    375     }
    376 
    377     /** return [0, pos+include_pos[ . Same as .first(pos+1), but provided for compatibility with .right_of() */
    378     C4_ALWAYS_INLINE C4_PURE basic_substring left_of(size_t pos, bool include_pos) const noexcept
    379     {
    380         C4_ASSERT(pos <= len || pos == npos);
    381         return (pos != npos) ?
    382             basic_substring(str, pos+include_pos) :
    383             *this;
    384     }
    385 
    386     /** return [pos+1, len[ */
    387     C4_ALWAYS_INLINE C4_PURE basic_substring right_of(size_t pos) const noexcept
    388     {
    389         C4_ASSERT(pos <= len || pos == npos);
    390         return (pos != npos) ?
    391             basic_substring(str + (pos + 1), len - (pos + 1)) :
    392             basic_substring(str + len, size_t(0));
    393     }
    394 
    395     /** return [pos+!include_pos, len[ */
    396     C4_ALWAYS_INLINE C4_PURE basic_substring right_of(size_t pos, bool include_pos) const noexcept
    397     {
    398         C4_ASSERT(pos <= len || pos == npos);
    399         return (pos != npos) ?
    400             basic_substring(str + (pos + !include_pos), len - (pos + !include_pos)) :
    401             basic_substring(str + len, size_t(0));
    402     }
    403 
    404 public:
    405 
    406     /** given @p subs a substring of the current string, get the
    407      * portion of the current string to the left of it */
    408     C4_ALWAYS_INLINE C4_PURE basic_substring left_of(ro_substr const subs) const noexcept
    409     {
    410         C4_ASSERT(is_super(subs) || subs.empty());
    411         auto ssb = subs.begin();
    412         auto b = begin();
    413         auto e = end();
    414         if(ssb >= b && ssb <= e)
    415             return sub(0, static_cast<size_t>(ssb - b));
    416         else
    417             return sub(0, 0);
    418     }
    419 
    420     /** given @p subs a substring of the current string, get the
    421      * portion of the current string to the right of it */
    422     C4_ALWAYS_INLINE C4_PURE basic_substring right_of(ro_substr const subs) const noexcept
    423     {
    424         C4_ASSERT(is_super(subs) || subs.empty());
    425         auto sse = subs.end();
    426         auto b = begin();
    427         auto e = end();
    428         if(sse >= b && sse <= e)
    429             return sub(static_cast<size_t>(sse - b), static_cast<size_t>(e - sse));
    430         else
    431             return sub(0, 0);
    432     }
    433 
    434     /** @} */
    435 
    436 public:
    437 
    438     /** @name Removing characters (trim()) / patterns (strip()) from the tips of the string */
    439     /** @{ */
    440 
    441     /** trim left */
    442     basic_substring triml(const C c) const
    443     {
    444         if( ! empty())
    445         {
    446             size_t pos = first_not_of(c);
    447             if(pos != npos)
    448                 return sub(pos);
    449         }
    450         return sub(0, 0);
    451     }
    452     /** trim left ANY of the characters.
    453      * @see stripl() to remove a pattern from the left */
    454     basic_substring triml(ro_substr chars) const
    455     {
    456         if( ! empty())
    457         {
    458             size_t pos = first_not_of(chars);
    459             if(pos != npos)
    460                 return sub(pos);
    461         }
    462         return sub(0, 0);
    463     }
    464 
    465     /** trim the character c from the right */
    466     basic_substring trimr(const C c) const
    467     {
    468         if( ! empty())
    469         {
    470             size_t pos = last_not_of(c, npos);
    471             if(pos != npos)
    472                 return sub(0, pos+1);
    473         }
    474         return sub(0, 0);
    475     }
    476     /** trim right ANY of the characters
    477      * @see stripr() to remove a pattern from the right  */
    478     basic_substring trimr(ro_substr chars) const
    479     {
    480         if( ! empty())
    481         {
    482             size_t pos = last_not_of(chars, npos);
    483             if(pos != npos)
    484                 return sub(0, pos+1);
    485         }
    486         return sub(0, 0);
    487     }
    488 
    489     /** trim the character c left and right */
    490     basic_substring trim(const C c) const
    491     {
    492         return triml(c).trimr(c);
    493     }
    494     /** trim left and right ANY of the characters
    495      * @see strip() to remove a pattern from the left and right */
    496     basic_substring trim(ro_substr const chars) const
    497     {
    498         return triml(chars).trimr(chars);
    499     }
    500 
    501     /** remove a pattern from the left
    502      * @see triml() to remove characters*/
    503     basic_substring stripl(ro_substr pattern) const
    504     {
    505         if( ! begins_with(pattern))
    506             return *this;
    507         return sub(pattern.len < len ? pattern.len : len);
    508     }
    509 
    510     /** remove a pattern from the right
    511      * @see trimr() to remove characters*/
    512     basic_substring stripr(ro_substr pattern) const
    513     {
    514         if( ! ends_with(pattern))
    515             return *this;
    516         return left_of(len - (pattern.len < len ? pattern.len : len));
    517     }
    518 
    519     /** @} */
    520 
    521 public:
    522 
    523     /** @name Lookup methods */
    524     /** @{ */
    525 
    526     inline size_t find(const C c, size_t start_pos=0) const
    527     {
    528         return first_of(c, start_pos);
    529     }
    530     inline size_t find(ro_substr pattern, size_t start_pos=0) const
    531     {
    532         C4_ASSERT(start_pos == npos || (start_pos >= 0 && start_pos <= len));
    533         if(len < pattern.len) return npos;
    534         for(size_t i = start_pos, e = len - pattern.len + 1; i < e; ++i)
    535         {
    536             bool gotit = true;
    537             for(size_t j = 0; j < pattern.len; ++j)
    538             {
    539                 C4_ASSERT(i + j < len);
    540                 if(str[i + j] != pattern.str[j])
    541                 {
    542                     gotit = false;
    543                     break;
    544                 }
    545             }
    546             if(gotit)
    547             {
    548                 return i;
    549             }
    550         }
    551         return npos;
    552     }
    553 
    554 public:
    555 
    556     /** count the number of occurrences of c */
    557     inline size_t count(const C c, size_t pos=0) const
    558     {
    559         C4_ASSERT(pos >= 0 && pos <= len);
    560         size_t num = 0;
    561         pos = find(c, pos);
    562         while(pos != npos)
    563         {
    564             ++num;
    565             pos = find(c, pos + 1);
    566         }
    567         return num;
    568     }
    569 
    570     /** count the number of occurrences of s */
    571     inline size_t count(ro_substr c, size_t pos=0) const
    572     {
    573         C4_ASSERT(pos >= 0 && pos <= len);
    574         size_t num = 0;
    575         pos = find(c, pos);
    576         while(pos != npos)
    577         {
    578             ++num;
    579             pos = find(c, pos + c.len);
    580         }
    581         return num;
    582     }
    583 
    584     /** get the substr consisting of the first occurrence of @p c after @p pos, or an empty substr if none occurs */
    585     inline basic_substring select(const C c, size_t pos=0) const
    586     {
    587         pos = find(c, pos);
    588         return pos != npos ? sub(pos, 1) : basic_substring();
    589     }
    590 
    591     /** get the substr consisting of the first occurrence of @p pattern after @p pos, or an empty substr if none occurs */
    592     inline basic_substring select(ro_substr pattern, size_t pos=0) const
    593     {
    594         pos = find(pattern, pos);
    595         return pos != npos ? sub(pos, pattern.len) : basic_substring();
    596     }
    597 
    598 public:
    599 
    600     struct first_of_any_result
    601     {
    602         size_t which;
    603         size_t pos;
    604         inline operator bool() const { return which != NONE && pos != npos; }
    605     };
    606 
    607     first_of_any_result first_of_any(ro_substr s0, ro_substr s1) const
    608     {
    609         ro_substr s[2] = {s0, s1};
    610         return first_of_any_iter(&s[0], &s[0] + 2);
    611     }
    612 
    613     first_of_any_result first_of_any(ro_substr s0, ro_substr s1, ro_substr s2) const
    614     {
    615         ro_substr s[3] = {s0, s1, s2};
    616         return first_of_any_iter(&s[0], &s[0] + 3);
    617     }
    618 
    619     first_of_any_result first_of_any(ro_substr s0, ro_substr s1, ro_substr s2, ro_substr s3) const
    620     {
    621         ro_substr s[4] = {s0, s1, s2, s3};
    622         return first_of_any_iter(&s[0], &s[0] + 4);
    623     }
    624 
    625     first_of_any_result first_of_any(ro_substr s0, ro_substr s1, ro_substr s2, ro_substr s3, ro_substr s4) const
    626     {
    627         ro_substr s[5] = {s0, s1, s2, s3, s4};
    628         return first_of_any_iter(&s[0], &s[0] + 5);
    629     }
    630 
    631     template<class It>
    632     first_of_any_result first_of_any_iter(It first_span, It last_span) const
    633     {
    634         for(size_t i = 0; i < len; ++i)
    635         {
    636             size_t curr = 0;
    637             for(It it = first_span; it != last_span; ++curr, ++it)
    638             {
    639                 auto const& chars = *it;
    640                 if((i + chars.len) > len) continue;
    641                 bool gotit = true;
    642                 for(size_t j = 0; j < chars.len; ++j)
    643                 {
    644                     C4_ASSERT(i + j < len);
    645                     if(str[i + j] != chars[j])
    646                     {
    647                         gotit = false;
    648                         break;
    649                     }
    650                 }
    651                 if(gotit)
    652                 {
    653                     return {curr, i};
    654                 }
    655             }
    656         }
    657         return {NONE, npos};
    658     }
    659 
    660 public:
    661 
    662     /** true if the first character of the string is @p c */
    663     bool begins_with(const C c) const
    664     {
    665         return len > 0 ? str[0] == c : false;
    666     }
    667 
    668     /** true if the first @p num characters of the string are @p c */
    669     bool begins_with(const C c, size_t num) const
    670     {
    671         if(len < num)
    672         {
    673             return false;
    674         }
    675         for(size_t i = 0; i < num; ++i)
    676         {
    677             if(str[i] != c)
    678             {
    679                 return false;
    680             }
    681         }
    682         return true;
    683     }
    684 
    685     /** true if the string begins with the given @p pattern */
    686     bool begins_with(ro_substr pattern) const
    687     {
    688         if(len < pattern.len)
    689         {
    690             return false;
    691         }
    692         for(size_t i = 0; i < pattern.len; ++i)
    693         {
    694             if(str[i] != pattern[i])
    695             {
    696                 return false;
    697             }
    698         }
    699         return true;
    700     }
    701 
    702     /** true if the first character of the string is any of the given @p chars */
    703     bool begins_with_any(ro_substr chars) const
    704     {
    705         if(len == 0)
    706         {
    707             return false;
    708         }
    709         for(size_t i = 0; i < chars.len; ++i)
    710         {
    711             if(str[0] == chars.str[i])
    712             {
    713                 return true;
    714             }
    715         }
    716         return false;
    717     }
    718 
    719     /** true if the last character of the string is @p c */
    720     bool ends_with(const C c) const
    721     {
    722         return len > 0 ? str[len-1] == c : false;
    723     }
    724 
    725     /** true if the last @p num characters of the string are @p c */
    726     bool ends_with(const C c, size_t num) const
    727     {
    728         if(len < num)
    729         {
    730             return false;
    731         }
    732         for(size_t i = len - num; i < len; ++i)
    733         {
    734             if(str[i] != c)
    735             {
    736                 return false;
    737             }
    738         }
    739         return true;
    740     }
    741 
    742     /** true if the string ends with the given @p pattern */
    743     bool ends_with(ro_substr pattern) const
    744     {
    745         if(len < pattern.len)
    746         {
    747             return false;
    748         }
    749         for(size_t i = 0, s = len-pattern.len; i < pattern.len; ++i)
    750         {
    751             if(str[s+i] != pattern[i])
    752             {
    753                 return false;
    754             }
    755         }
    756         return true;
    757     }
    758 
    759     /** true if the last character of the string is any of the given @p chars */
    760     bool ends_with_any(ro_substr chars) const
    761     {
    762         if(len == 0)
    763         {
    764             return false;
    765         }
    766         for(size_t i = 0; i < chars.len; ++i)
    767         {
    768             if(str[len - 1] == chars[i])
    769             {
    770                 return true;
    771             }
    772         }
    773         return false;
    774     }
    775 
    776 public:
    777 
    778     /** @return the first position where c is found in the string, or npos if none is found */
    779     size_t first_of(const C c, size_t start=0) const
    780     {
    781         C4_ASSERT(start == npos || (start >= 0 && start <= len));
    782         for(size_t i = start; i < len; ++i)
    783         {
    784             if(str[i] == c)
    785                 return i;
    786         }
    787         return npos;
    788     }
    789 
    790     /** @return the last position where c is found in the string, or npos if none is found */
    791     size_t last_of(const C c, size_t start=npos) const
    792     {
    793         C4_ASSERT(start == npos || (start >= 0 && start <= len));
    794         if(start == npos)
    795             start = len;
    796         for(size_t i = start-1; i != size_t(-1); --i)
    797         {
    798             if(str[i] == c)
    799                 return i;
    800         }
    801         return npos;
    802     }
    803 
    804     /** @return the first position where ANY of the chars is found in the string, or npos if none is found */
    805     size_t first_of(ro_substr chars, size_t start=0) const
    806     {
    807         C4_ASSERT(start == npos || (start >= 0 && start <= len));
    808         for(size_t i = start; i < len; ++i)
    809         {
    810             for(size_t j = 0; j < chars.len; ++j)
    811             {
    812                 if(str[i] == chars[j])
    813                     return i;
    814             }
    815         }
    816         return npos;
    817     }
    818 
    819     /** @return the last position where ANY of the chars is found in the string, or npos if none is found */
    820     size_t last_of(ro_substr chars, size_t start=npos) const
    821     {
    822         C4_ASSERT(start == npos || (start >= 0 && start <= len));
    823         if(start == npos)
    824             start = len;
    825         for(size_t i = start-1; i != size_t(-1); --i)
    826         {
    827             for(size_t j = 0; j < chars.len; ++j)
    828             {
    829                 if(str[i] == chars[j])
    830                     return i;
    831             }
    832         }
    833         return npos;
    834     }
    835 
    836 public:
    837 
    838     size_t first_not_of(const C c, size_t start=0) const
    839     {
    840         C4_ASSERT((start >= 0 && start <= len) || (start == len && len == 0));
    841         for(size_t i = start; i < len; ++i)
    842         {
    843             if(str[i] != c)
    844                 return i;
    845         }
    846         return npos;
    847     }
    848 
    849     size_t last_not_of(const C c, size_t start=npos) const
    850     {
    851         C4_ASSERT(start == npos || (start >= 0 && start <= len));
    852         if(start == npos)
    853             start = len;
    854         for(size_t i = start-1; i != size_t(-1); --i)
    855         {
    856             if(str[i] != c)
    857                 return i;
    858         }
    859         return npos;
    860     }
    861 
    862     size_t first_not_of(ro_substr chars, size_t start=0) const
    863     {
    864         C4_ASSERT((start >= 0 && start <= len) || (start == len && len == 0));
    865         for(size_t i = start; i < len; ++i)
    866         {
    867             bool gotit = true;
    868             for(size_t j = 0; j < chars.len; ++j)
    869             {
    870                 if(str[i] == chars.str[j])
    871                 {
    872                     gotit = false;
    873                     break;
    874                 }
    875             }
    876             if(gotit)
    877             {
    878                 return i;
    879             }
    880         }
    881         return npos;
    882     }
    883 
    884     size_t last_not_of(ro_substr chars, size_t start=npos) const
    885     {
    886         C4_ASSERT(start == npos || (start >= 0 && start <= len));
    887         if(start == npos)
    888             start = len;
    889         for(size_t i = start-1; i != size_t(-1); --i)
    890         {
    891             bool gotit = true;
    892             for(size_t j = 0; j < chars.len; ++j)
    893             {
    894                 if(str[i] == chars.str[j])
    895                 {
    896                     gotit = false;
    897                     break;
    898                 }
    899             }
    900             if(gotit)
    901             {
    902                 return i;
    903             }
    904         }
    905         return npos;
    906     }
    907 
    908     /** @} */
    909 
    910 public:
    911 
    912     /** @name Range lookup methods */
    913     /** @{ */
    914 
    915     /** get the range delimited by an open-close pair of characters.
    916      * @note There must be no nested pairs.
    917      * @note No checks for escapes are performed. */
    918     basic_substring pair_range(CC open, CC close) const
    919     {
    920         size_t b = find(open);
    921         if(b == npos)
    922             return basic_substring();
    923         size_t e = find(close, b+1);
    924         if(e == npos)
    925             return basic_substring();
    926         basic_substring ret = range(b, e+1);
    927         C4_ASSERT(ret.sub(1).find(open) == npos);
    928         return ret;
    929     }
    930 
    931     /** get the range delimited by a single open-close character (eg, quotes).
    932      * @note The open-close character can be escaped. */
    933     basic_substring pair_range_esc(CC open_close, CC escape=CC('\\'))
    934     {
    935         size_t b = find(open_close);
    936         if(b == npos) return basic_substring();
    937         for(size_t i = b+1; i < len; ++i)
    938         {
    939             CC c = str[i];
    940             if(c == open_close)
    941             {
    942                 if(str[i-1] != escape)
    943                 {
    944                     return range(b, i+1);
    945                 }
    946             }
    947         }
    948         return basic_substring();
    949     }
    950 
    951     /** get the range delimited by an open-close pair of characters,
    952      * with possibly nested occurrences. No checks for escapes are
    953      * performed. */
    954     basic_substring pair_range_nested(CC open, CC close) const
    955     {
    956         size_t b = find(open);
    957         if(b == npos) return basic_substring();
    958         size_t e, curr = b+1, count = 0;
    959         const char both[] = {open, close, '\0'};
    960         while((e = first_of(both, curr)) != npos)
    961         {
    962             if(str[e] == open)
    963             {
    964                 ++count;
    965                 curr = e+1;
    966             }
    967             else if(str[e] == close)
    968             {
    969                 if(count == 0) return range(b, e+1);
    970                 --count;
    971                 curr = e+1;
    972             }
    973         }
    974         return basic_substring();
    975     }
    976 
    977     basic_substring unquoted() const
    978     {
    979         constexpr const C dq('"'), sq('\'');
    980         if(len >= 2 && (str[len - 2] != C('\\')) &&
    981            ((begins_with(sq) && ends_with(sq))
    982             ||
    983             (begins_with(dq) && ends_with(dq))))
    984         {
    985             return range(1, len -1);
    986         }
    987         return *this;
    988     }
    989 
    990     /** @} */
    991 
    992 public:
    993 
    994     /** @name Number-matching query methods */
    995     /** @{ */
    996 
    997     /** @return true if the substring contents are a floating-point or integer number.
    998      * @note any leading or trailing whitespace will return false. */
    999     bool is_number() const
   1000     {
   1001         if(empty() || (first_non_empty_span().empty()))
   1002             return false;
   1003         if(first_uint_span() == *this)
   1004             return true;
   1005         if(first_int_span() == *this)
   1006             return true;
   1007         if(first_real_span() == *this)
   1008             return true;
   1009         return false;
   1010     }
   1011 
   1012     /** @return true if the substring contents are a real number.
   1013      * @note any leading or trailing whitespace will return false. */
   1014     bool is_real() const
   1015     {
   1016         if(empty() || (first_non_empty_span().empty()))
   1017             return false;
   1018         if(first_real_span() == *this)
   1019             return true;
   1020         return false;
   1021     }
   1022 
   1023     /** @return true if the substring contents are an integer number.
   1024      * @note any leading or trailing whitespace will return false. */
   1025     bool is_integer() const
   1026     {
   1027         if(empty() || (first_non_empty_span().empty()))
   1028             return false;
   1029         if(first_uint_span() == *this)
   1030             return true;
   1031         if(first_int_span() == *this)
   1032             return true;
   1033         return false;
   1034     }
   1035 
   1036     /** @return true if the substring contents are an unsigned integer number.
   1037      * @note any leading or trailing whitespace will return false. */
   1038     bool is_unsigned_integer() const
   1039     {
   1040         if(empty() || (first_non_empty_span().empty()))
   1041             return false;
   1042         if(first_uint_span() == *this)
   1043             return true;
   1044         return false;
   1045     }
   1046 
   1047     /** get the first span consisting exclusively of non-empty characters */
   1048     basic_substring first_non_empty_span() const
   1049     {
   1050         constexpr const ro_substr empty_chars(" \n\r\t");
   1051         size_t pos = first_not_of(empty_chars);
   1052         if(pos == npos)
   1053             return first(0);
   1054         auto ret = sub(pos);
   1055         pos = ret.first_of(empty_chars);
   1056         return ret.first(pos);
   1057     }
   1058 
   1059     /** get the first span which can be interpreted as an unsigned integer */
   1060     basic_substring first_uint_span() const
   1061     {
   1062         basic_substring ne = first_non_empty_span();
   1063         if(ne.empty())
   1064             return ne;
   1065         if(ne.str[0] == '-')
   1066             return first(0);
   1067         size_t skip_start = size_t(ne.str[0] == '+');
   1068         return ne._first_integral_span(skip_start);
   1069     }
   1070 
   1071     /** get the first span which can be interpreted as a signed integer */
   1072     basic_substring first_int_span() const
   1073     {
   1074         basic_substring ne = first_non_empty_span();
   1075         if(ne.empty())
   1076             return ne;
   1077         size_t skip_start = size_t(ne.str[0] == '+' || ne.str[0] == '-');
   1078         return ne._first_integral_span(skip_start);
   1079     }
   1080 
   1081     basic_substring _first_integral_span(size_t skip_start) const
   1082     {
   1083         C4_ASSERT(!empty());
   1084         if(skip_start == len)
   1085             return first(0);
   1086         C4_ASSERT(skip_start < len);
   1087         if(len >= skip_start + 3)
   1088         {
   1089             if(str[skip_start] != '0')
   1090             {
   1091                 for(size_t i = skip_start; i < len; ++i)
   1092                 {
   1093                     char c = str[i];
   1094                     if(c < '0' || c > '9')
   1095                         return i > skip_start && _is_delim_char(c) ? first(i) : first(0);
   1096                 }
   1097             }
   1098             else
   1099             {
   1100                 char next = str[skip_start + 1];
   1101                 if(next == 'x' || next == 'X')
   1102                 {
   1103                     skip_start += 2;
   1104                     for(size_t i = skip_start; i < len; ++i)
   1105                     {
   1106                         const char c = str[i];
   1107                         if( ! _is_hex_char(c))
   1108                             return i > skip_start && _is_delim_char(c) ? first(i) : first(0);
   1109                     }
   1110                     return *this;
   1111                 }
   1112                 else if(next == 'b' || next == 'B')
   1113                 {
   1114                     skip_start += 2;
   1115                     for(size_t i = skip_start; i < len; ++i)
   1116                     {
   1117                         const char c = str[i];
   1118                         if(c != '0' && c != '1')
   1119                             return i > skip_start && _is_delim_char(c) ? first(i) : first(0);
   1120                     }
   1121                     return *this;
   1122                 }
   1123                 else if(next == 'o' || next == 'O')
   1124                 {
   1125                     skip_start += 2;
   1126                     for(size_t i = skip_start; i < len; ++i)
   1127                     {
   1128                         const char c = str[i];
   1129                         if(c < '0' || c > '7')
   1130                             return i > skip_start && _is_delim_char(c) ? first(i) : first(0);
   1131                     }
   1132                     return *this;
   1133                 }
   1134             }
   1135         }
   1136         // must be a decimal, or it is not a an number
   1137         for(size_t i = skip_start; i < len; ++i)
   1138         {
   1139             const char c = str[i];
   1140             if(c < '0' || c > '9')
   1141                 return i > skip_start && _is_delim_char(c) ? first(i) : first(0);
   1142         }
   1143         return *this;
   1144     }
   1145 
   1146     /** get the first span which can be interpreted as a real (floating-point) number */
   1147     basic_substring first_real_span() const
   1148     {
   1149         basic_substring ne = first_non_empty_span();
   1150         if(ne.empty())
   1151             return ne;
   1152         size_t skip_start = (ne.str[0] == '+' || ne.str[0] == '-');
   1153         C4_ASSERT(skip_start == 0 || skip_start == 1);
   1154         // if we have at least three digits after the leading sign, it
   1155         // can be decimal, or hex, or bin or oct. Ex:
   1156         // non-decimal: 0x0, 0b0, 0o0
   1157         // decimal: 1.0, 10., 1e1, 100, inf, nan, infinity
   1158         if(ne.len >= skip_start+3)
   1159         {
   1160             // if it does not have leading 0, it must be decimal, or it is not a real
   1161             if(ne.str[skip_start] != '0')
   1162             {
   1163                 if(ne.str[skip_start] == 'i') // is it infinity or inf?
   1164                 {
   1165                     basic_substring word = ne._word_follows(skip_start + 1, "nfinity");
   1166                     if(word.len)
   1167                         return word;
   1168                     return ne._word_follows(skip_start + 1, "nf");
   1169                 }
   1170                 else if(ne.str[skip_start] == 'n') // is it nan?
   1171                 {
   1172                     return ne._word_follows(skip_start + 1, "an");
   1173                 }
   1174                 else // must be a decimal, or it is not a real
   1175                 {
   1176                     return ne._first_real_span_dec(skip_start);
   1177                 }
   1178             }
   1179             else // starts with 0. is it 0x, 0b or 0o?
   1180             {
   1181                 const char next = ne.str[skip_start + 1];
   1182                 // hexadecimal
   1183                 if(next == 'x' || next == 'X')
   1184                     return ne._first_real_span_hex(skip_start + 2);
   1185                 // binary
   1186                 else if(next == 'b' || next == 'B')
   1187                     return ne._first_real_span_bin(skip_start + 2);
   1188                 // octal
   1189                 else if(next == 'o' || next == 'O')
   1190                     return ne._first_real_span_oct(skip_start + 2);
   1191                 // none of the above. may still be a decimal.
   1192                 else
   1193                     return ne._first_real_span_dec(skip_start); // do not skip the 0.
   1194             }
   1195         }
   1196         // less than 3 chars after the leading sign. It is either a
   1197         // decimal or it is not a real. (cannot be any of 0x0, etc).
   1198         return ne._first_real_span_dec(skip_start);
   1199     }
   1200 
   1201     /** true if the character is a delimiter character *at the end* */
   1202     static constexpr C4_ALWAYS_INLINE C4_CONST bool _is_delim_char(char c) noexcept
   1203     {
   1204         return c == ' ' || c == '\n'
   1205             || c == ']' || c == ')'  || c == '}'
   1206             || c == ',' || c == ';' || c == '\r' || c == '\t' || c == '\0';
   1207     }
   1208 
   1209     /** true if the character is in [0-9a-fA-F] */
   1210     static constexpr C4_ALWAYS_INLINE C4_CONST bool _is_hex_char(char c) noexcept
   1211     {
   1212         return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
   1213     }
   1214 
   1215     C4_NO_INLINE C4_PURE basic_substring _word_follows(size_t pos, csubstr word) const noexcept
   1216     {
   1217         size_t posend = pos + word.len;
   1218         if(len >= posend && sub(pos, word.len) == word)
   1219             if(len == posend || _is_delim_char(str[posend]))
   1220                 return first(posend);
   1221         return first(0);
   1222     }
   1223 
   1224     // this function is declared inside the class to avoid a VS error with __declspec(dllimport)
   1225     C4_NO_INLINE C4_PURE basic_substring _first_real_span_dec(size_t pos) const noexcept
   1226     {
   1227         bool intchars = false;
   1228         bool fracchars = false;
   1229         bool powchars;
   1230         // integral part
   1231         for( ; pos < len; ++pos)
   1232         {
   1233             const char c = str[pos];
   1234             if(c >= '0' && c <= '9')
   1235             {
   1236                 intchars = true;
   1237             }
   1238             else if(c == '.')
   1239             {
   1240                 ++pos;
   1241                 goto fractional_part_dec;
   1242             }
   1243             else if(c == 'e' || c == 'E')
   1244             {
   1245                 ++pos;
   1246                 goto power_part_dec;
   1247             }
   1248             else if(_is_delim_char(c))
   1249             {
   1250                 return intchars ? first(pos) : first(0);
   1251             }
   1252             else
   1253             {
   1254                 return first(0);
   1255             }
   1256         }
   1257         // no . or p were found; this is either an integral number
   1258         // or not a number at all
   1259         return intchars ?
   1260             *this :
   1261             first(0);
   1262     fractional_part_dec:
   1263         C4_ASSERT(pos > 0);
   1264         C4_ASSERT(str[pos - 1] == '.');
   1265         for( ; pos < len; ++pos)
   1266         {
   1267             const char c = str[pos];
   1268             if(c >= '0' && c <= '9')
   1269             {
   1270                 fracchars = true;
   1271             }
   1272             else if(c == 'e' || c == 'E')
   1273             {
   1274                 ++pos;
   1275                 goto power_part_dec;
   1276             }
   1277             else if(_is_delim_char(c))
   1278             {
   1279                 return intchars || fracchars ? first(pos) : first(0);
   1280             }
   1281             else
   1282             {
   1283                 return first(0);
   1284             }
   1285         }
   1286         return intchars || fracchars ?
   1287             *this :
   1288             first(0);
   1289     power_part_dec:
   1290         C4_ASSERT(pos > 0);
   1291         C4_ASSERT(str[pos - 1] == 'e' || str[pos - 1] == 'E');
   1292         // either a + or a - is expected here, followed by more chars.
   1293         // also, using (pos+1) in this check will cause an early
   1294         // return when no more chars follow the sign.
   1295         if(len <= (pos+1) || ((!intchars) && (!fracchars)))
   1296             return first(0);
   1297         ++pos; // this was the sign.
   1298         // ... so the (pos+1) ensures that we enter the loop and
   1299         // hence that there exist chars in the power part
   1300         powchars = false;
   1301         for( ; pos < len; ++pos)
   1302         {
   1303             const char c = str[pos];
   1304             if(c >= '0' && c <= '9')
   1305                 powchars = true;
   1306             else if(powchars && _is_delim_char(c))
   1307                 return first(pos);
   1308             else
   1309                 return first(0);
   1310         }
   1311         return *this;
   1312     }
   1313 
   1314     // this function is declared inside the class to avoid a VS error with __declspec(dllimport)
   1315     C4_NO_INLINE C4_PURE basic_substring _first_real_span_hex(size_t pos) const noexcept
   1316     {
   1317         bool intchars = false;
   1318         bool fracchars = false;
   1319         bool powchars;
   1320         // integral part
   1321         for( ; pos < len; ++pos)
   1322         {
   1323             const char c = str[pos];
   1324             if(_is_hex_char(c))
   1325             {
   1326                 intchars = true;
   1327             }
   1328             else if(c == '.')
   1329             {
   1330                 ++pos;
   1331                 goto fractional_part_hex;
   1332             }
   1333             else if(c == 'p' || c == 'P')
   1334             {
   1335                 ++pos;
   1336                 goto power_part_hex;
   1337             }
   1338             else if(_is_delim_char(c))
   1339             {
   1340                 return intchars ? first(pos) : first(0);
   1341             }
   1342             else
   1343             {
   1344                 return first(0);
   1345             }
   1346         }
   1347         // no . or p were found; this is either an integral number
   1348         // or not a number at all
   1349         return intchars ?
   1350             *this :
   1351             first(0);
   1352     fractional_part_hex:
   1353         C4_ASSERT(pos > 0);
   1354         C4_ASSERT(str[pos - 1] == '.');
   1355         for( ; pos < len; ++pos)
   1356         {
   1357             const char c = str[pos];
   1358             if(_is_hex_char(c))
   1359             {
   1360                 fracchars = true;
   1361             }
   1362             else if(c == 'p' || c == 'P')
   1363             {
   1364                 ++pos;
   1365                 goto power_part_hex;
   1366             }
   1367             else if(_is_delim_char(c))
   1368             {
   1369                 return intchars || fracchars ? first(pos) : first(0);
   1370             }
   1371             else
   1372             {
   1373                 return first(0);
   1374             }
   1375         }
   1376         return intchars || fracchars ?
   1377             *this :
   1378             first(0);
   1379     power_part_hex:
   1380         C4_ASSERT(pos > 0);
   1381         C4_ASSERT(str[pos - 1] == 'p' || str[pos - 1] == 'P');
   1382         // either a + or a - is expected here, followed by more chars.
   1383         // also, using (pos+1) in this check will cause an early
   1384         // return when no more chars follow the sign.
   1385         if(len <= (pos+1) || (str[pos] != '+' && str[pos] != '-') || ((!intchars) && (!fracchars)))
   1386             return first(0);
   1387         ++pos; // this was the sign.
   1388         // ... so the (pos+1) ensures that we enter the loop and
   1389         // hence that there exist chars in the power part
   1390         powchars = false;
   1391         for( ; pos < len; ++pos)
   1392         {
   1393             const char c = str[pos];
   1394             if(c >= '0' && c <= '9')
   1395                 powchars = true;
   1396             else if(powchars && _is_delim_char(c))
   1397                 return first(pos);
   1398             else
   1399                 return first(0);
   1400         }
   1401         return *this;
   1402     }
   1403 
   1404     // this function is declared inside the class to avoid a VS error with __declspec(dllimport)
   1405     C4_NO_INLINE C4_PURE basic_substring _first_real_span_bin(size_t pos) const noexcept
   1406     {
   1407         bool intchars = false;
   1408         bool fracchars = false;
   1409         bool powchars;
   1410         // integral part
   1411         for( ; pos < len; ++pos)
   1412         {
   1413             const char c = str[pos];
   1414             if(c == '0' || c == '1')
   1415             {
   1416                 intchars = true;
   1417             }
   1418             else if(c == '.')
   1419             {
   1420                 ++pos;
   1421                 goto fractional_part_bin;
   1422             }
   1423             else if(c == 'p' || c == 'P')
   1424             {
   1425                 ++pos;
   1426                 goto power_part_bin;
   1427             }
   1428             else if(_is_delim_char(c))
   1429             {
   1430                 return intchars ? first(pos) : first(0);
   1431             }
   1432             else
   1433             {
   1434                 return first(0);
   1435             }
   1436         }
   1437         // no . or p were found; this is either an integral number
   1438         // or not a number at all
   1439         return intchars ?
   1440             *this :
   1441             first(0);
   1442     fractional_part_bin:
   1443         C4_ASSERT(pos > 0);
   1444         C4_ASSERT(str[pos - 1] == '.');
   1445         for( ; pos < len; ++pos)
   1446         {
   1447             const char c = str[pos];
   1448             if(c == '0' || c == '1')
   1449             {
   1450                 fracchars = true;
   1451             }
   1452             else if(c == 'p' || c == 'P')
   1453             {
   1454                 ++pos;
   1455                 goto power_part_bin;
   1456             }
   1457             else if(_is_delim_char(c))
   1458             {
   1459                 return intchars || fracchars ? first(pos) : first(0);
   1460             }
   1461             else
   1462             {
   1463                 return first(0);
   1464             }
   1465         }
   1466         return intchars || fracchars ?
   1467             *this :
   1468             first(0);
   1469     power_part_bin:
   1470         C4_ASSERT(pos > 0);
   1471         C4_ASSERT(str[pos - 1] == 'p' || str[pos - 1] == 'P');
   1472         // either a + or a - is expected here, followed by more chars.
   1473         // also, using (pos+1) in this check will cause an early
   1474         // return when no more chars follow the sign.
   1475         if(len <= (pos+1) || (str[pos] != '+' && str[pos] != '-') || ((!intchars) && (!fracchars)))
   1476             return first(0);
   1477         ++pos; // this was the sign.
   1478         // ... so the (pos+1) ensures that we enter the loop and
   1479         // hence that there exist chars in the power part
   1480         powchars = false;
   1481         for( ; pos < len; ++pos)
   1482         {
   1483             const char c = str[pos];
   1484             if(c >= '0' && c <= '9')
   1485                 powchars = true;
   1486             else if(powchars && _is_delim_char(c))
   1487                 return first(pos);
   1488             else
   1489                 return first(0);
   1490         }
   1491         return *this;
   1492     }
   1493 
   1494     // this function is declared inside the class to avoid a VS error with __declspec(dllimport)
   1495     C4_NO_INLINE C4_PURE basic_substring _first_real_span_oct(size_t pos) const noexcept
   1496     {
   1497         bool intchars = false;
   1498         bool fracchars = false;
   1499         bool powchars;
   1500         // integral part
   1501         for( ; pos < len; ++pos)
   1502         {
   1503             const char c = str[pos];
   1504             if(c >= '0' && c <= '7')
   1505             {
   1506                 intchars = true;
   1507             }
   1508             else if(c == '.')
   1509             {
   1510                 ++pos;
   1511                 goto fractional_part_oct;
   1512             }
   1513             else if(c == 'p' || c == 'P')
   1514             {
   1515                 ++pos;
   1516                 goto power_part_oct;
   1517             }
   1518             else if(_is_delim_char(c))
   1519             {
   1520                 return intchars ? first(pos) : first(0);
   1521             }
   1522             else
   1523             {
   1524                 return first(0);
   1525             }
   1526         }
   1527         // no . or p were found; this is either an integral number
   1528         // or not a number at all
   1529         return intchars ?
   1530             *this :
   1531             first(0);
   1532     fractional_part_oct:
   1533         C4_ASSERT(pos > 0);
   1534         C4_ASSERT(str[pos - 1] == '.');
   1535         for( ; pos < len; ++pos)
   1536         {
   1537             const char c = str[pos];
   1538             if(c >= '0' && c <= '7')
   1539             {
   1540                 fracchars = true;
   1541             }
   1542             else if(c == 'p' || c == 'P')
   1543             {
   1544                 ++pos;
   1545                 goto power_part_oct;
   1546             }
   1547             else if(_is_delim_char(c))
   1548             {
   1549                 return intchars || fracchars ? first(pos) : first(0);
   1550             }
   1551             else
   1552             {
   1553                 return first(0);
   1554             }
   1555         }
   1556         return intchars || fracchars ?
   1557             *this :
   1558             first(0);
   1559     power_part_oct:
   1560         C4_ASSERT(pos > 0);
   1561         C4_ASSERT(str[pos - 1] == 'p' || str[pos - 1] == 'P');
   1562         // either a + or a - is expected here, followed by more chars.
   1563         // also, using (pos+1) in this check will cause an early
   1564         // return when no more chars follow the sign.
   1565         if(len <= (pos+1) || (str[pos] != '+' && str[pos] != '-') || ((!intchars) && (!fracchars)))
   1566             return first(0);
   1567         ++pos; // this was the sign.
   1568         // ... so the (pos+1) ensures that we enter the loop and
   1569         // hence that there exist chars in the power part
   1570         powchars = false;
   1571         for( ; pos < len; ++pos)
   1572         {
   1573             const char c = str[pos];
   1574             if(c >= '0' && c <= '9')
   1575                 powchars = true;
   1576             else if(powchars && _is_delim_char(c))
   1577                 return first(pos);
   1578             else
   1579                 return first(0);
   1580         }
   1581         return *this;
   1582     }
   1583 
   1584     /** @} */
   1585 
   1586 public:
   1587 
   1588     /** @name Splitting methods */
   1589     /** @{ */
   1590 
   1591     /** returns true if the string has not been exhausted yet, meaning
   1592      * it's ok to call next_split() again. When no instance of sep
   1593      * exists in the string, returns the full string. When the input
   1594      * is an empty string, the output string is the empty string. */
   1595     bool next_split(C sep, size_t *C4_RESTRICT start_pos, basic_substring *C4_RESTRICT out) const
   1596     {
   1597         if(C4_LIKELY(*start_pos < len))
   1598         {
   1599             for(size_t i = *start_pos; i < len; i++)
   1600             {
   1601                 if(str[i] == sep)
   1602                 {
   1603                     out->assign(str + *start_pos, i - *start_pos);
   1604                     *start_pos = i+1;
   1605                     return true;
   1606                 }
   1607             }
   1608             out->assign(str + *start_pos, len - *start_pos);
   1609             *start_pos = len + 1;
   1610             return true;
   1611         }
   1612         else
   1613         {
   1614             bool valid = len > 0 && (*start_pos == len);
   1615             if(valid && str && str[len-1] == sep)
   1616             {
   1617                 out->assign(str + len, size_t(0)); // the cast is needed to prevent overload ambiguity
   1618             }
   1619             else
   1620             {
   1621                 out->assign(str + len + 1, size_t(0)); // the cast is needed to prevent overload ambiguity
   1622             }
   1623             *start_pos = len + 1;
   1624             return valid;
   1625         }
   1626     }
   1627 
   1628 private:
   1629 
   1630     struct split_proxy_impl
   1631     {
   1632         struct split_iterator_impl
   1633         {
   1634             split_proxy_impl const* m_proxy;
   1635             basic_substring m_str;
   1636             size_t m_pos;
   1637             NCC_ m_sep;
   1638 
   1639             split_iterator_impl(split_proxy_impl const* proxy, size_t pos, C sep)
   1640                 : m_proxy(proxy), m_pos(pos), m_sep(sep)
   1641             {
   1642                 _tick();
   1643             }
   1644 
   1645             void _tick()
   1646             {
   1647                 m_proxy->m_str.next_split(m_sep, &m_pos, &m_str);
   1648             }
   1649 
   1650             split_iterator_impl& operator++ () { _tick(); return *this; }
   1651             split_iterator_impl  operator++ (int) { split_iterator_impl it = *this; _tick(); return it; }
   1652 
   1653             basic_substring& operator*  () { return  m_str; }
   1654             basic_substring* operator-> () { return &m_str; }
   1655 
   1656             bool operator!= (split_iterator_impl const& that) const
   1657             {
   1658                 return !(this->operator==(that));
   1659             }
   1660             bool operator== (split_iterator_impl const& that) const
   1661             {
   1662                 C4_XASSERT((m_sep == that.m_sep) && "cannot compare split iterators with different separators");
   1663                 if(m_str.size() != that.m_str.size())
   1664                     return false;
   1665                 if(m_str.data() != that.m_str.data())
   1666                     return false;
   1667                 return m_pos == that.m_pos;
   1668             }
   1669         };
   1670 
   1671         basic_substring m_str;
   1672         size_t m_start_pos;
   1673         C m_sep;
   1674 
   1675         split_proxy_impl(basic_substring str_, size_t start_pos, C sep)
   1676             : m_str(str_), m_start_pos(start_pos), m_sep(sep)
   1677         {
   1678         }
   1679 
   1680         split_iterator_impl begin() const
   1681         {
   1682             auto it = split_iterator_impl(this, m_start_pos, m_sep);
   1683             return it;
   1684         }
   1685         split_iterator_impl end() const
   1686         {
   1687             size_t pos = m_str.size() + 1;
   1688             auto it = split_iterator_impl(this, pos, m_sep);
   1689             return it;
   1690         }
   1691     };
   1692 
   1693 public:
   1694 
   1695     using split_proxy = split_proxy_impl;
   1696 
   1697     /** a view into the splits */
   1698     split_proxy split(C sep, size_t start_pos=0) const
   1699     {
   1700         C4_XASSERT((start_pos >= 0 && start_pos < len) || empty());
   1701         auto ss = sub(0, len);
   1702         auto it = split_proxy(ss, start_pos, sep);
   1703         return it;
   1704     }
   1705 
   1706 public:
   1707 
   1708     /** pop right: return the first split from the right. Use
   1709      * gpop_left() to get the reciprocal part.
   1710      */
   1711     basic_substring pop_right(C sep=C('/'), bool skip_empty=false) const
   1712     {
   1713         if(C4_LIKELY(len > 1))
   1714         {
   1715             auto pos = last_of(sep);
   1716             if(pos != npos)
   1717             {
   1718                 if(pos + 1 < len) // does not end with sep
   1719                 {
   1720                     return sub(pos + 1); // return from sep to end
   1721                 }
   1722                 else // the string ends with sep
   1723                 {
   1724                     if( ! skip_empty)
   1725                     {
   1726                         return sub(pos + 1, 0);
   1727                     }
   1728                     auto ppos = last_not_of(sep); // skip repeated seps
   1729                     if(ppos == npos) // the string is all made of seps
   1730                     {
   1731                         return sub(0, 0);
   1732                     }
   1733                     // find the previous sep
   1734                     auto pos0 = last_of(sep, ppos);
   1735                     if(pos0 == npos) // only the last sep exists
   1736                     {
   1737                         return sub(0); // return the full string (because skip_empty is true)
   1738                     }
   1739                     ++pos0;
   1740                     return sub(pos0);
   1741                 }
   1742             }
   1743             else // no sep was found, return the full string
   1744             {
   1745                 return *this;
   1746             }
   1747         }
   1748         else if(len == 1)
   1749         {
   1750             if(begins_with(sep))
   1751             {
   1752                 return sub(0, 0);
   1753             }
   1754             return *this;
   1755         }
   1756         else // an empty string
   1757         {
   1758             return basic_substring();
   1759         }
   1760     }
   1761 
   1762     /** return the first split from the left. Use gpop_right() to get
   1763      * the reciprocal part. */
   1764     basic_substring pop_left(C sep = C('/'), bool skip_empty=false) const
   1765     {
   1766         if(C4_LIKELY(len > 1))
   1767         {
   1768             auto pos = first_of(sep);
   1769             if(pos != npos)
   1770             {
   1771                 if(pos > 0)  // does not start with sep
   1772                 {
   1773                     return sub(0, pos); //  return everything up to it
   1774                 }
   1775                 else  // the string starts with sep
   1776                 {
   1777                     if( ! skip_empty)
   1778                     {
   1779                         return sub(0, 0);
   1780                     }
   1781                     auto ppos = first_not_of(sep); // skip repeated seps
   1782                     if(ppos == npos) // the string is all made of seps
   1783                     {
   1784                         return sub(0, 0);
   1785                     }
   1786                     // find the next sep
   1787                     auto pos0 = first_of(sep, ppos);
   1788                     if(pos0 == npos) // only the first sep exists
   1789                     {
   1790                         return sub(0); // return the full string (because skip_empty is true)
   1791                     }
   1792                     C4_XASSERT(pos0 > 0);
   1793                     // return everything up to the second sep
   1794                     return sub(0, pos0);
   1795                 }
   1796             }
   1797             else // no sep was found, return the full string
   1798             {
   1799                 return sub(0);
   1800             }
   1801         }
   1802         else if(len == 1)
   1803         {
   1804             if(begins_with(sep))
   1805             {
   1806                 return sub(0, 0);
   1807             }
   1808             return sub(0);
   1809         }
   1810         else // an empty string
   1811         {
   1812             return basic_substring();
   1813         }
   1814     }
   1815 
   1816 public:
   1817 
   1818     /** greedy pop left. eg, csubstr("a/b/c").gpop_left('/')="c" */
   1819     basic_substring gpop_left(C sep = C('/'), bool skip_empty=false) const
   1820     {
   1821         auto ss = pop_right(sep, skip_empty);
   1822         ss = left_of(ss);
   1823         if(ss.find(sep) != npos)
   1824         {
   1825             if(ss.ends_with(sep))
   1826             {
   1827                 if(skip_empty)
   1828                 {
   1829                     ss = ss.trimr(sep);
   1830                 }
   1831                 else
   1832                 {
   1833                     ss = ss.sub(0, ss.len-1); // safe to subtract because ends_with(sep) is true
   1834                 }
   1835             }
   1836         }
   1837         return ss;
   1838     }
   1839 
   1840     /** greedy pop right. eg, csubstr("a/b/c").gpop_right('/')="a" */
   1841     basic_substring gpop_right(C sep = C('/'), bool skip_empty=false) const
   1842     {
   1843         auto ss = pop_left(sep, skip_empty);
   1844         ss = right_of(ss);
   1845         if(ss.find(sep) != npos)
   1846         {
   1847             if(ss.begins_with(sep))
   1848             {
   1849                 if(skip_empty)
   1850                 {
   1851                     ss = ss.triml(sep);
   1852                 }
   1853                 else
   1854                 {
   1855                     ss = ss.sub(1);
   1856                 }
   1857             }
   1858         }
   1859         return ss;
   1860     }
   1861 
   1862     /** @} */
   1863 
   1864 public:
   1865 
   1866     /** @name Path-like manipulation methods */
   1867     /** @{ */
   1868 
   1869     basic_substring basename(C sep=C('/')) const
   1870     {
   1871         auto ss = pop_right(sep, /*skip_empty*/true);
   1872         ss = ss.trimr(sep);
   1873         return ss;
   1874     }
   1875 
   1876     basic_substring dirname(C sep=C('/')) const
   1877     {
   1878         auto ss = basename(sep);
   1879         ss = ss.empty() ? *this : left_of(ss);
   1880         return ss;
   1881     }
   1882 
   1883     C4_ALWAYS_INLINE basic_substring name_wo_extshort() const
   1884     {
   1885         return gpop_left('.');
   1886     }
   1887 
   1888     C4_ALWAYS_INLINE basic_substring name_wo_extlong() const
   1889     {
   1890         return pop_left('.');
   1891     }
   1892 
   1893     C4_ALWAYS_INLINE basic_substring extshort() const
   1894     {
   1895         return pop_right('.');
   1896     }
   1897 
   1898     C4_ALWAYS_INLINE basic_substring extlong() const
   1899     {
   1900         return gpop_right('.');
   1901     }
   1902 
   1903     /** @} */
   1904 
   1905 public:
   1906 
   1907     /** @name Content-modification methods (only for non-const C) */
   1908     /** @{ */
   1909 
   1910     /** convert the string to upper-case
   1911      * @note this method requires that the string memory is writeable and is SFINAEd out for const C */
   1912     C4_REQUIRE_RW(void) toupper()
   1913     {
   1914         for(size_t i = 0; i < len; ++i)
   1915         {
   1916             str[i] = static_cast<C>(::toupper(str[i]));
   1917         }
   1918     }
   1919 
   1920     /** convert the string to lower-case
   1921      * @note this method requires that the string memory is writeable and is SFINAEd out for const C */
   1922     C4_REQUIRE_RW(void) tolower()
   1923     {
   1924         for(size_t i = 0; i < len; ++i)
   1925         {
   1926             str[i] = static_cast<C>(::tolower(str[i]));
   1927         }
   1928     }
   1929 
   1930 public:
   1931 
   1932     /** fill the entire contents with the given @p val
   1933      * @note this method requires that the string memory is writeable and is SFINAEd out for const C */
   1934     C4_REQUIRE_RW(void) fill(C val)
   1935     {
   1936         for(size_t i = 0; i < len; ++i)
   1937         {
   1938             str[i] = val;
   1939         }
   1940     }
   1941 
   1942 public:
   1943 
   1944     /** set the current substring to a copy of the given csubstr
   1945      * @note this method requires that the string memory is writeable and is SFINAEd out for const C */
   1946     C4_REQUIRE_RW(void) copy_from(ro_substr that, size_t ifirst=0, size_t num=npos)
   1947     {
   1948         C4_ASSERT(ifirst >= 0 && ifirst <= len);
   1949         num = num != npos ? num : len - ifirst;
   1950         num = num < that.len ? num : that.len;
   1951         C4_ASSERT(ifirst + num >= 0 && ifirst + num <= len);
   1952         // calling memcpy with null strings is undefined behavior
   1953         // and will wreak havoc in calling code's branches.
   1954         // see https://github.com/biojppm/rapidyaml/pull/264#issuecomment-1262133637
   1955         if(num)
   1956             memcpy(str + sizeof(C) * ifirst, that.str, sizeof(C) * num);
   1957     }
   1958 
   1959 public:
   1960 
   1961     /** reverse in place
   1962      * @note this method requires that the string memory is writeable and is SFINAEd out for const C */
   1963     C4_REQUIRE_RW(void) reverse()
   1964     {
   1965         if(len == 0) return;
   1966         detail::_do_reverse(str, str + len - 1);
   1967     }
   1968 
   1969     /** revert a subpart in place
   1970      * @note this method requires that the string memory is writeable and is SFINAEd out for const C */
   1971     C4_REQUIRE_RW(void) reverse_sub(size_t ifirst, size_t num)
   1972     {
   1973         C4_ASSERT(ifirst >= 0 && ifirst <= len);
   1974         C4_ASSERT(ifirst + num >= 0 && ifirst + num <= len);
   1975         if(num == 0) return;
   1976         detail::_do_reverse(str + ifirst, str + ifirst + num - 1);
   1977     }
   1978 
   1979     /** revert a range in place
   1980      * @note this method requires that the string memory is writeable and is SFINAEd out for const C */
   1981     C4_REQUIRE_RW(void) reverse_range(size_t ifirst, size_t ilast)
   1982     {
   1983         C4_ASSERT(ifirst >= 0 && ifirst <= len);
   1984         C4_ASSERT(ilast  >= 0 && ilast  <= len);
   1985         if(ifirst == ilast) return;
   1986         detail::_do_reverse(str + ifirst, str + ilast - 1);
   1987     }
   1988 
   1989 public:
   1990 
   1991     /** erase part of the string. eg, with char s[] = "0123456789",
   1992      * substr(s).erase(3, 2) = "01256789", and s is now "01245678989"
   1993      * @note this method requires that the string memory is writeable and is SFINAEd out for const C */
   1994     C4_REQUIRE_RW(basic_substring) erase(size_t pos, size_t num)
   1995     {
   1996         C4_ASSERT(pos >= 0 && pos+num <= len);
   1997         size_t num_to_move = len - pos - num;
   1998         memmove(str + pos, str + pos + num, sizeof(C) * num_to_move);
   1999         return basic_substring{str, len - num};
   2000     }
   2001 
   2002     /** @note this method requires that the string memory is writeable and is SFINAEd out for const C */
   2003     C4_REQUIRE_RW(basic_substring) erase_range(size_t first, size_t last)
   2004     {
   2005         C4_ASSERT(first <= last);
   2006         return erase(first, static_cast<size_t>(last-first));
   2007     }
   2008 
   2009     /** erase a part of the string.
   2010      * @note @p sub must be a substring of this string
   2011      * @note this method requires that the string memory is writeable and is SFINAEd out for const C */
   2012     C4_REQUIRE_RW(basic_substring) erase(ro_substr sub)
   2013     {
   2014         C4_ASSERT(is_super(sub));
   2015         C4_ASSERT(sub.str >= str);
   2016         return erase(static_cast<size_t>(sub.str - str), sub.len);
   2017     }
   2018 
   2019 public:
   2020 
   2021     /** replace every occurrence of character @p value with the character @p repl
   2022      * @return the number of characters that were replaced
   2023      * @note this method requires that the string memory is writeable and is SFINAEd out for const C */
   2024     C4_REQUIRE_RW(size_t) replace(C value, C repl, size_t pos=0)
   2025     {
   2026         C4_ASSERT((pos >= 0 && pos <= len) || pos == npos);
   2027         size_t did_it = 0;
   2028         while((pos = find(value, pos)) != npos)
   2029         {
   2030             str[pos++] = repl;
   2031             ++did_it;
   2032         }
   2033         return did_it;
   2034     }
   2035 
   2036     /** replace every occurrence of each character in @p value with
   2037      * the character @p repl.
   2038      * @return the number of characters that were replaced
   2039      * @note this method requires that the string memory is writeable and is SFINAEd out for const C */
   2040     C4_REQUIRE_RW(size_t) replace(ro_substr chars, C repl, size_t pos=0)
   2041     {
   2042         C4_ASSERT((pos >= 0 && pos <= len) || pos == npos);
   2043         size_t did_it = 0;
   2044         while((pos = first_of(chars, pos)) != npos)
   2045         {
   2046             str[pos++] = repl;
   2047             ++did_it;
   2048         }
   2049         return did_it;
   2050     }
   2051 
   2052     /** replace @p pattern with @p repl, and write the result into
   2053      * @dst. pattern and repl don't need equal sizes.
   2054      *
   2055      * @return the required size for dst. No overflow occurs if
   2056      * dst.len is smaller than the required size; this can be used to
   2057      * determine the required size for an existing container. */
   2058     size_t replace_all(rw_substr dst, ro_substr pattern, ro_substr repl, size_t pos=0) const
   2059     {
   2060         C4_ASSERT( ! pattern.empty()); //!< @todo relax this precondition
   2061         C4_ASSERT( ! this  ->overlaps(dst)); //!< @todo relax this precondition
   2062         C4_ASSERT( ! pattern.overlaps(dst));
   2063         C4_ASSERT( ! repl   .overlaps(dst));
   2064         C4_ASSERT((pos >= 0 && pos <= len) || pos == npos);
   2065         C4_SUPPRESS_WARNING_GCC_PUSH
   2066         C4_SUPPRESS_WARNING_GCC("-Warray-bounds")  // gcc11 has a false positive here
   2067         #if (!defined(__clang__)) && (defined(__GNUC__) && (__GNUC__ >= 7))
   2068         C4_SUPPRESS_WARNING_GCC("-Wstringop-overflow")  // gcc11 has a false positive here
   2069         #endif
   2070         #define _c4append(first, last)                                  \
   2071             {                                                           \
   2072                 C4_ASSERT((last) >= (first));                           \
   2073                 size_t num = static_cast<size_t>((last) - (first));     \
   2074                 if(num > 0 && sz + num <= dst.len)                      \
   2075                 {                                                       \
   2076                     memcpy(dst.str + sz, first, num * sizeof(C));       \
   2077                 }                                                       \
   2078                 sz += num;                                              \
   2079             }
   2080         size_t sz = 0;
   2081         size_t b = pos;
   2082         _c4append(str, str + pos);
   2083         do {
   2084             size_t e = find(pattern, b);
   2085             if(e == npos)
   2086             {
   2087                 _c4append(str + b, str + len);
   2088                 break;
   2089             }
   2090             _c4append(str + b, str + e);
   2091             _c4append(repl.begin(), repl.end());
   2092             b = e + pattern.size();
   2093         } while(b < len && b != npos);
   2094         return sz;
   2095         #undef _c4append
   2096         C4_SUPPRESS_WARNING_GCC_POP
   2097     }
   2098 
   2099     /** @} */
   2100 
   2101 }; // template class basic_substring
   2102 
   2103 
   2104 #undef C4_REQUIRE_RW
   2105 
   2106 
   2107 //-----------------------------------------------------------------------------
   2108 //-----------------------------------------------------------------------------
   2109 //-----------------------------------------------------------------------------
   2110 
   2111 
   2112 /** @name Adapter functions. to_substr() and to_csubstr() is used in
   2113  * generic code like format(), and allow adding construction of
   2114  * substrings from new types like containers. */
   2115 /** @{ */
   2116 
   2117 
   2118 /** neutral version for use in generic code */
   2119 C4_ALWAYS_INLINE substr to_substr(substr s) noexcept { return s; }
   2120 /** neutral version for use in generic code */
   2121 C4_ALWAYS_INLINE csubstr to_csubstr(substr s) noexcept { return s; }
   2122 /** neutral version for use in generic code */
   2123 C4_ALWAYS_INLINE csubstr to_csubstr(csubstr s) noexcept { return s; }
   2124 
   2125 
   2126 template<size_t N>
   2127 C4_ALWAYS_INLINE substr
   2128 to_substr(char (&s)[N]) noexcept { substr ss(s, N-1); return ss; }
   2129 template<size_t N>
   2130 C4_ALWAYS_INLINE csubstr
   2131 to_csubstr(const char (&s)[N]) noexcept { csubstr ss(s, N-1); return ss; }
   2132 
   2133 
   2134 /** @note this overload uses SFINAE to prevent it from overriding the array overload
   2135  * @see For a more detailed explanation on why the plain overloads cannot
   2136  * coexist, see http://cplusplus.bordoon.com/specializeForCharacterArrays.html */
   2137 template<class U>
   2138 C4_ALWAYS_INLINE typename std::enable_if<std::is_same<U, char*>::value, substr>::type
   2139 to_substr(U s) noexcept { substr ss(s); return ss; }
   2140 /** @note this overload uses SFINAE to prevent it from overriding the array overload
   2141  * @see For a more detailed explanation on why the plain overloads cannot
   2142  * coexist, see http://cplusplus.bordoon.com/specializeForCharacterArrays.html */
   2143 template<class U>
   2144 C4_ALWAYS_INLINE typename std::enable_if<std::is_same<U, const char*>::value || std::is_same<U, char*>::value, csubstr>::type
   2145 to_csubstr(U s) noexcept { csubstr ss(s); return ss; }
   2146 
   2147 
   2148 /** @} */
   2149 
   2150 
   2151 //-----------------------------------------------------------------------------
   2152 //-----------------------------------------------------------------------------
   2153 //-----------------------------------------------------------------------------
   2154 
   2155 template<typename C, size_t N> inline bool operator== (const char (&s)[N], basic_substring<C> const that) noexcept { return that.compare(s, N-1) == 0; }
   2156 template<typename C, size_t N> inline bool operator!= (const char (&s)[N], basic_substring<C> const that) noexcept { return that.compare(s, N-1) != 0; }
   2157 template<typename C, size_t N> inline bool operator<  (const char (&s)[N], basic_substring<C> const that) noexcept { return that.compare(s, N-1) >  0; }
   2158 template<typename C, size_t N> inline bool operator>  (const char (&s)[N], basic_substring<C> const that) noexcept { return that.compare(s, N-1) <  0; }
   2159 template<typename C, size_t N> inline bool operator<= (const char (&s)[N], basic_substring<C> const that) noexcept { return that.compare(s, N-1) >= 0; }
   2160 template<typename C, size_t N> inline bool operator>= (const char (&s)[N], basic_substring<C> const that) noexcept { return that.compare(s, N-1) <= 0; }
   2161 
   2162 template<typename C> inline bool operator== (const char c, basic_substring<C> const that) noexcept { return that.compare(c) == 0; }
   2163 template<typename C> inline bool operator!= (const char c, basic_substring<C> const that) noexcept { return that.compare(c) != 0; }
   2164 template<typename C> inline bool operator<  (const char c, basic_substring<C> const that) noexcept { return that.compare(c) >  0; }
   2165 template<typename C> inline bool operator>  (const char c, basic_substring<C> const that) noexcept { return that.compare(c) <  0; }
   2166 template<typename C> inline bool operator<= (const char c, basic_substring<C> const that) noexcept { return that.compare(c) >= 0; }
   2167 template<typename C> inline bool operator>= (const char c, basic_substring<C> const that) noexcept { return that.compare(c) <= 0; }
   2168 
   2169 
   2170 //-----------------------------------------------------------------------------
   2171 //-----------------------------------------------------------------------------
   2172 //-----------------------------------------------------------------------------
   2173 
   2174 /** @define C4_SUBSTR_NO_OSTREAM_LSHIFT doctest does not deal well with
   2175  * template operator<<
   2176  * @see https://github.com/onqtam/doctest/pull/431 */
   2177 #ifndef C4_SUBSTR_NO_OSTREAM_LSHIFT
   2178 #ifdef __clang__
   2179 #   pragma clang diagnostic push
   2180 #   pragma clang diagnostic ignored "-Wsign-conversion"
   2181 #elif defined(__GNUC__)
   2182 #   pragma GCC diagnostic push
   2183 #   pragma GCC diagnostic ignored "-Wsign-conversion"
   2184 #endif
   2185 
   2186 /** output the string to a stream */
   2187 template<class OStream, class C>
   2188 inline OStream& operator<< (OStream& os, basic_substring<C> s)
   2189 {
   2190     os.write(s.str, s.len);
   2191     return os;
   2192 }
   2193 
   2194 // this causes ambiguity
   2195 ///** this is used by google test */
   2196 //template<class OStream, class C>
   2197 //inline void PrintTo(basic_substring<C> s, OStream* os)
   2198 //{
   2199 //    os->write(s.str, s.len);
   2200 //}
   2201 
   2202 #ifdef __clang__
   2203 #   pragma clang diagnostic pop
   2204 #elif defined(__GNUC__)
   2205 #   pragma GCC diagnostic pop
   2206 #endif
   2207 #endif // !C4_SUBSTR_NO_OSTREAM_LSHIFT
   2208 
   2209 } // namespace c4
   2210 
   2211 
   2212 #ifdef __clang__
   2213 #   pragma clang diagnostic pop
   2214 #elif defined(__GNUC__)
   2215 #   pragma GCC diagnostic pop
   2216 #endif
   2217 
   2218 #endif /* _C4_SUBSTR_HPP_ */