1// tString.h 
2// 
3// tString is a simple and readable string class that implements sensible operators, including implicit casts. There is 
4// no UCS2 or UTF16 support since UTF8 is, in my opinion, superior and the way forward. tStrings will work with UTF8. 
5// You cannot stream (from cin etc) more than 512 chars into a string. This restriction is only for wacky << streaming. 
6// For conversions of arbitrary types to tStrings, see tsPrint in the higher level System module. 
7// 
8// Copyright (c) 2004-2006, 2015, 2017, 2019, 2020 Tristan Grimmer. 
9// Copyright (c) 2020 Stefan Wessels. 
10// Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby 
11// granted, provided that the above copyright notice and this permission notice appear in all copies. 
12// 
13// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL 
14// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 
15// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 
16// AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 
17// PERFORMANCE OF THIS SOFTWARE. 
18 
19#pragma once 
20#include "Foundation/tStandard.h" 
21#include "Foundation/tList.h" 
22 
23 
24struct tString 
25
26 tString() { TextData = &EmptyChar; } 
27 tString(const tString&); 
28 
29 // Construct a string with enough room for length characters. Length+1 characters are reserved to make room for the 
30 // null terminator. The reserved space is zeroed. 
31 explicit tString(int length); 
32 tString(const char*); 
33 tString(char); 
34 virtual ~tString(); 
35 
36 tString& operator=(const tString&); 
37 
38 bool IsEqual(const tString& s) const { return( !tStd::tStrcmp(TextData, s.TextData) ); } 
39 bool IsEqual(const char* s) const { return( !tStd::tStrcmp(TextData, s) ); } 
40 bool IsEqualCI(const tString& s) const { return( !tStd::tStricmp(TextData, s.TextData) ); } 
41 bool IsEqualCI(const char* s) const { return( !tStd::tStricmp(TextData, s) ); } 
42 
43 // These allow for implicit conversion to a character pointer. 
44 operator const char*() { return TextData; } 
45 operator const char*() const { return TextData; } 
46 
47 explicit operator uint32(); 
48 explicit operator uint32() const
49 
50 char& operator[](int i) { return TextData[i]; } 
51 friend tString operator+(const tString& prefix, const tString& suffix); 
52 tString& operator+=(const tString&); 
53 
54 void Set(const char*); 
55 int Length() const { return int(tStd::tStrlen(TextData)); } 
56 bool IsEmpty() const { return (TextData == &EmptyChar) || !tStd::tStrlen(TextData); } 
57 void Clear() { if (TextData != &EmptyChar) delete[] TextData; TextData = &EmptyChar; } 
58 
59 bool IsAlphabetic(bool includeUnderscore = true) const
60 bool IsNumeric(bool includeDecimal = false) const
61 bool IsAlphaNumeric(bool includeUnderscore = true, bool includeDecimal = false) const
62 
63 // Current string data is lost and enough space is reserved for length characters. The reserved memory is zeroed. 
64 void Reserve(int length); 
65 
66 tString Left(const char marker = ' ') const; // Returns a tString of the characters before the first marker. Returns the entire string if marker was not found. 
67 tString Right(const char marker = ' ') const; // Same as Left but chars after last marker. 
68 tString Left(int count) const; // Returns a tString of the first count chars. Return what's available if count > length. 
69 tString Right(int count) const; // Same as Left but returns last count chars. 
70 tString Mid(int start, int count) const; // Returns count chars from start (inclusive), or what's available if start+count > length. 
71 
72 // Extracts first word up to and not including first divider encountered. The tString is left with the remainder, 
73 // not including the divider. If divider isn't found, the entire string is returned and the tString is left empty. 
74 tString ExtractLeft(const char divider = ' '); 
75 
76 // Extracts word after last divider. The tString is left with the remainder, not including the divider. If the 
77 // divider isn't found, the entire string is returned and the tString is left empty. 
78 tString ExtractRight(const char divider = ' '); 
79 
80 // Returns a tString of the first count chars. Removes these from the current string. If count > length then what's 
81 // available is extracted. 
82 tString ExtractLeft(int count); 
83 
84 // Returns a tString of the last count chars. Removes these from the current string. If count > length then what's 
85 // available is extracted. 
86 tString ExtractRight(int count); 
87 
88 // Returns chars from start to count, but also removes that from the tString. If start + count > length then what's 
89 // available is extracted. 
90 tString ExtractMid(int start, int count); 
91 
92 char* Text() { return TextData; } 
93 const char* ConstText() const { return TextData; } 
94 
95 // Returns POD representation (Plain Old Data). For use with tPrintf and %s. 
96 const char* Pod() const { return TextData; } 
97 
98 // One more synonym for ConstText. For use with tPrintf and %s. 
99 const char* Chars() const { return TextData; } 
100 
101 // Returns index of first/last occurrence of char in the string. -1 if not found. Finds last if backwards flag is 
102 // set. The starting point may be specified. If backwards is false, the search proceeds forwards from the starting 
103 // point. If backwards is true, it proceeds backwards. If startIndex is -1, 0 is the starting point for a forward 
104 // search and length-1 is the starting point for a backwards search. 
105 int FindChar(const char, bool backwards = false, int startIndex = -1) const
106 
107 // Returns the index of the first character in the tString that is also somewhere in the null-terminated string 
108 // searchChars. Returns -1 if none of them match. 
109 int FindAny(const char* searchChars) const
110 
111 // Returns index of first character of the string s in the string. Returns -1 if not found. 
112 int FindString(const char* s, int startIndex = 0) const
113 
114 // Replace all occurrences of character c with character r. Returns number of characters replaced. 
115 int Replace(const char c, const char r); 
116 
117 // Replace all occurrences of string search with string replace. Returns the number of replacements. The replacement 
118 // is done in a forward direction. If replace is a different size than search, memory will be managed to accomadate 
119 // the larger or smaller resulting string and keep the memory footprint as small as possible. If they are the same 
120 // size, the function is faster and doesn't need to mess with memory. If replace is "" or 0, all occurrences of 
121 // search will be removed (replaced by nothing). 
122 int Replace(const char* search, const char* replace); 
123 
124 // Remove all occurrences of the character rem. Returns the number of characters removed. 
125 int Remove(const char rem); 
126 
127 // Removing a string simply calls Replace with a null second string. Returns how many rem strings were removed. 
128 int Remove(const char* rem) { return Replace(rem, nullptr); } 
129 
130 int RemoveLeading(const char* removeThese); // removeThese is a null-terminated list of chars to remove. 
131 int RemoveTrailing(const char* removeThese); // removeThese is a null-terminated list of chars to remove. 
132 int CountChar(char c) const; // Counts the number of occurrences of c. 
133 
134 void ToUpper() { tStd::tStrupr(TextData); } 
135 void ToLower() { tStd::tStrlwr(TextData); } 
136 
137 // UpCase and LowCase both modify the object as well as return a reference to it. Returning a reference makes it 
138 // easy to string together expressions such as: if (name.LowCase() == "ah") 
139 tString& UpCase() { ToUpper(); return *this; } 
140 tString& LowCase() { ToLower(); return *this; } 
141 
142 // See comment for tStrtoiT in tStandard.h for format requirements. The summary is that if base is -1, the function 
143 // looks one of the following prefixes in the string, defaulting to base 10 if none found. 
144 // 
145 // Base 16 prefixes: x X 0x 0X # 
146 // Base 10 prefixes: d D 0d 0D 
147 // Base 8 prefixes: o O 0o 0O @ 
148 // Base 2 prefixes: b B 0b 0B 
149 int GetAsInt(int base = -1) const { return GetAsInt32(base); } 
150 int32 GetAsInt32(int base = -1) const { return tStd::tStrtoi32(TextData, base); } 
151 int64 GetAsInt64(int base = -1) const { return tStd::tStrtoi64(TextData, base); } 
152 uint GetAsUInt(int base = -1) const { return GetAsUInt32(base); } 
153 uint32 GetAsUInt32(int base = -1) const { return tStd::tStrtoui32(TextData, base); } 
154 uint64 GetAsUInt64(int base = -1) const { return tStd::tStrtoui64(TextData, base); } 
155 
156 bool GetAsIntStrict(int& IntValue, int base = -1) const { return GetAsInt32Strict(IntValue, base); } 
157 bool GetAsInt32Strict(int32& Int32Value, int base = -1) const { return tStd::tStrtoi32Strict(TextData, Int32Value, base); } 
158 bool GetAsInt64Strict(int64& Int64Value, int base = -1) const { return tStd::tStrtoi64Strict(TextData, Int64Value, base); } 
159 bool GetAsUIntStrict(uint& UIntValue, int base = -1) const { return GetAsUInt32Strict(UIntValue, base); } 
160 bool GetAsUInt32Strict(uint32& UInt32Value, int base = -1) const { return tStd::tStrtoui32Strict(TextData, UInt32Value, base); } 
161 bool GetAsUInt64Strict(uint64& UInt64Value, int base = -1) const { return tStd::tStrtoui64Strict(TextData, UInt64Value, base); } 
162 
163 // Case insensitive. Interprets "true", "t", "yes", "y", "on", "enable", "enabled", "1", "+", and strings that 
164 // represent non-zero integers as boolean true. Otherwise false. 
165 bool GetAsBool() const { return tStd::tStrtob(TextData); } 
166 
167 float GetAsFloat() const /* Base 10 interpretation only. */ { return tStd::tStrtof(TextData); } 
168 double GetAsDouble() const /* Base 10 interpretation only. */ { return tStd::tStrtod(TextData); } 
169 
170 // Shorter synonyms. 
171 int AsInt(int base = -1) const { return GetAsInt(base); } 
172 int AsInt32(int base = -1) const { return GetAsInt32(base); } 
173 int64 AsInt64(int base = -1) const { return GetAsInt64(base); } 
174 uint AsUInt(int base = -1) const { return GetAsUInt(base); } 
175 uint AsUInt32(int base = -1) const { return GetAsUInt32(base); } 
176 uint64 AsUInt64(int base = -1) const { return GetAsUInt64(base); } 
177 bool AsBool() const { return GetAsBool(); } 
178 float AsFloat() const { return GetAsFloat(); } 
179 
180protected
181 char* TextData
182 static char EmptyChar; // All empty strings can use this. 
183}; 
184 
185 
186// Binary operator overloads should be outside the class so we can do things like if ("a" == b) where b is a tString. 
187inline bool operator==(const tString& a, const tString& b) { return !tStd::tStrcmp(a.Chars(), b.Chars()); } 
188inline bool operator!=(const tString& a, const tString& b) { return !!tStd::tStrcmp(a.Chars(), b.Chars()); } 
189inline bool operator==(const tString& a, const char* b) { return !tStd::tStrcmp(a.Chars(), b); } 
190inline bool operator!=(const tString& a, const char* b) { return !!tStd::tStrcmp(a.Chars(), b); } 
191inline bool operator==(const char* a, const tString& b) { return !tStd::tStrcmp(a, b.Chars()); } 
192inline bool operator!=(const char* a, const tString& b) { return !!tStd::tStrcmp(a, b.Chars()); } 
193 
194 
195 
196// The tStringItem class is just the tString class except they can be placed on tLists. 
197struct tStringItem : public tLink<tStringItem>, public tString 
198
199public
200 tStringItem() : tString() { } 
201 
202 // The tStringItem copy cons is missing, because as a list item can only be on one list at a time. 
203 tStringItem(const tString& s) : tString(s) { } 
204 tStringItem(int length) : tString(length) { } 
205 tStringItem(const char* c) : tString(c) { } 
206 tStringItem(char c) : tString(c) { } 
207 
208 // This call does NOT change the list that the tStringItem is on. The link remains unmodified. 
209 tStringItem& operator=(const tStringItem&); 
210}; 
211 
212 
213// Some utility functions that act on strings. 
214namespace tStd 
215
216 // Separates the src string into components based on the divider. If src was "abc_def_ghi", components will get 
217 // "abc", "def", and "ghi" appended to it. Returns the number of components appended to the components list. The 
218 // version that takes the string divider allows for multicharacter dividers. Note that "abc__def_ghi" will explode 
219 // to "abc", "", "def", and "ghi". Empty strings are preserved allowing things like exploding text files based on 
220 // linefeeds. You'll get one item per line even if the line only contains a linefeed. 
221 int tExplode(tList<tStringItem>& components, const tString& src, char divider = '_'); 
222 int tExplode(tList<tStringItem>& components, const tString& src, const tString& divider); 
223
224 
225 
226// Implementation below this line. 
227 
228 
229inline tString::tString(const char* t
230
231 if (t
232
233 int len = int(tStd::tStrlen(t)); 
234 if (len > 0
235
236 TextData = new char[1 + len]; 
237 tStd::tStrcpy(TextData, t); 
238 return
239
240
241 
242 TextData = &EmptyChar
243
244 
245 
246inline tString::tString(const tString& s
247
248 TextData = new char[1 + tStd::tStrlen(s.TextData)]; 
249 tStd::tStrcpy(TextData, s.TextData); 
250
251 
252 
253inline tString::tString(char c
254
255 TextData = new char[2]; 
256 TextData[0] = c
257 TextData[1] = '\0'
258
259 
260 
261inline tString::tString(int length
262
263 if (!length
264
265 TextData = &EmptyChar
266
267 else 
268
269 TextData = new char[1+length]; 
270 tStd::tMemset(TextData, 0, 1+length); 
271
272
273 
274 
275inline void tString::Reserve(int length
276
277 if (TextData != &EmptyChar
278 delete[] TextData
279 
280 if (length <= 0
281
282 TextData = &EmptyChar
283 return
284
285 
286 TextData = new char[length+1]; 
287 tStd::tMemset(TextData, 0, length+1); 
288
289 
290 
291inline int tString::CountChar(char c) const 
292
293 char* i = TextData
294 int count = 0
295 while (*i != '\0'
296 count += (*i++ == c) ? 1 : 0
297 
298 return count
299
300 
301 
302inline void tString::Set(const char* s
303
304 Clear(); 
305 if (!s
306 return
307 
308 int len = tStd::tStrlen(s); 
309 if (len <= 0
310 return
311 
312 TextData = new char[1 + len]; 
313 tStd::tStrcpy(TextData, s); 
314
315 
316 
317inline tString& tString::operator=(const tString& src
318
319 if (this == &src
320 return *this
321 
322 if (TextData != &EmptyChar
323 delete[] TextData
324 
325 TextData = new char[1 + src.Length()]; 
326 tStd::tStrcpy(TextData, src.TextData); 
327 return *this
328
329 
330 
331inline tString operator+(const tString& preStr, const tString& sufStr
332
333 tString buf( preStr.Length() + sufStr.Length() ); 
334 tStd::tStrcpy(buf.TextData, preStr.TextData); 
335 tStd::tStrcpy(buf.TextData + preStr.Length(), sufStr.TextData); 
336 
337 return buf
338
339 
340 
341inline tString& tString::operator+=(const tString& sufStr
342
343 if (sufStr.IsEmpty()) 
344 return *this
345 else 
346
347 char* newTextData = new char[ Length() + sufStr.Length() + 1 ]; 
348 tStd::tStrcpy(newTextData, TextData); 
349 tStd::tStrcpy(newTextData + Length(), sufStr.TextData); 
350 
351 if (TextData != &EmptyChar
352 delete[] TextData
353 
354 TextData = newTextData
355 return *this
356
357
358 
359 
360inline bool tString::IsAlphabetic(bool includeUnderscore) const  
361
362 if (TextData == &EmptyChar
363 return false
364 
365 const char* c = TextData
366 while (*c
367
368 if ( !((*c >= 'A' && *c <= 'Z') || (*c >= 'a' && *c <= 'z') || (includeUnderscore && *c == '_')) ) 
369 return false
370 c++; 
371
372 
373 return true
374
375 
376 
377inline bool tString::IsNumeric(bool includeDecimal) const  
378
379 if (TextData == &EmptyChar
380 return false
381 
382 const char* c = TextData
383 while (*c
384
385 if ( !((*c >= '0' && *c <= '9') || (includeDecimal && *c == '.')) ) 
386 return false
387 c++; 
388
389 
390 return true
391
392 
393 
394inline bool tString::IsAlphaNumeric(bool includeUnderscore, bool includeDecimal) const 
395
396 return (IsAlphabetic(includeUnderscore) || IsNumeric(includeDecimal)); 
397
398 
399 
400inline int tString::FindAny(const char* chars) const 
401
402 if (TextData == &EmptyChar
403 return -1
404  
405 int i = 0
406 while (TextData[i]) 
407
408 int j = 0
409 while (chars[j]) 
410
411 if (chars[j] == TextData[i]) 
412 return i
413 j++; 
414
415 i++; 
416
417 return -1
418
419 
420 
421inline int tString::FindChar(const char c, bool reverse, int start) const 
422
423 const char* pc = nullptr
424 
425 if (start == -1
426
427 if (reverse
428 start = Length() - 1
429 else 
430 start = 0
431
432 
433 if (reverse
434
435 for (int i = start; i >= 0; i--) 
436 if (TextData[i] == c
437
438 pc = TextData + i
439 break
440
441
442 else 
443 pc = tStd::tStrchr(&TextData[start], c); 
444 
445 if (!pc
446 return -1
447 
448 // Returns the index. 
449 return int(pc - TextData); 
450
451 
452 
453inline int tString::FindString(const char* s, int start) const 
454
455 int len = Length(); 
456 if (!len
457 return -1
458 
459 tAssert((start >= 0) && (start < Length())); 
460 const char* found = tStd::tStrstr(&TextData[start], s); 
461 if (found
462 return int(found - TextData); 
463 
464 return -1
465
466 
467 
468inline int tString::Replace(const char c, const char r
469
470 int numReplaced = 0
471 for (int i = 0; i < Length(); i++) 
472
473 if (TextData[i] == c
474
475 numReplaced++; 
476 TextData[i] = r
477
478
479 
480 return numReplaced
481
482 
483 
484inline tString::~tString() 
485
486 if (TextData != &EmptyChar
487 delete[] TextData
488
489 
490 
491inline tStringItem& tStringItem::operator=(const tStringItem& src
492
493 if (this == &src
494 return *this
495 
496 if (TextData != &EmptyChar
497 delete[] TextData
498 
499 TextData = new char[1 + src.Length()]; 
500 tStd::tStrcpy(TextData, src.TextData); 
501 return *this
502
503