1#pragma once
2
3#include <cstdarg>
4
5#include "prim/seadSafeString.h"
6
7namespace sead
8{
9class Heap;
10template <typename T>
11class StringBuilderBase
12{
13public:
14 static StringBuilderBase* create(s32 buffer_size, Heap* heap, s32 alignment);
15 static StringBuilderBase* create(const T* str, Heap* heap, s32 alignment);
16
17 StringBuilderBase(const StringBuilderBase<T>& other) = delete;
18
19 class iterator
20 {
21 public:
22 explicit iterator(const StringBuilderBase* builder) : mBuilder(builder), mIndex(0) {}
23 iterator(const StringBuilderBase* string, s32 index) : mBuilder(string), mIndex(index) {}
24 bool operator==(const iterator& rhs) const
25 {
26 return mBuilder == rhs.mBuilder && mIndex == rhs.mIndex;
27 }
28 bool operator!=(const iterator& rhs) const { return !(rhs == *this); }
29 iterator& operator++() { return mIndex++, *this; }
30 iterator& operator--() { return mIndex--, *this; }
31 const char& operator*() const { return mBuilder->at(mIndex); }
32
33 const StringBuilderBase* getBuilder() const { return mBuilder; }
34 s32 getIndex() const { return mIndex; }
35
36 protected:
37 const StringBuilderBase* mBuilder;
38 s32 mIndex;
39 };
40
41 iterator begin() const { return iterator(this, 0); }
42 iterator end() const { return iterator(this, mLength); }
43
44 // TODO: tokenBegin, tokenEnd
45
46 operator SafeStringBase<T>() const { return cstr(); }
47
48 s32 getLength() const { return mLength; }
49 s32 calcLength() const { return getLength(); }
50 s32 getBufferSize() const { return mBufferSize; }
51
52 const T* cstr() const;
53 const T& at(s32 idx) const;
54 const T& operator[](s32 idx) const { return at(idx); }
55
56 SafeStringBase<T> getPart(s32 at) const;
57 SafeStringBase<T> getPart(const iterator& it) const;
58
59 bool include(T c) const;
60 bool include(const T* str) const;
61
62 s32 findIndex(const T* str) const { return findIndex(str, 0); }
63 s32 findIndex(const T* str, s32 start_pos) const;
64 s32 rfindIndex(const T* str) const;
65
66 bool isEmpty() const { return mLength == 0; }
67 bool startsWith(const T* prefix) const;
68 bool endsWith(const T* suffix) const;
69 bool isEqual(const T* str) const;
70 s32 comparen(const T* str, s32 n) const;
71
72 void clear();
73
74 /// Copy up to copyLength characters to the beginning of the string, then writes NUL.
75 /// @param src Source string
76 /// @param copy_length Number of characters from src to copy (must not cause a buffer overflow)
77 s32 copy(const T* src, s32 copy_length = -1);
78 /// Copy up to copyLength characters to the specified position, then writes NUL if the copy
79 /// makes this string longer.
80 /// @param at Start position (-1 for end of string)
81 /// @param src Source string
82 /// @param copyLength Number of characters from src to copy (must not cause a buffer overflow)
83 s32 copyAt(s32 at, const T* src, s32 copy_length = -1);
84 /// Copy up to copyLength characters to the beginning of the string, then writes NUL.
85 /// Silently truncates the source string if the buffer is too small.
86 /// @param src Source string
87 /// @param copyLength Number of characters from src to copy
88 s32 cutOffCopy(const T* src, s32 copy_length = -1);
89 /// Copy up to copyLength characters to the specified position, then writes NUL if the copy
90 /// makes this string longer.
91 /// Silently truncates the source string if the buffer is too small.
92 /// @param at Start position (-1 for end of string)
93 /// @param src Source string
94 /// @param copyLength Number of characters from src to copy
95 s32 cutOffCopyAt(s32 at, const T* src, s32 copy_length = -1);
96 /// Copy up to copyLength characters to the specified position, then *always* writes NUL.
97 /// @param at Start position (-1 for end of string)
98 /// @param src Source string
99 /// @param copyLength Number of characters from src to copy (must not cause a buffer overflow)
100 s32 copyAtWithTerminate(s32 at, const T* src, s32 copy_length = -1);
101
102 s32 format(const T* format, ...);
103 s32 formatV(const T* format, std::va_list args)
104 {
105 mLength = formatImpl_(mBuffer, mBufferSize, format, args);
106 return mLength;
107 }
108 s32 appendWithFormat(const T* format, ...);
109 s32 appendWithFormatV(const T* format, std::va_list args)
110 {
111 const s32 ret = formatImpl_(mBuffer + mLength, mBufferSize - mLength, format, args);
112 mLength += ret;
113 return ret;
114 }
115
116 /// Append append_length characters from str.
117 s32 append(const T* str, s32 append_length);
118 /// Append a character.
119 s32 append(T c) { return append(c, 1); }
120 /// Append a character n times.
121 s32 append(T c, s32 n);
122
123 /// Remove num characters from the end of the string.
124 /// @return the number of characters that were removed
125 s32 chop(s32 chop_num);
126 /// Remove the last character if it is equal to c.
127 /// @return the number of characters that were removed
128 s32 chopMatchedChar(T c);
129 /// Remove the last character if it is equal to any of the specified characters.
130 /// @param characters List of characters to remove
131 /// @return the number of characters that were removed
132 s32 chopMatchedChar(const T* characters);
133 /// Remove the last character if it is unprintable.
134 /// @warning The behavior of this function is not standard: a character is considered
135 /// unprintable if it is <= 0x20 or == 0x7F. In particular, the space character is unprintable.
136 /// @return the number of characters that were removed
137 s32 chopUnprintableAsciiChar();
138
139 /// Remove trailing characters that are in the specified list.
140 /// @param characters List of characters to remove
141 /// @return the number of characters that were removed
142 s32 rstrip(const T* characters);
143 /// Remove trailing characters that are unprintable.
144 /// @warning The behavior of this function is not standard: a character is considered
145 /// unprintable if it is <= 0x20 or == 0x7F. In particular, the space character is unprintable.
146 /// @return the number of characters that were removed
147 s32 rstripUnprintableAsciiChars();
148
149 /// Trim a string to only keep trimLength characters.
150 /// @return the new length
151 s32 trim(s32 trim_length);
152 /// Trim a string to only keep trimLength characters.
153 /// @return the new length
154 s32 trimMatchedString(const T* str);
155
156 /// @return the number of characters that were replaced
157 s32 replaceChar(T old_char, T new_char);
158 /// @return the number of characters that were replaced
159 s32 replaceCharList(const SafeStringBase<T>& old_chars, const SafeStringBase<T>& new_chars);
160
161 s32 convertFromMultiByteString(const char* str, s32 str_length);
162 s32 convertFromWideCharString(const char16* str, s32 str_length);
163
164 s32 cutOffAppend(const T* str, s32 append_length);
165 s32 cutOffAppend(T c, s32 num);
166
167 s32 prepend(const T* str, s32 prepend_length);
168 s32 prepend(T c, s32 num);
169
170protected:
171 StringBuilderBase(T* buffer, s32 buffer_size)
172 : mBuffer(buffer), mLength(0), mBufferSize(buffer_size)
173 {
174 mBuffer[0] = SafeStringBase<T>::cNullChar;
175 }
176
177 static StringBuilderBase<T>* createImpl_(s32 buffer_size, Heap* heap, s32 alignment);
178 static s32 formatImpl_(T* dst, s32 dst_size, const T* format, std::va_list arg);
179
180 template <typename OtherType>
181 s32 convertFromOtherType_(const OtherType* src, s32 src_size);
182
183 T* getMutableStringTop_() const { return mBuffer; }
184
185 T* mBuffer;
186 s32 mLength;
187 s32 mBufferSize;
188};
189
190using StringBuilder = StringBuilderBase<char>;
191using WStringBuilder = StringBuilderBase<char16>;
192
193template <s32 N>
194class FixedStringBuilder : public StringBuilder
195{
196public:
197 FixedStringBuilder() : StringBuilder(mStorage, N) {}
198
199private:
200 char mStorage[N];
201};
202
203template <typename T>
204inline const T* StringBuilderBase<T>::cstr() const
205{
206 return mBuffer;
207}
208
209// UNCHECKED
210template <typename T>
211inline const T& StringBuilderBase<T>::at(s32 idx) const
212{
213 if (idx < 0 || idx > mLength)
214 {
215 SEAD_ASSERT_MSG(false, "index(%d) out of range[0, %d]", idx, mLength);
216 return SafeStringBase<T>::cNullChar;
217 }
218 return mBuffer[idx];
219}
220
221// UNCHECKED
222template <typename T>
223inline SafeStringBase<T> StringBuilderBase<T>::getPart(s32 at) const
224{
225 if (at < 0 || at > mLength)
226 {
227 SEAD_ASSERT_MSG(false, "index(%d) out of range[0, %d]", at, mLength);
228 return SafeStringBase<T>::cEmptyString;
229 }
230
231 return SafeStringBase<T>(mBuffer + at);
232}
233
234// UNCHECKED
235template <typename T>
236inline SafeStringBase<T> StringBuilderBase<T>::getPart(const StringBuilderBase::iterator& it) const
237{
238 return getPart(it.getIndex());
239}
240
241// UNCHECKED
242template <typename T>
243inline bool StringBuilderBase<T>::include(T c) const
244{
245 for (s32 i = 0; i < mLength; ++i)
246 {
247 if (mBuffer[i] == c)
248 return true;
249 }
250 return false;
251}
252
253// UNCHECKED
254template <typename T>
255inline bool StringBuilderBase<T>::include(const T* str) const
256{
257 return findIndex(str) != -1;
258}
259
260// UNCHECKED
261template <typename T>
262inline s32 StringBuilderBase<T>::findIndex(const T* str, s32 start_pos) const
263{
264 const s32 len = calcLength();
265
266 if (start_pos < 0 || start_pos > len)
267 {
268 SEAD_ASSERT_MSG(false, "start_pos(%d) out of range[0, %d]", start_pos, len);
269 return -1;
270 }
271
272 const s32 sub_str_len = calcStrLength_(str);
273
274 for (s32 i = start_pos; i <= len - sub_str_len; ++i)
275 {
276 if (SafeStringBase<T>(&mBuffer[i]).comparen(str, sub_str_len) == 0)
277 return i;
278 }
279 return -1;
280}
281
282// UNCHECKED
283template <typename T>
284inline s32 StringBuilderBase<T>::rfindIndex(const T* str) const
285{
286 const s32 len = calcLength();
287 const s32 sub_str_len = calcStrLength_(str);
288
289 for (s32 i = len - sub_str_len; i >= 0; --i)
290 {
291 if (SafeStringBase<T>(&mBuffer[i]).comparen(str, sub_str_len) == 0)
292 return i;
293 }
294 return -1;
295}
296
297// UNCHECKED
298template <typename T>
299inline bool StringBuilderBase<T>::startsWith(const T* prefix) const
300{
301 return findIndex(prefix) == 0;
302}
303
304// UNCHECKED
305template <typename T>
306inline bool StringBuilderBase<T>::isEqual(const T* str) const
307{
308 for (s32 i = 0; i < mLength; i++)
309 {
310 if (str[i] == SafeStringBase<T>::cNullChar)
311 return false;
312 if (mBuffer[i] != str[i])
313 return false;
314 }
315 return true;
316}
317
318// UNCHECKED
319template <typename T>
320inline s32 StringBuilderBase<T>::comparen(const T* str, s32 n) const
321{
322 if (n > mLength)
323 {
324 SEAD_ASSERT_MSG(false, "paramater(%d) out of bounds [0, %d]", n, mLength);
325 n = mLength;
326 }
327
328 for (s32 i = 0; i < n; ++i)
329 {
330 if (mBuffer[i] < str[i])
331 return -1;
332
333 if (mBuffer[i] > str[i])
334 return 1;
335
336 if (str[i] == SafeStringBase<T>::cNullChar)
337 return 1;
338 }
339
340 return 0;
341}
342
343// UNCHECKED
344template <typename T>
345inline void StringBuilderBase<T>::clear()
346{
347 mBuffer[0] = SafeStringBase<T>::cNullChar;
348 mLength = 0;
349}
350} // namespace sead
351