-
Notifications
You must be signed in to change notification settings - Fork 135
Expand file tree
/
Copy pathstringzilla.hpp
More file actions
5598 lines (4840 loc) 路 255 KB
/
Copy pathstringzilla.hpp
File metadata and controls
5598 lines (4840 loc) 路 255 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
* @brief StringZilla C++ wrapper improving over the performance of `std::string_view` and `std::string`,
* mostly for substring search, adding approximate matching functionality, and C++23 functionality
* to a C++11 compatible implementation.
*
* This implementation is aiming to be compatible with C++11, while implementing the C++23 functionality.
* By default, it includes C++ STL headers, but that can be avoided to minimize compilation overhead.
* https://artificial-mind.net/projects/compile-health/
*
* @see StringZilla: https://github.com/ashvardanian/StringZilla/blob/main/README.md
* @see C++ Standard String: https://en.cppreference.com/w/cpp/header/string
*
* @file include/stringzilla/stringzilla.hpp
* @author Ash Vardanian
*/
#ifndef STRINGZILLA_HPP_
#define STRINGZILLA_HPP_
#include "stringzilla/types.hpp"
/**
* @brief For higher safety, we annotate the lifetime bound of the returned string slices.
* https://clang.llvm.org/docs/AttributeReference.html#id11
* https://lemire.me/blog/2024/07/26/safer-code-in-c-with-lifetime-bounds/
*/
#if !defined(__has_cpp_attribute)
#define sz_lifetime_bound_
#elif __has_cpp_attribute(msvc::lifetimebound)
#define sz_lifetime_bound_ [[msvc::lifetimebound]]
#elif __has_cpp_attribute(clang::lifetimebound)
#define sz_lifetime_bound_ [[clang::lifetimebound]]
#elif __has_cpp_attribute(lifetimebound)
#define sz_lifetime_bound_ [[lifetimebound]]
#else
#define sz_lifetime_bound_
#endif
#if !SZ_AVOID_STL
#include <cstddef> // `std::size_t`
#include <cstdint> // `std::int8_t`
#include <iosfwd> // `std::basic_ostream`
#include <stdexcept> // `std::out_of_range`
#include <array> // `std::array`
#include <bitset> // `std::bitset`
#include <string> // `std::string`
#include <vector> // `std::vector`
#if SZ_IS_CPP17_ && defined(__cpp_lib_string_view)
#include <string_view> // `std::string_view`
#endif
#endif
#include <stringzilla/stringzilla.h>
namespace ashvardanian {
namespace stringzilla {
template <typename>
class basic_byteset;
template <typename>
class basic_string_slice;
template <typename, typename>
class basic_string;
template <typename>
class utf8_uncased_needle;
using string_span = basic_string_slice<char>;
using string_view = basic_string_slice<char const>;
template <std::size_t count_characters>
using carray = char[count_characters];
#pragma region Memory Operations
/**
* @brief Analog to @b `std::memset`, but with a more efficient implementation.
* @param target The pointer to the target memory region.
* @param value The byte value to set.
* @param n The number of bytes to copy.
* @see https://en.cppreference.com/w/cpp/string/byte/memset
*/
inline void memset(void *target, char value, std::size_t n) noexcept {
return sz_fill(reinterpret_cast<sz_ptr_t>(target), n, value);
}
/**
* @brief Analog to @b `std::memmove`, but with a more efficient implementation.
* @param target The pointer to the target memory region.
* @param source The pointer to the source memory region.
* @param n The number of bytes to copy.
* @see https://en.cppreference.com/w/cpp/string/byte/memmove
*/
inline void memmove(void *target, void const *source, std::size_t n) noexcept {
return sz_move(reinterpret_cast<sz_ptr_t>(target), reinterpret_cast<sz_cptr_t>(source), n);
}
/**
* @brief Analog to @b `std::memcpy`, but with a more efficient implementation.
* @param target The pointer to the target memory region.
* @param source The pointer to the source memory region.
* @param n The number of bytes to copy.
* @see https://en.cppreference.com/w/cpp/string/byte/memcpy
*/
inline void memcpy(void *target, void const *source, std::size_t n) noexcept {
return sz_copy(reinterpret_cast<sz_ptr_t>(target), reinterpret_cast<sz_cptr_t>(source), n);
}
#pragma endregion
#pragma region Character Sets
/**
* @brief The concatenation of the `ascii_lowercase` and `ascii_uppercase`. This value is not locale-dependent.
* @see https://docs.python.org/3/library/string.html#string.ascii_letters
*/
inline carray<52> const &ascii_letters() noexcept {
static carray<52> const all = {
//
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
};
return all;
}
/**
* @brief The lowercase letters "abcdefghijklmnopqrstuvwxyz". This value is not locale-dependent.
* @see https://docs.python.org/3/library/string.html#string.ascii_lowercase
*/
inline carray<26> const &ascii_lowercase() noexcept {
static carray<26> const all = {
//
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
};
return all;
}
/**
* @brief The uppercase letters "ABCDEFGHIJKLMNOPQRSTUVWXYZ". This value is not locale-dependent.
* @see https://docs.python.org/3/library/string.html#string.ascii_uppercase
*/
inline carray<26> const &ascii_uppercase() noexcept {
static carray<26> const all = {
//
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
};
return all;
}
/**
* @brief Printable ASCII characters, including: `digits`, `ascii_letters`, `punctuation`, and `whitespace`.
* @see https://docs.python.org/3/library/string.html#string.printable
*/
inline carray<100> const &ascii_printables() noexcept {
static carray<100> const all = {
//
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D',
'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<',
'=', '>', '?', '@', '[', '\\', ']', '^', '_', '`', '{', '|', '}', '~', ' ', '\t', '\n', '\r', '\f', '\v',
};
return all;
}
/**
* @brief Non-printable ASCII control characters. Includes all codes from 0 to 31 and 127.
*/
inline carray<33> const &ascii_controls() noexcept {
static carray<33> const all = {
//
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 127,
};
return all;
}
/**
* @brief The digits "0123456789".
* @see https://docs.python.org/3/library/string.html#string.digits
*/
inline carray<10> const &digits() noexcept {
static carray<10> const all = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
return all;
}
/**
* @brief The letters "0123456789abcdefABCDEF".
* @see https://docs.python.org/3/library/string.html#string.hexdigits
*/
inline carray<22> const &hexdigits() noexcept {
static carray<22> const all = {
//
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', //
'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F',
};
return all;
}
/**
* @brief The letters "01234567".
* @see https://docs.python.org/3/library/string.html#string.octdigits
*/
inline carray<8> const &octdigits() noexcept {
static carray<8> const all = {'0', '1', '2', '3', '4', '5', '6', '7'};
return all;
}
/**
* @brief ASCII characters considered punctuation characters in the C locale: @b !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~.
* @see https://docs.python.org/3/library/string.html#string.punctuation
*/
inline carray<32> const &punctuation() noexcept {
static carray<32> const all = {
//
'!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':',
';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '_', '`', '{', '|', '}', '~',
};
return all;
}
/**
* @brief Whitespace ASCII characters, including: space, tab, linefeed, return, formfeed, and vertical tab.
* @see https://docs.python.org/3/library/string.html#string.whitespace
*/
inline carray<6> const &whitespaces() noexcept {
static carray<6> const all = {' ', '\t', '\n', '\r', '\f', '\v'};
return all;
}
/**
* @brief ASCII characters that are considered line delimiters.
* @see https://docs.python.org/3/library/stdtypes.html#str.splitlines
*/
inline carray<8> const &newlines() noexcept {
static carray<8> const all = {'\n', '\r', '\f', '\v', '\x1C', '\x1D', '\x1E', '\x85'};
return all;
}
/**
* @brief ASCII characters forming the BASE64 encoding alphabet: a-z, A-Z, 0-9, +, and /.
* @see https://docs.python.org/3/library/base64.html
*/
inline carray<64> const &base64() noexcept {
static carray<64> const all = {
//
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/',
};
return all;
}
/**
* @brief A set of characters represented as a bitset with 256 slots.
*/
template <typename char_type_ = char>
class basic_byteset {
sz_byteset_t bitset_;
public:
using char_type = char_type_;
sz_constexpr_if_cpp14 basic_byteset() noexcept {
// ! Instead of relying on the `sz_byteset_init`, we have to reimplement it to support `constexpr`.
bitset_._u64s[0] = 0, bitset_._u64s[1] = 0, bitset_._u64s[2] = 0, bitset_._u64s[3] = 0;
}
explicit sz_constexpr_if_cpp14 basic_byteset(std::initializer_list<char_type> chars) noexcept : basic_byteset() {
// ! Instead of relying on the `sz_byteset_add(&bitset_, c)`, we have to reimplement it to support `constexpr`.
for (auto c : chars) bitset_._u64s[sz_bitcast_(sz_u8_t, c) >> 6] |= (1ull << (sz_bitcast_(sz_u8_t, c) & 63u));
}
explicit sz_constexpr_if_cpp14 basic_byteset(char_type const *chars, std::size_t count_characters) noexcept
: basic_byteset() {
for (std::size_t i = 0; i < count_characters; ++i) {
char_type c = chars[i];
bitset_._u64s[sz_bitcast_(sz_u8_t, c) >> 6] |= (1ull << (sz_bitcast_(sz_u8_t, c) & 63u));
}
}
template <std::size_t count_characters>
explicit sz_constexpr_if_cpp14 basic_byteset(std::array<char_type, count_characters> const &chars) noexcept
: basic_byteset() {
static_assert(count_characters > 0, "Character array cannot be empty");
for (std::size_t i = 0; i < count_characters; ++i) {
char_type c = chars[i];
bitset_._u64s[sz_bitcast_(sz_u8_t, c) >> 6] |= (1ull << (sz_bitcast_(sz_u8_t, c) & 63u));
}
}
sz_constexpr_if_cpp14 basic_byteset(basic_byteset const &other) noexcept : bitset_(other.bitset_) {}
sz_constexpr_if_cpp14 basic_byteset &operator=(basic_byteset const &other) noexcept {
bitset_ = other.bitset_;
return *this;
}
sz_constexpr_if_cpp14 basic_byteset operator|(basic_byteset other) const noexcept {
basic_byteset result = *this; //? Variable declaration in a `constexpr` function is a C++14 extension
result.bitset_._u64s[0] |= other.bitset_._u64s[0], result.bitset_._u64s[1] |= other.bitset_._u64s[1],
result.bitset_._u64s[2] |= other.bitset_._u64s[2], result.bitset_._u64s[3] |= other.bitset_._u64s[3];
return result;
}
inline basic_byteset &add(char_type c) noexcept {
sz_byteset_add(&bitset_, sz_bitcast_(sz_u8_t, c));
return *this;
}
inline std::size_t size() const noexcept {
return //
sz_u64_popcount(bitset_._u64s[0]) + sz_u64_popcount(bitset_._u64s[1]) + //
sz_u64_popcount(bitset_._u64s[2]) + sz_u64_popcount(bitset_._u64s[3]);
}
inline sz_byteset_t &raw() noexcept { return bitset_; }
inline sz_byteset_t const &raw() const noexcept { return bitset_; }
inline bool contains(char_type c) const noexcept { return sz_byteset_contains(&bitset_, sz_bitcast_(sz_u8_t, c)); }
inline basic_byteset inverted() const noexcept {
basic_byteset result = *this;
sz_byteset_invert(&result.bitset_);
return result;
}
};
using byteset = basic_byteset<char>;
inline byteset ascii_letters_set() { return byteset {ascii_letters(), sizeof(ascii_letters())}; }
inline byteset ascii_lowercase_set() { return byteset {ascii_lowercase(), sizeof(ascii_lowercase())}; }
inline byteset ascii_uppercase_set() { return byteset {ascii_uppercase(), sizeof(ascii_uppercase())}; }
inline byteset ascii_printables_set() { return byteset {ascii_printables(), sizeof(ascii_printables())}; }
inline byteset ascii_controls_set() { return byteset {ascii_controls(), sizeof(ascii_controls())}; }
inline byteset digits_set() { return byteset {digits(), sizeof(digits())}; }
inline byteset hexdigits_set() { return byteset {hexdigits(), sizeof(hexdigits())}; }
inline byteset octdigits_set() { return byteset {octdigits(), sizeof(octdigits())}; }
inline byteset punctuation_set() { return byteset {punctuation(), sizeof(punctuation())}; }
inline byteset whitespaces_set() { return byteset {whitespaces(), sizeof(whitespaces())}; }
inline byteset newlines_set() { return byteset {newlines(), sizeof(newlines())}; }
inline byteset base64_set() { return byteset {base64(), sizeof(base64())}; }
/**
* @brief A look-up table for character replacement operations. Exactly 256 bytes for byte-to-byte replacement.
* @warning For larger character types should be allocated on the heap.
*/
template <typename char_type_ = char>
class basic_look_up_table {
static_assert(sizeof(char_type_) == 1 || sizeof(char_type_) == 2 || sizeof(char_type_) == 4,
"Character type must be 1, 2, or 4 bytes long");
static constexpr std::size_t size_k = sizeof(char_type_) == 1 ? 256ul
: sizeof(char_type_) == 2 ? 65536ul
: 4294967296ul;
static constexpr std::size_t bytes_k = size_k * sizeof(char_type_);
using unsigned_type_ = typename std::make_unsigned<char_type_>::type;
char_type_ lut_[size_k];
public:
using char_type = char_type_;
basic_look_up_table() noexcept { memset(&lut_[0], 0, bytes_k); }
explicit basic_look_up_table(char_type const (&chars)[size_k]) noexcept { memcpy(&lut_[0], chars, bytes_k); }
basic_look_up_table(std::array<char_type, size_k> const &chars) noexcept {
memcpy(&lut_[0], chars.data(), bytes_k);
}
basic_look_up_table(basic_look_up_table const &other) noexcept { memcpy(&lut_[0], other.lut_, bytes_k); }
basic_look_up_table &operator=(basic_look_up_table const &other) noexcept {
memcpy(&lut_[0], other.lut_, bytes_k);
return *this;
}
/**
* @brief Creates a look-up table with a one-to-one mapping of characters to themselves.
* @see Similar to `std::iota` filling, but properly handles signed integer casts.
*/
static basic_look_up_table identity() noexcept {
basic_look_up_table result;
for (std::size_t i = 0; i < size_k; ++i) { result.lut_[i] = static_cast<unsigned_type_>(i); }
return result;
}
inline sz_cptr_t raw() const noexcept { return reinterpret_cast<sz_cptr_t>(&lut_[0]); }
inline char_type &operator[](char_type c) noexcept { return lut_[sz_bitcast_(unsigned_type_, c)]; }
inline char_type const &operator[](char_type c) const noexcept { return lut_[sz_bitcast_(unsigned_type_, c)]; }
};
using look_up_table = basic_look_up_table<char>;
#pragma endregion
#pragma region Ranges of Search Matches
struct end_sentinel_type {};
struct include_overlaps_type {};
struct exclude_overlaps_type {};
#if SZ_IS_CPP17_
inline static constexpr end_sentinel_type end_sentinel;
inline static constexpr include_overlaps_type include_overlaps;
inline static constexpr exclude_overlaps_type exclude_overlaps;
#endif
/**
* @brief Zero-cost wrapper around the `.find` member function of string-like classes.
* @see https://en.cppreference.com/w/cpp/string/basic_string/find
*/
template <typename string_type_, typename overlaps_type_ = include_overlaps_type, typename needle_type_ = string_type_>
struct matcher_find {
using size_type = typename string_type_::size_type;
needle_type_ needle_;
matcher_find(needle_type_ needle = {}) noexcept : needle_(needle) {}
size_type needle_length() const noexcept { return needle_.length(); }
size_type operator()(string_type_ haystack) const noexcept { return haystack.find(needle_); }
size_type skip_length() const noexcept {
// TODO: Apply Galil rule to match repetitive patterns in strictly linear time.
// Floor at 1 so an empty needle still advances.
return is_same_type<overlaps_type_, include_overlaps_type>::value //
? 1
: sz_max_of_two(needle_.length(), size_type(1));
}
};
/**
* @brief Zero-cost wrapper around the `.rfind` member function of string-like classes.
* @see https://en.cppreference.com/w/cpp/string/basic_string/rfind
*/
template <typename string_type_, typename overlaps_type_ = include_overlaps_type, typename needle_type_ = string_type_>
struct matcher_rfind {
using size_type = typename string_type_::size_type;
needle_type_ needle_;
matcher_rfind(needle_type_ needle = {}) noexcept : needle_(needle) {}
size_type needle_length() const noexcept { return needle_.length(); }
size_type operator()(string_type_ haystack) const noexcept { return haystack.rfind(needle_); }
size_type skip_length() const noexcept {
// TODO: Apply Galil rule to match repetitive patterns in strictly linear time.
// Floor at 1 so an empty needle still advances.
return is_same_type<overlaps_type_, include_overlaps_type>::value //
? 1
: sz_max_of_two(needle_.length(), size_type(1));
}
};
/**
* @brief Zero-cost wrapper around the `.find_first_of` member function of string-like classes.
* @see https://en.cppreference.com/w/cpp/string/basic_string/find_first_of
*/
template <typename haystack_type_, typename needles_type_ = haystack_type_>
struct matcher_find_first_of {
using size_type = typename haystack_type_::size_type;
needles_type_ needles_;
constexpr size_type needle_length() const noexcept { return 1; }
constexpr size_type skip_length() const noexcept { return 1; }
size_type operator()(haystack_type_ haystack) const noexcept { return haystack.find_first_of(needles_); }
};
/**
* @brief Zero-cost wrapper around the `.find_last_of` member function of string-like classes.
* @see https://en.cppreference.com/w/cpp/string/basic_string/find_last_of
*/
template <typename haystack_type_, typename needles_type_ = haystack_type_>
struct matcher_find_last_of {
using size_type = typename haystack_type_::size_type;
needles_type_ needles_;
constexpr size_type needle_length() const noexcept { return 1; }
constexpr size_type skip_length() const noexcept { return 1; }
size_type operator()(haystack_type_ haystack) const noexcept { return haystack.find_last_of(needles_); }
};
/**
* @brief Zero-cost wrapper around the `.find_first_not_of` member function of string-like classes.
* @see https://en.cppreference.com/w/cpp/string/basic_string/find_first_not_of
*/
template <typename haystack_type_, typename needles_type_ = haystack_type_>
struct matcher_find_first_not_of {
using size_type = typename haystack_type_::size_type;
needles_type_ needles_;
constexpr size_type needle_length() const noexcept { return 1; }
constexpr size_type skip_length() const noexcept { return 1; }
size_type operator()(haystack_type_ haystack) const noexcept { return haystack.find_first_not_of(needles_); }
};
/**
* @brief Zero-cost wrapper around the `.find_last_not_of` member function of string-like classes.
* @see https://en.cppreference.com/w/cpp/string/basic_string/find_last_not_of
*/
template <typename haystack_type_, typename needles_type_ = haystack_type_>
struct matcher_find_last_not_of {
using size_type = typename haystack_type_::size_type;
needles_type_ needles_;
constexpr size_type needle_length() const noexcept { return 1; }
constexpr size_type skip_length() const noexcept { return 1; }
size_type operator()(haystack_type_ haystack) const noexcept { return haystack.find_last_not_of(needles_); }
};
/**
* @brief Helper to detect if a type has a nested `::string_view` typedef.
* Uses SFINAE with no STL dependencies for `std::enabled_if` or `std::void_t`.
*/
template <typename type_>
struct has_string_view_member_ {
private:
template <typename candidate_>
static char test_(typename candidate_::string_view *);
template <typename candidate_>
static int test_(...);
public:
static constexpr bool value = sizeof(test_<type_>(0)) == sizeof(char);
};
/**
* @brief Helper to extract the appropriate view type for a string-like type.
* For StringZilla types, uses the nested ::string_view typedef.
* For STL types (like std::string_view), uses the type itself.
*/
template <typename string_type_, bool has_nested_view_ = has_string_view_member_<string_type_>::value>
struct string_view_for {
// Default: use the type itself (for STL types)
using type = string_type_;
};
// Specialization for types with nested ::string_view
template <typename string_type_>
struct string_view_for<string_type_, true> {
// For StringZilla types with nested ::string_view
using type = typename string_type_::string_view;
};
/**
* @brief Helper to detect if a type can be shrunk from the front, as the range iterators require.
* Uses SFINAE with no STL dependencies, same shape as `has_string_view_member_`.
*
* Owning types like `std::string` have no `remove_prefix`, so they can be passed to the
* range-producing functions but fail deep inside the iterator. This probe backs a `static_assert`
* that rejects them at the call site instead.
*/
template <typename type_>
struct has_remove_prefix_member_ {
private:
template <typename candidate_>
static char test_(candidate_ *candidate, decltype(candidate->remove_prefix(1)) * = nullptr);
template <typename candidate_>
static int test_(...);
public:
static constexpr bool value = sizeof(test_<type_>(nullptr)) == sizeof(char);
};
/**
* @brief Infers how a haystack is stored inside a range-producing view.
*
* The range-producing functions take a forwarding reference, so the value category of the argument
* decides the storage. An @b lvalue is borrowed as a non-owning view into the caller's buffer - no
* copy is made, and the produced slices stay comparable against iterators of the original string.
* An @b rvalue is stored by value, keeping the temporary alive for as long as the range walks it.
*
* Borrowing matters beyond the copy: a view over a copied haystack yields slices that point into
* that private copy, so measuring them against the caller's iterators gives meaningless offsets.
*/
template <typename haystack_type_>
struct range_haystack_for {
using type = haystack_type_;
};
// A `const` lvalue must shed its qualifier first. StringZilla types survive it either way, as
// `has_string_view_member_` reaches the nested typedef through the `const`, but an STL view is its own
// view type - `std::string_view const` would keep the qualifier and lose its `remove_prefix`.
template <typename haystack_type_>
struct range_haystack_for<haystack_type_ &> {
using type = typename string_view_for<typename std::remove_cv<haystack_type_>::type>::type;
};
/**
* @brief Infers how a needle is stored inside a matcher.
*
* Unlike the haystack, the needle is always stored by value in its own type, which is what keeps
* `find_all(haystack, sz::string("x"))` safe - viewing a temporary needle would leave the matcher
* pointing at freed memory for the whole life of the range. Raw character buffers are the exception:
* they have no `length()` member to match against, and string literals have static storage, so they
* are narrowed to the haystack's view type instead.
*/
template <typename view_type_, typename needle_type_>
struct range_needle_for {
using type = needle_type_;
};
template <typename view_type_, std::size_t length_>
struct range_needle_for<view_type_, char[length_]> {
using type = view_type_;
};
template <typename view_type_, std::size_t length_>
struct range_needle_for<view_type_, char const[length_]> {
using type = view_type_;
};
template <typename view_type_>
struct range_needle_for<view_type_, char *> {
using type = view_type_;
};
template <typename view_type_>
struct range_needle_for<view_type_, char const *> {
using type = view_type_;
};
/** @brief Storage for a forwarded haystack - a borrowed view for lvalues, an owned copy for rvalues. */
template <typename haystack_type_>
using range_haystack_type = typename range_haystack_for<haystack_type_>::type;
/**
* @brief Rejects haystacks that cannot be walked, before the range types are instantiated.
*
* The check has to live in a trait rather than in the function body: the return type of every
* range-producing function mentions the matcher, and a haystack like a bare string literal explodes
* inside `matcher_find` while the body is still uninstantiated. Sitting on the path to the view type
* means this diagnostic is the one the caller sees.
*/
template <typename view_type_, bool has_remove_prefix_ = has_remove_prefix_member_<view_type_>::value>
struct range_walkable_view_ {
using type = view_type_;
};
template <typename view_type_>
struct range_walkable_view_<view_type_, false> {
static_assert(has_remove_prefix_member_<view_type_>::value,
"Range haystacks need a `remove_prefix` member - pass a view, or `.view()` of an owning string.");
using type = view_type_;
};
/** @brief The slice type produced when iterating a forwarded haystack. */
template <typename haystack_type_>
using range_view_type =
typename range_walkable_view_<typename string_view_for<range_haystack_type<haystack_type_>>::type>::type;
/** @brief Storage for a needle paired with the haystack view type it will be matched against. */
template <typename haystack_type_, typename needle_type_>
using range_needle_type = typename range_needle_for<range_view_type<haystack_type_>, needle_type_>::type;
/**
* @brief A range of string slices representing the matches of a substring search.
*
* @note Lifetime semantics: Stores forwarded objects (including owning strings) to maintain lifetime.
* Iterators receive lightweight views only, ensuring safe iteration without ownership concerns.
* @note For-loop optimized: Iterators are lightweight views with minimal register pressure, ideal for
* high-performance applications where cache efficiency and register allocation matter.
* @note Sentinel support: Supports sentinel-based iteration via `operator==(end_sentinel_type)` for
* efficient termination without constructing full end iterators.
* @note Compatible with C++23 ranges, C++11 string views, and of course, StringZilla.
* @see Similar to a pair of `boost::algorithm::find_iterator`.
*/
template <typename string_type_, typename matcher_type_>
class find_matches_view {
public:
using string_type = string_type_;
using matcher_type = matcher_type_;
using string_view_type = typename string_view_for<string_type>::type;
private:
matcher_type matcher_;
string_type haystack_;
public:
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using value_type = string_view_type;
using pointer = string_view_type; // Needed for compatibility with STL container constructors.
using reference = string_view_type; // Needed for compatibility with STL container constructors.
find_matches_view(string_type haystack, matcher_type needle) noexcept
: matcher_(std::move(needle)), haystack_(std::move(haystack)) {}
class iterator {
matcher_type matcher_;
string_view_type remaining_;
public:
using iterator_category = std::forward_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = string_view_type;
using pointer = string_view_type; // Needed for compatibility with STL container constructors.
using reference = string_view_type; // Needed for compatibility with STL container constructors.
iterator(string_view_type haystack, matcher_type matcher) noexcept : matcher_(matcher), remaining_(haystack) {
auto position = matcher_(remaining_);
remaining_.remove_prefix(position != string_type::npos ? position : remaining_.size());
}
pointer operator->() const noexcept = delete;
value_type operator*() const noexcept { return remaining_.substr(0, matcher_.needle_length()); }
iterator &operator++() noexcept {
remaining_.remove_prefix(matcher_.skip_length());
auto position = matcher_(remaining_);
remaining_.remove_prefix(position != string_type::npos ? position : remaining_.size());
return *this;
}
iterator operator++(int) noexcept {
iterator temp = *this;
++(*this);
return temp;
}
// Assumes both iterators point to the same underlying string.
bool operator!=(iterator const &other) const noexcept { return remaining_.data() != other.remaining_.data(); }
bool operator==(iterator const &other) const noexcept { return remaining_.data() == other.remaining_.data(); }
bool operator!=(end_sentinel_type) const noexcept { return !remaining_.empty(); }
bool operator==(end_sentinel_type) const noexcept { return remaining_.empty(); }
};
iterator begin() const noexcept { return {string_view_type(haystack_), matcher_}; }
iterator end() const noexcept { return {string_view_type(haystack_.data() + haystack_.size(), 0ull), matcher_}; }
size_type size() const noexcept { return static_cast<size_type>(ssize()); }
difference_type ssize() const noexcept { return std::distance(begin(), end()); }
bool empty() const noexcept { return begin() == end_sentinel_type {}; }
bool include_overlaps() const noexcept { return matcher_.skip_length() < matcher_.needle_length(); }
/** @brief Copies the matches into a container. */
template <typename container_>
void to(container_ &container) {
for (auto it_ = this->begin(); it_ != this->end(); ++it_) container.push_back(*it_);
}
/** @brief Copies the matches into a consumed container, returning it at the end. */
template <typename container_>
container_ to() {
return container_ {begin(), end()};
}
};
/**
* @brief A range of string slices representing the matches of a @b reverse-order substring search.
*
* @note Lifetime semantics: Stores forwarded objects (including owning strings) to maintain lifetime.
* Iterators receive lightweight views only, ensuring safe iteration without ownership concerns.
* @note For-loop optimized: Iterators are lightweight views with minimal register pressure, ideal for
* high-performance applications where cache efficiency and register allocation matter.
* @note Sentinel support: Supports sentinel-based iteration via `operator==(end_sentinel_type)` for
* efficient termination without constructing full end iterators.
* @note Compatible with C++23 ranges, C++11 string views, and of course, StringZilla.
* @see Similar to a pair of `boost::algorithm::find_iterator`.
*/
template <typename string_type_, typename matcher_type_>
class rfind_matches_view {
public:
using string_type = string_type_;
using matcher_type = matcher_type_;
using string_view_type = typename string_view_for<string_type>::type;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using value_type = string_view_type;
using pointer = string_view_type; // Needed for compatibility with STL container constructors.
using reference = string_view_type; // Needed for compatibility with STL container constructors.
private:
matcher_type matcher_;
string_type haystack_;
public:
rfind_matches_view(string_type haystack, matcher_type needle) noexcept
: matcher_(std::move(needle)), haystack_(std::move(haystack)) {}
class iterator {
matcher_type matcher_;
string_view_type remaining_;
public:
using iterator_category = std::forward_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = string_view_type;
using pointer = string_view_type; // Needed for compatibility with STL container constructors.
using reference = string_view_type; // Needed for compatibility with STL container constructors.
iterator(string_view_type haystack, matcher_type matcher) noexcept : matcher_(matcher), remaining_(haystack) {
auto position = matcher_(remaining_);
remaining_.remove_suffix( //
position != string_type::npos //
? remaining_.size() - position - matcher_.needle_length()
: remaining_.size());
}
pointer operator->() const noexcept = delete;
value_type operator*() const noexcept {
return remaining_.substr(remaining_.size() - matcher_.needle_length());
}
iterator &operator++() noexcept {
remaining_.remove_suffix(matcher_.skip_length());
auto position = matcher_(remaining_);
remaining_.remove_suffix( //
position != string_type::npos //
? remaining_.size() - position - matcher_.needle_length()
: remaining_.size());
return *this;
}
iterator operator++(int) noexcept {
iterator temp = *this;
++(*this);
return temp;
}
// Assumes both iterators point to the same underlying string.
// This has to be `.data() + .size()`, to be compatible with `std::string_view` on MSVC.
bool operator!=(iterator const &other) const noexcept {
return remaining_.data() + remaining_.size() != other.remaining_.data() + other.remaining_.size();
}
bool operator==(iterator const &other) const noexcept {
return remaining_.data() + remaining_.size() == other.remaining_.data() + other.remaining_.size();
}
bool operator!=(end_sentinel_type) const noexcept { return !remaining_.empty(); }
bool operator==(end_sentinel_type) const noexcept { return remaining_.empty(); }
};
iterator begin() const noexcept { return {string_view_type(haystack_), matcher_}; }
iterator end() const noexcept { return {string_view_type(haystack_.data(), 0ull), matcher_}; }
size_type size() const noexcept { return static_cast<size_type>(ssize()); }
difference_type ssize() const noexcept { return std::distance(begin(), end()); }
bool empty() const noexcept { return begin() == end_sentinel_type {}; }
bool include_overlaps() const noexcept { return matcher_.skip_length() < matcher_.needle_length(); }
/** @brief Copies the matches into a container. */
template <typename container_>
void to(container_ &container) {
for (auto it_ = this->begin(); it_ != this->end(); ++it_) container.push_back(*it_);
}
/** @brief Copies the matches into a consumed container, returning it at the end. */
template <typename container_>
container_ to() {
return container_ {begin(), end()};
}
};
/**
* @brief A range of string slices for different splits of the data.
*
* @note Lifetime semantics: Stores forwarded objects (including owning strings) to maintain lifetime.
* Iterators receive lightweight views only, ensuring safe iteration without ownership concerns.
* @note For-loop optimized: Iterators are lightweight views with minimal register pressure, ideal for
* high-performance applications where cache efficiency and register allocation matter.
* @note Sentinel support: Supports sentinel-based iteration via `operator==(end_sentinel_type)` for
* efficient termination without constructing full end iterators.
* @note Compatible with C++23 ranges, C++11 string views, and of course, StringZilla.
* @see Similar to a pair of `boost::algorithm::split_iterator`.
*
* In some sense, represents the inverse operation to `find_matches_view`, as it reports not the search matches
* but the data between them. Meaning that for `N` search matches, there will be `N+1` elements in the range.
* Unlike ::find_matches_view, this range can't be empty. It also can't report overlapping intervals.
*/
template <typename string_type_, typename matcher_type_, bool skip_empty_ = false>
class find_splits_view {
public:
using string_type = string_type_;
using matcher_type = matcher_type_;
using string_view_type = typename string_view_for<string_type>::type;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using value_type = string_view_type;
using pointer = string_view_type; // Needed for compatibility with STL container constructors.
using reference = string_view_type; // Needed for compatibility with STL container constructors.
private:
matcher_type matcher_;
string_type haystack_;
public:
find_splits_view(string_type haystack, matcher_type needle) noexcept
: matcher_(std::move(needle)), haystack_(std::move(haystack)) {}
class iterator {
char const *start_; // Start of current segment
char const *end_; // End of haystack (immutable)
size_type match_length_; // Length of current segment
matcher_type matcher_;
/** @brief Advance to the next segment (one delimiter). */
void advance_() noexcept {
start_ += match_length_;
if (start_ > end_) return;
if (start_ == end_) { // The final empty segment was just yielded; move past `end_` to terminate.
++start_, match_length_ = 0;
return;
}
// A zero-length delimiter (empty needle) still occupies one scan step, or `start_` would
// never move and the same zero-width match would repeat forever.
start_ += sz_max_of_two(matcher_.needle_length(), size_type(1));
if (start_ > end_) {
match_length_ = 0;
return;
}
string_view_type remaining(start_, static_cast<size_type>(end_ - start_));
auto position = matcher_(remaining);
match_length_ = position != string_type::npos ? position : remaining.size();
}
/** @brief When `skip_empty_`, advance past zero-length segments. No-op otherwise. */
void settle_() noexcept {
if (skip_empty_)
while (start_ <= end_ && match_length_ == 0) advance_();
}
public:
using iterator_category = std::forward_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = string_view_type;
using pointer = string_view_type; // Needed for compatibility with STL container constructors.
using reference = string_view_type; // Needed for compatibility with STL container constructors.
iterator(string_view_type haystack, matcher_type matcher) noexcept
: start_(haystack.data()), end_(haystack.data() + haystack.size()), match_length_(0), matcher_(matcher) {
// Empty delimiter: no split.
if (matcher_.needle_length() == 0) {
match_length_ = haystack.size();
settle_();
return;
}
auto position = matcher_(haystack);
match_length_ = position != string_type::npos ? position : haystack.size();
settle_();
}
iterator(string_view_type haystack, matcher_type matcher, end_sentinel_type) noexcept
: start_(haystack.data() + haystack.size() + 1), end_(haystack.data() + haystack.size()), match_length_(0),
matcher_(matcher) {}
pointer operator->() const noexcept = delete;
value_type operator*() const noexcept { return string_view_type(start_, match_length_); }
iterator &operator++() noexcept {
advance_(), settle_();
return *this;
}
iterator operator++(int) noexcept {
iterator temp = *this;
++(*this);
return temp;
}
bool operator!=(iterator const &other) const noexcept { return start_ != other.start_; }
bool operator==(iterator const &other) const noexcept { return start_ == other.start_; }
bool operator!=(end_sentinel_type) const noexcept { return start_ <= end_; }
bool operator==(end_sentinel_type) const noexcept { return start_ > end_; }
bool is_last() const noexcept { return start_ + match_length_ == end_; }
};
iterator begin() const noexcept { return {string_view_type(haystack_), matcher_}; }
iterator end() const noexcept {
return {string_view_type(haystack_.end(), 0), matcher_, end_sentinel_type {}};
}
size_type size() const noexcept { return static_cast<size_type>(ssize()); }
difference_type ssize() const noexcept { return std::distance(begin(), end()); }
constexpr bool empty() const noexcept { return false; }
/** @brief Copies the matches into a container. */
template <typename container_>
void to(container_ &container) {
for (auto it_ = this->begin(); it_ != this->end(); ++it_) container.push_back(*it_);
}
/** @brief Copies the matches into a consumed container, returning it at the end. */
template <typename container_>
container_ to(container_ &&container = {}) {
for (auto it_ = this->begin(); it_ != this->end(); ++it_) container.push_back(*it_);
return std::move(container);
}
};
/**
* @brief A range of string slices for different splits of the data in @b reverse-order.
*
* @note Lifetime semantics: Stores forwarded objects (including owning strings) to maintain lifetime.
* Iterators receive lightweight views only, ensuring safe iteration without ownership concerns.
* @note For-loop optimized: Iterators are lightweight views with minimal register pressure, ideal for
* high-performance applications where cache efficiency and register allocation matter.
* @note Sentinel support: Supports sentinel-based iteration via `operator==(end_sentinel_type)` for
* efficient termination without constructing full end iterators.
* @note Compatible with C++23 ranges, C++11 string views, and of course, StringZilla.
* @see Similar to a pair of `boost::algorithm::split_iterator`.
*
* In some sense, represents the inverse operation to `find_matches_view`, as it reports not the search matches
* but the data between them. Meaning that for `N` search matches, there will be `N+1` elements in the range.
* Unlike ::find_matches_view, this range can't be empty. It also can't report overlapping intervals.
*/
template <typename string_type_, typename matcher_type_, bool skip_empty_ = false>
class rfind_splits_view {
public:
using string_type = string_type_;
using matcher_type = matcher_type_;
using string_view_type = typename string_view_for<string_type>::type;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using value_type = string_view_type;
using pointer = string_view_type; // Needed for compatibility with STL container constructors.
using reference = string_view_type; // Needed for compatibility with STL container constructors.