#pragma once #include #include #include namespace sead { class Heap; template class BufferedSafeStringBase; template class SafeStringBase { public: /// Iterates over every character of a string. /// Note that this is extremely inefficient and leads to quadratic time complexity /// because of the redundant calls to calcLength() in operator*. class iterator { public: explicit iterator(const SafeStringBase* string) : mString(string), mIndex(0) {} iterator(const SafeStringBase* string, s32 index) : mString(string), mIndex(index) {} bool operator==(const iterator& rhs) const { return mString == rhs.mString && mIndex == rhs.mIndex; } bool operator!=(const iterator& rhs) const { return !(rhs == *this); } iterator& operator++() { return mIndex++, *this; } iterator& operator--() { return mIndex--, *this; } const char& operator*() const { return mString->at(mIndex); } const SafeStringBase* getString() const { return mString; } s32 getIndex() const { return mIndex; } protected: const SafeStringBase* mString; s32 mIndex; }; /// Iterates over a string as if it were split by one or several delimiter characters. class token_iterator : public iterator { public: token_iterator(const SafeStringBase* string, const SafeStringBase& delimiter) : iterator(string), mDelimiter(delimiter) { } token_iterator(const SafeStringBase* string, s32 index, const SafeStringBase& delimiter) : iterator(string, index), mDelimiter(delimiter) { } bool operator==(const token_iterator& rhs) const { return static_cast(*this) == static_cast(rhs); } bool operator!=(const token_iterator& rhs) const { return !(rhs == *this); } token_iterator& operator++(); token_iterator& operator--(); s32 get(BufferedSafeStringBase* out) const; inline s32 getAndForward(BufferedSafeStringBase* out); s32 cutOffGet(BufferedSafeStringBase* out) const; s32 cutOffGetAndForward(BufferedSafeStringBase* out); private: const SafeStringBase mDelimiter; }; SafeStringBase() : mStringTop(&cNullChar) {} SafeStringBase(const T* str) : mStringTop(str) { SEAD_ASSERT_MSG(str != nullptr, "str must not be nullptr."); } SafeStringBase(const SafeStringBase& other) = default; virtual ~SafeStringBase() = default; virtual SafeStringBase& operator=(const SafeStringBase& other); bool operator==(const SafeStringBase& rhs) const { return isEqual(rhs); } bool operator!=(const SafeStringBase& rhs) const { return !(*this == rhs); } iterator begin() const { return iterator(this, 0); } iterator end() const { return iterator(this, calcLength()); } token_iterator tokenBegin(const SafeStringBase& delimiter) const { return token_iterator(this, delimiter); } token_iterator tokenEnd(const SafeStringBase& delimiter) const { return token_iterator(this, calcLength() + 1, delimiter); } const T* cstr() const { assureTerminationImpl_(); return mStringTop; } inline const T& at(s32 idx) const; inline const T& operator[](s32 idx) const { return at(idx); } inline s32 calcLength() const; inline SafeStringBase getPart(s32 at) const; inline SafeStringBase getPart(const iterator& it) const; inline SafeStringBase getPart(const token_iterator& it) const; inline bool include(const T& c) const; inline bool include(const SafeStringBase& str) const; bool isEqual(const SafeStringBase& str) const; inline s32 compare(const SafeStringBase& str) const { return comparen(str, cMaximumLength); } inline s32 comparen(const SafeStringBase& str, s32 n) const; s32 findIndex(const SafeStringBase& str) const; s32 findIndex(const SafeStringBase& str, s32 start_pos) const; s32 rfindIndex(const SafeStringBase& str) const; iterator findIterator(const SafeStringBase& str) const { return {this, findIndex(str)}; } iterator rfindIterator(const SafeStringBase& str) const { return {this, rfindIndex(str)}; } bool isEmpty() const; bool startsWith(const SafeStringBase& prefix) const; bool endsWith(const SafeStringBase& suffix) const; static const T cNullChar; static const T cLineBreakChar; static const SafeStringBase cEmptyString; static const s32 cMaximumLength = 0x80000; protected: virtual void assureTerminationImpl_() const {} const T& unsafeAt_(s32 idx) const { return mStringTop[idx]; } const T* mStringTop; }; template <> const SafeStringBase SafeStringBase::cEmptyString; template s32 replaceStringImpl_(T* dst, s32* length, s32 dst_size, const T* src, s32 src_size, const SafeStringBase& old_str, const SafeStringBase& new_str, bool* is_buffer_overflow); template class BufferedSafeStringBase : public SafeStringBase { public: __attribute__((always_inline)) BufferedSafeStringBase(T* buffer, s32 size) : SafeStringBase(buffer) { mBufferSize = size; if (size <= 0) { SEAD_ASSERT_MSG(false, "Invalied buffer size(%d).\n", this->getBufferSize()); this->mStringTop = nullptr; this->mBufferSize = 0; } else { this->assureTerminationImpl_(); } } BufferedSafeStringBase(const BufferedSafeStringBase&) = default; ~BufferedSafeStringBase() override = default; BufferedSafeStringBase& operator=(const SafeStringBase& other) override; const T& operator[](s32 idx) const; T* getBuffer() { assureTerminationImpl_(); return getMutableStringTop_(); } s32 getBufferSize() const { return mBufferSize; } /// Copy up to copyLength characters to the beginning of the string, then writes NUL. /// @param src Source string /// @param copyLength Number of characters from src to copy (must not cause a buffer overflow) inline s32 copy(const SafeStringBase& src, s32 copyLength = -1); /// Copy up to copyLength characters to the specified position, then writes NUL if the copy /// makes this string longer. /// @param at Start position (-1 for end of string) /// @param src Source string /// @param copyLength Number of characters from src to copy (must not cause a buffer overflow) inline s32 copyAt(s32 at, const SafeStringBase& src, s32 copyLength = -1); /// Copy up to copyLength characters to the beginning of the string, then writes NUL. /// Silently truncates the source string if the buffer is too small. /// @param src Source string /// @param copyLength Number of characters from src to copy inline s32 cutOffCopy(const SafeStringBase& src, s32 copyLength = -1); /// Copy up to copyLength characters to the specified position, then writes NUL if the copy /// makes this string longer. /// Silently truncates the source string if the buffer is too small. /// @param at Start position (-1 for end of string) /// @param src Source string /// @param copyLength Number of characters from src to copy inline s32 cutOffCopyAt(s32 at, const SafeStringBase& src, s32 copyLength = -1); /// Copy up to copyLength characters to the specified position, then *always* writes NUL. /// @param at Start position (-1 for end of string) /// @param src Source string /// @param copyLength Number of characters from src to copy (must not cause a buffer overflow) inline s32 copyAtWithTerminate(s32 at, const SafeStringBase& src, s32 copyLength = -1); s32 format(const T* format, ...); s32 formatV(const T* format, std::va_list args); s32 appendWithFormat(const T* formatStr, ...); s32 appendWithFormatV(const T* formatStr, std::va_list args); /// Append append_length characters from str. s32 append(const SafeStringBase& str, s32 append_length = -1); /// Append a character. s32 append(T c) { return append(c, 1); } /// Append a character n times. s32 append(T c, s32 n); // Implementation note: These member functions appear to be inlined in most titles. // However, StringBuilderBase conveniently duplicates the APIs and implementations of // SafeStringBase and BufferedSafeString: some assertion messages are even identical, // and the good news is that most StringBuilderBase functions are not inlined! /// Append prepend_length characters from str. /// @return the new length s32 prepend(const SafeStringBase& str, s32 prepend_length = -1); /// Remove num characters from the end of the string. /// @return the number of characters that were removed s32 chop(s32 num); /// Remove the last character if it is equal to c. /// @return the number of characters that were removed s32 chopMatchedChar(T c); /// Remove the last character if it is equal to any of the specified characters. /// @param characters List of characters to remove /// @return the number of characters that were removed s32 chopMatchedChar(const T* characters); /// Remove the last character if it is unprintable. /// @warning The behavior of this function is not standard: a character is considered /// unprintable if it is <= 0x20 or == 0x7F. In particular, the space character is unprintable. /// @return the number of characters that were removed s32 chopUnprintableAsciiChar(); /// Remove trailing characters that are in the specified list. /// @param characters List of characters to remove /// @return the number of characters that were removed s32 rstrip(const T* characters); /// Remove trailing characters that are unprintable. /// @warning The behavior of this function is not standard: a character is considered /// unprintable if it is <= 0x20 or == 0x7F. In particular, the space character is unprintable. /// @return the number of characters that were removed s32 rstripUnprintableAsciiChars(); /// Trim a string to only keep trimLength characters. /// @return the new length inline s32 trim(s32 trim_length); /// Remove the specified suffix from the string if it ends with the suffix. /// @return the new length inline s32 trimMatchedString(const SafeStringBase& suffix); /// Remove the specified suffix from the string if it ends with the suffix. /// Alias of trimMatchedString. /// @return the new length inline s32 removeSuffix(const SafeStringBase& suffix) { return trimMatchedString(suffix); } /// @return the number of characters that were replaced inline s32 replaceChar(T old_char, T new_char); /// @return the number of characters that were replaced inline s32 replaceCharList(const SafeStringBase& old_chars, const SafeStringBase& new_chars); /// Set the contents of this string to target_str, after replacing occurrences of old_str in /// target_str with new_str. /// @return the number of replaced occurrences inline s32 setReplaceString(const SafeStringBase& target_str, const SafeStringBase& old_str, const SafeStringBase& new_str); /// Replace occurrences of old_str in this string with new_str. /// @return the number of replaced occurrences inline s32 replaceString(const SafeStringBase& old_str, const SafeStringBase& new_str); s32 convertFromMultiByteString(const SafeStringBase& str, s32 str_length); s32 convertFromWideCharString(const SafeStringBase& str, s32 str_length); inline void clear() { getMutableStringTop_()[0] = this->cNullChar; } protected: void assureTerminationImpl_() const override; T* getMutableStringTop_() { return const_cast(this->mStringTop); } static s32 formatImpl_(T* dst, s32 dst_size, const T* format, std::va_list arg); template s32 convertFromOtherType_(const SafeStringBase& src, s32 src_size); s32 mBufferSize; }; template class FixedSafeStringBase : public BufferedSafeStringBase { public: FixedSafeStringBase() : BufferedSafeStringBase(mBuffer, L) { this->clear(); } FixedSafeStringBase(const SafeStringBase& str) : BufferedSafeStringBase(mBuffer, L) { this->copy(str); } FixedSafeStringBase(const FixedSafeStringBase& str) : BufferedSafeStringBase(mBuffer, L) { this->copy(str); } ~FixedSafeStringBase() override = default; FixedSafeStringBase& operator=(const FixedSafeStringBase& other) { this->copy(other); return *this; } FixedSafeStringBase& operator=(const SafeStringBase& other) override { this->copy(other); return *this; } T mBuffer[L]; }; typedef SafeStringBase SafeString; typedef SafeStringBase WSafeString; typedef BufferedSafeStringBase BufferedSafeString; typedef BufferedSafeStringBase WBufferedSafeString; template <> s32 BufferedSafeStringBase::format(const char* formatStr, ...); template <> s32 BufferedSafeStringBase::format(const char16* formatStr, ...); template <> s32 BufferedSafeStringBase::formatV(const char* formatStr, va_list args); template <> s32 BufferedSafeStringBase::formatV(const char16* formatStr, va_list args); template <> s32 BufferedSafeStringBase::appendWithFormat(const char* formatStr, ...); template <> s32 BufferedSafeStringBase::appendWithFormat(const char16* formatStr, ...); template <> s32 BufferedSafeStringBase::appendWithFormatV(const char* formatStr, va_list args); template <> s32 BufferedSafeStringBase::appendWithFormatV(const char16* formatStr, va_list args); template class FixedSafeString : public FixedSafeStringBase { public: FixedSafeString() : FixedSafeStringBase() {} FixedSafeString(const SafeString& str) : FixedSafeStringBase(str) {} FixedSafeString(const FixedSafeString& other) : FixedSafeString(static_cast(other)) { } FixedSafeString& operator=(const FixedSafeString& other) { this->copy(other); return *this; } FixedSafeString& operator=(const SafeStringBase& other) override { this->copy(other); return *this; } }; template class WFixedSafeString : public FixedSafeStringBase { public: WFixedSafeString() : FixedSafeStringBase() {} WFixedSafeString(const WSafeString& str) : FixedSafeStringBase(str) {} }; template class FormatFixedSafeString : public FixedSafeString { public: FormatFixedSafeString() : FormatFixedSafeString("") {} #ifdef __GNUC__ [[gnu::format(printf, 2, 3)]] #endif explicit FormatFixedSafeString(const char* format, ...) : FixedSafeString() { std::va_list args; va_start(args, format); this->formatV(format, args); va_end(args); } ~FormatFixedSafeString() override = default; }; template class WFormatFixedSafeString : public WFixedSafeString { public: explicit WFormatFixedSafeString(const char16* format, ...) { std::va_list args; va_start(args, format); this->formatV(format, args); va_end(args); } ~WFormatFixedSafeString() override = default; }; template class HeapSafeStringBase : public BufferedSafeStringBase { public: HeapSafeStringBase(Heap* heap, const SafeStringBase& string, s32 alignment = sizeof(void*)) : BufferedSafeStringBase(new (heap, alignment) T[string.calcLength() + 1](), string.calcLength() + 1) { this->copy(string); } HeapSafeStringBase(const HeapSafeStringBase&) = delete; HeapSafeStringBase& operator=(const HeapSafeStringBase&) = delete; HeapSafeStringBase(HeapSafeStringBase&& other) noexcept { this->mStringTop = other.mStringTop; other.mStringTop = nullptr; } HeapSafeStringBase& operator=(HeapSafeStringBase&& other) noexcept { this->mStringTop = other.mStringTop; other.mStringTop = nullptr; return *this; } ~HeapSafeStringBase() override { if (this->mStringTop) delete[] this->mStringTop; } HeapSafeStringBase& operator=(const SafeStringBase& other) override; }; using HeapSafeString = HeapSafeStringBase; using WHeapSafeString = HeapSafeStringBase; inline bool operator<(const SafeString& lhs, const SafeString& rhs) { return lhs.compare(rhs) < 0; } inline bool operator>(const SafeString& lhs, const SafeString& rhs) { return lhs.compare(rhs) > 0; } inline bool operator<=(const SafeString& lhs, const SafeString& rhs) { return lhs.compare(rhs) <= 0; } inline bool operator>=(const SafeString& lhs, const SafeString& rhs) { return lhs.compare(rhs) >= 0; } } // namespace sead #define SEAD_PRIM_SAFE_STRING_H_ #include #undef SEAD_PRIM_SAFE_STRING_H_