Stroika Library 3.0d24
 
Loading...
Searching...
No Matches
String.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#include <algorithm>
7#include <climits>
8#include <cstdarg>
9#include <istream>
10#include <regex>
11#include <string>
12
15#include "Stroika/Foundation/Characters/SDKString.h"
19#include "Stroika/Foundation/Containers/Set.h"
20#include "Stroika/Foundation/Containers/Support/ReserveTweaks.h"
23#include "Stroika/Foundation/Execution/Exceptions.h"
24#include "Stroika/Foundation/Execution/Throw.h"
25#include "Stroika/Foundation/Math/Common.h"
27#include "Stroika/Foundation/Memory/Common.h"
29
30#include "String.h"
31
32using namespace Stroika::Foundation;
35using namespace Stroika::Foundation::Common;
36
37using Memory::MakeSharedPtr;
40
41// see Satisfies Concepts:
42static_assert (regular<String>);
43
44#if qStroika_Foundation_Characters_AsPathAutoMapMSYSAndCygwin
45#include <filesystem>
46#endif
47
48namespace {
49
50 /**
51 * Helper for sharing implementation code on string reps
52 * This REP is templated on CHAR_T. The key is that ALL characters for that string fit inside
53 * CHAR_T, so that the implementation can store them as an array, and index.
54 * So mixed 1,2,3 byte characters all get stored in a char32_t array, and a string with all ascii
55 * characters get stored in a char (1byte stride) array.
56 *
57 * \note - the KEY design choice in StringRepHelperAllFitInSize_::Rep<CHAR_T> is that it contains no
58 * multi-code-point characters. This is what allows the simple calculation of array index
59 * to character offset. So use
60 * StringRepHelperAllFitInSize_::Rep<ASCII> for ascii text
61 * StringRepHelperAllFitInSize_::Rep<LATIN1> for ISOLatin1 text
62 * StringRepHelperAllFitInSize_::Rep<char16_t> for ISOLatin1/anything which is a 2-byte unicode char (not surrogates)
63 * StringRepHelperAllFitInSize_::Rep<char32_t> for anything else - this always works
64 */
65 struct StringRepHelperAllFitInSize_ : String {
66 template <IUNICODECanUnambiguouslyConvertFrom CHAR_T>
67 struct Rep : public _IRep {
68 private:
69 using inherited = _IRep;
70
71 protected:
72 span<const CHAR_T> _fData;
73
74#if qStroika_Foundation_Debug_AssertionsChecked
75 private:
76 mutable unsigned int fOutstandingIterators_{};
77#endif
78
79 protected:
80 Rep () = default;
81 Rep (span<const CHAR_T> s)
82 requires (not same_as<CHAR_T, char8_t>) // char8 ironically involves 2-byte characters, cuz only ascii encoded as 1 byte
83 : _fData{s}
84 {
85 if constexpr (same_as<CHAR_T, char> or same_as<CHAR_T, char8_t>) {
86 Require (Character::IsASCII (s));
87 }
88 // Any 8-bit sequence valid for Latin1
89 if constexpr (same_as<CHAR_T, char16_t>) {
91 }
92 }
93 Rep& operator= (span<const CHAR_T> s)
94 {
95#if qStroika_Foundation_Debug_AssertionsChecked
96 Require (fOutstandingIterators_ == 0);
97#endif
98 if constexpr (same_as<CHAR_T, char> or same_as<CHAR_T, char8_t>) {
99 Require (Character::IsASCII (s));
100 }
101 if constexpr (same_as<CHAR_T, char16_t>) {
103 }
104 _fData = s;
105 return *this;
106 }
107
108 public:
109 // String::_IRep OVERRIDES
110 virtual Character GetAt (size_t index) const noexcept override
111 {
112 Require (index < _fData.size ());
113 // NOTE - this is safe because we never construct this type with surrogates
114 return Character{static_cast<char32_t> (_fData[index])};
115 }
116 virtual PeekSpanData PeekData (optional<PeekSpanData::StorageCodePointType> /*preferred*/) const noexcept override
117 {
118 // IGNORE preferred, cuz we return what is in our REP - since returning a direct pointer to that data - no conversion possible
119 if constexpr (same_as<CHAR_T, ASCII>) {
120 return PeekSpanData{PeekSpanData::StorageCodePointType::eAscii, {.fAscii = _fData}};
121 }
122 if constexpr (same_as<CHAR_T, Latin1>) {
123 return PeekSpanData{PeekSpanData::StorageCodePointType::eSingleByteLatin1, {.fSingleByteLatin1 = _fData}};
124 }
125 else if constexpr (sizeof (CHAR_T) == 2) {
126 // reinterpret_cast needed cuz of wchar_t case
127 return PeekSpanData{PeekSpanData::StorageCodePointType::eChar16,
128 {.fChar16 = span<const char16_t>{reinterpret_cast<const char16_t*> (_fData.data ()), _fData.size ()}}};
129 }
130 else if constexpr (sizeof (CHAR_T) == 4) {
131 // reinterpret_cast needed cuz of wchar_t case
132 return PeekSpanData{PeekSpanData::StorageCodePointType::eChar32,
133 {.fChar32 = span<const char32_t>{reinterpret_cast<const char32_t*> (_fData.data ()), _fData.size ()}}};
134 }
135 }
136
137 // Overrides for Iterable<Character>
138 // @todo - MAYBE override Apply/Find and a few others to not use default 'iterator object' implementation that has lots of indirect virtual calls
139 public:
140 virtual shared_ptr<Iterable<Character>::_IRep> Clone () const override
141 {
142 AssertNotReached (); // Since String reps now immutable, this should never be called
143 return nullptr;
144 }
145 virtual Traversal::Iterator<value_type> MakeIterator () const override
146 {
147 // NOTE - UNDETECTED CALLER ERROR - if iterator constructed and used after string rep destroyed (never changed) -- LGP 2023-07-07
148 struct MyIterRep_ final : Iterator<Character>::IRep, public Memory::UseBlockAllocationIfAppropriate<MyIterRep_> {
149 span<const CHAR_T> fData_; // clone span (not underlying data)
150 size_t fIdx_{0};
151#if qStroika_Foundation_Debug_AssertionsChecked
152 const Rep* fOwningRep_;
153#endif
154 MyIterRep_ (span<const CHAR_T> data
156 ,
157 const Rep* dbgRep
158#endif
159 )
160 : fData_{data}
162 , fOwningRep_{dbgRep}
163#endif
164 {
165#if qStroika_Foundation_Debug_AssertionsChecked
166 ++fOwningRep_->fOutstandingIterators_;
167#endif
168 }
169#if qStroika_Foundation_Debug_AssertionsChecked
170 virtual ~MyIterRep_ () override
171 {
172 Require (fOwningRep_->fOutstandingIterators_ > 0); // if this fails, probably cuz fOwningRep_ destroyed
173 --fOwningRep_->fOutstandingIterators_;
174 }
175#endif
176
177 virtual unique_ptr<Iterator<Character>::IRep> Clone () const override
178 {
179 return make_unique<MyIterRep_> (fData_.subspan (fIdx_)
181 ,
182 fOwningRep_
183#endif
184 );
185 }
186 virtual bool AtEnd () const override
187 {
188 Assert (fIdx_ <= fData_.size ());
189 return fIdx_ == fData_.size ();
190 }
191 virtual optional<Character> Current () const override
192 {
193 if (fIdx_ < fData_.size ()) {
194 return Character{static_cast<char32_t> (fData_[fIdx_])};
195 }
196 else {
197 return nullopt;
198 }
199 }
200 virtual optional<Character> More () override
201 {
202 Require (fIdx_ < fData_.size ());
203 ++fIdx_;
204 if (fIdx_ < fData_.size ()) [[likely]] {
205 // NOTE - this is safe because we never construct this type with surrogates
206 return Character{static_cast<char32_t> (fData_[fIdx_])};
207 }
208 else {
209 return nullopt;
210 }
211 }
212 virtual bool Equals (const IRep* rhs) const override
213 {
214 RequireNotNull (rhs);
215 RequireMember (rhs, MyIterRep_);
216 const MyIterRep_* rrhs = Debug::UncheckedDynamicCast<const MyIterRep_*> (rhs);
217 return fData_.data () == rrhs->fData_.data () and fIdx_ == rrhs->fIdx_;
218 }
219 };
220 return Iterator<Character>{make_unique<MyIterRep_> (this->_fData
221
223 ,
224 this
225#endif
226
227 )};
228 }
229 virtual size_t size () const override
230 {
231 return _fData.size ();
232 }
233 virtual bool empty () const override
234 {
235 return _fData.empty ();
236 }
237 virtual Traversal::Iterator<value_type> Find (bool findFirst, const function<bool (ArgByValueType<value_type> item)>& that,
238 Execution::SequencePolicy seq) const override
239 {
240 return inherited::Find (findFirst, that, seq); // @todo rewrite FOR PERFORMANCE to operate on fData_
241 }
242 };
243 };
244
245 /**
246 * Simple string rep, which dynamically allocates its storage on the heap, through an indirect pointer reference.
247 * \note This class may assure nul-terminated (kAddNullTerminator_), and so 'capacity' always at least one greater than length.
248 */
249 struct DynamicallyAllocatedString : StringRepHelperAllFitInSize_ {
250 template <IUNICODECanUnambiguouslyConvertFrom CHAR_T>
251 struct Rep final : public StringRepHelperAllFitInSize_::Rep<CHAR_T>, public Memory::UseBlockAllocationIfAppropriate<Rep<CHAR_T>> {
252 private:
253 using inherited = StringRepHelperAllFitInSize_::Rep<CHAR_T>;
254
255 public:
256 Rep (span<const CHAR_T> t1)
257 : inherited{mkBuf_ (t1)}
258 {
259 }
260 Rep () = delete;
261 Rep (const Rep&) = delete;
262
263 public:
264 nonvirtual Rep& operator= (const Rep&) = delete;
265
266 public:
267 virtual ~Rep () override
268 {
269 delete[] this->_fData.data ();
270 }
271
272 private:
273 static span<CHAR_T> mkBuf_ (size_t length)
274 {
275 size_t capacity = AdjustCapacity_ (length);
276 Assert (length <= capacity);
277 if constexpr (kAddNullTerminator_) {
278 Assert (length + 1 <= capacity);
279 }
280 CHAR_T* newBuf = new CHAR_T[capacity];
281 return span{newBuf, capacity};
282 }
283 static span<CHAR_T> mkBuf_ (span<const CHAR_T> t1)
284 {
285 size_t len = t1.size ();
286 span<CHAR_T> buf = mkBuf_ (len); // note buf span is over capacity, not size
287 Assert (buf.size () >= len);
288 auto result = Memory::CopyBytes (t1, buf);
289 if constexpr (kAddNullTerminator_) {
290 Assert (len + 1 <= buf.size ());
291 *(buf.data () + len) = '\0';
292 }
293 return result; // return span of just characters, even if we have extra NUL-byte (outside span)
294 }
295
296 public:
297 // String::_IRep OVERRIDES
298 virtual const wchar_t* c_str_peek () const noexcept override
299 {
300 // @todo NOTE DEPRECATED SINCE STROIKA v3.0d13, and same for kAddNullTerminator_
301 if constexpr (kAddNullTerminator_) {
302 Assert (*(this->_fData.data () + this->_fData.size ()) == '\0'); // dont index into buf cuz we cheat and go one past end on purpose
303 return reinterpret_cast<const wchar_t*> (this->_fData.data ());
304 }
305 else {
306 return nullptr;
307 }
308 }
309
310 private:
311 // Stick nul-terminator byte just past the end of the span
312 static constexpr bool kAddNullTerminator_ = sizeof (CHAR_T) == sizeof (wchar_t); // costs nothing to nul-terminate in this case
313
314 private:
315 static size_t AdjustCapacity_ (size_t initialCapacity)
316 {
317 size_t result = initialCapacity;
318 if constexpr (kAddNullTerminator_) {
319 ++result;
320 }
321 return result;
322 }
323 };
324 };
325
326 /**
327 * Most Stroika strings use this 'rep': FixedCapacityInlineStorageString_
328 *
329 * This String rep is like BufferedString_, except that the storage is inline in one struct/allocation
330 * for better memory allocation performance, and more importantly, better locality of data (more cpu cache friendly)
331 */
332 struct FixedCapacityInlineStorageString_ : StringRepHelperAllFitInSize_ {
333 template <IUNICODECanUnambiguouslyConvertFrom CHAR_T, size_t CAPACITY>
334 struct Rep final : public StringRepHelperAllFitInSize_::Rep<CHAR_T>,
335 public Memory::UseBlockAllocationIfAppropriate<Rep<CHAR_T, CAPACITY>> {
336 private:
337 using inherited = StringRepHelperAllFitInSize_::Rep<CHAR_T>;
338
339 private:
340 bool IncludesNullTerminator_ () const
341 {
342 if constexpr (sizeof (CHAR_T) == sizeof (wchar_t)) {
343 return this->_fData.size () < CAPACITY; // else no room
344 }
345 else {
346 return false;
347 }
348 }
349
350 private:
351 CHAR_T fBuf_[CAPACITY];
352
353 public:
354 Rep (span<const CHAR_T> t1)
355 : inherited{}
356 {
357 // must do this logic after base construction since references data member which doesn't exist
358 // til after base class construction. SHOULDNT really matter (since uninitialized data), but on
359 // g++-11, and other compilers, detected as vptr UB violation if we access first
360 Require (t1.size () <= CAPACITY);
361 inherited::operator= (Memory::CopyBytes (t1, span<CHAR_T>{fBuf_}));
362 if (IncludesNullTerminator_ ()) {
363 Assert (t1.size () + 1 <= CAPACITY);
364 fBuf_[t1.size ()] = CHAR_T{'\0'};
365 }
366 }
367 Rep () = delete;
368 Rep (const Rep&) = delete;
369
370 public:
371 nonvirtual Rep& operator= (const Rep&) = delete;
372
373 public:
374 // String::_IRep OVERRIDES
375 virtual const wchar_t* c_str_peek () const noexcept override
376 {
377 if (IncludesNullTerminator_ ()) {
378 Assert (*(this->_fData.data () + this->_fData.size ()) == '\0'); // dont index into buf cuz we cheat and go one past end on purpose
379 return reinterpret_cast<const wchar_t*> (this->_fData.data ());
380 }
381 else {
382 return nullptr;
383 }
384 }
385 };
386 };
387
388 /**
389 * For static full app lifetime string constants...
390 */
391 struct StringConstant_ : public StringRepHelperAllFitInSize_ {
392 using inherited = String;
393
394 template <IUNICODECanUnambiguouslyConvertFrom CHAR_T>
395 class DirectIndexRep final : public StringRepHelperAllFitInSize_::Rep<CHAR_T>,
396 public Memory::UseBlockAllocationIfAppropriate<Rep<CHAR_T>> {
397 private:
398 using inherited = StringRepHelperAllFitInSize_::Rep<CHAR_T>;
399
400 public:
401 DirectIndexRep (span<const CHAR_T> s)
402 : inherited{s} // don't copy memory - but copy raw pointers! So they MUST BE (externally promised) 'externally owned for the application lifetime and constant' - like c++ string constants
403 {
404 }
405
406 public:
407 // String::_IRep OVERRIDES
408 virtual const wchar_t* c_str_peek () const noexcept override
409 {
410 return nullptr;
411 }
412 };
413 };
414
415 /*
416 * Used for String{move(some_string)}
417 */
418 struct StdStringDelegator_ : public StringRepHelperAllFitInSize_ {
419 using inherited = String;
420
421 template <IStdBasicStringCompatibleCharacter CHAR_T>
422 class Rep final : public StringRepHelperAllFitInSize_::Rep<CHAR_T>, public Memory::UseBlockAllocationIfAppropriate<Rep<CHAR_T>> {
423 private:
424 using inherited = StringRepHelperAllFitInSize_::Rep<CHAR_T>;
425
426 public:
427 Rep (basic_string<CHAR_T>&& s)
428 : inherited{span<const CHAR_T>{}}
429 , fMovedData_{move (s)}
430 {
431 inherited::operator= (span{fMovedData_.data (), fMovedData_.size ()}); // must grab after move
432 }
433
434 public:
435 // String::_IRep OVERRIDES
436 virtual const wchar_t* c_str_peek () const noexcept override
437 {
438 if constexpr (same_as<CHAR_T, wchar_t>) {
439 return fMovedData_.c_str ();
440 }
441 else {
442 return nullptr;
443 }
444 }
445
446 private:
447 basic_string<CHAR_T> fMovedData_;
448 };
449 };
450
451 /**
452 * Delegate to original String::Rep, and add in support for c_str ()
453 */
454 struct StringWithCStr_ : public String {
455 public:
456 class Rep final : public _IRep, public Memory::UseBlockAllocationIfAppropriate<Rep> {
457 private:
458 shared_ptr<_IRep> fUnderlyingRep_;
459 wstring fCString_;
460
461 public:
462 // Caller MUST ASSURE generates right size of Rep based on size in underlyingRepPDS
463 Rep (const shared_ptr<_IRep>& underlyingRep)
464 : fUnderlyingRep_{underlyingRep}
465 , fCString_{}
466 {
467 Memory::StackBuffer<wchar_t> possibleUsedBuf;
468 auto wideSpan = String::GetData<wchar_t> (underlyingRep->PeekData (nullopt), &possibleUsedBuf);
469 fCString_.assign (wideSpan.begin (), wideSpan.end ());
470 }
471
472 // Overrides for Iterable<Character>
473 public:
474 virtual shared_ptr<Iterable<Character>::_IRep> Clone () const override
475 {
476 return fUnderlyingRep_->Clone ();
477 }
478 virtual Traversal::Iterator<value_type> MakeIterator () const override
479 {
480 return fUnderlyingRep_->MakeIterator ();
481 }
482 virtual size_t size () const override
483 {
484 return fUnderlyingRep_->size ();
485 }
486 virtual bool empty () const override
487 {
488 return fUnderlyingRep_->empty ();
489 }
490 virtual Traversal::Iterator<value_type> Find (bool findFirst, const function<bool (ArgByValueType<value_type> item)>& that,
491 Execution::SequencePolicy seq) const override
492 {
493 return fUnderlyingRep_->Find (findFirst, that, seq);
494 }
495
496 // String::_IRep overrides - delegate
497 public:
498 virtual Character GetAt (size_t index) const noexcept override
499 {
500 return fUnderlyingRep_->GetAt (index);
501 }
502 virtual PeekSpanData PeekData ([[maybe_unused]] optional<PeekSpanData::StorageCodePointType> preferred) const noexcept override
503 {
504 return fUnderlyingRep_->PeekData (preferred);
505 }
506 virtual const wchar_t* c_str_peek () const noexcept override
507 {
508 return fCString_.c_str ();
509 }
510 };
511 };
512}
513
514namespace {
515 template <typename FACET>
516 struct deletable_facet_ final : FACET {
517 template <typename... Args>
518 deletable_facet_ (Args&&... args)
519 : FACET{forward<Args> (args)...}
520 {
521 }
522 ~deletable_facet_ () = default;
523 };
524}
525
526/*
527 ********************************************************************************
528 ******* Characters::Private_::RegularExpression_GetCompiled ********************
529 ********************************************************************************
530 */
531const wregex& Characters::Private_::RegularExpression_GetCompiled (const RegularExpression& regExp)
532{
533 return regExp.GetCompiled ();
534}
535
536/*
537 ********************************************************************************
538 ************************************* String ***********************************
539 ********************************************************************************
540 */
541shared_ptr<String::_IRep> String::CTORFromBasicStringView_ (const basic_string_view<ASCII>& str)
542{
543 RequireExpression (Character::IsASCII (span{str.data (), str.size ()}));
544 return MakeSharedPtr<StringConstant_::DirectIndexRep<ASCII>> (span{str.data (), str.size ()});
545}
546
547shared_ptr<String::_IRep> String::CTORFromBasicStringView_ (const basic_string_view<char8_t>& str)
548{
549 if (Character::IsASCII (span{str.data (), str.size ()})) {
550 return MakeSharedPtr<StringConstant_::DirectIndexRep<ASCII>> (Memory::SpanBytesCast<span<const ASCII>> (span{str.data (), str.size ()}));
551 }
552 else {
553 return mk_ (span<const char8_t>{str.data (), str.size ()}); // copies data
554 }
555}
556
557shared_ptr<String::_IRep> String::CTORFromBasicStringView_ (const basic_string_view<char16_t>& str)
558{
559 if (UTFConvert::AllFitsInTwoByteEncoding (span{str})) {
560 return MakeSharedPtr<StringConstant_::DirectIndexRep<char16_t>> (span{str.data (), str.size ()});
561 }
562 else {
563 return mk_ (span<const char16_t>{str.data (), str.size ()}); // copies data
564 }
565}
566
567shared_ptr<String::_IRep> String::CTORFromBasicStringView_ (const basic_string_view<char32_t>& str)
568{
569 return MakeSharedPtr<StringConstant_::DirectIndexRep<char32_t>> (span{str.data (), str.size ()});
570}
571
572shared_ptr<String::_IRep> String::CTORFromBasicStringView_ (const basic_string_view<wchar_t>& str)
573{
574 return MakeSharedPtr<StringConstant_::DirectIndexRep<wchar_t>> (span{str.data (), str.size ()});
575}
576
577String String::FromStringConstant (span<const ASCII> s)
578{
579 Require (Character::IsASCII (s));
581}
582
583String String::FromStringConstant (span<const char16_t> s)
584{
587 }
588 else {
589 return String{s};
590 }
591}
592
593String String::FromStringConstant (span<const char32_t> s)
594{
596}
597
598String String::FromNarrowString (span<const char> s, const locale& l)
599{
600 // Note: this could use CodeCvt, but directly using std::codecvt in this case pretty simple, and
601 // more efficient this way --LGP 2023-02-14
602
603 // See http://en.cppreference.com/w/cpp/locale/codecvt/~codecvt
606
607 // http://en.cppreference.com/w/cpp/locale/codecvt/in
610 const char* from_next;
611 wchar_t* to_next;
612 codecvt_base::result result =
613 cvt.in (mbstate, s.data (), s.data () + s.size (), from_next, targetBuf.data (), targetBuf.data () + targetBuf.size (), to_next);
614 if (result != codecvt_base::ok) [[unlikely]] {
615 static const auto kException_ = Execution::RuntimeErrorException{"Error converting locale multibyte string to UNICODE"sv};
617 }
618 return String{span<const wchar_t>{targetBuf.data (), static_cast<size_t> (to_next - targetBuf.data ())}};
619}
620
621shared_ptr<String::_IRep> String::mkEmpty_ ()
622{
623 static constexpr wchar_t kEmptyCStr_[] = L"";
624 static const shared_ptr<_IRep> s_ = MakeSharedPtr<StringConstant_::DirectIndexRep<wchar_t>> (span{std::begin (kEmptyCStr_), 0});
625 return s_;
626}
627
628template <typename CHAR_T>
629inline auto String::mk_nocheck_ (span<const CHAR_T> s) -> shared_ptr<_IRep>
630 requires (same_as<CHAR_T, ASCII> or same_as<CHAR_T, Latin1> or same_as<CHAR_T, char16_t> or same_as<CHAR_T, char32_t>)
631{
632 // No check means needed checking done before, so these assertions just help enforce that
633 if constexpr (same_as<CHAR_T, ASCII>) {
634 Require (Character::IsASCII (s)); // avoid later assertion error
635 }
636 else if constexpr (same_as<CHAR_T, Latin1>) {
637 // nothing to check
638 }
639 else if constexpr (sizeof (CHAR_T) == 2) {
640 Require (UTFConvert::AllFitsInTwoByteEncoding (s)); // avoid later assertion error
641 }
642 else {
643 // again - if larger, nothing to check
644 }
645
646 /**
647 * We want to TARGET using block-allocator of 64 bytes. This works well for typical (x86) machine
648 * caches, and divides up nicely, and leaves enuf room for a decent number of characters typically.
649 *
650 * So compute/guestimate a few sizes, and add static_asserts to check where we can. Often if these fail
651 * you can just get rid/or fix them. Not truly counted on, just trying ot generate vaguely reasonable
652 * number of characters to use.
653 */
654 constexpr size_t kBaseOfFixedBufSize_ = sizeof (StringRepHelperAllFitInSize_::Rep<CHAR_T>);
655 static_assert (kBaseOfFixedBufSize_ < 64); // this code below assumes, so must re-tune if this ever fails
656 if constexpr (qStroika_Foundation_Common_Platform_Windows and not qStroika_Foundation_Debug_AssertionsChecked) {
657 static_assert (kBaseOfFixedBufSize_ == 3 * sizeof (void*));
658 if constexpr (sizeof (void*) == 4) {
659 static_assert (kBaseOfFixedBufSize_ == 12);
660 }
661 else if constexpr (sizeof (void*) == 8) {
662 static_assert (kBaseOfFixedBufSize_ == 24);
663 }
664 }
665 constexpr size_t kOverheadSizeForMakeShared_ =
666 qStroika_Foundation_Common_Platform_Windows ? (sizeof (void*) == 4 ? 12 : 16) : sizeof (unsigned long) * 2;
667#if qStroika_Foundation_Common_Platform_Windows
668 static_assert (kOverheadSizeForMakeShared_ == sizeof (_Ref_count_base)); // not critically counted on, just to debug/fix sizes
669#endif
670 static constexpr size_t kNElts1_ = (64 - kBaseOfFixedBufSize_ - kOverheadSizeForMakeShared_) / sizeof (CHAR_T);
671 static constexpr size_t kNElts2_ = (96 - kBaseOfFixedBufSize_ - kOverheadSizeForMakeShared_) / sizeof (CHAR_T);
672 static constexpr size_t kNElts3_ = (128 - kBaseOfFixedBufSize_ - kOverheadSizeForMakeShared_) / sizeof (CHAR_T);
673
674 // These checks are NOT important, just for documentation/reference
675 if constexpr (qStroika_Foundation_Common_Platform_Windows and sizeof (CHAR_T) == 1 and not qStroika_Foundation_Debug_AssertionsChecked) {
676 if constexpr (sizeof (void*) == 4) {
677 static_assert (kNElts1_ == 40);
678 static_assert (kNElts2_ == 72);
679 static_assert (kNElts3_ == 104);
680 }
681 if constexpr (sizeof (void*) == 8) {
682 static_assert (kNElts1_ == 24);
683 static_assert (kNElts2_ == 56);
684 static_assert (kNElts3_ == 88);
685 }
686 }
687
688 static_assert (qStroika_Foundation_Debug_AssertionsChecked or kNElts1_ >= 6); // crazy otherwise
689 static_assert (kNElts2_ > kNElts1_); // ""
690 static_assert (kNElts3_ > kNElts2_); // ""
691
692 static_assert (sizeof (FixedCapacityInlineStorageString_::Rep<CHAR_T, kNElts1_>) == 64 - kOverheadSizeForMakeShared_); // not quite guaranteed but close
693 static_assert (sizeof (FixedCapacityInlineStorageString_::Rep<CHAR_T, kNElts2_>) == 96 - kOverheadSizeForMakeShared_); // ""
694 static_assert (sizeof (FixedCapacityInlineStorageString_::Rep<CHAR_T, kNElts3_>) == 128 - kOverheadSizeForMakeShared_); // ""
695
696 size_t sz = s.size ();
697 if (sz <= kNElts1_) {
698 return MakeSharedPtr<FixedCapacityInlineStorageString_::Rep<CHAR_T, kNElts1_>> (s);
699 }
700 else if (sz <= kNElts2_) {
701 return MakeSharedPtr<FixedCapacityInlineStorageString_::Rep<CHAR_T, kNElts2_>> (s);
702 }
703 else if (sz <= kNElts3_) {
704 return MakeSharedPtr<FixedCapacityInlineStorageString_::Rep<CHAR_T, kNElts3_>> (s);
705 }
706 return MakeSharedPtr<DynamicallyAllocatedString::Rep<CHAR_T>> (s);
707}
708
709template <>
710auto String::mk_ (basic_string<char>&& s) -> shared_ptr<_IRep>
711{
712 Character::CheckASCII (span{s.data (), s.size ()});
713 return MakeSharedPtr<StdStringDelegator_::Rep<ASCII>> (move (s));
714}
715
716template <>
717auto String::mk_ (basic_string<char16_t>&& s) -> shared_ptr<_IRep>
718{
719 if (UTFConvert::AllFitsInTwoByteEncoding (Memory::ConstSpan (span{s.data (), s.size ()}))) {
720 return MakeSharedPtr<StdStringDelegator_::Rep<char16_t>> (move (s));
721 }
722 // copy the data if any surrogates
723 Memory::StackBuffer<char32_t> wideUnicodeBuf{Memory::eUninitialized, UTFConvert::ComputeTargetBufferSize<char32_t> (span{s.data (), s.size ()})};
724 return mk_nocheck_ (Memory::ConstSpan (UTFConvert::kThe.ConvertSpan (span{s.data (), s.size ()}, span{wideUnicodeBuf})));
725}
726
727template <>
728auto String::mk_ (basic_string<char32_t>&& s) -> shared_ptr<_IRep>
729{
730 return MakeSharedPtr<StdStringDelegator_::Rep<char32_t>> (move (s));
731}
732
733template <>
734auto String::mk_ (basic_string<wchar_t>&& s) -> shared_ptr<_IRep>
735{
736 if constexpr (sizeof (wchar_t) == 2) {
737 if (UTFConvert::AllFitsInTwoByteEncoding (Memory::ConstSpan (span{s.data (), s.size ()}))) {
738 return MakeSharedPtr<StdStringDelegator_::Rep<wchar_t>> (move (s));
739 }
740 // copy the data if any surrogates
741 Memory::StackBuffer<char32_t> wideUnicodeBuf{Memory::eUninitialized,
742 UTFConvert::ComputeTargetBufferSize<char32_t> (span{s.data (), s.size ()})};
743 return mk_nocheck_ (Memory::ConstSpan (UTFConvert::kThe.ConvertSpan (span{s.data (), s.size ()}, span{wideUnicodeBuf})));
744 }
745 else {
746 return MakeSharedPtr<StdStringDelegator_::Rep<wchar_t>> (move (s));
747 }
748}
749
750String String::Concatenate_ (const String& rhs) const
751{
752 // KISS, simple default 'fall-thru' case
754 span leftSpan = GetData (&ignoredA);
756 span rightSpan = rhs.GetData (&ignoredB);
757 Memory::StackBuffer<char32_t> buf{Memory::eUninitialized, leftSpan.size () + rightSpan.size ()};
758 copy (leftSpan.begin (), leftSpan.end (), buf.data ());
759 copy (rightSpan.begin (), rightSpan.end (), buf.data () + leftSpan.size ());
760 return mk_ (span{buf});
761}
762
763void String::SetCharAt (Character c, size_t i)
764{
765 // @Todo - redo with check if char is actually changing and if so use
766 // mk/4 4 arg string maker instead.??? Or some such...
767 Require (i >= 0);
768 Require (i < size ());
769 // Expensive, but you can use StringBuilder directly to avoid the performance costs
770 StringBuilder sb{*this};
771 Require (i < size ());
772 sb.SetAt (c, i);
773 *this = sb;
774}
775
776String String::InsertAt (span<const Character> s, size_t at) const
777{
778 Require (at >= 0);
779 Require (at <= size ());
780 if (s.empty ()) {
781 return *this;
782 }
785 StringBuilder sb{thisStrData.subspan (0, at)};
786 sb.Append (s);
787 sb.Append (thisStrData.subspan (at));
788 return sb;
789}
790
791String String::RemoveAt (size_t from, size_t to) const
792{
793 Require (from <= to);
794 Require (to <= size ());
795 if (from == to) {
796 return *this;
797 }
798 if (from == 0) {
799 return SubString (to);
800 }
801 _SafeReadRepAccessor accessor{this};
802 size_t length = accessor._ConstGetRep ().size ();
803 if (to == length) {
804 return SubString (0, from);
805 }
806 else {
808 span d = GetData (&ignored1);
809 Memory::StackBuffer<char32_t> buf{Memory::eUninitialized, d.size () - (to - from)};
810 span<char32_t> bufSpan{buf.data (), buf.size ()};
811 span s1 = d.subspan (0, from);
812 span s2 = d.subspan (to);
813 Memory::CopyBytes (s1, bufSpan);
814 Memory::CopyBytes (s2, bufSpan.subspan (s1.size ()));
815 return String{mk_ (bufSpan)};
816 }
817}
818
820{
821 String tmp = {*this};
822 if (auto o = tmp.Find (c, eWithCase)) {
823 return tmp.RemoveAt (*o);
824 }
825 return tmp;
826}
827String String::RemoveFirstIf (const String& subString) const
828{
829 if (auto o = this->Find (subString, eWithCase)) {
830 return this->SubString (0, *o) + this->SubString (*o + subString.length ());
831 }
832 return *this;
833}
834
836{
837 // @todo REIMPL WITH STRINGBUILDER
838 // quick and dirty inefficient implementation
839 String tmp = {*this};
840 while (auto o = tmp.Find (c, eWithCase)) {
841 tmp = tmp.RemoveAt (*o);
842 }
843 return tmp;
844}
845String String::RemoveAll (const String& subString) const
846{
847 // @todo REIMPL WITH STRINGBUILDER
848 // quick and dirty inefficient implementation
849 String tmp = {*this};
850 while (auto o = tmp.Find (subString, eWithCase)) {
851 tmp = tmp.SubString (0, *o) + tmp.SubString (*o + subString.length ());
852 }
853 return tmp;
854}
855
856optional<size_t> String::Find (Character c, size_t startAt, CompareOptions co) const
857{
858 PeekSpanData pds = GetPeekSpanData<ASCII> ();
859 // OPTIMIZED PATHS: Common case(s) and should be fast
861 if (c.IsASCII ()) {
862 span<const char> examineSpan = pds.fAscii.subspan (startAt);
863 if (co == eWithCase) {
864 if (auto i = std::find (examineSpan.begin (), examineSpan.end (), c.GetAsciiCode ()); i != examineSpan.end ()) {
865 return i - examineSpan.begin () + startAt;
866 }
867 }
868 else {
869 char lc = c.ToLowerCase ().GetAsciiCode ();
870 size_t reportIdx = startAt;
871 for (auto ci : examineSpan) {
872 if (tolower (ci) == lc) {
873 return reportIdx;
874 }
875 ++reportIdx;
876 }
877 }
878 return nullopt; // not found, possibly cuz not ascii
879 }
880 }
881 // fallback on more generic algorithm - and copy to full character objects
882 //
883 // performance notes
884 // Could iterate using CharAt() and that would perform better in the case where you find c early
885 // in a string, and the string is short. The problem with the current code is that it converts the
886 // entire string (could be long) and then might not look at much of the converted data.
887 // on the other hand, if our reps are either 'ascii or char32_t wide' - which we may end up with - then
888 // this isn't too bad - cuz no copying for char32_ case either...
891 Require (startAt <= charSpan.size ());
893 switch (co) {
894 case eCaseInsensitive: {
896 for (auto i = examineSpan.begin (); i != examineSpan.end (); ++i) {
897 if (i->ToLowerCase () == lcc) {
898 return startAt + (i - examineSpan.begin ());
899 }
900 }
901 } break;
902 case eWithCase: {
903 if (auto i = std::find (examineSpan.begin (), examineSpan.end (), c); i != examineSpan.end ()) {
904 return startAt + i - examineSpan.begin ();
905 }
906 } break;
907 }
908 return nullopt; // not found any which way
909}
910
911optional<size_t> String::Find (const String& subString, size_t startAt, CompareOptions co) const
912{
913 //@todo: FIX HORRIBLE PERFORMANCE!!!
914 _SafeReadRepAccessor accessor{this};
915 Require (startAt <= accessor._ConstGetRep ().size ());
916
917 size_t subStrLen = subString.size ();
918 if (subStrLen == 0) {
919 return (accessor._ConstGetRep ().size () == 0) ? optional<size_t>{} : 0;
920 }
921 if (accessor._ConstGetRep ().size () < subStrLen) {
922 return {}; // important test cuz size_t is unsigned
923 }
924
925 size_t limit = accessor._ConstGetRep ().size () - subStrLen;
926 switch (co) {
927 case eCaseInsensitive: {
928 for (size_t i = startAt; i <= limit; ++i) {
929 for (size_t j = 0; j < subStrLen; ++j) {
930 if (accessor._ConstGetRep ().GetAt (i + j).ToLowerCase () != subString[j].ToLowerCase ()) {
931 goto nogood1;
932 }
933 }
934 return i;
935 nogood1:;
936 }
937 } break;
938 case eWithCase: {
939 for (size_t i = startAt; i <= limit; ++i) {
940 for (size_t j = 0; j < subStrLen; ++j) {
941 if (accessor._ConstGetRep ().GetAt (i + j) != subString[j]) {
942 goto nogood2;
943 }
944 }
945 return i;
946 nogood2:;
947 }
948 } break;
949 }
950 return {};
951}
952
953optional<pair<size_t, size_t>> String::Find (const RegularExpression& regEx, size_t startAt) const
954{
955 Require (startAt <= size ());
956 wstring tmp = As<wstring> ();
957 Require (startAt < tmp.size ());
958 tmp = tmp.substr (startAt);
959 wsmatch res;
960 regex_search (tmp, res, regEx.GetCompiled ());
961 if (res.size () >= 1) {
962 size_t startOfMatch = startAt + res.position ();
964 }
965 return {};
966}
967
968Containers::Sequence<size_t> String::FindEach (const String& string2SearchFor, CompareOptions co) const
969{
970 vector<size_t> result;
971 for (optional<size_t> i = Find (string2SearchFor, 0, co); i; i = Find (string2SearchFor, *i, co)) {
972 result.push_back (*i);
973 *i += string2SearchFor.length (); // this cannot point past end of this string because we FOUND string2SearchFor
974 }
976}
977
979{
981 //@TODO - FIX - IF we get back zero length match
982 wstring tmp{As<wstring> ()};
983 wsmatch res;
984 regex_search (tmp, res, regEx.GetCompiled ());
985 size_t nMatches = res.size ();
986 result.reserve (nMatches);
987 for (size_t mi = 0; mi < nMatches; ++mi) {
988 size_t matchLen = res.length (mi); // avoid populating with lots of empty matches - special case of empty search
989 if (matchLen != 0) {
990 result.push_back (pair<size_t, size_t>{res.position (mi), matchLen});
991 }
992 }
994}
995
997{
999 wstring tmp{As<wstring> ()};
1000 for (wsregex_iterator i = wsregex_iterator{tmp.begin (), tmp.end (), regEx.GetCompiled ()}; i != wsregex_iterator (); ++i) {
1001 wsmatch match{*i};
1002 Assert (match.size () != 0);
1003 size_t n = match.size ();
1005 for (size_t j = 1; j < n; ++j) {
1006 s.Append (match.str (j));
1007 }
1008 result.push_back (RegularExpressionMatch{match.str (0), s});
1009 }
1011}
1012
1014{
1015 vector<String> result;
1016 wstring tmp{As<wstring> ()};
1017 for (wsregex_iterator i = wsregex_iterator{tmp.begin (), tmp.end (), regEx.GetCompiled ()}; i != wsregex_iterator (); ++i) {
1018 result.push_back (String{i->str ()});
1019 }
1021}
1022
1023optional<size_t> String::RFind (Character c) const noexcept
1024{
1025 //@todo: FIX HORRIBLE PERFORMANCE!!!
1026 _SafeReadRepAccessor accessor{this};
1027 const _IRep& useRep = accessor._ConstGetRep ();
1028 size_t length = useRep.size ();
1029 for (size_t i = length; i > 0; --i) {
1030 if (useRep.GetAt (i - 1) == c) {
1031 return i - 1;
1032 }
1033 }
1034 return nullopt;
1035}
1036
1037optional<size_t> String::RFind (const String& subString) const
1038{
1039 //@todo: FIX HORRIBLE PERFORMANCE!!!
1040 /*
1041 * Do quickie implementation, and don't worry about efficiency...
1042 */
1043 size_t subStrLen = subString.size ();
1044 if (subStrLen == 0) {
1045 return ((size () == 0) ? optional<size_t>{} : size () - 1);
1046 }
1047
1048 size_t limit = size () - subStrLen + 1;
1049 for (size_t i = limit; i > 0; --i) {
1050 if (SubString (i - 1, i - 1 + subStrLen) == subString) {
1051 return i - 1;
1052 }
1053 }
1054 return nullopt;
1055}
1056
1057String String::Replace (size_t from, size_t to, const String& replacement) const
1058{
1061 Require (from <= to);
1062 Require (to <= this->size ());
1063 Assert (to < thisSpan.size ());
1064 StringBuilder sb{thisSpan.subspan (0, from)};
1066 sb.Append (thisSpan.subspan (to));
1067 Ensure (sb == SubString (0, from) + replacement + SubString (to));
1068 return sb;
1069}
1070
1071bool String::StartsWith (const Character& c, CompareOptions co) const
1072{
1073 _SafeReadRepAccessor accessor{this};
1074 if (accessor._ConstGetRep ().size () == 0) {
1075 return false;
1076 }
1077 return Character::EqualsComparer{co}(accessor._ConstGetRep ().GetAt (0), c);
1078}
1079
1080bool String::StartsWith (const String& subString, CompareOptions co) const
1081{
1082 Require (not subString.empty ());
1083 if (subString.size () > size ()) {
1084 return false;
1085 }
1086#if qStroika_Foundation_Debug_AssertionsChecked
1087 bool referenceResult = ThreeWayComparer{co}(SubString (0, subString.size ()), subString) == 0;
1088#endif
1093 bool result = Character::Compare (thisData.subspan (0, subStrData.size ()), subStrData, co) == 0;
1094#if qStroika_Foundation_Debug_AssertionsChecked
1095 Ensure (result == referenceResult);
1096#endif
1097 return result;
1098}
1099
1100bool String::EndsWith (const Character& c, CompareOptions co) const
1101{
1102 _SafeReadRepAccessor accessor{this};
1103 const _IRep& useRep = accessor._ConstGetRep ();
1104 size_t thisStrLen = useRep.size ();
1105 if (thisStrLen == 0) {
1106 return false;
1107 }
1108 return Character::EqualsComparer{co}(useRep.GetAt (thisStrLen - 1), c);
1109}
1110
1111bool String::EndsWith (const String& subString, CompareOptions co) const
1112{
1113 Require (not subString.empty ());
1114 _SafeReadRepAccessor subStrAccessor{&subString};
1115 _SafeReadRepAccessor accessor{this};
1116 size_t thisStrLen = accessor._ConstGetRep ().size ();
1117 size_t subStrLen = subString.size ();
1118 if (subStrLen > thisStrLen) {
1119 return false;
1120 }
1121#if qStroika_Foundation_Debug_AssertionsChecked
1123#endif
1128 bool result = Character::Compare (thisData.subspan (thisStrLen - subStrLen), subStrData, co) == 0;
1129#if qStroika_Foundation_Debug_AssertionsChecked
1130 Ensure (result == referenceResult);
1131#endif
1132 return result;
1133}
1134
1135String String::AssureEndsWith (const Character& c, CompareOptions co) const
1136{
1137 if (EndsWith (c, co)) {
1138 return *this;
1139 }
1140 StringBuilder sb = *this;
1141 sb.Append (c);
1142 return sb;
1143}
1144
1145bool String::Matches (const RegularExpression& regEx) const
1146{
1147 wstring tmp{As<wstring> ()};
1148 return regex_match (tmp.begin (), tmp.end (), regEx.GetCompiled ());
1149}
1150
1151bool String::Matches (const RegularExpression& regEx, Sequence<String>* matches) const
1152{
1154 //tmphack
1155 wstring tmp{As<wstring> ()};
1157 if (regex_match (tmp, base_match, regEx.GetCompiled ())) {
1158 matches->clear ();
1159 for (size_t i = 1; i < base_match.size (); ++i) {
1160 matches->Append (base_match[i].str ());
1161 }
1162 return true;
1163 }
1164 return false;
1165}
1166
1167String String::ReplaceAll (const RegularExpression& regEx, const String& with) const
1168{
1169 return String{regex_replace (As<wstring> (), regEx.GetCompiled (), with.As<wstring> ())};
1170}
1171
1172String String::ReplaceAll (const String& string2SearchFor, const String& with, CompareOptions co) const
1173{
1174 Require (not string2SearchFor.empty ());
1175 // simplistic quickie impl...
1176 String result{*this};
1177 optional<size_t> i{0};
1178 while ((i = result.Find (string2SearchFor, *i, co))) {
1179 result = result.SubString (0, *i) + with + result.SubString (*i + string2SearchFor.length ());
1180 *i += with.length ();
1181 }
1182 return result;
1183}
1184
1185String String::ReplaceAll (const function<bool (Character)>& replaceCharP, const String& with) const
1186{
1188 for (Character i : *this) {
1189 if (replaceCharP (i)) {
1190 sb << with;
1191 }
1192 else {
1193 sb << i;
1194 }
1195 }
1196 return sb;
1197}
1198
1199String String::ReplaceAll (const Set<Character>& charSet, const String& with) const
1200{
1202 for (Character i : *this) {
1203 if (charSet.Contains (i)) {
1204 sb << with;
1205 }
1206 else {
1207 sb << i;
1208 }
1209 }
1210 return sb;
1211}
1212
1214{
1219 bool everChanged{false};
1220 for (auto ci = charSpan.begin (); ci != charSpan.end (); ++ci) {
1221 Character c = *ci;
1222 if (c == '\r') {
1223 // peek at next character - and if we have a CRLF sequence - then advance pointer
1224 // (so we skip next NL) and pretend this was an NL..
1225 if (ci + 1 != charSpan.end () and *(ci + 1) == '\n') {
1226 ++ci;
1227 }
1228 everChanged = true;
1229 c = '\n';
1230 }
1231 sb << c;
1232 }
1233 if (everChanged) {
1234 return sb;
1235 }
1236 else {
1237 return *this;
1238 }
1239}
1240
1241String String::NormalizeSpace (Character useSpaceCharacter) const
1242{
1243 return ReplaceAll ("\\s+"_RegEx, String{useSpaceCharacter});
1244}
1245
1250Sequence<String> String::Tokenize (const function<bool (Character)>& isTokenSeparator) const
1251{
1253 bool inToken = false;
1255 size_t len = size ();
1256 for (size_t i = 0; i != len; ++i) {
1257 Character c = GetCharAt (i);
1258 bool newInToken = not isTokenSeparator (c);
1259 if (inToken != newInToken) {
1260 if (inToken) {
1261 String s{curToken.str ()};
1262 r += s;
1263 curToken.clear ();
1264 inToken = false;
1265 }
1266 else {
1267 inToken = true;
1268 }
1269 }
1270 if (inToken) {
1271 curToken << c;
1272 }
1273 }
1274 if (inToken) {
1275 String s{curToken.str ()};
1276 r += s;
1277 }
1278 return r;
1279}
1280
1281Sequence<String> String::Tokenize (const RegularExpression& isSeparator) const
1282{
1284 size_t len = this->length ();
1285 for (size_t startAt = 0; startAt < len;) {
1287 Assert (ofi->first >= startAt);
1288 Assert (ofi->first <= ofi->second);
1289 if (ofi->first == ofi->second) [[unlikely]] {
1290 static const auto kException_ =
1291 Execution::RuntimeErrorException{"separator regular expression argument to Tokenize must be non-empty or not match"sv};
1293 }
1294 if (ofi->first > startAt) {
1295 r += SubString (startAt, ofi->first);
1296 }
1297 else {
1298 Assert (startAt == 0); // special case - start of string
1299 }
1300 startAt = ofi->second;
1301 Assert (startAt <= len);
1302 }
1303 else {
1304 r += SubString (startAt); // if no match, the rest of the string is a non-separator
1305 break;
1306 }
1307 }
1308 return r;
1309}
1310Sequence<String> String::Tokenize (const Set<Character>& delimiters) const
1311{
1312 /*
1313 * @todo Inefficient impl, to encourage code saving. Do more efficiently.
1314 */
1315 return Tokenize ([delimiters] (Character c) -> bool { return delimiters.Contains (c); });
1316}
1317
1319{
1322 for (auto i = this->MakeIterator (); i; ++i) {
1323 Character c = *i;
1324 // look for \r, \r\n, or \n
1325 switch (c.GetCharacterCode ()) {
1326 case '\r': {
1327 auto ii = i;
1328 ++ii;
1329 if (ii and *ii == '\n') {
1330 i = ii;
1331 }
1332 r += curLineSB.str ();
1333 curLineSB.clear ();
1334 break;
1335 }
1336 case '\n': {
1337 r += curLineSB.str ();
1338 curLineSB.clear ();
1339 break;
1340 }
1341 default: {
1342 curLineSB.push_back (c);
1343 break;
1344 }
1345 }
1346 }
1347 if (not curLineSB.empty ()) { // non-terminated lines included
1348 r += curLineSB.str ();
1349 }
1350 return r;
1351}
1352
1353Sequence<String> String::Grep (const String& fgrepArg) const
1354{
1356 for (auto i : AsLines ()) {
1357 if (i.Contains (fgrepArg)) {
1358 r += i;
1359 }
1360 }
1361 return r;
1362}
1363Sequence<String> String::Grep (const RegularExpression& egrepArg) const
1364{
1366 for (auto i : AsLines ()) {
1367 if (i.Matches (egrepArg)) {
1368 r += i;
1369 }
1370 }
1371 return r;
1372}
1373
1374optional<String> String::Col (size_t i) const
1375{
1376 static const RegularExpression kWS_ = "\\s+"_RegEx;
1377 return Col (i, kWS_);
1378}
1379
1380optional<String> String::Col (size_t i, const RegularExpression& separator) const
1381{
1382 return Tokenize (separator).Nth (i);
1383}
1384
1385String String::SubString_ (const _SafeReadRepAccessor& thisAccessor, size_t from, size_t to) const
1386{
1387 constexpr bool kWholeStringOptionization_ =
1388 false; // empirically, this costs about 1%. My WAG is that 1% cost not a good tradeoff cuz I dont think this gets triggered that often - LGP 2023-09-26
1389 Require (from <= to);
1390 Require (to <= this->size ());
1391
1392 // Could do this more simply, but since this function is a bottleneck, handle each representation case separately
1393 if (from == to) [[unlikely]] {
1394 return mkEmpty_ ();
1395 }
1396 PeekSpanData psd = thisAccessor._ConstGetRep ().PeekData (nullopt);
1397 switch (psd.fInCP) {
1398 case PeekSpanData::eAscii: {
1399 if constexpr (kWholeStringOptionization_) {
1400 if (from == 0 and to == psd.fAscii.size ()) [[unlikely]] {
1401 return *this; // unclear if this optimization is worthwhile
1402 }
1403 }
1404 return mk_nocheck_ (psd.fAscii.subspan (from, to - from)); // no check cuz we already know its all ASCII and nothing smaller
1405 }
1407 if constexpr (kWholeStringOptionization_) {
1408 if (from == 0 and to == psd.fSingleByteLatin1.size ()) [[unlikely]] {
1409 return *this; // unclear if this optimization is worthwhile
1410 }
1411 }
1412 return mk_ (psd.fSingleByteLatin1.subspan (from, to - from)); // note still needs to re-examine text, cuz subset maybe pure ascii (etc)
1413 }
1414 case PeekSpanData::eChar16: {
1415 if constexpr (kWholeStringOptionization_) {
1416 if (from == 0 and to == psd.fChar16.size ()) [[unlikely]] {
1417 return *this; // unclear if this optimization is worthwhile
1418 }
1419 }
1420 return mk_ (psd.fChar16.subspan (from, to - from)); // note still needs to re-examine text, cuz subset maybe pure ascii (etc)
1421 }
1422 case PeekSpanData::eChar32: {
1423 if constexpr (kWholeStringOptionization_) {
1424 if (from == 0 and to == psd.fChar32.size ()) [[unlikely]] {
1425 return *this; // unclear if this optimization is worthwhile
1426 }
1427 }
1428 return mk_ (psd.fChar32.subspan (from, to - from)); // note still needs to re-examine text, cuz subset maybe pure ascii (etc)
1429 }
1430 default:
1432 return String{};
1433 }
1434}
1435
1436String String::Repeat (unsigned int count) const
1437{
1438 switch (count) {
1439 case 0:
1440 return String{};
1441 case 1:
1442 return *this;
1443 case 2:
1444 return *this + *this;
1445 default: {
1446 StringBuilder result;
1447 for (unsigned int i = 0; i < count; ++i) {
1448 result << *this;
1449 }
1450 return result;
1451 }
1452 }
1453}
1454
1455String String::LTrim (bool (*shouldBeTrimmed) (Character)) const
1456{
1458 auto referenceImpl = [&] () {
1459 _SafeReadRepAccessor accessor{this};
1460 size_t length = accessor._ConstGetRep ().size ();
1461 for (size_t i = 0; i < length; ++i) {
1462 if (not(*shouldBeTrimmed) (accessor._ConstGetRep ().GetAt (i))) {
1463 if (i == 0) {
1464 return *this; // no change in string
1465 }
1466 else {
1467 return SubString (i, length);
1468 }
1469 }
1470 }
1471 return String{}; // all trimmed
1472 };
1473 auto commonAlgorithm = [&]<typename T> (span<const T> lowLevelCharSpan) -> String {
1474 size_t length = lowLevelCharSpan.size ();
1475 for (size_t i = 0; i < length; ++i) {
1476 static_assert (Common::IAnyOf<T, ASCII, Latin1, char32_t>); // this works for ASCII, Latin1, char32_t, but for char16_t - not so much - trickier
1478 // drop not-so-subtle hint to optimizer this is likely the function, and can be called, and hopefully hoisted outside the loop, and inlined
1479 bool thisCharacterTrimmed = [&] () {
1481 return Character::IsWhitespace (c);
1482 }
1483 else {
1484 return shouldBeTrimmed (c);
1485 }
1486 }();
1488 if (i == 0) {
1489#if qStroika_Foundation_Debug_AssertionsChecked
1490 Assert (*this == referenceImpl ());
1491#endif
1492 return *this; // no change in string
1493 }
1494 else {
1495#if qStroika_Foundation_Debug_AssertionsChecked
1496 Assert (mk_ (lowLevelCharSpan.subspan (i)) == referenceImpl ());
1497#endif
1498 return mk_ (lowLevelCharSpan.subspan (i));
1499 }
1500 }
1501 }
1502 return String{}; // all trimmed
1503 };
1504 _SafeReadRepAccessor accessor{this};
1505 PeekSpanData psd = accessor._ConstGetRep ().PeekData (nullopt);
1506 switch (psd.fInCP) {
1507 case PeekSpanData::eAscii: {
1508 return commonAlgorithm (psd.fAscii);
1509 }
1511 return commonAlgorithm (psd.fSingleByteLatin1);
1512 }
1513 case PeekSpanData::eChar32: {
1514 return commonAlgorithm (psd.fChar32);
1515 }
1516 }
1517 return referenceImpl (); // due to tricks with surrogates, and rarity, not worth worrying about char16_t case
1518}
1519
1520String String::RTrim (bool (*shouldBeTrimmed) (Character)) const
1521{
1523 auto referenceImpl = [&] () {
1524 _SafeReadRepAccessor accessor{this};
1525 ptrdiff_t length = accessor._ConstGetRep ().size ();
1527 for (; endOfFirstTrim != 0; --endOfFirstTrim) {
1528 if ((*shouldBeTrimmed) (accessor._ConstGetRep ().GetAt (endOfFirstTrim - 1))) {
1529 // keep going backwards
1530 }
1531 else {
1532 break;
1533 }
1534 }
1535 if (endOfFirstTrim == 0) {
1536 return String{}; // all trimmed
1537 }
1538 else if (endOfFirstTrim == length) {
1539 return *this; // nothing trimmed
1540 }
1541 else {
1542 return SubString (0, endOfFirstTrim);
1543 }
1544 };
1545
1546 auto commonAlgorithm = [&]<typename T> (span<const T> lowLevelCharSpan) -> String {
1547 size_t length = lowLevelCharSpan.size ();
1549 for (; endOfFirstTrim != 0; --endOfFirstTrim) {
1550 static_assert (Common::IAnyOf<T, ASCII, Latin1, char32_t>); // this works for ASCII, Latin1, char32_t, but for char16_t - not so much - trickier
1552 // drop not-so-subtle hint to optimizer this is likely the function, and can be called, and hopefully hoisted outside the loop, and inlined
1553 bool thisCharacterTrimmed = [&] () {
1555 return Character::IsWhitespace (c);
1556 }
1557 else {
1558 return shouldBeTrimmed (c);
1559 }
1560 }();
1562 // keep going backwards
1563 }
1564 else {
1565 break;
1566 }
1567 }
1568 if (endOfFirstTrim == 0) {
1569#if qStroika_Foundation_Debug_AssertionsChecked
1570 Assert (String{} == referenceImpl ());
1571#endif
1572 return String{}; // all trimmed
1573 }
1574 else if (static_cast<size_t> (endOfFirstTrim) == length) {
1575#if qStroika_Foundation_Debug_AssertionsChecked
1576 Assert (*this == referenceImpl ());
1577#endif
1578 return *this; // nothing trimmed
1579 }
1580 else {
1581#if qStroika_Foundation_Debug_AssertionsChecked
1582 Assert (mk_ (lowLevelCharSpan.subspan (0, endOfFirstTrim)) == referenceImpl ());
1583#endif
1584 return mk_ (lowLevelCharSpan.subspan (0, endOfFirstTrim)); //return SubString (0, endOfFirstTrim);
1585 }
1586 };
1587
1588 _SafeReadRepAccessor accessor{this};
1589 PeekSpanData psd = accessor._ConstGetRep ().PeekData (nullopt);
1590 switch (psd.fInCP) {
1591 case PeekSpanData::eAscii: {
1592 return commonAlgorithm (psd.fAscii);
1593 }
1595 return commonAlgorithm (psd.fSingleByteLatin1);
1596 }
1597 case PeekSpanData::eChar32: {
1598 return commonAlgorithm (psd.fChar32);
1599 }
1600 }
1601 return referenceImpl (); // due to tricks with surrogates, and rarity, not worth worrying about char16_t case
1602}
1603
1604String String::Trim (bool (*shouldBeTrimmed) (Character)) const
1605{
1607
1608 auto referenceImpl = [&] () { return LTrim (shouldBeTrimmed).RTrim (shouldBeTrimmed); };
1609
1610 // declared here to encourage inlining the common case of Character::IsWhitespace
1611 auto useCharTrimmedFunc = [&] (Character c) {
1613 return Character::IsWhitespace (c);
1614 }
1615 else {
1616 return shouldBeTrimmed (c);
1617 }
1618 };
1619
1620 auto commonAlgorithm = [&]<typename T> (span<const T> lowLevelCharSpan) -> String {
1621 size_t length = lowLevelCharSpan.size ();
1622 size_t firstKeptIdx = 0;
1623 for (; firstKeptIdx < length; ++firstKeptIdx) {
1624 static_assert (Common::IAnyOf<T, ASCII, Latin1, char32_t>); // this works for ASCII, Latin1, char32_t, but for char16_t - not so much - trickier
1626 if (not useCharTrimmedFunc (c)) {
1627 break;
1628 }
1629 }
1631 for (; static_cast<size_t> (endOfFirstTrim) != firstKeptIdx; --endOfFirstTrim) {
1632 static_assert (Common::IAnyOf<T, ASCII, Latin1, char32_t>); // this works for ASCII, Latin1, char32_t, but for char16_t - not so much - trickier
1634 if (useCharTrimmedFunc (c)) {
1635 // keep going backwards
1636 }
1637 else {
1638 break;
1639 }
1640 }
1641 if (firstKeptIdx == 0 and static_cast<size_t> (endOfFirstTrim) == length) {
1642#if qStroika_Foundation_Debug_AssertionsChecked
1643 Assert (*this == referenceImpl ());
1644#endif
1645 return *this; // nothing changed, just bump reference count on shared_ptr
1646 }
1647 if (firstKeptIdx == length) {
1648#if qStroika_Foundation_Debug_AssertionsChecked
1649 Assert (String{} == referenceImpl ());
1650#endif
1651 return String{}; // trimmed everything way
1652 }
1653 Assert (static_cast<ptrdiff_t> (firstKeptIdx) < endOfFirstTrim);
1654#if qStroika_Foundation_Debug_AssertionsChecked
1655 Assert (mk_ (lowLevelCharSpan.subspan (firstKeptIdx, endOfFirstTrim - firstKeptIdx)) == referenceImpl ());
1656#endif
1657 return mk_ (lowLevelCharSpan.subspan (firstKeptIdx, endOfFirstTrim - firstKeptIdx));
1658 };
1659
1660 _SafeReadRepAccessor accessor{this};
1661 PeekSpanData psd = accessor._ConstGetRep ().PeekData (nullopt);
1662 switch (psd.fInCP) {
1663 case PeekSpanData::eAscii: {
1664 return commonAlgorithm (psd.fAscii);
1665 }
1667 return commonAlgorithm (psd.fSingleByteLatin1);
1668 }
1669 case PeekSpanData::eChar32: {
1670 return commonAlgorithm (psd.fChar32);
1671 }
1672 }
1673 return referenceImpl (); // due to tricks with surrogates, and rarity, not worth worrying about char16_t case
1674}
1675
1676String String::StripAll (bool (*removeCharIf) (Character)) const
1677{
1679
1680 // NB: optimize special case where removeCharIf is always false
1681 //
1682 // Walk string and find first character we need to remove
1683 StringBuilder<StringBuilder_Options<char32_t>> result{*this}; // StringBuilder_Options<char32_t> so operator[] is fast
1684 size_t n = result.size ();
1685 for (size_t i = 0; i < n; ++i) {
1686 Character c = result[i];
1687 if (removeCharIf (c)) {
1688 // on first removal, clone part of string done so far, and start appending
1689 StringBuilder tmp = result.As<String> ().SubString (0, i);
1690 // Now keep iterating IN THIS LOOP appending characters and return at the end of this loop
1691 ++i;
1692 for (; i < n; ++i) {
1693 c = result[i];
1694 if (not removeCharIf (c)) {
1695 tmp += c;
1696 }
1697 }
1698 return tmp;
1699 }
1700 }
1701 return *this; // if we NEVER get removeCharIf return false, just clone this
1702}
1703
1704String String::Join (const Iterable<String>& list, const String& separator)
1705{
1706 StringBuilder result;
1707 for (const String& i : list) {
1708 result << i << separator;
1709 }
1710 if (result.empty ()) {
1711 return result.str ();
1712 }
1713 else {
1714 return result.str ().SubString (0, -static_cast<int> (separator.size ()));
1715 }
1716}
1717
1719{
1720 StringBuilder result;
1721 bool changed{false}; // if no change, no need to allocate new object
1722 _SafeReadRepAccessor accessor{this};
1723 PeekSpanData psd = accessor._ConstGetRep ().PeekData (nullopt);
1724 if (psd.fInCP == PeekSpanData::eAscii) [[likely]] {
1725 // optimization but other case would work no matter what
1726 for (auto c : psd.fAscii) {
1727 if (isupper (c)) {
1728 changed = true;
1729 result.push_back (static_cast<ASCII> (tolower (c)));
1730 }
1731 else {
1732 result.push_back (c);
1733 }
1734 }
1735 }
1736 else {
1738 for (Character c : GetData (psd, &maybeIgnoreBuf1)) {
1739 if (c.IsUpperCase ()) {
1740 changed = true;
1741 result.push_back (c.ToLowerCase ());
1742 }
1743 else {
1744 result.push_back (c);
1745 }
1746 }
1747 }
1748 if (changed) {
1749 return result.str ();
1750 }
1751 else {
1752 return *this;
1753 }
1754}
1755
1757{
1758 StringBuilder result;
1759 bool changed{false}; // if no change, no need to allocate new object
1760 _SafeReadRepAccessor accessor{this};
1761 PeekSpanData psd = accessor._ConstGetRep ().PeekData (nullopt);
1762 if (psd.fInCP == PeekSpanData::eAscii) [[likely]] {
1763 // optimization but other case would work no matter what
1764 for (auto c : psd.fAscii) {
1765 if (islower (c)) {
1766 changed = true;
1767 result.push_back (static_cast<ASCII> (toupper (c)));
1768 }
1769 else {
1770 result.push_back (c);
1771 }
1772 }
1773 }
1774 else {
1776 for (Character c : GetData (psd, &maybeIgnoreBuf1)) {
1777 if (c.IsLowerCase ()) {
1778 changed = true;
1779 result.push_back (c.ToUpperCase ());
1780 }
1781 else {
1782 result.push_back (c);
1783 }
1784 }
1785 }
1786 if (changed) {
1787 return result.str ();
1788 }
1789 else {
1790 return *this;
1791 }
1792}
1793
1795{
1796 // It is all whitespace if the first non-whitespace character is 'EOF'
1797 return not Find ([] (Character c) -> bool { return not c.IsWhitespace (); });
1798}
1799
1800String String::LimitLength (size_t maxLen, StringShorteningPreference keepPref, const String& ellipsis) const
1801{
1802 // @todo Consider making this the 'REFERENCE' impl, and doing a specific one with a specific StringBuilder, and doing
1803 // the trim/split directly, if I see this show up in a profile, for performance sake --LGP 2023-12-11
1804 if (length () < maxLen) [[likely]] {
1805 return *this; // frequent optimization
1806 }
1807 String operateOn = [&] () {
1808 switch (keepPref) {
1809 case StringShorteningPreference::ePreferKeepLeft:
1810 return LTrim ();
1811 case StringShorteningPreference::ePreferKeepRight:
1812 return RTrim ();
1813 case StringShorteningPreference::ePreferKeepMid:
1814 return Trim (); // not sure we need to trim - but probably best
1815 default:
1817 return *this;
1818 }
1819 }();
1820 if (operateOn.length () <= maxLen) {
1821 return operateOn;
1822 }
1823 size_t useLen = [&] () {
1824 size_t useLen = maxLen;
1825 size_t ellipsisTotalLen = ellipsis.length ();
1826 if (keepPref == StringShorteningPreference::ePreferKeepMid) {
1827 ellipsisTotalLen *= 2;
1828 }
1829 if (useLen > ellipsisTotalLen) {
1831 }
1832 else {
1833 useLen = 0;
1834 }
1835 return useLen;
1836 }();
1837 switch (keepPref) {
1838 case StringShorteningPreference::ePreferKeepLeft:
1839 return operateOn.substr (0, useLen) + ellipsis;
1840 case StringShorteningPreference::ePreferKeepRight:
1841 return ellipsis + operateOn.substr (operateOn.length () - useLen);
1842 case StringShorteningPreference::ePreferKeepMid:
1843 return ellipsis + operateOn.substr (operateOn.length () / 2 - useLen / 2, useLen) + ellipsis;
1844 default:
1846 return *this;
1847 }
1848}
1849
1850string String::AsNarrowString (const locale& l) const
1851{
1852 // Note: this could use CodeCvt, but directly using std::codecvt in this case pretty simple, and
1853 // more efficient this way --LGP 2023-02-14
1854
1855 // See http://en.cppreference.com/w/cpp/locale/codecvt/~codecvt
1858
1861 // http://en.cppreference.com/w/cpp/locale/codecvt/out
1863 const wchar_t* from_next;
1864 char* to_next;
1865 Memory::StackBuffer<char> into{Memory::eUninitialized, thisData.size () * 5}; // not sure what size is always big enuf
1866 codecvt_base::result result =
1867 cvt.out (mbstate, thisData.data (), thisData.data () + thisData.size (), from_next, into.data (), into.end (), to_next);
1868 if (result != codecvt_base::ok) [[unlikely]] {
1869 static const auto kException_ = Execution::RuntimeErrorException{"Error converting locale multibyte string to UNICODE"sv};
1871 }
1872 return string{into.data (), to_next};
1873}
1874
1875string String::AsNarrowString (const locale& l, AllowMissingCharacterErrorsFlag) const
1876{
1877 // Note: this could use CodeCvt, but directly using std::codecvt in this case pretty simple, and
1878 // more efficient this way --LGP 2023-02-14
1879
1880 // See http://en.cppreference.com/w/cpp/locale/codecvt/~codecvt
1883
1886 // http://en.cppreference.com/w/cpp/locale/codecvt/out
1888 Memory::StackBuffer<char> into{Memory::eUninitialized, thisData.size () * 5}; // not sure what size is always big enuf
1889 const wchar_t* readFrom = thisData.data ();
1890 char* intoIndex = into.data ();
1891Again:
1892 const wchar_t* from_next{nullptr};
1893 char* to_next{nullptr};
1894 codecvt_base::result result = cvt.out (mbstate, readFrom, thisData.data () + thisData.size (), from_next, intoIndex, into.end (), to_next);
1895 if (result != codecvt_base::ok) [[unlikely]] {
1896 if (from_next != thisData.data () + thisData.size ()) {
1897 readFrom = from_next + 1; // unclear how much to skip (due to surrogates) - but likely this is a good guess
1898 *to_next = '?'; // write 'bad' character
1899 intoIndex = to_next + 1;
1900 goto Again;
1901 }
1902 }
1903 return string{into.data (), to_next};
1904}
1905
1906void String::erase (size_t from)
1907{
1908 *this = RemoveAt (from, size ());
1909}
1910
1911void String::erase (size_t from, size_t count)
1912{
1913 // https://github.com/SophistSolutions/Stroika/issues/579 (STK-445)
1914 // @todo - NOT ENVELOPE THREADSAFE
1915 // MUST ACQUIRE ACCESSOR HERE - not just that RemoteAt threadsafe - but must SYNC at this point - need AssureExternallySycnonized stuff here!!!
1916 //
1917 // TODO: Double check STL definition - but I think they allow for count to be 'too much' - and silently trim to end...
1918 size_t max2Erase = static_cast<size_t> (max (static_cast<ptrdiff_t> (0), static_cast<ptrdiff_t> (size ()) - static_cast<ptrdiff_t> (from)));
1919 *this = RemoveAt (from, from + min (count, max2Erase));
1920}
1921
1922const wchar_t* String::c_str () const noexcept
1923{
1924 // UNSAFE - DEPRECATED - lose before v3 actually released -- LGP 2023-06-28
1926 DISABLE_COMPILER_GCC_WARNING_START ("GCC diagnostic ignored \"-Wdeprecated-declarations\"");
1927 DISABLE_COMPILER_CLANG_WARNING_START ("clang diagnostic ignored \"-Wdeprecated-declarations\"");
1928 return const_cast<String*> (this)->c_str ();
1929 DISABLE_COMPILER_MSC_WARNING_END (4996);
1930 DISABLE_COMPILER_GCC_WARNING_END ("GCC diagnostic ignored \"-Wdeprecated-declarations\"");
1931 DISABLE_COMPILER_CLANG_WARNING_END ("clang diagnostic ignored \"-Wdeprecated-declarations\"");
1932}
1933const wchar_t* String::c_str ()
1934{
1935 // DEPRECATED SINCE STROIKA v3.0d13
1936 // Rarely used mechanism, of replacing the underlying rep, for the iterable, as needed
1937 _SafeReadRepAccessor accessor{this};
1938 const wchar_t* result = accessor._ConstGetRep ().c_str_peek ();
1939 if (result == nullptr) {
1940 _fRep = MakeSharedPtr<StringWithCStr_::Rep> (accessor._ConstGetRepSharedPtr ());
1941 result = _SafeReadRepAccessor{this}._ConstGetRep ().c_str_peek ();
1942 AssertNotNull (result);
1943 }
1944 EnsureNotNull (result);
1945 Ensure (result[size ()] == '\0' or (::wcslen (result) > size () and sizeof (wchar_t) == 2)); // if there are surrogates, wcslen () might be larger than size
1946 return result;
1947}
1948
1949[[noreturn]] void String::ThrowInvalidAsciiException_ ()
1950{
1951 static const auto kException_ = Execution::RuntimeErrorException{"Error converting non-ascii text to string"sv};
1953}
1954
1955#if qStroika_Foundation_Characters_AsPathAutoMapMSYSAndCygwin
1956template <>
1957std::filesystem::path String::As<std::filesystem::path> () const
1958{
1959 // CYGWIN creates paths like /cygdrive/c/folder for c:/folder
1960 // MSYS creates paths like /c/folder for c:/folder
1961 static const String kMSYSDrivePrefix_ = "/"sv;
1962 static const String kCygrivePrefix_ = "/cygdrive/"sv;
1963 if (StartsWith (kCygrivePrefix_)) {
1964 String ss = SubString (kCygrivePrefix_.length ());
1965 if (ss.length () > 1 and ss[0].IsASCII () and ss[0].IsAlphabetic () and ss[1] == '/') {
1966 wstring w = ss.As<wstring> (); // now map c/folder to c:/folder
1967 w.insert (w.begin () + 1, ':');
1968 return filesystem::path{w};
1969 }
1970 }
1971 if (StartsWith (kMSYSDrivePrefix_)) {
1972 String ss = SubString (kMSYSDrivePrefix_.length ());
1973 if (ss.length () > 1 and ss[0].IsASCII () and ss[0].IsAlphabetic () and ss[1] == '/') {
1974 wstring w = ss.As<wstring> (); // now map c/folder to c:/folder
1975 w.insert (w.begin () + 1, ':');
1976 return filesystem::path{w};
1977 }
1978 }
1979 return filesystem::path{As<wstring> ()};
1980}
1981#endif
1982
1983/*
1984 ********************************************************************************
1985 ****************************** StringCombiner **********************************
1986 ********************************************************************************
1987 */
1988template <>
1989String StringCombiner<String>::operator() (const String& lhs, const String& rhs, bool isLast) const
1990{
1991 StringBuilder sb{lhs};
1992 if (isLast and fSpecialSeparatorForLastPair) [[unlikely]] {
1993 sb << *fSpecialSeparatorForLastPair;
1994 }
1995 else {
1996 sb << fSeparator;
1997 }
1998 sb << rhs;
1999 return sb;
2000}
2001
2002/*
2003 ********************************************************************************
2004 ******************* Iterable<Characters::String>::Join *************************
2005 ********************************************************************************
2006 */
2007namespace Stroika::Foundation::Traversal {
2008 // specialized as performance optimization
2009 template <>
2010 Characters::String Iterable<Characters::String>::Join (const Characters::String& separator, const optional<Characters::String>& finalSeparator) const
2011 {
2012 using namespace Characters;
2013#if qStroika_Foundation_Debug_AssertionsChecked
2014 String referenceResult =
2016 Characters::StringCombiner<String>{.fSeparator = separator, .fSpecialSeparatorForLastPair = finalSeparator});
2017#endif
2018 StringBuilder sb;
2019 size_t cnt = this->size ();
2020 this->Apply ([&, idx = 0u] (const String& i) mutable {
2021 if (idx == 0) {
2022 sb = i;
2023 }
2024 else {
2025 if (finalSeparator and idx + 1 == cnt) [[unlikely]] {
2026 sb << *finalSeparator;
2027 }
2028 else {
2029 sb << separator;
2030 }
2031 sb << i;
2032 }
2033 ++idx;
2034 });
2035#if qStroika_Foundation_Debug_AssertionsChecked
2036 Ensure (sb == referenceResult);
2037#endif
2038 return sb;
2039 }
2040}
2041
2042/*
2043 ********************************************************************************
2044 ********************************** operator<< **********************************
2045 ********************************************************************************
2046 */
2047wostream& Characters::operator<< (wostream& out, const String& s)
2048{
2049 Memory::StackBuffer<wchar_t> maybeIgnoreBuf1;
2050 span<const wchar_t> sData = s.GetData (&maybeIgnoreBuf1);
2051 out.write (sData.data (), sData.size ());
2052 return out;
2053}
2054ostream& Characters::operator<< (ostream& out, const String& s)
2055{
2056 return out << s.AsNarrowSDKString (eIgnoreErrors);
2057}
2058
2059/*
2060 ********************************************************************************
2061 *********** hash<Stroika::Foundation::Characters::String> **********************
2062 ********************************************************************************
2063 */
2064size_t std::hash<String>::operator() (const String& arg) const
2065{
2066 using namespace Cryptography::Digest;
2067 using DIGESTER = Digester<Algorithm::SuperFastHash>; // pick arbitrarily which algorithm to use for now -- err on the side of quick and dirty
2068 static constexpr DIGESTER kDigester_{};
2069 // Note this could easily use char8_t, wchar_t, char32_t, or whatever. Choose char8_t on the theory that
2070 // this will most often avoid a copy, and making the most often case faster is probably a win. Also, even close, it
2071 // will have less 'empty space' and be more compact, so will digest faster.
2072 Memory::StackBuffer<char8_t> maybeIgnoreBuf1;
2073 span<const char8_t> s = arg.GetData (&maybeIgnoreBuf1);
2074 if (s.empty ()) {
2075 static const size_t kZeroDigest_ = kDigester_ (nullptr, nullptr);
2076 return kZeroDigest_;
2077 }
2078 else {
2079 return kDigester_ (as_bytes (s));
2080 }
2081}
2082
2083/*
2084 ********************************************************************************
2085 ******************** DataExchange::DefaultSerializer<String> *******************
2086 ********************************************************************************
2087 */
2089{
2090 //
2091 // Could have used char8_t, char16_t, or char32_t here quite plausibly. Chose char8_t for several reasons:
2092 // > Nearly always smallest representation (assuming most data is ascii)
2093 // > It is cross-platform/portable - not byte order dependent (NOT a promise going forward, so maybe
2094 // not a good thing - but a thing)
2095 // > Since we expect most data reps to be ascii, this will involve the least copying, most likely, in
2096 // the GetData call
2097 //
2098 Memory::StackBuffer<char8_t> maybeIgnoreBuf1;
2099 return Memory::BLOB{as_bytes (arg.GetData (&maybeIgnoreBuf1))};
2100}
#define AssertNotNull(p)
Definition Assertions.h:334
#define EnsureNotNull(p)
Definition Assertions.h:341
#define RequireMember(p, c)
Definition Assertions.h:327
#define RequireNotReached()
Definition Assertions.h:386
#define qStroika_Foundation_Debug_AssertionsChecked
The qStroika_Foundation_Debug_AssertionsChecked flag determines if assertions are checked and validat...
Definition Assertions.h:49
#define RequireNotNull(p)
Definition Assertions.h:348
#define RequireExpression(c)
Definition Assertions.h:268
#define AssertNotReached()
Definition Assertions.h:356
conditional_t< qStroika_Foundation_Memory_PreferBlockAllocation and andTrueCheck, BlockAllocationUseHelper< T >, Common::Empty > UseBlockAllocationIfAppropriate
Use this to enable block allocation for a particular class. Beware of subclassing.
bool Equals(const T *lhs, const T *rhs)
strcmp or wsccmp() as appropriate == 0
constexpr bool IsASCII() const noexcept
Return true iff the given character (or all in span) is (are) in the ascii range [0....
static constexpr void CheckASCII(span< const CHAR_T > s)
if not IsASCII (arg) throw RuntimeException...
nonvirtual Character ToLowerCase() const noexcept
nonvirtual ASCII GetAsciiCode() const noexcept
static constexpr strong_ordering Compare(span< const CHAR_T, E1 > lhs, span< const CHAR_T, E2 > rhs, CompareOptions co) noexcept
nonvirtual bool IsLowerCase() const noexcept
constexpr char32_t GetCharacterCode() const noexcept
Return the char32_t UNICODE code-point associated with this character.
nonvirtual Character ToUpperCase() const noexcept
constexpr bool IsWhitespace() const noexcept
nonvirtual bool IsUpperCase() const noexcept
RegularExpression is a compiled regular expression which can be used to match on a String class.
virtual Character GetAt(size_t index) const noexcept=0
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
nonvirtual size_t size() const noexcept
nonvirtual void Append(span< const CHAR_T > s)
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
nonvirtual size_t length() const noexcept
Definition String.inl:1053
nonvirtual String ToUpperCase() const
Definition String.cpp:1756
static String FromNarrowString(const char *from, const locale &l)
Definition String.inl:342
nonvirtual bool Matches(const RegularExpression &regEx) const
Definition String.cpp:1145
nonvirtual bool IsWhitespace() const
Definition String.cpp:1794
nonvirtual String NormalizeTextToNL() const
Definition String.cpp:1213
static String Join(const Iterable< String > &list, const String &separator=", "sv)
Definition String.cpp:1704
static String FromStringConstant(const CHAR_T(&cString)[SIZE])
Take the given argument data (constant span) - which must remain unchanged - constant - for the appli...
Definition String.inl:388
nonvirtual String NormalizeSpace(Character useSpaceCharacter=' ') const
Replace sequences of whitespace characters (space, tab, newline etc) with a single space (or argument...
Definition String.cpp:1241
nonvirtual Containers::Sequence< pair< size_t, size_t > > FindEach(const RegularExpression &regEx) const
Definition String.cpp:978
nonvirtual String Repeat(unsigned int count) const
Definition String.cpp:1436
nonvirtual String LimitLength(size_t maxLen, StringShorteningPreference keepPref=StringShorteningPreference::ePreferKeepLeft) const
return the first maxLen (or fewer if string shorter) characters of this string (adding ellipsis if tr...
Definition String.inl:747
nonvirtual String RemoveAll(Character c) const
Definition String.cpp:835
nonvirtual Containers::Sequence< RegularExpressionMatch > FindEachMatch(const RegularExpression &regEx) const
Definition String.cpp:996
nonvirtual String RemoveFirstIf(Character c) const
Definition String.cpp:819
nonvirtual string AsNarrowSDKString() const
Definition String.inl:836
nonvirtual optional< String > Col(size_t i) const
Useful to replace 'awk print $3' - replace with Col(2) - zero based.
Definition String.cpp:1374
nonvirtual String InsertAt(Character c, size_t at) const
Definition String.inl:721
nonvirtual string AsNarrowString(const locale &l) const
Definition String.cpp:1850
nonvirtual size_t size() const noexcept
Definition String.inl:536
nonvirtual bool EndsWith(const Character &c, CompareOptions co=eWithCase) const
Definition String.cpp:1100
nonvirtual String ToLowerCase() const
Definition String.cpp:1718
nonvirtual String ReplaceAll(const RegularExpression &regEx, const String &with) const
Definition String.cpp:1167
nonvirtual String Replace(size_t from, size_t to, const String &replacement) const
Definition String.cpp:1057
nonvirtual String SubString(SZ from) const
nonvirtual String Trim(bool(*shouldBeTrimmed)(Character)=Character::IsWhitespace) const
Definition String.cpp:1604
nonvirtual bool StartsWith(const Character &c, CompareOptions co=eWithCase) const
Definition String.cpp:1071
nonvirtual String StripAll(bool(*removeCharIf)(Character)) const
Definition String.cpp:1676
nonvirtual String AssureEndsWith(const Character &c, CompareOptions co=eWithCase) const
Return *this if it ends with argument character, or append 'c' so that it ends with a 'c'.
Definition String.cpp:1135
nonvirtual Containers::Sequence< String > AsLines() const
break the String into a series of lines;
Definition String.cpp:1318
nonvirtual String LTrim(bool(*shouldBeTrimmed)(Character)=Character::IsWhitespace) const
Definition String.cpp:1455
nonvirtual Containers::Sequence< String > Grep(const String &fgrepArg) const
Breaks this string into Lines, with AsLines (), and applies the argument filter (as if with ....
Definition String.cpp:1353
nonvirtual Containers::Sequence< String > FindEachString(const RegularExpression &regEx) const
Definition String.cpp:1013
nonvirtual optional< size_t > RFind(Character c) const noexcept
Definition String.cpp:1023
static span< const CHAR_TYPE > GetData(const PeekSpanData &pds, Memory::StackBuffer< CHAR_TYPE, STACK_BUFFER_SZ > *possiblyUsedBuffer)
return the constant character data inside the string (rep) in the form of a span, possibly quickly an...
Definition String.inl:969
nonvirtual Containers::Sequence< String > Tokenize() const
Definition String.cpp:1246
nonvirtual String RemoveAt(size_t charAt) const
Definition String.inl:610
nonvirtual String RTrim(bool(*shouldBeTrimmed)(Character)=Character::IsWhitespace) const
Definition String.cpp:1520
nonvirtual optional< size_t > Find(Character c, CompareOptions co=eWithCase) const
Definition String.inl:687
static const UTFConvert kThe
Nearly always use this default UTFConvert.
Definition UTFConvert.h:369
static constexpr bool AllFitsInTwoByteEncoding(span< const CHAR_T > s) noexcept
Sequence_stdvector<T> is an std::vector-based concrete implementation of the Sequence<T> container pa...
A generalization of a vector: a container whose elements are keyed by the natural numbers.
nonvirtual void push_back(ArgByValueType< value_type > item)
Definition Sequence.inl:782
nonvirtual void Append(ArgByValueType< value_type > item)
Definition Sequence.inl:615
Set<T> is a container of T, where once an item is added, additionally adds () do nothing.
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
nonvirtual size_t size() const noexcept
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
Definition Iterable.h:238
nonvirtual RESULT_T Join(const CONVERT_TO_RESULT &convertToResult=kDefaultToStringConverter<>, const COMBINER &combiner=Characters::kDefaultStringCombiner) const
ape the JavaScript/python 'join' function - take the parts of 'this' iterable and combine them into a...
nonvirtual size_t size() const
Returns the number of items contained.
Definition Iterable.inl:311
nonvirtual Iterator< Character > MakeIterator() const
Create an iterator object which can be used to traverse the 'Iterable'.
Definition Iterable.inl:305
An Iterator<T> is a copyable object which allows traversing the contents of some container.
Definition Iterator.h:253
concept - trivial shorthand for variadic same_as A or same_as B, or ...
Definition Concepts.h:120
char ASCII
Stroika's string/character classes treat 'char' as being an ASCII character.
Definition Character.h:59
wostream & operator<<(wostream &out, const String &s)
Definition String.cpp:2047
conditional_t<(sizeof(CHECK_T)<=2 *sizeof(void *)) and is_trivially_copyable_v< CHECK_T >, CHECK_T, const CHECK_T & > ArgByValueType
This is an alias for 'T' - but how we want to pass it on stack as formal parameter.
Definition TypeHints.h:36
SequencePolicy
equivalent which of 4 types being used std::execution::sequenced_policy, parallel_policy,...
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
Definition Throw.inl:43
Summary data for raw contents of rep - each rep will support at least one of these span forms.
Definition String.h:1280
StringCombiner is a simple function object used to combine two strings visually - used in Iterable<>:...
Definition String.h:1933