| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | //====================================================================================================================== | ||
| 2 | //! @file | ||
| 3 | /* | ||
| 4 | TTS - Tiny Test System | ||
| 5 | Copyright : TTS Contributors & Maintainers | ||
| 6 | SPDX-License-Identifier: BSL-1.0 | ||
| 7 | */ | ||
| 8 | //====================================================================================================================== | ||
| 9 | #pragma once | ||
| 10 | |||
| 11 | #include <tts/tools/concepts.hpp> | ||
| 12 | #include <tts/tools/preprocessor.hpp> | ||
| 13 | #include <cstdarg> | ||
| 14 | |||
| 15 | TTS_DISABLE_WARNING_PUSH | ||
| 16 | TTS_DISABLE_WARNING_CRT_SECURE | ||
| 17 | |||
| 18 | namespace tts | ||
| 19 | { | ||
| 20 | //==================================================================================================================== | ||
| 21 | /** | ||
| 22 | @ingroup tools-text | ||
| 23 | @brief Lightweight string-like object | ||
| 24 | |||
| 25 | `tts::text` is a simple string-like object that can be used to represent and manipulate text | ||
| 26 | data in TTS. It provides basic functionalities such as construction from formatted strings, | ||
| 27 | concatenation, and stream output support | ||
| 28 | |||
| 29 | @groupheader{Example} | ||
| 30 | @snippet doc/text.cpp snippet | ||
| 31 | |||
| 32 | @see tts::as_text | ||
| 33 | **/ | ||
| 34 | //==================================================================================================================== | ||
| 35 | struct text | ||
| 36 | { | ||
| 37 | /// Default constructor | ||
| 38 | 12002 | text() = default; | |
| 39 | |||
| 40 | /// Construct from C-style string | ||
| 41 | 4078 | explicit text(char const* ptr) | |
| 42 | 4078 | : text() | |
| 43 | { | ||
| 44 |
1/2✓ Branch 0 taken 4078 times.
✗ Branch 1 not taken.
|
4078 | if(ptr) |
| 45 | { | ||
| 46 | 4078 | std::size_t len = strlen(ptr); | |
| 47 |
2/2✓ Branch 0 taken 3948 times.
✓ Branch 1 taken 130 times.
|
4078 | if(len > 0) |
| 48 | { | ||
| 49 | 3948 | data_ = reinterpret_cast<char*>(malloc(len + 1)); // NOSONAR | |
| 50 |
1/2✓ Branch 0 taken 3948 times.
✗ Branch 1 not taken.
|
3948 | if(data_) |
| 51 | { | ||
| 52 | 3948 | size_ = len; | |
| 53 | 3948 | memcpy(data_, ptr, size_); | |
| 54 | 3948 | data_[ size_ ] = '\0'; | |
| 55 | } | ||
| 56 | } | ||
| 57 | } | ||
| 58 | 4078 | } | |
| 59 | |||
| 60 | /** | ||
| 61 | @brief Construct from formatted string | ||
| 62 | |||
| 63 | tts::text is constructible from a C-style format string and a variable number of arguments. | ||
| 64 | |||
| 65 | @param format C-style format string | ||
| 66 | @param args Arguments to format according to `format` | ||
| 67 | **/ | ||
| 68 | template<typename... Args> | ||
| 69 | 8792 | explicit text(char const* format, Args... args) | |
| 70 | 8792 | : text() | |
| 71 | { | ||
| 72 | 8792 | assign(format, args...); | |
| 73 | 8792 | } | |
| 74 | |||
| 75 | /// Copy constructor | ||
| 76 | 832 | text(text const& other) | |
| 77 | 832 | : text() | |
| 78 | { | ||
| 79 | // Strictly check for positive size and valid pointer to prevent allocation errors | ||
| 80 |
2/4✓ Branch 0 taken 832 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 832 times.
✗ Branch 3 not taken.
|
832 | if(other.size_ > 0 && other.data_) |
| 81 | { | ||
| 82 | 832 | data_ = reinterpret_cast<char*>(malloc(other.size_ + 1)); // NOSONAR | |
| 83 |
1/2✓ Branch 0 taken 832 times.
✗ Branch 1 not taken.
|
832 | if(data_) |
| 84 | { | ||
| 85 | 832 | size_ = other.size_; | |
| 86 | 832 | memcpy(data_, other.data_, size_); | |
| 87 | 832 | data_[ size_ ] = '\0'; | |
| 88 | } | ||
| 89 | } | ||
| 90 | 832 | } | |
| 91 | |||
| 92 | /// Move constructor | ||
| 93 | 896 | text(text&& other) noexcept | |
| 94 | 896 | : text() | |
| 95 | { | ||
| 96 | 896 | swap(other); | |
| 97 | 896 | } | |
| 98 | |||
| 99 | /// Copy assignment operator | ||
| 100 | 1 | text& operator=(text const& other) | |
| 101 | { | ||
| 102 | 1 | text local(other); | |
| 103 | 1 | swap(local); | |
| 104 | 2 | return *this; | |
| 105 | 1 | } | |
| 106 | |||
| 107 | /// Move assignment operator | ||
| 108 | 361 | text& operator=(text&& other) noexcept | |
| 109 | { | ||
| 110 | 361 | text local(std::move(other)); | |
| 111 | 361 | swap(local); | |
| 112 | 722 | return *this; | |
| 113 | 361 | } | |
| 114 | |||
| 115 | /// Destructor | ||
| 116 | 12786 | ~text() | |
| 117 | { | ||
| 118 |
2/2✓ Branch 0 taken 9176 times.
✓ Branch 1 taken 3610 times.
|
12786 | if(data_) free(data_); // NOSONAR |
| 119 | 12786 | } | |
| 120 | |||
| 121 | /// Swap contents with another text instance | ||
| 122 | 2815 | void swap(text& o) noexcept | |
| 123 | { | ||
| 124 | 2815 | std::swap(o.data_, data_); | |
| 125 | 2815 | std::swap(o.size_, size_); | |
| 126 | 2815 | } | |
| 127 | |||
| 128 | /// Concatenate another text instance | ||
| 129 | 933 | text& operator+=(text const& other) | |
| 130 | { | ||
| 131 | 933 | text local {"%.*s%.*s", size_, data(), other.size_, other.data()}; | |
| 132 | 933 | swap(local); | |
| 133 | 1866 | return *this; | |
| 134 | 933 | } | |
| 135 | |||
| 136 | /// Concatenate a C-style string | ||
| 137 | 624 | text& operator+=(char const* other) | |
| 138 | { | ||
| 139 |
1/2✓ Branch 0 taken 624 times.
✗ Branch 1 not taken.
|
624 | if(other) |
| 140 | { | ||
| 141 | 624 | text local {"%.*s%s", size_, data(), other}; | |
| 142 | 624 | swap(local); | |
| 143 | 624 | } | |
| 144 | 624 | return *this; | |
| 145 | } | ||
| 146 | |||
| 147 | /// Concatenate and return a new text instance | ||
| 148 | 8 | text operator+(text const& other) const | |
| 149 | { | ||
| 150 | 8 | text local(*this); | |
| 151 |
1/1✓ Branch 1 taken 8 times.
|
16 | return local += other; |
| 152 | 8 | } | |
| 153 | |||
| 154 | /// Stream output operator | ||
| 155 | template<_::stream OS> friend OS& operator<<(OS& os, text const& t) | ||
| 156 | { | ||
| 157 | if(t.data_) | ||
| 158 | { | ||
| 159 | for(size_t i = 0; i < t.size_; ++i) | ||
| 160 | os << t.data_[ i ]; | ||
| 161 | } | ||
| 162 | return os; | ||
| 163 | } | ||
| 164 | |||
| 165 | /// Check if the text is empty | ||
| 166 | 3682 | bool is_empty() const | |
| 167 | { | ||
| 168 | 3682 | return size_ == 0; | |
| 169 | } | ||
| 170 | |||
| 171 | /// Get the size of the text | ||
| 172 | size_t size() const | ||
| 173 | { | ||
| 174 | return size_; | ||
| 175 | } | ||
| 176 | |||
| 177 | /// Get a pointer to the constant underlying character data | ||
| 178 | /// Returns an empty string literal if data_ is null to prevent %s crashes | ||
| 179 | 7225 | [[nodiscard]] char const* data() const | |
| 180 | { | ||
| 181 |
2/2✓ Branch 0 taken 6986 times.
✓ Branch 1 taken 239 times.
|
7225 | return data_ ? data_ : ""; |
| 182 | } | ||
| 183 | |||
| 184 | /// Get a const iterator to the beginning of the text | ||
| 185 | 8 | decltype(auto) begin() const | |
| 186 | { | ||
| 187 | 8 | return data_; | |
| 188 | } | ||
| 189 | |||
| 190 | /// Get an iterator to the beginning of the text | ||
| 191 | decltype(auto) begin() | ||
| 192 | { | ||
| 193 | return data_; | ||
| 194 | } | ||
| 195 | |||
| 196 | /// Get a const iterator to the end of the text | ||
| 197 | 8 | decltype(auto) end() const | |
| 198 | { | ||
| 199 | 8 | return data_ + size_; | |
| 200 | } | ||
| 201 | |||
| 202 | /// Get an iterator to the end of the text | ||
| 203 | decltype(auto) end() | ||
| 204 | { | ||
| 205 | return data_ + size_; | ||
| 206 | } | ||
| 207 | |||
| 208 | /// Equality comparison operator | ||
| 209 | 31 | friend bool operator==(text const& a, text const& b) noexcept | |
| 210 | { | ||
| 211 |
2/2✓ Branch 0 taken 21 times.
✓ Branch 1 taken 10 times.
|
31 | if(a.size_ != b.size_) return false; |
| 212 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
|
10 | if(a.is_empty()) return true; |
| 213 | 10 | return strcmp(a.data_, b.data_) == 0; | |
| 214 | } | ||
| 215 | |||
| 216 | /// Equality comparison operator with C-style string | ||
| 217 | 1076 | template<std::size_t N> friend bool operator==(text const& a, char const (&b)[ N ]) noexcept | |
| 218 | { | ||
| 219 |
2/2✓ Branch 1 taken 490 times.
✓ Branch 2 taken 48 times.
|
1076 | if(a.is_empty()) return N == 1; |
| 220 | 96 | return strcmp(a.data_, &b[ 0 ]) == 0; | |
| 221 | } | ||
| 222 | |||
| 223 | /// Three-way comparison operator with other text instance | ||
| 224 | friend std::strong_ordering operator<=>(text const& a, text const& b) noexcept | ||
| 225 | { | ||
| 226 | if(a.is_empty() && b.is_empty()) return std::strong_ordering::equal; | ||
| 227 | if(a.is_empty()) return std::strong_ordering::less; | ||
| 228 | if(b.is_empty()) return std::strong_ordering::greater; | ||
| 229 | |||
| 230 | size_t const size = a.size_ < b.size_ ? a.size_ : b.size_; | ||
| 231 | int const cmp = strncmp(a.data_, b.data_, size); | ||
| 232 | |||
| 233 | if(cmp != 0) return cmp <=> 0; | ||
| 234 | return a.size_ <=> b.size_; | ||
| 235 | } | ||
| 236 | |||
| 237 | /// Three-way comparison operator with C-style string | ||
| 238 | template<std::size_t N> friend auto operator<=>(text const& a, char const (&b)[ N ]) noexcept | ||
| 239 | { | ||
| 240 | return a <=> text {b}; | ||
| 241 | } | ||
| 242 | |||
| 243 | private: | ||
| 244 | 4396 | void assign(char const* format, ...) // NOSONAR - one body for every argument list | |
| 245 | { | ||
| 246 | va_list args; | ||
| 247 | va_list probe; | ||
| 248 | 4396 | va_start(args, format); | |
| 249 | 4396 | va_copy(probe, args); | |
| 250 | |||
| 251 | // Store result in local int to check for errors/negatives | ||
| 252 | 4396 | int len = vsnprintf(nullptr, 0, format, probe); | |
| 253 | 4396 | va_end(probe); | |
| 254 |
1/2✓ Branch 0 taken 4396 times.
✗ Branch 1 not taken.
|
4396 | if(len > 0) |
| 255 | { | ||
| 256 | 4396 | auto sz = static_cast<std::size_t>(len); | |
| 257 | 4396 | data_ = reinterpret_cast<char*>(malloc(sz + 1)); // NOSONAR | |
| 258 |
1/2✓ Branch 0 taken 4396 times.
✗ Branch 1 not taken.
|
4396 | if(data_) |
| 259 | { | ||
| 260 | 4396 | size_ = sz; // Only assign size if alloc succeeds | |
| 261 | 4396 | vsnprintf(data_, size_ + 1, format, args); | |
| 262 | } | ||
| 263 | } | ||
| 264 | 4396 | va_end(args); | |
| 265 | 4396 | } | |
| 266 | |||
| 267 | char* data_ = nullptr; | ||
| 268 | size_t size_ = 0; | ||
| 269 | }; | ||
| 270 | |||
| 271 | /// Concatenate text with C-style string | ||
| 272 | 402 | inline text operator+(text const& lhs, char const* rhs) | |
| 273 | { | ||
| 274 | 402 | text that(lhs); | |
| 275 |
1/1✓ Branch 1 taken 402 times.
|
804 | return that += rhs; |
| 276 | 402 | } | |
| 277 | |||
| 278 | /// Concatenate text with C-style string | ||
| 279 | 10 | inline text operator+(char const* lhs, text const& rhs) | |
| 280 | { | ||
| 281 | 10 | text that(lhs); | |
| 282 |
1/1✓ Branch 1 taken 10 times.
|
20 | return that += rhs; |
| 283 | 10 | } | |
| 284 | |||
| 285 | inline namespace literals | ||
| 286 | { | ||
| 287 | 1 | inline auto operator""_txt(char const* ptr, std::size_t sz) | |
| 288 | { | ||
| 289 | 1 | return text("%.*s", sz, ptr); | |
| 290 | } | ||
| 291 | } | ||
| 292 | } | ||
| 293 | |||
| 294 | TTS_DISABLE_WARNING_POP | ||
| 295 |