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