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