├── .gitignore ├── LICENSE ├── README.md ├── include └── sso_string.h ├── meson.build ├── meson_options.txt ├── src └── sso_string.c └── tests ├── meson.build └── tests.c /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .clang_complete 3 | .vscode 4 | tests/main.c -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Precisamento LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sso_string 2 | 3 | sso_string is a mutable string library in C that uses the short string optimization to avoid unnecessary allocations of small strings at the cost of a small amount of speed. It was primarily modelled after the libc++ std::string class, and its api mostly mirrors the C++ std::string api. It has been tested using MSVC and gcc (via WSL). 4 | 5 | sso_string aims to provide a memory efficient string representation for low-level/embedded applications, while also providing high-level string manipulation functions. As such it includes functionality for building strings, handling UTF-8 characters, splitting strings, etc. 6 | 7 | 8 | ## Example 9 | 10 | ``` c 11 | #include 12 | #include 13 | 14 | int main(void) { 15 | // Create a string value on the stack and print it. 16 | String str = string_create("Hello, world!"); 17 | puts(string_data(&str)); 18 | 19 | // Still needs to free its inner resources. 20 | string_free_resources(&str); 21 | 22 | // Create a string on the heap. 23 | String* ref = string_create_ref("I rate this library "); 24 | 25 | // Append some data using printf format specifiers. 26 | string_format_cstr(ref, "%d/%d stars!", 5, 5); 27 | 28 | // Print it. 29 | puts(string_data(ref)); 30 | 31 | // Free the string and its resources. 32 | string_free(ref); 33 | } 34 | ``` 35 | 36 | ### Strings and C-Strings 37 | 38 | There are many apis built around c-strings, and a string library wouldn't be worth using in most cases if it can't interface with them. sso_string provides alternative functions that accept c-strings for any function where it makes sense. It can also grab the internal c-string representation using `string_data` (`const char*`) or `string_cstr` (`char*`). These are `NULL` terminated and can be used just like normal c-strings, as long as the caller doesn't try and resize them. 39 | 40 | ## Including 41 | 42 | sso_string consists of a single header/source file pair, `include/sso_string.h` and `src/sso_string.c` which you can easily copy into your project (or add this repo as a submodule). It can also be added as a subproject when building with Meson. 43 | 44 | ### Meson 45 | 46 | This is a sample Meson wrap file that can be copied to add this library as a subproject. 47 | 48 | ``` 49 | [wrap-git] 50 | 51 | directory = sso_string 52 | url = https://github.com/mystborn/sso_string.git 53 | revision = master 54 | clone-recursive = true 55 | ``` 56 | 57 | Then you can easily grab the dependency from the meson.build file: 58 | 59 | ``` meson 60 | sso_string_proj = subproject('sso_string') 61 | sso_string = sso_string_proj.get_variable('sso_string_dep') 62 | ``` 63 | 64 | ## Documentation 65 | 66 | Currently there is no official documentation, but the header file has thorough documentation of each function in the style of doxygen comments. For specific function usage, if it's not clear from the documentation, there is probably a unit test for it that can be used as a template. 67 | 68 | ## Building 69 | 70 | The project uses the meson build system by default, but it shouldn't be hard to switch to cmake or another build tool if desired. It also uses check to perform the unit tests. On windows, you'll most likely need to specify `check`s location (usually "C:\Program Files (86)\check"). On \*nix OS', you'll either need to have check installed (most likely) or you can specify its location, provided it has a directory structure like the following: 71 | 72 | ``` 73 | check/ 74 | include/ 75 | check.h 76 | lib/ 77 | libcheck.lib 78 | libcompat.lib 79 | ``` 80 | 81 | This is an example of how to build the project using meson on windows. 82 | 83 | ```sh 84 | C:\\sso> mkdir build 85 | C:\\sso> cd build 86 | C:\\sso> meson .. --buildtype=release 87 | C:\\sso\\build> meson configure "-Dcheck_location=C:\\Program Files (86)\\check" 88 | C:\\sso\\build> ninja test 89 | ``` 90 | 91 | ## Todo 92 | 93 | * API Additions 94 | * string_trim (High) 95 | * string_pad (High) 96 | * Custom allocation functions (Medium) 97 | * `sso_string_malloc`, `sso_string_calloc`, `sso_string_realloc`, `sso_string_free` 98 | macros that can be defined by the user, but that use the std library versions by default). 99 | * Grapheme Clusters (Low) 100 | * These are characters that are represented using multiple codepoints. 101 | * Should include functions for iterating, adding, removing, etc, grapheme clusters in a string. 102 | * They will start with `string_gc_*` just like the `string_u8_*` functions. 103 | * Tests 104 | * There are not enough tests that check failure conditions. So as long as the input is correct, the functions should work fine, but their might be issues when the input is invalid. 105 | * CMake Integration 106 | * Documentation 107 | * More Comments 108 | * Just need more comments throughout to explain some of the more esoteric implementations/design decisions. 109 | * Bug Fixes 110 | * I am not aware of any major bugs at the moment, but there are likely some. These need to be identified and fixed. 111 | * Improve Speed 112 | * The current version of the library is more focused on usability and correctness. Future versions will start putting more 113 | emphasis towards execution speed. 114 | * Considering the main purpose of the short string optimization is memory performance, continuing to improve on this should be a major priority. -------------------------------------------------------------------------------- /include/sso_string.h: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2019 Precisamento LLC 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | 26 | 27 | 28 | 29 | /* 30 | This file contains definitions and functions for a String data type, 31 | built to allocate on the heap as little as possible, without sacrificing 32 | any extra memory, at the cost of a little speed. It's implementation 33 | closely mirrors parts of libc++'s basic_string type. 34 | 35 | The type is small enough to pass by value, but you should not do that 36 | at all if you plan to modify the string in any way. 37 | 38 | Essentially all bounds checking is done using assert, so if that is something desired 39 | in a production environment, it will have to be implemented on the callers side. 40 | */ 41 | 42 | 43 | #ifndef SSO_STRING_SSO_STRING_H 44 | #define SSO_STRING_SSO_STRING_H 45 | 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | 55 | #include 56 | 57 | #ifdef SSO_STRING_BUILD 58 | #if defined(_WIN32) 59 | #define SSO_STRING_EXPORT __declspec(dllexport) 60 | #elif defined(__ELF__) 61 | #define SSO_STRING_EXPORT __attribute__((visibility ("default"))) 62 | #else 63 | #define SSO_STRING_EXPORT 64 | #endif 65 | #else 66 | #if defined(_WIN32) 67 | #define SSO_STRING_EXPORT __declspec(dllimport) 68 | #else 69 | #define SSO_STRING_EXPORT 70 | #endif 71 | #endif 72 | 73 | #if (defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN) || \ 74 | defined(__BIG_ENDIAN__) || \ 75 | defined(__ARMEB__) || \ 76 | defined(__THUMBEB__) || \ 77 | defined(__AARCH64EB__) || \ 78 | defined(_MIBSEB) || defined(__MIBSEB) || defined(__MIBSEB__) || \ 79 | defined(_M_PPC) || \ 80 | defined(STRING_BIG_ENDIAN) 81 | 82 | #define SSO_STRING_BIG_ENDIAN 83 | 84 | #elif (defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN) || \ 85 | defined(__LITTLE_ENDIAN__) || \ 86 | defined(__ARMEL__) || \ 87 | defined(__THUMBEL__) || \ 88 | defined(__AARCH64EL__) || \ 89 | defined(_MIPSEL) || defined(__MIPSEL) || defined(__MIPSEL__) || \ 90 | defined(_WIN32) || \ 91 | defined(STRING_LITTLE_ENDIAN) 92 | 93 | #define SSO_STRING_LITTLE_ENDIAN 94 | 95 | #else 96 | #error "I don't know what architecture this is!" 97 | #endif 98 | 99 | // This should never be defined unless the compiler defines SIZE_MAX 100 | // to be different from max(uint32_t) or max(uint64_t), in which case it 101 | // should be the number of bits in size_t - 8. 102 | #ifndef SSO_STRING_SHIFT 103 | 104 | // SIZE_MAX == max(uint32_t) 105 | #if SIZE_MAX == 4294967295 106 | 107 | #define SSO_STRING_SHIFT 24 108 | 109 | // SIZE_MAX == max(uint64_t) 110 | #elif SIZE_MAX == 18446744073709551615 111 | 112 | #define SSO_STRING_SHIFT 56 113 | 114 | #else 115 | 116 | #error "Unable to determine the number of bits in size_t, please define SSO_STRING_SHIFT before including this file as the number of bits in size_t - 8" 117 | 118 | #endif 119 | #endif 120 | 121 | #ifndef SSO_STRING_ASSERT_ARG 122 | #define SSO_STRING_ASSERT_ARG assert 123 | #endif 124 | 125 | #ifndef SSO_STRING_ASSERT_BOUNDS 126 | #define SSO_STRING_ASSERT_BOUNDS assert 127 | #endif 128 | 129 | struct sso_string_long { 130 | size_t cap; 131 | size_t size; 132 | char* data; 133 | }; 134 | 135 | #ifdef SSO_STRING_LITTLE_ENDIAN 136 | 137 | enum { SSO_STRING_LONG_FLAG = 0x01 }; 138 | 139 | #else 140 | 141 | enum { SSO_STRING_LONG_FLAG = 0xF0 }; 142 | 143 | #endif 144 | 145 | enum { 146 | SSO_STRING_MIN_CAP = ((sizeof(struct sso_string_long) - 1)/sizeof(char) > 2 ? 147 | (sizeof(struct sso_string_long) - 1)/sizeof(char) : 2) - 1 148 | }; 149 | 150 | #define STRING_MAX (SIZE_MAX >> 1) 151 | 152 | struct sso_string_short { 153 | union { 154 | unsigned char size; 155 | char lx; 156 | }; 157 | char data[SSO_STRING_MIN_CAP + 1]; 158 | }; 159 | 160 | typedef union String { 161 | struct sso_string_long l; 162 | struct sso_string_short s; 163 | } String; 164 | 165 | /** 166 | A numeric representation of a unicode character/codepoint. 167 | */ 168 | typedef uint32_t Char32; 169 | 170 | /** 171 | Initializes a string from a c-string. 172 | 173 | @param str A pointer to the string to initialize. 174 | @param cstr The contents to initialize the string with. 175 | 176 | @return true on success, false on allocation failure. 177 | */ 178 | SSO_STRING_EXPORT bool string_init(String* str, const char* cstr); 179 | 180 | /** 181 | Initializes a string from a subsection of a c-string. 182 | 183 | @param str A pointer to the string to initialize. 184 | @param cstr The contents to initialize the string with. 185 | @param len The number of characters to copy into str. 186 | 187 | @return true on success, false on allocation failure. 188 | */ 189 | SSO_STRING_EXPORT bool string_init_size(String* str, const char* cstr, size_t length); 190 | 191 | /** 192 | Creates and initializes a new string value. 193 | 194 | @param cstr The contents to initialize the string with. 195 | 196 | @return The initialized String value. 197 | 198 | @remarks There is no way to check for allocation failure. 199 | */ 200 | static inline String string_create(const char* cstr); 201 | 202 | /** 203 | Allocates and initializes a new string. 204 | 205 | @param cstr The contents to initialize the string with. 206 | 207 | @return The initialized String reference. Must be manually freed. 208 | NULL on allocation failure. 209 | */ 210 | static inline String* string_create_ref(const char* cstr); 211 | 212 | /** 213 | Frees any resources used by a string, but does not free 214 | the string itself. 215 | 216 | @param str The string to clean up. 217 | */ 218 | static inline void string_free_resources(String* str); 219 | 220 | /** 221 | Frees any resources used by a string, then frees the string itself. 222 | 223 | @param str The string to clean up. 224 | */ 225 | static inline void string_free(String* str); 226 | 227 | /** 228 | Gets the character data held by a string. This data cannot be altered. 229 | 230 | @param str The string to get the internal representation of. 231 | 232 | @return The internal representation of the string as a c-string. 233 | */ 234 | static inline const char* string_data(const String* str); 235 | 236 | /** 237 | Gets the character data held by a string. This data should be altered carefully. 238 | 239 | @param str The string to get the internal representation of. 240 | 241 | @return The internal representation of the string as a c-string. 242 | */ 243 | static inline char* string_cstr(String* str); 244 | 245 | /** 246 | Gets the number of bytes in a string, ignoring any terminating characters. 247 | 248 | @param str The string to get the size of. 249 | 250 | @return The number of bytes on the string. 251 | */ 252 | static inline size_t string_size(const String* str); 253 | 254 | /** 255 | Gets the number of codepoints in a string. 256 | 257 | @param str The string to get the codepoint count of. 258 | 259 | @return The number of codepoints in the string. 260 | */ 261 | SSO_STRING_EXPORT size_t string_u8_codepoints(const String* str); 262 | 263 | /** 264 | Gets the number of characters a string can potential hold without resizing. 265 | This does NOT include the NULL terminating character. 266 | 267 | @param str The string to get the capacity of. 268 | 269 | @return The internal capacity of the string. 270 | */ 271 | static inline size_t string_capacity(const String* str); 272 | 273 | /** 274 | Gets the character at the specified index in a string. 275 | 276 | @param str The string to get the character from. 277 | @param index The index of the character to get. 278 | 279 | @return The character at the specified index. 280 | */ 281 | static inline char string_get(const String* str, size_t index); 282 | 283 | /** 284 | Gets the unicode character at the specified byte index. 285 | 286 | @param str The string to get the unicode character from. 287 | @param index The starting byte index of the unicode character. 288 | 289 | @return Thee unicode character starting at the specified byte index. 290 | */ 291 | SSO_STRING_EXPORT Char32 string_u8_get(const String* str, size_t index); 292 | 293 | /** 294 | Gets the unicode character at the specified byte index, 295 | optionally getting the number of bytes that the character 296 | takes in the string. 297 | 298 | @param str The string to get the unicode character from. 299 | @param index The starting byte index of the unicode character. 300 | @param out_size If not NULL, contains the number of bytes used to represent unicode character. 301 | 302 | @return Thee unicode character starting at the specified byte index. 303 | 304 | @remark This function can be used to easily iterate over a UTF-8 string. 305 | */ 306 | SSO_STRING_EXPORT Char32 string_u8_get_with_size(const String* str, size_t index, int* out_size); 307 | 308 | /** 309 | Gets the unicode character size at the specified byte index in bytes. 310 | 311 | @param str The string to gert the unicode character size from. 312 | @param index The starting byte index of the unicode character. 313 | 314 | @return The number of bytes used to represent the unicode character. 315 | */ 316 | SSO_STRING_EXPORT int string_u8_codepoint_size(const String* str, size_t index); 317 | 318 | /** 319 | Sets the character at the specified index in a string. 320 | 321 | @param str The string to modify. 322 | @param index The index of the character to replace. 323 | @param value The new character to replace the existing character with. 324 | */ 325 | static inline void string_set(String* str, size_t index, char value); 326 | 327 | /** 328 | Replaces the unicode character at the specified index in a string. 329 | 330 | @param str The string to modify. 331 | @param index The index of the character to replace. 332 | @param value The new character to replace the existing character with. 333 | 334 | @return true on success, false on allocation failure. 335 | */ 336 | SSO_STRING_EXPORT bool string_u8_set(String* str, size_t index, Char32 value); 337 | 338 | /** 339 | Determines if a string has no characters. 340 | 341 | @return true if the string is empty; false otherwise. 342 | */ 343 | static inline bool string_empty(const String* str); 344 | 345 | /** 346 | Determines if the string is NULL or has no characters. 347 | 348 | @return true if the string is NULL or empty; false otherwise. 349 | */ 350 | static inline bool string_is_null_or_empty(const String* str); 351 | 352 | /** 353 | Working with ASCII or UTF-8 strings, determines if the string is NULL, 354 | empty, or comprised of only whitespace characters. 355 | 356 | @return true if the string is NULL, empty, or comprised only of whitespace characters; false otherwise. 357 | */ 358 | SSO_STRING_EXPORT bool string_u8_is_null_or_whitespace(const String* str); 359 | 360 | /** 361 | Ensures that a string has a capacity large enough to hold a specified number of characters. 362 | Does not include any terminating characters. 363 | 364 | @param str - The string to potentially enlarge. 365 | @param reserve - The desired minimum capacity, not including the NULL terminating character. 366 | 367 | @return true on success, false on allocation failure. 368 | */ 369 | static inline bool string_reserve(String* str, size_t reserve); 370 | 371 | /** 372 | Removes any excess memory not being used by a string. 373 | 374 | @param str The string to shrink. 375 | */ 376 | SSO_STRING_EXPORT void string_shrink_to_fit(String* str); 377 | 378 | /** 379 | Clears the contents of a string, but does not free any allocated memory. 380 | 381 | @param str The string to clear. 382 | 383 | @see string_shrink_to_fit 384 | */ 385 | static inline void string_clear(String* str); 386 | 387 | /** 388 | Inserts a c-string into a string at the specified index. 389 | 390 | @param str The string to insert into. 391 | @param value The c-string to insert. 392 | @param index The index of the string to insert the value into. 393 | 394 | @return true on success, false on allocation failure. 395 | */ 396 | static inline bool string_insert_cstr(String* str, const char* value, size_t index); 397 | 398 | /** 399 | Inserts a string into another string at the specified index. 400 | 401 | @param str The string to insert into. 402 | @param value The string to insert. 403 | @param index The index of the string to insert the value into. 404 | 405 | @return true on success, false on allocation failure. 406 | */ 407 | static inline bool string_insert_string(String* str, const String* value, size_t index); 408 | 409 | /** 410 | Inserts a section of a c-string into a string at the specified index. 411 | 412 | @param str The string to insert into. 413 | @param value The c-string to insert. 414 | @param index The index of the string to insert the value into. 415 | @param start The starting index of the value to insert. 416 | @param count The number of characters following start to insert. 417 | 418 | @return true on success, false on allocation failure. 419 | */ 420 | static inline bool string_insert_cstr_part(String* str, const char* value, size_t index, size_t start, size_t count); 421 | 422 | /** 423 | Inserts a section of a string into another string at the specified index. 424 | 425 | @param str The string to insert into. 426 | @param value The string to insert. 427 | @param index The index of the string to insert the value into. 428 | @param start The starting index of the value to insert. 429 | @param count The number of characters following start to insert. 430 | 431 | @return true on success, false on allocation failure. 432 | */ 433 | static inline bool string_insert_string_part(String* str, const String* value, size_t index, size_t start, size_t count); 434 | 435 | /** 436 | Removes a section from a string. 437 | 438 | @param str The string to modify. 439 | @param index The starting index of the characters to erase. 440 | @param count The number of characters following index to erase. 441 | */ 442 | SSO_STRING_EXPORT void string_erase(String* str, size_t index, size_t count); 443 | 444 | /** 445 | Appends a character to the end of a string. 446 | 447 | @param str The string to append to. 448 | @param value The character to append. 449 | 450 | @return true on success, false on allocation failure. 451 | 452 | */ 453 | SSO_STRING_EXPORT bool string_push_back(String* str, char value); 454 | 455 | /** 456 | Appends a unicode character to the end of a string. 457 | 458 | @param str The string to append to. 459 | @param value The unicode character to append. 460 | 461 | @return true on success, false on allocation failure. 462 | */ 463 | SSO_STRING_EXPORT bool string_u8_push_back(String* str, Char32 value); 464 | 465 | /** 466 | Removes a character from the end of a string and returns 467 | the characters value. 468 | 469 | @param str The string to get the last character of. 470 | 471 | @return The last character of the string if any, '\\0' otherwise. 472 | */ 473 | SSO_STRING_EXPORT char string_pop_back(String* str); 474 | 475 | /** 476 | Removes a unicode character from the end of a string and 477 | returns the characters value. 478 | 479 | @param str The string to get the last unicode character of. 480 | 481 | @return The last unicode character of the string if any, '\\0' otherwise. 482 | */ 483 | SSO_STRING_EXPORT Char32 string_u8_pop_back(String* str); 484 | 485 | /** 486 | Appends a c-string to the end of a string. 487 | 488 | @param str The string to append to. 489 | @param value The c-string to append. 490 | 491 | @return true on success, false on allocation failure. 492 | */ 493 | static inline bool string_append_cstr(String* str, const char* value); 494 | 495 | /** 496 | Appends a string to the end of another string. 497 | 498 | @param str The string to append to. 499 | @param value The string to append. 500 | 501 | @return true on success, false on allocation failure. 502 | */ 503 | static inline bool string_append_string(String* str, const String* value); 504 | 505 | /** 506 | Appends a section of a c-string to the end of a string. 507 | 508 | @param str The string to append to. 509 | @param value The c-string to append. 510 | @param start The starting index of the section of the value to append. 511 | @param count The number of characters following start to append. 512 | 513 | @return true on success, false on allocation failure. 514 | */ 515 | static inline bool string_append_cstr_part( 516 | String* str, 517 | const char* value, 518 | size_t start, 519 | size_t count); 520 | 521 | /** 522 | Appends a section of a string to the end of another string. 523 | 524 | @param str The string to append to. 525 | @param value The string to append. 526 | @param start The starting index of the section of the value to append. 527 | @param count The number of characters following start to append. 528 | 529 | @return true on success, false on allocation failure. 530 | */ 531 | static inline bool string_append_string_part( 532 | String* str, 533 | const String* value, 534 | size_t start, 535 | size_t count); 536 | 537 | /** 538 | Compares a string and a c-string in the same fashion as strcmp. 539 | 540 | @param str The string on the left side of the operation. 541 | @param value The c-string on the right side of the operation. 542 | 543 | @return A negative value if str < value, zero if str == value, a positive value if str > value. 544 | */ 545 | static inline int string_compare_cstr(const String* str, const char* value); 546 | 547 | /** 548 | Compares two strings in the same fashion as strcmp. 549 | 550 | @param str The string on the left side of the operation. 551 | @param value The string on the right side of the operation. 552 | 553 | @return A negative value if str < value, zero if str == value, a positive value if str > value. 554 | */ 555 | static inline int string_compare_string(const String* str, const String* value); 556 | 557 | /** 558 | Determines if the contents of a String is equivalent to a c-string. 559 | 560 | @param str The string on the left side of the operation. 561 | @param value The c-string on the right side of the operation. 562 | 563 | @return true if the values are equivalent; false otherwise. 564 | */ 565 | static inline bool string_equals_cstr(const String* str, const char* value); 566 | 567 | /** 568 | Determines if the contents of two strings are equivalent. 569 | 570 | @param str The string on the left side of the operation. 571 | @param value The string on the right side of the operation. 572 | 573 | @return true if the values are equivalent; false otherwise. 574 | */ 575 | static inline bool string_equals_string(const String* str, const String* value); 576 | 577 | /** 578 | Determines if a string starts with the characters in a c-string. 579 | 580 | @param str The string to check the beginning of. 581 | @param value The value to check the beginning of the string for. 582 | 583 | @return true if the string starts with the value; false otherwise. 584 | */ 585 | static inline bool string_starts_with_cstr(const String* str, const char* value); 586 | 587 | /** 588 | Determines if a string starts with the characters in another string. 589 | 590 | @param str The string to check the beginning of. 591 | @param value The value to check the beginning of the string for. 592 | 593 | @return true if the string starts with the value; false otherwise. 594 | */ 595 | static inline bool string_starts_with_string(const String* str, const String* value); 596 | 597 | /** 598 | Determines if a string ends with the characters in a c-string. 599 | 600 | @param str The string to check the ending of. 601 | @param value The value to check the ending of the string for. 602 | 603 | @return true if the string ends with the value; false otherwise. 604 | */ 605 | static inline bool string_ends_with_cstr(const String* str, const char* value); 606 | 607 | /** 608 | Determines if a string ends with the characters in another string. 609 | 610 | @param str The string to check the ending of. 611 | @param value The value to check the ending of the string for. 612 | 613 | @return true if the string ends with the value; false otherwise. 614 | */ 615 | static inline bool string_ends_with_string(const String* str, const String* value); 616 | 617 | /** 618 | Replaces a section of a string with the characters in a c-string. 619 | 620 | @param str The string whose contents are to be replaced. 621 | @param pos The starting position of the contents to replace. 622 | @param count The number of characters following pos to replace. 623 | @param value The c-string value to replace the section with. 624 | 625 | @return true on success, false on allocation failure. 626 | */ 627 | static inline bool string_replace_cstr(String* str, size_t pos, size_t count, const char* value); 628 | 629 | /** 630 | Replaces a section of a string with the characters in another string. 631 | 632 | @param str The string whose contents are to be replaced. 633 | @param pos The starting position of the contents to replace. 634 | @param count The number of characters following pos to replace. 635 | @param value The string value to replace the section with. 636 | 637 | @return true on success, false on allocation failure. 638 | */ 639 | static inline bool string_replace_string(String* str, size_t pos, size_t count, const String* value); 640 | 641 | /** 642 | Initializes a string with the data from a slice of another string. 643 | 644 | @param str The string to get the slice from. 645 | @param pos The starting position of the slice. 646 | @param count The number of characters following pos to copy into the substring. 647 | @param out_value The string that will be initialized with the contents of the substring. 648 | This value should not be initialized by the caller, or it might cause a memory leak. 649 | 650 | @return true on success, false on allocation failure. 651 | */ 652 | static inline bool string_substring(const String* str, size_t pos, size_t count, String* out_value); 653 | 654 | /** 655 | Initializes a string with the data of another string. 656 | 657 | @param str The string to copy. 658 | @param out_value The string to copy the contents into. 659 | This value should not be initialized by the caller, or it might cause a memory leak. 660 | 661 | @return true on success, false on allocation failure. 662 | */ 663 | static inline bool string_copy(const String* str, String* out_value); 664 | 665 | /** 666 | Copies the data from a slice of a string into a c-string, overwriting any 667 | previous data. Does not add a terminating character at the end. 668 | 669 | @param str The string to copy. 670 | @param cstr The c-string to copy into. 671 | @param pos The starting position of the string to start copying into the c-string. 672 | @param count The number of characters following pos to copy into the c-string. 673 | */ 674 | static inline void string_copy_to(const String* str, char* cstr, size_t pos, size_t count); 675 | 676 | /** 677 | Resizes a string, adding the specified character to fill any new spots. 678 | Removes trailing characters if the new size is smaller than the current 679 | size. 680 | 681 | @param str The string to resize. 682 | @param count The new size of the string. 683 | @param ch The character to fill anyh new spots with. 684 | 685 | @return true on success, false on allocation failure. 686 | */ 687 | SSO_STRING_EXPORT bool string_resize(String* str, size_t count, char ch); 688 | 689 | /** 690 | Swaps the contents of two strings. 691 | 692 | @param left The string on the left side of the operation. 693 | @param right The string on the right side of the operation. 694 | */ 695 | static inline void string_swap(String* left, String* right); 696 | 697 | /** 698 | Finds the starting index of the first occurrence of a c-string in a string. 699 | 700 | @param str The string to search. 701 | @param pos The starting position in the string to start searching. 702 | @param value The c-string value to search for. 703 | 704 | @return The starting index of the substring on success, or SIZE_MAX if the substring couldn't be found. 705 | */ 706 | static inline size_t string_find_cstr(const String* str, size_t pos, const char* value); 707 | 708 | /** 709 | Finds the starting index of the first occurrence of a string in another string. 710 | 711 | @param str The string to search. 712 | @param pos The starting position in the string to start searching. 713 | @param value The string value to search for. 714 | 715 | @return The starting index of the substring on success, or SIZE_MAX if the substring couldn't be found. 716 | */ 717 | static inline size_t string_find_string(const String* str, size_t pos, const String* value); 718 | 719 | /** 720 | Finds the starting index of the first occurrence of part of a c-string in a string. 721 | 722 | @param str The string to search. 723 | @param pos The starting position in the string to start searching. 724 | @param value The c-string value to search for. 725 | @param start The beginning index of value to search for in the string. 726 | @param length The number of characters following start in value to search for in the string. 727 | 728 | @return The starting index of the substring on success, or SIZE_MAX if the substring couldn't be found. 729 | */ 730 | static inline size_t string_find_substr_cstr(const String* str, size_t pos, const char* value, size_t start, size_t length); 731 | 732 | /** 733 | Finds the starting index of the first occurrence of part of a string in another string. 734 | 735 | @param str The string to search. 736 | @param pos The starting position in the string to start searching. 737 | @param value The string value to search for. 738 | @param start The beginning index of value to search for in the string. 739 | @param length The number of characters following start in value to search for in the string. 740 | 741 | @return The starting index of the substring on success, or SIZE_MAX if the substring couldn't be found. 742 | */ 743 | static inline size_t string_find_substr_string(const String* str, size_t pos, const String* value, size_t start, size_t length); 744 | 745 | /** 746 | Finds the starting index of the last occurrence of a c-string in a string. 747 | 748 | @param str The string to search. 749 | @param pos The starting position in the string to start searching, starting from the back. 750 | @param value The c-string value to search for. 751 | 752 | @return The starting index of the substring on success, or SIZE_MAX if the substring couldn't be found. 753 | */ 754 | static inline size_t string_rfind_cstr(const String* str, size_t pos, const char* value); 755 | 756 | /** 757 | Finds the starting index of the last occurrence of a string in another string. 758 | 759 | @param str The string to search. 760 | @param pos The starting position in the string to start searching, starting from the back. 761 | @param value The string value to search for. 762 | 763 | @return The starting index of the substring on success, or SIZE_MAX if the substring couldn't be found. 764 | */ 765 | static inline size_t string_rfind_string(const String* str, size_t pos, const String* value); 766 | 767 | /** 768 | Finds the starting index of the last occurrence of part of a c-string in a string. 769 | 770 | @param str The string to search. 771 | @param pos The starting position in the string to start searching, starting from the back. 772 | @param value The c-string value to search for. 773 | @param start The beginning index of value to search for in the string. 774 | @param length The number of characters following start in value to search for in the string. 775 | 776 | @return The starting index of the substring on success, or SIZE_MAX if the substring couldn't be found. 777 | */ 778 | static inline size_t string_rfind_substr_cstr(const String* str, size_t pos, const char* value, size_t start, size_t length); 779 | 780 | /** 781 | Finds the starting index of the last occurrence of part of a string in another string. 782 | 783 | @param str The string to search. 784 | @param pos The starting position in the string to start searching, starting from the back. 785 | @param value The string value to search for. 786 | @param start The beginning index of value to search for in the string. 787 | @param length The number of characters following start in value to search for in the string. 788 | 789 | @return The starting index of the substring on success, or SIZE_MAX if the substring couldn't be found. 790 | */ 791 | static inline size_t string_rfind_substr_string(const String* str, size_t pos, const String* value, size_t start, size_t length); 792 | 793 | /** 794 | Reverses the bytes in-place in a string. 795 | 796 | @param str The string to reverse. 797 | */ 798 | SSO_STRING_EXPORT void string_reverse_bytes(String* str); 799 | 800 | /** 801 | Reverses the contents of a string in-place based on UTF-8 codepoints. 802 | 803 | @param str The string to reverse. 804 | */ 805 | SSO_STRING_EXPORT void string_u8_reverse_codepoints(String* str); 806 | 807 | /** 808 | Joins an array of strings together into a single string with a 809 | common separator in between each of them. 810 | 811 | @param str The string that stores the result. If it has a value, 812 | the result is appended to the end. It needs to be initialized. 813 | 814 | @param separator A string to separate each array value in the result. 815 | @param values An array of strings to combine. 816 | @param value_count The number of strings in the values array. 817 | */ 818 | SSO_STRING_EXPORT bool string_join( 819 | String* str, 820 | const String* separator, 821 | const String* values, 822 | size_t value_count); 823 | 824 | /** 825 | Joins an array of strings together into a single string with a 826 | common separator in between each of them. 827 | 828 | @param str The string that stores the result. If it has a value, 829 | the result is appended to the end. It needs to be initialized. 830 | 831 | @param separator A string to separate each array value in the result. 832 | @param values An array of string references to combine. 833 | @param value_count The number of strings in the values array. 834 | */ 835 | SSO_STRING_EXPORT bool string_join_refs( 836 | String* str, 837 | const String* separator, 838 | const String** values, 839 | size_t value_count); 840 | 841 | /** 842 | A constant that can be used to indicate that a string split function will allocate the result. 843 | Should be passed as the results_count parameter. 844 | */ 845 | #define STRING_SPLIT_ALLOCATE -1 846 | 847 | /** 848 | Splits a string into segments based on a separator. 849 | 850 | @param str The string to split apart. 851 | @param separator The string to split on. 852 | @param results A contiguous array of strings that will store the split segments. 853 | Should be NULL if results_count is a negative number. 854 | 855 | @param results_count The number of elements available in the results array. If this is 856 | a negative number, the return value will be allocated by the function. 857 | 858 | @param results_filled A pointer that will contain the number of string segments added to the results array. 859 | @param skip_empty Determines if empty elements should be skipped or added to the results array. 860 | @param init_results Determines if the segment strings need to be initialized by this function. 861 | 862 | @return The value passed to results if results_count is a positive number. Otherwise, it's an array created 863 | by this function that will need to be manually freed. Either way, an array containing the 864 | split string segments. Returns NULL on error. 865 | */ 866 | SSO_STRING_EXPORT String* string_split( 867 | const String* str, 868 | const String* separator, 869 | String* results, 870 | int results_count, 871 | int* results_filled, 872 | bool skip_empty, 873 | bool init_results); 874 | 875 | /** 876 | Splits a string into segments based on a separator. 877 | 878 | @param str The string to split apart. 879 | @param separator The string to split on. 880 | @param results A non-contiguous array of strings that will store the split segments. 881 | Should be NULL if results_count is a negative number. 882 | 883 | @param results_count The number of elements available in the results array. If this is 884 | a negative number, the return value will be allocated by the function. 885 | 886 | @param results_filled A pointer that will contain the number of string segments added to the results array. 887 | @param skip_empty Determines if empty elements should be skipped or added to the results array. 888 | @param init_results Determines if the segment strings need to be allocated by this function. 889 | If this is false, the results will need to be allocated/initialized beforehand. 890 | 891 | @return The value passed to results if results_count is a positive number. Otherwise, it's an array created 892 | by this function that will need to be manually freed. Either way, an array containing the 893 | split string segments. Returns NULL on error. 894 | */ 895 | SSO_STRING_EXPORT String** string_split_refs( 896 | const String* str, 897 | const String* separator, 898 | String** results, 899 | int results_count, 900 | int* results_filled, 901 | bool skip_empty, 902 | bool allocate_results); 903 | 904 | /** 905 | Formats a string using printf format specifiers. 906 | 907 | @param result A string that stores the result of the format operation. 908 | If this is NULL, this function allocates a string for the return value. 909 | Otherwise it appends the formatted data to the end. 910 | 911 | @param format A string that contains the text and format specifiers to be written. 912 | @param ... The format specifier values. 913 | 914 | @return result if the argument was non-null. Otherwise a newly allocated string 915 | that contains the format result. NULL on error. 916 | */ 917 | SSO_STRING_EXPORT String* string_format_string(String* result, const String* format, ...); 918 | 919 | /** 920 | Formats a string using printf format specifiers. 921 | 922 | @param result A string that stores the result of the format operation. 923 | If this is NULL, this function allocates a string for the return value. 924 | Otherwise it appends the formatted data to the end. 925 | 926 | @param format A c-string that contains the text and format specifiers to be written. 927 | @param ... The format specifier values. 928 | 929 | @return result if the argument was non-null. Otherwise a newly allocated string 930 | that contains the format result. NULL on error. 931 | */ 932 | SSO_STRING_EXPORT String* string_format_cstr(String* result, const char* format, ...); 933 | 934 | /** 935 | Formats a string using printf format specifiers. 936 | 937 | @param result A string that stores the result of the format operation. 938 | If this is NULL, this function allocates a string for the return value. 939 | Otherwise it appends the formatted data to the end. 940 | 941 | @param format A string that contains the text and format specifiers to be written. 942 | @param argp A list of the variadic arguments originally passed to a variadic function. 943 | The position of the argument may be changed, so pass a copy to this method if that isn't desired. 944 | 945 | @return result if the argument was non-null. Otherwise a newly allocated string 946 | that contains the format result. NULL on error. 947 | */ 948 | SSO_STRING_EXPORT String* string_format_args_string(String* result, const String* format, va_list argp); 949 | 950 | /** 951 | Formats a string using printf format specifiers. 952 | 953 | @param result A string that stores the result of the format operation. 954 | If this is NULL, this function allocates a string for the return value. 955 | Otherwise it appends the formatted data to the end. 956 | 957 | @param format A c-string that contains the text and format specifiers to be written. 958 | @param argp A list of the variadic arguments originally passed to a variadic function. 959 | The position of the argument may be changed, so pass a copy to this method if that isn't desired. 960 | 961 | @return result if the argument was non-null. Otherwise a newly allocated string 962 | that contains the format result. NULL on error. 963 | */ 964 | SSO_STRING_EXPORT String* string_format_args_cstr(String* result, const char* format, va_list argp); 965 | 966 | /** 967 | Creates a hash code from a string using the fnv1-a algorithm. 968 | 969 | @param str The string to generate a hash for. 970 | 971 | @return A hash code for the string. 972 | */ 973 | SSO_STRING_EXPORT size_t string_hash(String* str); 974 | 975 | 976 | 977 | // Internal Functions 978 | // 979 | // These can technically be called by the user, but should be avoided if possible. 980 | // 981 | // The string_set_size functions are especially useful when interfacing with an api 982 | // that writes to a c-string. The internal contents of the string can be overwritten 983 | // using the api, and the size can be safely updated by the caller. WARNING: make sure 984 | // the buffer is big enough using string_reserve when performing this type of operation. 985 | 986 | #define SSO_STRING_SHORT_RESERVE_FAIL 0 987 | #define SSO_STRING_SHORT_RESERVE_SUCCEED 1 988 | #define SSO_STRING_SHORT_RESERVE_RESIZE 2 989 | 990 | static inline size_t sso_string_next_cap(size_t current, size_t desired); 991 | static inline bool sso_string_is_long(const String* str); 992 | static inline size_t sso_string_short_size(const String* str); 993 | static inline size_t sso_string_short_cap(const String* str); 994 | static inline size_t sso_string_long_size(const String* str); 995 | static inline size_t sso_string_long_cap(const String* str); 996 | static inline void sso_string_long_set_cap(String* str, size_t cap); 997 | static inline void sso_string_long_set_size(String* str, size_t size); 998 | static inline void sso_string_short_set_size(String* str, size_t size); 999 | SSO_STRING_EXPORT bool sso_string_long_reserve(String* str, size_t reserve); 1000 | SSO_STRING_EXPORT int sso_string_short_reserve(String* str, size_t reserve); 1001 | SSO_STRING_EXPORT bool sso_string_insert_impl(String* str, const char* value, size_t index, size_t length); 1002 | SSO_STRING_EXPORT bool sso_string_append_impl(String* str, const char* value, size_t length); 1003 | SSO_STRING_EXPORT bool sso_string_replace_impl(String* str, size_t pos, size_t count, const char* value, size_t length); 1004 | SSO_STRING_EXPORT size_t sso_string_find_impl(const String* str, size_t pos, const char* value, size_t length); 1005 | SSO_STRING_EXPORT size_t sso_string_find_substr_impl(const String* str, size_t pos, const char* value, size_t length); 1006 | SSO_STRING_EXPORT size_t sso_string_rfind_impl(const String* str, size_t pos, const char* value, size_t length); 1007 | 1008 | 1009 | 1010 | 1011 | 1012 | // The following functions have a size small enough to be inlined, so they are defined here in the header. 1013 | 1014 | static inline size_t sso_string_next_cap(size_t current, size_t desired) { 1015 | if(current > desired) 1016 | return current; 1017 | current *= 2; 1018 | current = (size_t)max(current, desired); 1019 | return current < STRING_MAX ? current : STRING_MAX; 1020 | } 1021 | 1022 | static inline bool sso_string_is_long(const String* str) { 1023 | return str->s.size & SSO_STRING_LONG_FLAG; 1024 | } 1025 | 1026 | static inline size_t sso_string_short_size(const String* str) { 1027 | #ifdef SSO_STRING_LITTLE_ENDIAN 1028 | return str->s.size >> 1; 1029 | #else 1030 | return str->s.size; 1031 | #endif 1032 | } 1033 | 1034 | static inline size_t sso_string_short_cap(const String* str) { 1035 | return SSO_STRING_MIN_CAP; 1036 | } 1037 | 1038 | static inline size_t sso_string_long_size(const String* str) { 1039 | return str->l.size; 1040 | } 1041 | 1042 | static inline size_t sso_string_long_cap(const String* str) { 1043 | #ifdef SSO_STRING_LITTLE_ENDIAN 1044 | return str->l.cap >> 1; 1045 | #else 1046 | return str->l.cap & ~((size_t)SSO_STRING_LONG_FLAG << SSO_STRING_SHIFT); 1047 | #endif 1048 | } 1049 | 1050 | static inline void sso_string_long_set_cap(String* str, size_t cap) { 1051 | #ifdef SSO_STRING_LITTLE_ENDIAN 1052 | str->l.cap = (cap << 1); 1053 | #else 1054 | str->l.cap = cap; 1055 | #endif 1056 | str->s.size |= SSO_STRING_LONG_FLAG; 1057 | } 1058 | 1059 | static inline void sso_string_long_set_size(String* str, size_t size) { 1060 | str->l.size = size; 1061 | } 1062 | 1063 | static inline void sso_string_short_set_size(String* str, size_t size) { 1064 | #ifdef SSO_STRING_LITTLE_ENDIAN 1065 | str->s.size = (size << 1); 1066 | #else 1067 | str->s.size = size; 1068 | #endif 1069 | } 1070 | 1071 | static inline String string_create(const char* cstr) { 1072 | String str; 1073 | string_init(&str, cstr); 1074 | return str; 1075 | } 1076 | 1077 | static inline String* string_create_ref(const char* cstr) { 1078 | String* str = malloc(sizeof(String)); 1079 | 1080 | if(!str || !string_init(str, cstr)) { 1081 | free(str); 1082 | return NULL; 1083 | } 1084 | return str; 1085 | } 1086 | 1087 | static inline void string_free_resources(String* str) { 1088 | if(str && sso_string_is_long(str)) { 1089 | free(str->l.data); 1090 | } 1091 | } 1092 | 1093 | static inline void string_free(String* str) { 1094 | string_free_resources(str); 1095 | free(str); 1096 | } 1097 | 1098 | static inline char* string_cstr(String* str) { 1099 | return sso_string_is_long(str) ? str->l.data : str->s.data; 1100 | } 1101 | 1102 | static inline const char* string_data(const String* str) { 1103 | return sso_string_is_long(str) ? str->l.data : str->s.data; 1104 | } 1105 | 1106 | static inline size_t string_size(const String* str) { 1107 | return sso_string_is_long(str) ? sso_string_long_size(str) : sso_string_short_size(str); 1108 | } 1109 | 1110 | static inline size_t string_capacity(const String* str) { 1111 | return sso_string_is_long(str) ? sso_string_long_cap(str) : sso_string_short_cap(str); 1112 | } 1113 | 1114 | static inline char string_get(const String* str, size_t index) { 1115 | SSO_STRING_ASSERT_ARG(str); 1116 | if(sso_string_is_long(str)) { 1117 | SSO_STRING_ASSERT_BOUNDS(index < sso_string_long_size(str)); 1118 | return str->l.data[index]; 1119 | } else { 1120 | SSO_STRING_ASSERT_BOUNDS(index < sso_string_short_size(str)); 1121 | return str->s.data[index]; 1122 | } 1123 | } 1124 | 1125 | static inline void string_set(String* str, size_t index, char value) { 1126 | SSO_STRING_ASSERT_ARG(str); 1127 | if(sso_string_is_long(str)) { 1128 | SSO_STRING_ASSERT_BOUNDS(index < sso_string_long_size(str)); 1129 | str->l.data[index] = value; 1130 | } else { 1131 | SSO_STRING_ASSERT_BOUNDS(index < sso_string_short_size(str)); 1132 | str->s.data[index] = value; 1133 | } 1134 | } 1135 | 1136 | 1137 | static inline bool string_empty(const String* str) { 1138 | SSO_STRING_ASSERT_ARG(str); 1139 | return sso_string_is_long(str) ? (sso_string_long_size(str) == 0) 1140 | : (sso_string_short_size(str) == 0); 1141 | } 1142 | 1143 | static inline bool string_reserve(String* str, size_t reserve) { 1144 | SSO_STRING_ASSERT_ARG(str); 1145 | if(sso_string_is_long(str)) { 1146 | return sso_string_long_reserve(str, reserve); 1147 | } else { 1148 | return sso_string_short_reserve(str, reserve) != SSO_STRING_SHORT_RESERVE_FAIL; 1149 | } 1150 | } 1151 | 1152 | static inline void string_clear(String* str) { 1153 | SSO_STRING_ASSERT_ARG(str); 1154 | 1155 | if(sso_string_is_long(str)) { 1156 | str->l.data[0] = 0; 1157 | sso_string_long_set_size(str, 0); 1158 | } else { 1159 | str->s.data[0] = 0; 1160 | sso_string_short_set_size(str, 0); 1161 | } 1162 | } 1163 | 1164 | static inline bool string_insert_cstr(String* str, const char* value, size_t index) { 1165 | return sso_string_insert_impl(str, value, index, strlen(value)); 1166 | } 1167 | 1168 | static inline bool string_insert_string(String* str, const String* value, size_t index) { 1169 | return sso_string_insert_impl(str, string_data(value), index, string_size(value)); 1170 | } 1171 | 1172 | static inline bool string_insert_cstr_part( 1173 | String* str, 1174 | const char* value, 1175 | size_t index, 1176 | size_t start, 1177 | size_t length) 1178 | { 1179 | return sso_string_insert_impl(str, value + start, index, length); 1180 | } 1181 | 1182 | static inline bool string_insert_string_part( 1183 | String* str, 1184 | const String* value, 1185 | size_t index, 1186 | size_t start, 1187 | size_t length) 1188 | { 1189 | return sso_string_insert_impl(str, string_data(value) + start, index, length); 1190 | } 1191 | 1192 | static inline bool string_append_cstr(String* str, const char* value) { 1193 | return sso_string_append_impl(str, value, strlen(value)); 1194 | } 1195 | 1196 | static inline bool string_append_string(String* str, const String* value) { 1197 | return sso_string_append_impl(str, string_data(value), string_size(value)); 1198 | } 1199 | 1200 | static inline bool string_append_cstr_part( 1201 | String* str, 1202 | const char* value, 1203 | size_t start, 1204 | size_t count) 1205 | { 1206 | return sso_string_append_impl(str, value + start, count); 1207 | } 1208 | 1209 | static inline bool string_append_string_part( 1210 | String* str, 1211 | const String* value, 1212 | size_t start, 1213 | size_t count) 1214 | { 1215 | return sso_string_append_impl(str, string_data(value) + start, count); 1216 | } 1217 | 1218 | static inline int sso_string_compare_impl(const String* str, const char* value, size_t length) { 1219 | SSO_STRING_ASSERT_ARG(str); 1220 | SSO_STRING_ASSERT_ARG(value); 1221 | size_t size = string_size(str); 1222 | if(size != length) 1223 | return size < length ? -1 : 1; 1224 | 1225 | return strncmp(string_data(str), value, length); 1226 | } 1227 | 1228 | static inline int string_compare_cstr(const String* str, const char* value) { 1229 | return sso_string_compare_impl(str, value, strlen(value)); 1230 | } 1231 | 1232 | static inline int string_compare_string(const String* str, const String* value) { 1233 | return sso_string_compare_impl(str, string_data(value), string_size(value)); 1234 | } 1235 | 1236 | static inline bool string_equals_cstr(const String* str, const char* value) { 1237 | return string_compare_cstr(str, value) == 0; 1238 | } 1239 | 1240 | static inline bool string_equals_string(const String* str, const String* value) { 1241 | return string_compare_string(str, value) == 0; 1242 | } 1243 | 1244 | static inline bool sso_string_starts_with_impl(const String* str, const char* value, size_t length) { 1245 | SSO_STRING_ASSERT_ARG(str); 1246 | SSO_STRING_ASSERT_ARG(value); 1247 | size_t size = string_size(str); 1248 | if(length > size) 1249 | return false; 1250 | 1251 | return strncmp(string_data(str), value, length) == 0; 1252 | } 1253 | 1254 | 1255 | static inline bool string_starts_with_cstr(const String* str, const char* value) { 1256 | return sso_string_starts_with_impl(str, value, strlen(value)); 1257 | } 1258 | 1259 | static inline bool string_starts_with_string(const String* str, const String* value) { 1260 | return sso_string_starts_with_impl(str, string_data(value), string_size(value)); 1261 | } 1262 | 1263 | static inline bool sso_string_ends_with_impl(const String* str, const char* value, size_t length) { 1264 | SSO_STRING_ASSERT_ARG(str); 1265 | SSO_STRING_ASSERT_ARG(value); 1266 | size_t size = string_size(str); 1267 | if(length > size) 1268 | return false; 1269 | 1270 | return strncmp(string_data(str) + (size - length), value, length) == 0; 1271 | } 1272 | 1273 | static inline bool string_ends_with_cstr(const String* str, const char* value) { 1274 | return sso_string_ends_with_impl(str, value, strlen(value)); 1275 | } 1276 | 1277 | static inline bool string_ends_with_string(const String* str, const String* value) { 1278 | return sso_string_ends_with_impl(str, string_data(value), string_size(value)); 1279 | } 1280 | 1281 | 1282 | static inline bool string_replace_cstr(String* str, size_t pos, size_t count, const char* value) { 1283 | return sso_string_replace_impl(str, pos, count, value, strlen(value)); 1284 | } 1285 | 1286 | 1287 | static inline bool string_replace_string(String* str, size_t pos, size_t count, const String* value) { 1288 | return sso_string_replace_impl(str, pos, count, string_data(value), string_size(value)); 1289 | } 1290 | 1291 | static inline bool string_substring(const String* str, size_t pos, size_t count, String* value) { 1292 | SSO_STRING_ASSERT_BOUNDS(pos + count <= string_size(str)); 1293 | return string_init_size(value, string_data(str) + pos, count); 1294 | } 1295 | 1296 | static inline bool string_copy(const String* str, String* out_value) { 1297 | SSO_STRING_ASSERT_ARG(str); 1298 | SSO_STRING_ASSERT_ARG(out_value); 1299 | return string_init(out_value, string_data(str)); 1300 | } 1301 | 1302 | static inline void string_copy_to(const String* str, char* cstr, size_t pos, size_t count) { 1303 | SSO_STRING_ASSERT_ARG(str); 1304 | SSO_STRING_ASSERT_ARG(cstr); 1305 | SSO_STRING_ASSERT_BOUNDS(pos + count <= string_size(str)); 1306 | memmove(cstr, string_data(str) + pos, count); 1307 | } 1308 | 1309 | static inline void string_swap(String* left, String* right) { 1310 | SSO_STRING_ASSERT_ARG(left); 1311 | SSO_STRING_ASSERT_ARG(right); 1312 | String temp = *right; 1313 | memmove(right, left, sizeof(String)); 1314 | memmove(left, &temp, sizeof(String)); 1315 | } 1316 | 1317 | static inline size_t string_find_cstr(const String* str, size_t pos, const char* value) { 1318 | return sso_string_find_impl(str, pos, value, strlen(value)); 1319 | } 1320 | 1321 | static inline size_t string_find_string(const String* str, size_t pos, const String* value) { 1322 | return sso_string_find_impl(str, pos, string_data(value), string_size(value)); 1323 | } 1324 | 1325 | 1326 | static inline size_t string_find_substr_cstr(const String* str, size_t pos, const char* value, size_t start, size_t length) { 1327 | return sso_string_find_substr_impl(str, pos, value + start, length); 1328 | } 1329 | 1330 | static inline size_t string_find_substr_string(const String* str, size_t pos, const String* value, size_t start, size_t length) { 1331 | return sso_string_find_substr_impl(str, pos, string_data(value) + start, length); 1332 | } 1333 | 1334 | static inline size_t string_rfind_cstr(const String* str, size_t pos, const char* value) { 1335 | return sso_string_rfind_impl(str, pos, value, strlen(value)); 1336 | } 1337 | 1338 | static inline size_t string_rfind_string(const String* str, size_t pos, const String* value) { 1339 | return sso_string_rfind_impl(str, pos, string_data(value), string_size(value)); 1340 | } 1341 | 1342 | static inline size_t string_rfind_substr_cstr(const String* str, size_t pos, const char* value, size_t start, size_t length) { 1343 | return sso_string_rfind_impl(str, pos, value + start, length); 1344 | } 1345 | 1346 | static inline size_t string_rfind_substr_string(const String* str, size_t pos, const String* value, size_t start, size_t length) { 1347 | return sso_string_rfind_impl(str, pos, string_data(value) + start, length); 1348 | } 1349 | 1350 | static inline bool string_is_null_or_empty(const String* str) { 1351 | return !str || string_size(str) == 0; 1352 | } 1353 | 1354 | // If C11 is available, use the _Generic macro to select the correct 1355 | // string function, otherwise just default to using cstrings. 1356 | 1357 | #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L) 1358 | 1359 | #define string_insert(str, value, index) \ 1360 | _Generic((value), \ 1361 | char*: string_insert_cstr, \ 1362 | const char*: string_insert_cstr, \ 1363 | String*: string_insert_string, \ 1364 | const String*: string_insert_string) \ 1365 | ((str), (value), (index)) 1366 | 1367 | #define string_insert_part(str, value, index, start, length) \ 1368 | _Generic((value), \ 1369 | char*: string_insert_cstr_part, \ 1370 | const char*: string_insert_cstr_part, \ 1371 | String*: string_insert_string_part, \ 1372 | const String*: string_insert_string_part) \ 1373 | ((str), (value), (index), (start), (length)) 1374 | 1375 | #define string_append(str, value) \ 1376 | _Generic((value), \ 1377 | char*: string_append_cstr, \ 1378 | const char*: string_append_cstr, \ 1379 | String*: string_append_string, \ 1380 | const String*: string_append_string) \ 1381 | ((str), (value)) 1382 | 1383 | #define string_equals(str, value) \ 1384 | _Generic((value), \ 1385 | char*: string_equals_cstr, \ 1386 | const char*: string_equals_cstr, \ 1387 | String*: string_starts_with_string, \ 1388 | const String*: string_starts_with_string) \ 1389 | ((str), (value)) 1390 | 1391 | #define string_compare(str, value) \ 1392 | _Generic((value), \ 1393 | char*: string_compare_cstr, \ 1394 | const char*: string_compare_cstr, \ 1395 | String*: string_compare_string, \ 1396 | const String*: string_compare_string) \ 1397 | ((str), (value)) 1398 | 1399 | #define string_starts_with(str, value) \ 1400 | _Generic((value), \ 1401 | char*: string_starts_with_cstr, \ 1402 | const char*: string_starts_with_cstr, \ 1403 | String*: string_starts_with_string, \ 1404 | const String*: string_starts_with_string) \ 1405 | ((str), (value)) 1406 | 1407 | #define string_ends_with(str, value) \ 1408 | _Generic((value), \ 1409 | char*: string_ends_with_cstr, \ 1410 | const char*: string_ends_with_cstr, \ 1411 | String*: string_ends_with_string, \ 1412 | const String*: string_ends_with_string) \ 1413 | ((str), (value)) 1414 | 1415 | #define string_replace(str, pos, count, value) \ 1416 | _Generic((value), \ 1417 | char*: string_replace_cstr, \ 1418 | const char*: string_replace_cstr, \ 1419 | String*: string_replace_string, \ 1420 | const String*: string_replace_string) \ 1421 | ((str), (pos), (count), (value)) 1422 | 1423 | #define string_find(str, pos, value) \ 1424 | _Generic((value), \ 1425 | char*: string_find_cstr, \ 1426 | const char*: string_find_cstr, \ 1427 | String*: string_find_string, \ 1428 | const String*: string_find_string) \ 1429 | ((str), (pos), (value)) 1430 | 1431 | #define string_find_substr(str, pos, value, start, count) \ 1432 | _Generic((value) \ 1433 | char*: string_find_substr_cstr, \ 1434 | const char*: string_find_substr_cstr, \ 1435 | String* string_find_substr_string, \ 1436 | const String*: string_find_substr_string)\ 1437 | ((str), (pos), (value), (start), (count)) 1438 | 1439 | #define string_rfind(str, pos, value) \ 1440 | _Generic((value), \ 1441 | char*: string_rfind_cstr, \ 1442 | const char*: string_rfind_cstr, \ 1443 | String*: string_rfind_string, \ 1444 | const String*: string_rfind_string) \ 1445 | ((str), (pos), (value)) 1446 | 1447 | #define string_rfind_part(str, pos, value, start, count) \ 1448 | _Generic((value), \ 1449 | char*: string_rfind_part_cstr, \ 1450 | const char*: string_rfind_part_cstr, \ 1451 | String*: string_rfind_part_string, \ 1452 | const String*: string_rfind_part_string) \ 1453 | ((str), (pos), (value), (start), (count)) 1454 | 1455 | #define string_format(str, format, ...) \ 1456 | _Generic((format), \ 1457 | char*: string_format_cstr, \ 1458 | const char*: string_format_cstr, \ 1459 | String*: string_format_string, \ 1460 | const String*: string_format_string) \ 1461 | ((str), (format), __VA_ARGS__) 1462 | 1463 | #define string_format_args(str, format, ...) \ 1464 | _Generic((format), \ 1465 | char*: string_format_args_cstr, \ 1466 | const char*: string_format_args_cstr, \ 1467 | String*: string_format_args_string, \ 1468 | const String*: string_format_args_string) \ 1469 | ((str), (format), __VA_ARGS__) 1470 | 1471 | #else 1472 | 1473 | #define string_insert(str, value, index) string_insert_cstr(str, value, index) 1474 | #define string_insert_part(str, value, index, start, length) string_insert_cstr_part(str, value, index, start, length) 1475 | #define string_append(str, value) string_append_cstr(str, value) 1476 | #define string_equals(str, value) string_equals_cstr(str, value) 1477 | #define string_compare(str, value) string_compare_cstr(str, value) 1478 | #define string_starts_with(str, value) string_starts_with_cstr(str, value) 1479 | #define string_ends_with(str, value) string_ends_with_cstr(str, value) 1480 | #define string_replace(str, pos, count, value) string_replace_cstr(str, pos, count, value) 1481 | #define string_find(str, pos, value) string_find_cstr(str, pos, value) 1482 | #define string_rfind(str, pos, value) string_rfind_cstr(str, pos, value) 1483 | #define string_format(str, format, ...) string_format_cstr(str, format, __VA_ARGS__) 1484 | #define string_format_args(str, format, argp) string_format_args_cstr(str, format, argp) 1485 | 1486 | #endif // C11 1487 | 1488 | #endif -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('sso_string', 'c') 2 | 3 | c_comp = meson.get_compiler('c') 4 | check_location = get_option('check_location') 5 | 6 | include_files = include_directories(['include']) 7 | sources = [ './src/sso_string.c' ] 8 | 9 | sso_string = static_library( 10 | 'sso_string', 11 | sources, 12 | c_args: ['/DSSO_STRING_BUILD'], 13 | include_directories: include_files, 14 | install: true, 15 | name_suffix: 'lib', 16 | name_prefix: '' 17 | ) 18 | 19 | sso_string_shared = shared_library( 20 | 'sso_string', 21 | sources, 22 | c_args: ['/DSSO_STRING_BUILD'], 23 | version: '1.1.0', 24 | include_directories: include_files, 25 | install: true, 26 | ) 27 | 28 | # This allows for other projects to use this as a subproject. 29 | sso_string_dep = declare_dependency( 30 | include_directories: include_files, 31 | link_with: sso_string_shared 32 | ) 33 | 34 | if check_location != '' 35 | test_inc = include_directories(['include', check_location + '/include']) 36 | subdir('tests') 37 | elif c_comp.compiles('#include ') 38 | test_inc = include_files 39 | subdir('tests') 40 | endif -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('check_location', type: 'string', description: 'The location of the unit testing library Check. Leave blank to exclude tests.', value: '') -------------------------------------------------------------------------------- /src/sso_string.c: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2019 Precisamento LLC 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | #include 26 | 27 | #include 28 | 29 | #if defined(SSO_STRING_SINGLE_THREAD) 30 | 31 | #define SSO_THREAD_LOCAL 32 | 33 | #elif defined(_MSC_VER) 34 | 35 | #define SSO_THREAD_LOCAL __declspec(thread) 36 | 37 | #elif defined(__GNUC__) || defined(__clang__) 38 | 39 | #define SSO_THREAD_LOCAL __thread 40 | 41 | #elif __STDC_VERSION__ >= 201112L 42 | #ifndef __STDC_NO_THREADS__ 43 | #ifdef thread_local 44 | 45 | #include 46 | 47 | #define SSO_THREAD_LOCAL thread_local 48 | 49 | #endif 50 | #endif 51 | #endif 52 | 53 | #define U8_SINGLE 0x7F 54 | #define U8_DOUBLE 0xE0 55 | #define U8_TRIPLE 0xF0 56 | #define U8_QUAD 0xF8 57 | 58 | static inline void sso_string_set_size(String* str, size_t size) { 59 | if(sso_string_is_long(str)) 60 | sso_string_long_set_size(str, size); 61 | else 62 | sso_string_short_set_size(str, size); 63 | } 64 | 65 | SSO_STRING_EXPORT bool string_init(String* str, const char* cstr) { 66 | SSO_STRING_ASSERT_ARG(str); 67 | 68 | if(cstr == NULL) 69 | cstr = ""; 70 | 71 | size_t len = strlen(cstr); 72 | if (len <= SSO_STRING_MIN_CAP) { 73 | memcpy(str->s.data, cstr, len); 74 | str->s.data[len] = 0; 75 | 76 | sso_string_short_set_size(str, len); 77 | } else { 78 | size_t cap = sso_string_next_cap(0, len); 79 | str->l.data = malloc(cap + 1); 80 | if(!str->l.data) 81 | return false; 82 | memcpy(str->l.data, cstr, len); 83 | str->l.data[len] = 0; 84 | str->l.size = len; 85 | sso_string_long_set_cap(str, cap); 86 | } 87 | 88 | return true; 89 | } 90 | 91 | SSO_STRING_EXPORT bool string_init_size(String* str, const char* cstr, size_t len) { 92 | if(cstr == NULL) 93 | cstr = ""; 94 | 95 | SSO_STRING_ASSERT_ARG(str); 96 | SSO_STRING_ASSERT_BOUNDS(len <= strlen(cstr)); 97 | 98 | if (len <= SSO_STRING_MIN_CAP) { 99 | memcpy(str->s.data, cstr, len); 100 | str->s.data[len] = 0; 101 | 102 | sso_string_short_set_size(str, len); 103 | } else { 104 | size_t cap = sso_string_next_cap(0, len); 105 | str->l.data = malloc(cap + 1); 106 | if(!str->l.data) 107 | return false; 108 | memcpy(str->l.data, cstr, len); 109 | str->l.data[len] = 0; 110 | str->l.size = len; 111 | sso_string_long_set_cap(str, cap); 112 | } 113 | 114 | return true; 115 | } 116 | 117 | SSO_STRING_EXPORT size_t string_u8_codepoints(const String* str) { 118 | SSO_STRING_ASSERT_ARG(str); 119 | 120 | const unsigned char* data = (unsigned char*)string_data(str); 121 | size_t count = 0; 122 | while(*data) { 123 | count++; 124 | 125 | if(*data <= U8_SINGLE) 126 | data += 1; 127 | else if(*data < U8_DOUBLE) 128 | data += 2; 129 | else if(*data < U8_TRIPLE) 130 | data += 3; 131 | else 132 | data += 4; 133 | } 134 | 135 | return count; 136 | } 137 | 138 | SSO_STRING_EXPORT Char32 string_u8_get(const String* str, size_t index) { 139 | SSO_STRING_ASSERT_ARG(str); 140 | 141 | size_t size = string_size(str); 142 | if(index >= size) 143 | return '\0'; 144 | 145 | const unsigned char* data = (unsigned char*)string_data(str) + index; 146 | 147 | // TODO: Test for control characters (0x80 - 0xBF) 148 | 149 | if(*data <= U8_SINGLE) { 150 | return *data; 151 | } else if(*data < U8_DOUBLE) { 152 | return (((*data) & ~U8_DOUBLE) << 6) | ((*(data + 1)) & 0x3f); 153 | } else if(*data < U8_TRIPLE) { 154 | return (((*data) & ~U8_TRIPLE) << 12) | (((*(data + 1)) & 0x3f) << 6) | ((*(data + 2)) & 0x3f); 155 | } else { 156 | return (((*data) & ~U8_QUAD) << 18) | (((*(data + 1)) & 0x3f) << 12) | (((*(data + 2)) & 0x3f) << 6) | ((*(data + 3)) & 0x3f); 157 | } 158 | } 159 | 160 | SSO_STRING_EXPORT Char32 string_u8_get_with_size(const String* str, size_t index, int* out_size) { 161 | SSO_STRING_ASSERT_ARG(str); 162 | 163 | if(!out_size) 164 | return string_u8_get(str, index); 165 | 166 | size_t size = string_size(str); 167 | if(index >= size) { 168 | *out_size = 0; 169 | return '\0'; 170 | } 171 | 172 | const unsigned char* data = (unsigned char*)string_data(str) + index; 173 | 174 | if(*data <= U8_SINGLE) { 175 | *out_size = 1; 176 | return *data; 177 | } else if(*data < U8_DOUBLE) { 178 | *out_size = 2; 179 | return (((*data) & ~U8_DOUBLE) << 6) | ((*(data + 1)) & 0x3f); 180 | } else if(*data < U8_TRIPLE) { 181 | *out_size = 3; 182 | return (((*data) & ~U8_TRIPLE) << 12) | (((*(data + 1)) & 0x3f) << 6) | ((*(data + 2)) & 0x3f); 183 | } else { 184 | *out_size = 4; 185 | return (((*data) & ~U8_QUAD) << 18) | (((*(data + 1)) & 0x3f) << 12) | (((*(data + 2)) & 0x3f) << 6) | ((*(data + 3)) & 0x3f); 186 | } 187 | } 188 | 189 | SSO_STRING_EXPORT int string_u8_codepoint_size(const String* str, size_t index) { 190 | SSO_STRING_ASSERT_ARG(str); 191 | 192 | size_t size = string_size(str); 193 | if(index >= size) 194 | return 0; 195 | 196 | const char* data = string_data(str) + index; 197 | 198 | if((unsigned char)*data <= U8_SINGLE) 199 | return 1; 200 | else if((unsigned char)*data < U8_DOUBLE) 201 | return 2; 202 | else if((unsigned char)*data < U8_TRIPLE) 203 | return 3; 204 | else 205 | return 4; 206 | } 207 | 208 | static bool sso_string_u8_assure_codepoint_space( 209 | String* str, 210 | size_t index, 211 | int old_codepoint, 212 | int new_codepoint) 213 | { 214 | if(new_codepoint == old_codepoint) 215 | return true; 216 | 217 | int offset = new_codepoint - old_codepoint; 218 | size_t size = string_size(str); 219 | 220 | if(offset > 0) { 221 | if(!string_reserve(str, size + offset)) 222 | return false; 223 | } 224 | 225 | char* data = string_cstr(str); 226 | char* ptr = data; 227 | 228 | memmove(data + index + new_codepoint, data + index + old_codepoint, size - index - old_codepoint); 229 | ptr = data; 230 | sso_string_set_size(str, size + offset); 231 | data[size + offset] = '\0'; 232 | 233 | return true; 234 | } 235 | 236 | SSO_STRING_EXPORT bool string_u8_set(String* str, size_t index, Char32 value) { 237 | SSO_STRING_ASSERT_ARG(str); 238 | 239 | size_t size = string_size(str); 240 | 241 | assert(index <= string_size(str)); 242 | 243 | if(index == size) 244 | return string_u8_push_back(str, value); 245 | 246 | int old_codepoint = string_u8_codepoint_size(str, index); 247 | char* data; 248 | 249 | if(value < 0x80) { 250 | if(!sso_string_u8_assure_codepoint_space(str, index, old_codepoint, 1)) 251 | return false; 252 | 253 | data = string_cstr(str); 254 | data[index] = (char)value; 255 | 256 | } else if(value < 0x800) { 257 | if(!sso_string_u8_assure_codepoint_space(str, index, old_codepoint, 2)) 258 | return false; 259 | 260 | data = string_cstr(str); 261 | data[index] = 0xC0 | ((value >> 6) & 0x1F); 262 | data[index + 1] = 0x80 | (value & 0x3f); 263 | 264 | } else if(value < 0x10000) { 265 | if(!sso_string_u8_assure_codepoint_space(str, index, old_codepoint, 3)) 266 | return false; 267 | 268 | data = string_cstr(str); 269 | 270 | data[index] = 0xE0 | ((value >> 12) & 0xF); 271 | data[index + 1] = 0x80 | ((value >> 6) & 0x3F); 272 | data[index + 2] = 0x80 | (value & 0x3F); 273 | } else { 274 | assert(value <= 0x10FFFF); 275 | if(!sso_string_u8_assure_codepoint_space(str, index, old_codepoint, 4)) 276 | return false; 277 | 278 | data = string_cstr(str); 279 | 280 | data[index] = 0xF0 | ((value >> 18) & 0x7); 281 | data[index + 1] = 0x80 | ((value >> 12) & 0x3F); 282 | data[index + 2] = 0x80 | ((value >> 6) & 0x3F); 283 | data[index + 3] = 0x80 | (value & 0x3F); 284 | } 285 | 286 | return true; 287 | } 288 | 289 | SSO_STRING_EXPORT bool string_u8_is_null_or_whitespace(const String* str) { 290 | // These tables define the whitespace characters in the various codepoint sizes. 291 | 292 | if(string_is_null_or_empty(str)) 293 | return true; 294 | 295 | size_t size = string_size(str); 296 | const char* data = string_data(str); 297 | size_t i = 0; 298 | 299 | while(i < size) { 300 | int cp; 301 | Char32 next = string_u8_get_with_size(str, i, &cp); 302 | switch(next) { 303 | case 9: 304 | case 10: 305 | case 11: 306 | case 12: 307 | case 13: 308 | case 32: 309 | case 133: 310 | case 160: 311 | case 5760: 312 | case 6158: 313 | case 8192: 314 | case 8193: 315 | case 8194: 316 | case 8195: 317 | case 8196: 318 | case 8197: 319 | case 8198: 320 | case 8199: 321 | case 8200: 322 | case 8201: 323 | case 8202: 324 | case 8203: 325 | case 8204: 326 | case 8205: 327 | case 8232: 328 | case 8233: 329 | case 8287: 330 | case 8288: 331 | case 12288: 332 | case 65279: 333 | break; 334 | default: 335 | return false; 336 | } 337 | 338 | i += cp; 339 | } 340 | 341 | return true; 342 | } 343 | 344 | SSO_STRING_EXPORT bool sso_string_long_reserve(String* str, size_t reserve) { 345 | SSO_STRING_ASSERT_ARG(str); 346 | 347 | size_t current = sso_string_long_cap(str); 348 | if(reserve <= current) 349 | return true; 350 | 351 | reserve = sso_string_next_cap(current, reserve); 352 | void* buffer = realloc(str->l.data, reserve + 1); 353 | if(!buffer) 354 | return false; 355 | 356 | str->l.data = buffer; 357 | str->l.data[reserve] = 0; 358 | 359 | sso_string_long_set_cap(str, reserve); 360 | return true; 361 | } 362 | 363 | SSO_STRING_EXPORT int sso_string_short_reserve(String* str, size_t reserve) { 364 | SSO_STRING_ASSERT_ARG(str); 365 | 366 | if(reserve <= SSO_STRING_MIN_CAP) 367 | return SSO_STRING_SHORT_RESERVE_SUCCEED; 368 | 369 | reserve = sso_string_next_cap(SSO_STRING_MIN_CAP, reserve); 370 | char* data = malloc(sizeof(char) * (reserve + 1)); 371 | if(!data) 372 | return SSO_STRING_SHORT_RESERVE_FAIL; 373 | 374 | memmove(data, str->s.data, sso_string_short_size(str)); 375 | data[reserve] = 0; 376 | 377 | str->l.size = sso_string_short_size(str); 378 | str->l.data = data; 379 | 380 | sso_string_long_set_cap(str, reserve); 381 | 382 | return SSO_STRING_SHORT_RESERVE_RESIZE; 383 | } 384 | 385 | SSO_STRING_EXPORT void string_shrink_to_fit(String* str) { 386 | SSO_STRING_ASSERT_ARG(str); 387 | 388 | if(!sso_string_is_long(str)) 389 | return; 390 | 391 | size_t s = sso_string_long_size(str); 392 | 393 | if(s == sso_string_long_cap(str)) 394 | return; 395 | 396 | if(s <= SSO_STRING_MIN_CAP) { 397 | memmove(str->s.data, str->l.data, s); 398 | str->s.data[s] = 0; 399 | // This will clear the long flag. 400 | sso_string_short_set_size(str, s); 401 | } else { 402 | str->l.data = realloc(str->l.data, s + 1); 403 | str->l.data[s] = 0; 404 | sso_string_long_set_cap(str, s); 405 | } 406 | } 407 | 408 | SSO_STRING_EXPORT bool sso_string_insert_impl(String* str, const char* value, size_t index, size_t length) { 409 | SSO_STRING_ASSERT_ARG(str); 410 | SSO_STRING_ASSERT_ARG(value); 411 | 412 | size_t current_size = string_size(str); 413 | 414 | SSO_STRING_ASSERT_BOUNDS(current_size + length < STRING_MAX); 415 | 416 | if(!string_reserve(str, current_size + length)) 417 | return false; 418 | char* data = string_cstr(str); 419 | data[current_size + length] = 0; 420 | if(index != current_size) 421 | memmove(data + index + length, data + index, current_size - index); 422 | memmove(data + index, value, length); 423 | sso_string_set_size(str, current_size + length); 424 | return true; 425 | } 426 | 427 | 428 | 429 | SSO_STRING_EXPORT void string_erase(String* str, size_t index, size_t count) { 430 | SSO_STRING_ASSERT_ARG(str); 431 | 432 | size_t current_size = string_size(str); 433 | assert(index + count <= current_size); 434 | char* data = string_cstr(str); 435 | memmove(data + index, data + index + count, current_size - index - count); 436 | current_size -= count; 437 | if(sso_string_is_long(str)) { 438 | sso_string_long_set_size(str, current_size); 439 | str->l.data[current_size] = 0; 440 | } else { 441 | sso_string_short_set_size(str, current_size); 442 | str->s.data[current_size] = 0; 443 | } 444 | } 445 | 446 | SSO_STRING_EXPORT bool string_push_back(String* str, char value) { 447 | SSO_STRING_ASSERT_ARG(str); 448 | 449 | size_t size; 450 | char* data; 451 | 452 | if(sso_string_is_long(str)) { 453 | size = sso_string_long_size(str) + 1; 454 | if(!sso_string_long_reserve(str, size)) 455 | return false; 456 | sso_string_long_set_size(str, size); 457 | data = str->l.data; 458 | } else { 459 | size = sso_string_short_size(str) + 1; 460 | switch(sso_string_short_reserve(str, size)) { 461 | case SSO_STRING_SHORT_RESERVE_RESIZE: 462 | sso_string_long_set_size(str, size); 463 | data = str->l.data; 464 | break; 465 | case SSO_STRING_SHORT_RESERVE_SUCCEED: 466 | sso_string_short_set_size(str, size); 467 | data = str->s.data; 468 | break; 469 | case SSO_STRING_SHORT_RESERVE_FAIL: 470 | return false; 471 | } 472 | } 473 | data[size-1] = value; 474 | data[size] = 0; 475 | return true; 476 | } 477 | 478 | SSO_STRING_EXPORT bool string_u8_push_back(String* str, Char32 value) { 479 | SSO_STRING_ASSERT_ARG(str); 480 | 481 | size_t size = string_size(str); 482 | char* data; 483 | 484 | if(value < 0x80) { 485 | if(!string_reserve(str, size + 1)) 486 | return false; 487 | data = string_cstr(str); 488 | data[size++] = (char)value; 489 | } else if(value < 0x800) { 490 | if(!string_reserve(str, size + 2)) 491 | return false; 492 | 493 | data = string_cstr(str); 494 | data[size++] = 0xC0 | ((value >> 6) & 0x1F); 495 | data[size++] = 0x80 | (value & 0x3f); 496 | 497 | } else if(value < 0x10000) { 498 | if(!string_reserve(str, size + 3)) 499 | return false; 500 | 501 | data = string_cstr(str); 502 | 503 | data[size++] = 0xE0 | ((value >> 12) & 0xF); 504 | data[size++] = 0x80 | ((value >> 6) & 0x3F); 505 | data[size++] = 0x80 | (value & 0x3F); 506 | } else { 507 | assert(value <= 0x10FFFF); 508 | if(!string_reserve(str, size + 4)) 509 | return false; 510 | 511 | data = string_cstr(str); 512 | 513 | data[size++] = 0xF0 | ((value >> 18) & 0x7); 514 | data[size++] = 0x80 | ((value >> 12) & 0x3F); 515 | data[size++] = 0x80 | ((value >> 6) & 0x3F); 516 | data[size++] = 0x80 | (value & 0x3F); 517 | } 518 | 519 | sso_string_set_size(str, size); 520 | data[size] = '\0'; 521 | return true; 522 | } 523 | 524 | SSO_STRING_EXPORT char string_pop_back(String* str) { 525 | SSO_STRING_ASSERT_ARG(str); 526 | 527 | size_t size; 528 | if(sso_string_is_long(str)) { 529 | size = sso_string_long_size(str) - 1; 530 | if(size == -1) 531 | return 0; 532 | sso_string_long_set_size(str, size); 533 | } else { 534 | size = sso_string_short_size(str) - 1; 535 | if(size == -1) 536 | return 0; 537 | sso_string_short_set_size(str, size); 538 | } 539 | char* data = string_cstr(str); 540 | char result = data[size]; 541 | data[size] = 0; 542 | return result; 543 | } 544 | 545 | SSO_STRING_EXPORT Char32 string_u8_pop_back(String* str) { 546 | SSO_STRING_ASSERT_ARG(str); 547 | 548 | size_t size = string_size(str); 549 | if(size == 0) 550 | return 0; 551 | 552 | char* data = string_cstr(str); 553 | int shift = 0; 554 | Char32 result = 0; 555 | 556 | unsigned char part = (unsigned char)data[--size]; 557 | while((part & 0xC0) == 0x80) { 558 | unsigned int stripped_part = (~0x80 & part); 559 | unsigned int shifted = (stripped_part << (6 * shift++)); 560 | result |= shifted; 561 | part = (unsigned char)data[--size]; 562 | } 563 | 564 | if(part >= 0xF0) 565 | result |= (part & 0x07) << 18; 566 | else if(part >= 0xE0) 567 | result |= (part & 0x0F) << 12; 568 | else if(part >= 0xC0) 569 | result |= (part & 0x1F) << 6; 570 | else 571 | result |= part; 572 | 573 | sso_string_set_size(str, size); 574 | data[size] = 0; 575 | 576 | return result; 577 | } 578 | 579 | SSO_STRING_EXPORT bool sso_string_append_impl(String* str, const char* value, size_t length) { 580 | SSO_STRING_ASSERT_ARG(str); 581 | SSO_STRING_ASSERT_ARG(value); 582 | 583 | size_t size = string_size(str); 584 | if(!string_reserve(str, size + length)) 585 | return false; 586 | char* data = string_cstr(str); 587 | memmove(data + size, value, length); 588 | data[size + length] = 0; 589 | sso_string_set_size(str, size + length); 590 | return true; 591 | } 592 | 593 | SSO_STRING_EXPORT bool sso_string_replace_impl(String* str, size_t pos, size_t count, const char* value, size_t length) { 594 | if(length == 0) 595 | string_erase(str, pos, count); 596 | 597 | SSO_STRING_ASSERT_ARG(str); 598 | SSO_STRING_ASSERT_ARG(value); 599 | 600 | size_t size = string_size(str); 601 | 602 | SSO_STRING_ASSERT_BOUNDS(pos + count <= size); 603 | 604 | char* data; 605 | if(count == length) { 606 | data = (char*)string_cstr(str); 607 | memmove(data + pos, value, length); 608 | } else if(count > length) { 609 | data = (char*)string_cstr(str); 610 | size_t offset = pos + count; 611 | memmove(data + pos + length, data + offset, size - offset); 612 | memmove(data + pos, value, length); 613 | size_t end = pos + length + (size - offset); 614 | data[end] = 0; 615 | if(sso_string_is_long(str)) 616 | sso_string_long_set_size(str, end); 617 | else 618 | sso_string_short_set_size(str, end); 619 | } else { 620 | size_t offset = length - count; 621 | 622 | if(!string_reserve(str, size + offset)) 623 | return false; 624 | 625 | size_t cap = string_capacity(str); 626 | data = (char*)string_cstr(str); 627 | memmove(data + pos + length, data + pos + count, size - (pos + count)); 628 | memmove(data + pos, value, length); 629 | data[size+offset] = 0; 630 | if(sso_string_is_long(str)) 631 | sso_string_long_set_size(str, size + offset); 632 | else 633 | sso_string_short_set_size(str, size + offset); 634 | } 635 | 636 | return true; 637 | } 638 | 639 | SSO_STRING_EXPORT bool string_resize(String* str, size_t count, char ch) { 640 | SSO_STRING_ASSERT_ARG(str); 641 | SSO_STRING_ASSERT_BOUNDS(count < STRING_MAX); 642 | 643 | if(!string_reserve(str, count)) 644 | return false; 645 | 646 | char* data = string_cstr(str); 647 | 648 | size_t size; 649 | 650 | if(sso_string_is_long(str)) { 651 | size = sso_string_long_size(str); 652 | sso_string_long_set_size(str, count); 653 | } else { 654 | size = sso_string_short_size(str); 655 | sso_string_short_set_size(str, count); 656 | } 657 | 658 | for(size_t i = size; i < count; i++) 659 | data[i] = ch; 660 | 661 | data[count] = 0; 662 | return true; 663 | } 664 | 665 | SSO_STRING_EXPORT size_t sso_string_find_impl(const String* str, size_t pos, const char* value, size_t length) { 666 | SSO_STRING_ASSERT_ARG(str); 667 | SSO_STRING_ASSERT_ARG(value); 668 | 669 | if(pos + length > string_size(str)) 670 | return SIZE_MAX; 671 | 672 | const char* data = string_data(str); 673 | char* result = strstr(data + pos, value); 674 | if(result == NULL) 675 | return SIZE_MAX; 676 | return result - data; 677 | } 678 | 679 | SSO_STRING_EXPORT size_t sso_string_find_substr_impl(const String* str, size_t pos, const char* value, size_t length) { 680 | SSO_STRING_ASSERT_ARG(str); 681 | SSO_STRING_ASSERT_ARG(value); 682 | 683 | // If the length of the find string is bigger than the search string, 684 | // early exit. 685 | size_t size = string_size(str); 686 | if(pos + length > size) 687 | return SIZE_MAX; 688 | 689 | size -= length; 690 | 691 | const char* data = string_data(str); 692 | for(size_t i = pos; i < size; i++) { 693 | if(strncmp(data + i, value, length) == 0) 694 | return i; 695 | } 696 | 697 | return SIZE_MAX; 698 | } 699 | 700 | SSO_STRING_EXPORT size_t sso_string_rfind_impl(const String* str, size_t pos, const char* value, size_t length) { 701 | SSO_STRING_ASSERT_ARG(str); 702 | SSO_STRING_ASSERT_ARG(value); 703 | 704 | // If the length of the find string is bigger than the search string, 705 | // early exit. 706 | size_t size = string_size(str); 707 | 708 | if(pos > size || length > size) 709 | return SIZE_MAX; 710 | 711 | if(pos < length) 712 | pos = length; 713 | 714 | // Turn the negative offset into a normal offset. 715 | pos = size - pos; 716 | 717 | const char* data = string_data(str); 718 | 719 | do { 720 | if (strncmp(data + pos, value, length) == 0) 721 | return pos; 722 | } 723 | while (pos-- != 0); 724 | 725 | return SIZE_MAX; 726 | } 727 | 728 | // Todo: Attempt to use intrinsic bswap 729 | 730 | static inline void string_reverse_bytes_impl(char* start, char* end) { 731 | while (start < end) { 732 | char tmp = *start; 733 | *start++ = *end; 734 | *end-- = tmp; 735 | } 736 | } 737 | 738 | SSO_STRING_EXPORT void string_reverse_bytes(String* str) { 739 | SSO_STRING_ASSERT_ARG(str); 740 | 741 | size_t size = string_size(str); 742 | char* start = string_cstr(str); 743 | char* end = start + size - 1; 744 | 745 | string_reverse_bytes_impl(start, end); 746 | } 747 | 748 | SSO_STRING_EXPORT void string_u8_reverse_codepoints(String* str) { 749 | SSO_STRING_ASSERT_ARG(str); 750 | 751 | size_t size = string_size(str); 752 | char* data; 753 | char* start = data = string_cstr(str); 754 | char* end = start + size - 1; 755 | char tmp; 756 | 757 | // Reverse string bytewise 758 | string_reverse_bytes_impl(start, end); 759 | 760 | start = NULL; 761 | 762 | // Todo: Speed this up 763 | // Fix up utf8 characters. 764 | for (size_t i = 0; i < size; i++) { 765 | switch((*(data + i)) & 0xC0) { 766 | case 0xC0: 767 | string_reverse_bytes_impl(start, data + i); 768 | start = NULL; 769 | break; 770 | case 0x80: 771 | if(!start) 772 | start = data + i; 773 | break; 774 | } 775 | } 776 | } 777 | 778 | SSO_STRING_EXPORT bool string_join( 779 | String* str, 780 | const String* separator, 781 | const String* values, 782 | size_t value_count) 783 | { 784 | if(value_count == 0) 785 | return true; 786 | 787 | SSO_STRING_ASSERT_ARG(str); 788 | SSO_STRING_ASSERT_ARG(separator); 789 | SSO_STRING_ASSERT_ARG(values); 790 | 791 | // Put the joined values in a temporary variable to avoid 792 | // altering the result string if the operation fails. 793 | 794 | // Append the temp string to the result (str) at the end 795 | // to finalize the operation. 796 | bool result = false; 797 | String temp = string_create(""); 798 | 799 | if(!string_append_string(&temp, values)) 800 | goto cleanup; 801 | 802 | for(size_t i = 1; i < value_count; i++) { 803 | if(!string_append_string(&temp, separator)) 804 | goto cleanup; 805 | 806 | if(!string_append_string(&temp, values + i)) 807 | goto cleanup; 808 | } 809 | 810 | if(!string_append_string(str, &temp)) 811 | goto cleanup; 812 | 813 | result = true; 814 | 815 | cleanup: 816 | string_free_resources(&temp); 817 | 818 | return result; 819 | } 820 | 821 | SSO_STRING_EXPORT bool string_join_refs( 822 | String* str, 823 | const String* separator, 824 | const String** values, 825 | size_t value_count) 826 | { 827 | if(value_count == 0) 828 | return true; 829 | 830 | SSO_STRING_ASSERT_ARG(str); 831 | SSO_STRING_ASSERT_ARG(separator); 832 | SSO_STRING_ASSERT_ARG(values); 833 | 834 | // Put the joined values in a temporary variable to avoid 835 | // altering the result string if the operation fails. 836 | 837 | // Append the temp string to the result (str) at the end 838 | // to finalize the operation. 839 | bool result = false; 840 | String temp = string_create(""); 841 | 842 | if(!string_append_string(&temp, values[0])) 843 | goto cleanup; 844 | 845 | for(size_t i = 1; i < value_count; i++) { 846 | if(!string_append_string(&temp, separator)) 847 | goto cleanup; 848 | 849 | if(!string_append_string(&temp, values[i])) 850 | goto cleanup; 851 | } 852 | 853 | if(!string_append_string(str, &temp)) 854 | goto cleanup; 855 | 856 | result = true; 857 | 858 | cleanup: 859 | string_free_resources(&temp); 860 | 861 | return result; 862 | } 863 | 864 | SSO_STRING_EXPORT String* string_split( 865 | const String* str, 866 | const String* separator, 867 | String* results, 868 | int results_count, 869 | int* results_filled, 870 | bool skip_empty, 871 | bool init_results) 872 | { 873 | // Make sure the required inputs aren't NULL. 874 | SSO_STRING_ASSERT_ARG(str); 875 | SSO_STRING_ASSERT_ARG(separator); 876 | 877 | // results only needs to be initialized when results_count is > 0. 878 | // On 0, it doesn't matter what state it is. 879 | // On a negative number, results will be allocated by the function. 880 | if(results_count > 0) 881 | SSO_STRING_ASSERT_ARG(results); 882 | 883 | // Nothing needs to happen if there is no room for results. Return immediately. 884 | if(results_count == 0) { 885 | *results_filled = 0; 886 | return results; 887 | } 888 | 889 | // Determine if the results array needs to be allocated. If so, create an 890 | // initial buffer that has space for two items. This is the starting space since 891 | // splitting small items tends to be pretty common (e.g. an array of key-value pairs). 892 | bool allocate_results = results_count < 0; 893 | if(allocate_results) { 894 | results_count = 2; 895 | results = malloc(results_count * sizeof(*results)); 896 | if(!results) 897 | return NULL; 898 | 899 | // The results always have to be initialized by this function if the results array 900 | // was also created by it. UB otherwise. 901 | init_results = true; 902 | } 903 | 904 | int count = 0; 905 | size_t start = 0; 906 | size_t size = string_size(str); 907 | 908 | while(true) { 909 | // Find the next instance of the separator. If the index is SIZE_MAX, 910 | // the function reached the end of the string without finding it. 911 | size_t next = string_find_string(str, start, separator); 912 | if(next == SIZE_MAX) 913 | next = size; 914 | 915 | size_t copy_length = next - start; 916 | if(copy_length != 0 || !skip_empty) { 917 | // Add the string segment to the results array. If the value fails to be added, 918 | // there was an allocation error, so just break out of the function right away. 919 | if(init_results) { 920 | if(!string_init_size(results + count, string_data(str) + start, copy_length)) 921 | goto error; 922 | } else { 923 | if(!string_append_string_part(results + count, str, start, copy_length)) 924 | goto error; 925 | } 926 | 927 | if(++count == results_count) { 928 | if(allocate_results) { 929 | results_count *= 2; 930 | void* array = realloc(results, results_count * sizeof(*results)); 931 | if(!array) 932 | goto error; 933 | 934 | results = array; 935 | } else { 936 | *results_filled = count; 937 | return results; 938 | } 939 | } 940 | } 941 | 942 | start = next + string_size(separator); 943 | 944 | // This indicates that the end of the string has been reached, and 945 | // that all relevant substrings have been copied into the results array. 946 | if(start >= size) { 947 | *results_filled = count; 948 | return results; 949 | } 950 | } 951 | 952 | error: 953 | if(allocate_results) 954 | free(results); 955 | 956 | *results_filled = 0; 957 | return NULL; 958 | } 959 | 960 | SSO_STRING_EXPORT String** string_split_refs( 961 | const String* str, 962 | const String* separator, 963 | String** results, 964 | int results_count, 965 | int* results_filled, 966 | bool skip_empty, 967 | bool init_results) 968 | { 969 | // Make sure the required inputs aren't NULL. 970 | SSO_STRING_ASSERT_ARG(str); 971 | SSO_STRING_ASSERT_ARG(separator); 972 | 973 | // results only needs to be initialized when results_count is > 0. 974 | // On 0, it doesn't matter what state it is. 975 | // On a negative number, results will be allocated by the function. 976 | if(results_count > 0) 977 | SSO_STRING_ASSERT_ARG(results); 978 | 979 | // Nothing needs to happen if there is no room for results. Return immediately. 980 | if(results_count == 0) { 981 | *results_filled = 0; 982 | return results; 983 | } 984 | 985 | // Determine if the results array needs to be allocated. If so, create an 986 | // initial buffer that has space for two items. This is the starting space since 987 | // splitting small items tends to be pretty common (e.g. an array of key-value pairs). 988 | bool allocate_results = results_count < 0; 989 | if(allocate_results) { 990 | results_count = 2; 991 | results = malloc(results_count * sizeof(*results)); 992 | if(!results) 993 | return NULL; 994 | 995 | // The results always have to be initialized by this function if the results array 996 | // was also created by it. UB otherwise. 997 | init_results = true; 998 | } 999 | 1000 | int count = 0; 1001 | size_t start = 0; 1002 | size_t size = string_size(str); 1003 | 1004 | while(true) { 1005 | // Find the next instance of the separator. If the index is SIZE_MAX, 1006 | // the function reached the end of the string without finding it. 1007 | size_t next = string_find_string(str, start, separator); 1008 | if(next == SIZE_MAX) 1009 | next = size; 1010 | 1011 | size_t copy_length = next - start; 1012 | if(copy_length != 0 || !skip_empty) { 1013 | // Add the string segment to the results array. If the value fails to be added, 1014 | // there was an allocation error, so just break out of the function right away. 1015 | if(init_results) { 1016 | String* slice = string_create_ref(""); 1017 | if(!slice) 1018 | goto error; 1019 | results[count] = slice; 1020 | } 1021 | 1022 | if(!string_append_string_part(results[count], str, start, copy_length)) 1023 | goto error; 1024 | 1025 | if(++count == results_count) { 1026 | if(allocate_results) { 1027 | results_count *= 2; 1028 | void* array = realloc(results, results_count * sizeof(*results)); 1029 | if(!array) 1030 | goto error; 1031 | 1032 | results = array; 1033 | } else { 1034 | *results_filled = count; 1035 | return results; 1036 | } 1037 | } 1038 | } 1039 | 1040 | start = next + string_size(separator); 1041 | 1042 | // This indicates that the end of the string has been reached, and 1043 | // that all relevant substrings have been copied into the results array. 1044 | if(start >= size) { 1045 | *results_filled = count; 1046 | return results; 1047 | } 1048 | } 1049 | 1050 | error: 1051 | if(allocate_results) { 1052 | for(size_t i = 0; i < count; i++) 1053 | string_free(results[i]); 1054 | 1055 | free(results); 1056 | } 1057 | 1058 | *results_filled = 0; 1059 | return NULL; 1060 | } 1061 | 1062 | SSO_STRING_EXPORT String* string_format_string(String* result, const String* format, ...) { 1063 | va_list argp; 1064 | 1065 | va_start(argp, format); 1066 | 1067 | result = string_format_args_cstr(result, string_data(format), argp); 1068 | 1069 | va_end(argp); 1070 | 1071 | return result; 1072 | } 1073 | 1074 | SSO_STRING_EXPORT String* string_format_cstr(String* result, const char* format, ...) { 1075 | va_list argp; 1076 | 1077 | va_start(argp, format); 1078 | 1079 | result = string_format_args_cstr(result, format, argp); 1080 | 1081 | va_end(argp); 1082 | 1083 | return result; 1084 | } 1085 | 1086 | SSO_STRING_EXPORT String* string_format_args_string(String* result, const String* format, va_list argp) { 1087 | return string_format_args_cstr(result, string_data(format), argp); 1088 | } 1089 | 1090 | SSO_STRING_EXPORT String* string_format_args_cstr(String* result, const char* format, va_list argp) { 1091 | #define SSO_STRING_FORMAT_BUFFER_SIZE 256 1092 | 1093 | #ifdef SSO_THREAD_LOCAL 1094 | 1095 | static SSO_THREAD_LOCAL char buffer[SSO_STRING_FORMAT_BUFFER_SIZE]; 1096 | 1097 | #else 1098 | 1099 | char buffer[SSO_STRING_FORMAT_BUFFER_SIZE]; 1100 | 1101 | #endif 1102 | 1103 | SSO_STRING_ASSERT_ARG(format); 1104 | 1105 | size_t original_size = SIZE_MAX; 1106 | if(!result) { 1107 | result = string_create_ref(""); 1108 | if(!result) 1109 | return NULL; 1110 | 1111 | } else { 1112 | original_size = string_size(result); 1113 | } 1114 | 1115 | // argp may have to be used again (if the result of vsnprint is > 256), so 1116 | // make a copy of it to be used with the initial format. 1117 | va_list copy; 1118 | va_copy(copy, argp); 1119 | 1120 | int written = vsnprintf(buffer, SSO_STRING_FORMAT_BUFFER_SIZE, format, copy); 1121 | va_end(copy); 1122 | 1123 | if(written < 0) { 1124 | goto error; 1125 | } else if(written < SSO_STRING_FORMAT_BUFFER_SIZE) { 1126 | if(!string_append_cstr(result, buffer))\ 1127 | goto error; 1128 | return result; 1129 | } else { 1130 | if(!string_reserve(result, string_size(result) + written)) 1131 | goto error; 1132 | 1133 | written = vsnprintf( 1134 | string_cstr(result), 1135 | written + 1, 1136 | format, 1137 | argp); 1138 | 1139 | if(written < 0) 1140 | goto error; 1141 | 1142 | size_t new_size = original_size == SIZE_MAX ? written : original_size + written; 1143 | 1144 | // The string should always be long at this point, but do the check just in case. 1145 | if(sso_string_is_long(result)) 1146 | sso_string_long_set_size(result, new_size); 1147 | else 1148 | sso_string_short_set_size(result, new_size); 1149 | } 1150 | 1151 | return result; 1152 | 1153 | error: 1154 | if(original_size == SIZE_MAX) 1155 | string_free_resources(result); 1156 | else 1157 | string_cstr(result)[original_size] = '\0'; 1158 | 1159 | return NULL; 1160 | } 1161 | 1162 | #if SSO_STRING_SHIFT == 24 1163 | 1164 | #define SSO_FNV_PRIME 0x01000193 1165 | #define SSO_FNV_OFFSET 0x811c9dc5 1166 | 1167 | #elif SSO_STRING_SHIFT == 56 1168 | 1169 | #define SSO_FNV_PRIME 0x00000100000001B3 1170 | #define SSO_FNV_OFFSET 0xcbf29ce484222325 1171 | 1172 | #endif 1173 | 1174 | SSO_STRING_EXPORT size_t string_hash(String* str) { 1175 | const unsigned char* data = string_data(str); 1176 | size_t hash = SSO_FNV_OFFSET; 1177 | while(*data != 0) 1178 | hash = (*(data++) ^ hash) * SSO_FNV_PRIME; 1179 | 1180 | return hash; 1181 | } -------------------------------------------------------------------------------- /tests/meson.build: -------------------------------------------------------------------------------- 1 | if check_location != '' 2 | check_lib = check_location + '/lib' 3 | 4 | compat = c_comp.find_library('compat', 5 | required: true, 6 | dirs: check_lib) 7 | 8 | check = c_comp.find_library('check', 9 | required: true, 10 | dirs: check_lib) 11 | 12 | deps = [compat, check] 13 | else 14 | check = c_comp.find_library('check', 15 | required: true) 16 | 17 | deps = [check] 18 | endif 19 | 20 | link_args = [] 21 | 22 | if c_comp.get_id() == 'msvc' and get_option('buildtype') == 'release' 23 | link_args += '/NODEFAULTLIB:MSVCRTD' 24 | endif 25 | 26 | 27 | string_tests = executable('string_tests', 28 | 'tests.c', 29 | include_directories: test_inc, 30 | dependencies: deps, 31 | link_with: sso_string_shared, 32 | link_args: link_args 33 | ) 34 | 35 | test('String Tests', string_tests) -------------------------------------------------------------------------------- /tests/tests.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "../include/sso_string.h" 7 | 8 | static String small; 9 | static String large; 10 | 11 | #define HELLO "hello" 12 | #define HELLO_SIZE 5 13 | #define KANA "こんにちは" 14 | #define KANA_SIZE 15 15 | #define KANA_WIDE L"\u3053\u3093\u306B\u3061\x306F"; 16 | #define ALPHABET "abcdefghijklmnopqrstuvwxyz" 17 | #define ALPHABET_SIZE 20 18 | #define MIXED "hこeんlいlちoは" 19 | #define MIXED_SIZE 20 20 | #define ALL_CODEPOINTS { 0x0001, 0x007F, 0x00080, 0x007FF, 0x00800, 0x0FFFF, 0x10000, 0x10FFFF } 21 | #define ALL_CODEPOINTS_SIZE 20 22 | 23 | #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) 24 | 25 | static void string_start(void) { 26 | string_init(&small, "hello"); 27 | string_init(&large, ALPHABET); 28 | } 29 | 30 | static void string_reset(void) { 31 | string_free_resources(&small); 32 | string_free_resources(&large); 33 | } 34 | 35 | START_TEST(string_small_has_small_flag) { 36 | String str = string_create(""); 37 | ck_assert(!sso_string_is_long(&str)); 38 | string_free_resources(&str); 39 | } 40 | END_TEST 41 | 42 | START_TEST(string_long_has_long_flag) { 43 | String str = string_create(ALPHABET); 44 | ck_assert(sso_string_is_long(&str)); 45 | string_free_resources(&str); 46 | } 47 | END_TEST 48 | 49 | START_TEST(string_switches_size) { 50 | // This test not only checks that the string 51 | // switches size correctly, but also that 52 | // the capacity behaviour is the same for small strings 53 | // and large strings. 54 | 55 | String str = string_create(""); 56 | 57 | int cap = string_capacity(&str); 58 | 59 | while(string_size(&str) <= cap) { 60 | ck_assert(!sso_string_is_long(&str)); 61 | string_push_back(&str, 'a'); 62 | } 63 | 64 | ck_assert(sso_string_is_long(&str)); 65 | 66 | cap = string_capacity(&str); 67 | 68 | while(string_size(&str) <= cap) { 69 | ck_assert(string_capacity(&str) == cap); 70 | string_push_back(&str, 'b'); 71 | } 72 | 73 | ck_assert(string_capacity(&str) != cap); 74 | 75 | cap = string_capacity(&str); 76 | 77 | while(string_size(&str) <= cap) { 78 | ck_assert(string_capacity(&str) == cap); 79 | string_push_back(&str, 'c'); 80 | } 81 | 82 | ck_assert(string_capacity(&str) != cap); 83 | string_free_resources(&str); 84 | } 85 | END_TEST 86 | 87 | START_TEST(string_is_correct_size) { 88 | // This test just makes sure that the string does not take up 89 | // any more space with the short string optimization than it 90 | // would with a naive implementation 91 | ck_assert(sizeof(String) == sizeof(size_t) * 2 + sizeof(char*)); 92 | } 93 | END_TEST 94 | 95 | START_TEST(string_init_copies_cstr) { 96 | char value[] = "moo"; 97 | String str = string_create(value); 98 | 99 | ck_assert(value != string_data(&str)); 100 | string_free_resources(&str); 101 | } 102 | END_TEST 103 | 104 | START_TEST(string_small_data) { 105 | ck_assert(strcmp(string_data(&small), HELLO) == 0); 106 | } 107 | END_TEST 108 | 109 | START_TEST(string_large_data) { 110 | ck_assert(strcmp(string_data(&large), ALPHABET) == 0); 111 | } 112 | END_TEST 113 | 114 | START_TEST(string_small_cstr) { 115 | ck_assert(strcmp(string_cstr(&small), HELLO) == 0); 116 | 117 | // The reference to cstr and data should be the same 118 | // but data is an immutable view. 119 | ck_assert(string_cstr(&small) == string_data(&small)); 120 | } 121 | END_TEST 122 | 123 | START_TEST(string_large_cstr) { 124 | ck_assert(strcmp(string_cstr(&large), ALPHABET) == 0); 125 | 126 | // The reference to cstr and data should be the same 127 | // but data is an immutable view. 128 | ck_assert(string_cstr(&large) == string_data(&large)); 129 | } 130 | END_TEST 131 | 132 | START_TEST(string_small_size) { 133 | ck_assert(string_size(&small) == strlen(HELLO)); 134 | } 135 | END_TEST 136 | 137 | START_TEST(string_large_size) { 138 | ck_assert(string_size(&large) == strlen(ALPHABET)); 139 | } 140 | END_TEST 141 | 142 | START_TEST(string_size_dynamic) { 143 | String str = string_create(""); 144 | for(int i = 0; i < 100; i++) { 145 | ck_assert(string_size(&str) == i); 146 | string_push_back(&str, 'a'); 147 | } 148 | string_free_resources(&str); 149 | } 150 | END_TEST 151 | 152 | START_TEST(string_size_utf8) { 153 | String str = string_create(KANA); 154 | ck_assert(string_size(&str) == KANA_SIZE); 155 | ck_assert(string_size(&str) == strlen(string_data(&str))); 156 | 157 | string_free_resources(&str); 158 | 159 | 160 | string_init(&str, ""); 161 | Char32 all_codepoints[] = ALL_CODEPOINTS; 162 | for(int i = 0; i < ARRAY_SIZE(all_codepoints); i++) 163 | string_u8_push_back(&str, all_codepoints[i]); 164 | 165 | ck_assert(string_size(&str) == ALL_CODEPOINTS_SIZE); 166 | ck_assert(string_size(&str) == strlen(string_data(&str))); 167 | string_free_resources(&str); 168 | } 169 | END_TEST 170 | 171 | START_TEST(string_u8_codepoints_ascii) { 172 | ck_assert(string_u8_codepoints(&small) == strlen(HELLO)); 173 | } 174 | END_TEST 175 | 176 | START_TEST(string_u8_codepoints_utf8) { 177 | String str = string_create(KANA); 178 | ck_assert(string_u8_codepoints(&str) == 5); 179 | string_free_resources(&str); 180 | } 181 | END_TEST 182 | 183 | START_TEST(string_u8_codepoints_all) { 184 | String str = string_create(""); 185 | Char32 all_codepoints[] = ALL_CODEPOINTS; 186 | for(int i = 0; i < ARRAY_SIZE(all_codepoints); i++) 187 | string_u8_push_back(&str, all_codepoints[i]); 188 | 189 | ck_assert(string_u8_codepoints(&str) == ARRAY_SIZE(all_codepoints)); 190 | string_free_resources(&str); 191 | } 192 | END_TEST 193 | 194 | START_TEST(string_get_all) { 195 | String kana = string_create(KANA); 196 | String strings[3] = { small, large, kana }; 197 | for(int i = 0; i < 3; i++) { 198 | String* str = strings + i; 199 | const char* data = string_data(str); 200 | for(int j = 0; j < string_size(str); j++) 201 | ck_assert(string_get(str, j) == data[j]); 202 | } 203 | string_free_resources(&kana); 204 | } 205 | END_TEST 206 | 207 | START_TEST(string_u8_get_ascii) { 208 | String strings[2] = { small, large }; 209 | for(int i = 2; i < 2; i++) { 210 | String* str = strings + i; 211 | const char* data = string_data(str); 212 | for(int j = 0; j < string_size(str); j++) 213 | ck_assert(string_get(str, j) == data[j]); 214 | } 215 | } 216 | END_TEST 217 | 218 | START_TEST(string_u8_get_utf8) { 219 | String str = string_create(KANA); 220 | wchar_t wide[] = KANA_WIDE; 221 | for(int i = 0; i * 3 < string_size(&str); i++) { 222 | Char32 kana = string_u8_get(&str, i * 3); 223 | ck_assert(kana == wide[i]); 224 | } 225 | string_free_resources(&str); 226 | } 227 | END_TEST 228 | 229 | START_TEST(string_u8_get_all) { 230 | Char32 all_codepoints[] = ALL_CODEPOINTS; 231 | String str = string_create(""); 232 | 233 | for(int i = 0; i < ARRAY_SIZE(all_codepoints); i++) 234 | ck_assert(string_u8_push_back(&str, all_codepoints[i])); 235 | 236 | int size = 1; 237 | int index = 0; 238 | for(int i = 0; i < ARRAY_SIZE(all_codepoints); i++) { 239 | Char32 character = string_u8_get(&str, index); 240 | index += size; 241 | if(i % 2 == 1) 242 | size++; 243 | } 244 | string_free_resources(&str); 245 | } 246 | END_TEST 247 | 248 | START_TEST(string_u8_get_with_size_ascii) { 249 | String strings[2] = { small, large }; 250 | for(int i = 2; i < 2; i++) { 251 | String* str = strings + i; 252 | const char* data = string_data(str); 253 | int index = 0; 254 | int size; 255 | Char32 current; 256 | do { 257 | current = string_u8_get_with_size(str, index, &size); 258 | ck_assert(current == data[index]); 259 | index += size; 260 | } 261 | while(current); 262 | } 263 | } 264 | END_TEST 265 | 266 | START_TEST(string_u8_get_with_size_utf8) { 267 | String str = string_create(KANA); 268 | wchar_t wide[] = KANA_WIDE; 269 | int index = 0; 270 | int size; 271 | Char32 current; 272 | do { 273 | current = string_u8_get_with_size(&str, index, &size); 274 | ck_assert(current == wide[index / 3]); 275 | index += size; 276 | } 277 | while(current); 278 | string_free_resources(&str); 279 | } 280 | END_TEST 281 | 282 | START_TEST(string_u8_get_with_size_all) { 283 | Char32 all_codepoints[] = ALL_CODEPOINTS; 284 | String str = string_create(""); 285 | 286 | for(int i = 0; i < ARRAY_SIZE(all_codepoints); i++) 287 | ck_assert(string_u8_push_back(&str, all_codepoints[i])); 288 | 289 | int index = 0; 290 | int count = 0; 291 | int expected_size = 1; 292 | int size; 293 | Char32 current; 294 | do { 295 | current = string_u8_get_with_size(&str, index, &size); 296 | index += size; 297 | if(current) { 298 | ck_assert(current == all_codepoints[count]); 299 | ck_assert(size == expected_size); 300 | count++; 301 | if(count % 2 == 0) 302 | expected_size++; 303 | } 304 | } 305 | while(current); 306 | 307 | ck_assert(count == ARRAY_SIZE(all_codepoints)); 308 | string_free_resources(&str); 309 | } 310 | END_TEST 311 | 312 | START_TEST(string_u8_codepoint_size_ascii) { 313 | String strings[2] = { small, large }; 314 | for(int i = 2; i < 2; i++) { 315 | String* str = strings + i; 316 | int index = 0; 317 | int count = 0; 318 | int size; 319 | do { 320 | size = string_u8_codepoint_size(str, index); 321 | ck_assert(size == 1); 322 | index += size; 323 | if(size) 324 | count++; 325 | } 326 | while(size); 327 | 328 | ck_assert(count == string_u8_codepoints(str)); 329 | } 330 | } 331 | END_TEST 332 | 333 | START_TEST(string_u8_codepoint_size_utf8) { 334 | String str = string_create(KANA); 335 | int index = 0; 336 | int count = 0; 337 | int size; 338 | do { 339 | size = string_u8_codepoint_size(&str, index); 340 | index += size; 341 | if(size) { 342 | ck_assert(size == 3); 343 | count++; 344 | } 345 | } 346 | while(size); 347 | 348 | ck_assert(count == string_u8_codepoints(&str)); 349 | string_free_resources(&str); 350 | } 351 | END_TEST 352 | 353 | START_TEST(string_u8_codepoint_size_all) { 354 | Char32 all_codepoints[] = ALL_CODEPOINTS; 355 | String str = string_create(""); 356 | 357 | for(int i = 0; i < ARRAY_SIZE(all_codepoints); i++) 358 | ck_assert(string_u8_push_back(&str, all_codepoints[i])); 359 | 360 | int index = 0; 361 | int count = 0; 362 | int expected_size = 1; 363 | int size; 364 | do { 365 | size = string_u8_codepoint_size(&str, index); 366 | index += size; 367 | if(size) { 368 | ck_assert(size == expected_size); 369 | count++; 370 | if(count % 2 == 0) 371 | expected_size++; 372 | } 373 | } 374 | while(size); 375 | 376 | ck_assert(count == string_u8_codepoints(&str)); 377 | string_free_resources(&str); 378 | } 379 | END_TEST 380 | 381 | START_TEST(string_set_ascii) { 382 | char* start = "abc"; 383 | char* middle = "aba"; 384 | char* end = "cba"; 385 | 386 | String str = string_create(start); 387 | 388 | string_set(&str, 2, 'a'); 389 | ck_assert(string_get(&str, 2) == 'a'); 390 | ck_assert(strcmp(string_data(&str), middle) == 0); 391 | 392 | string_set(&str, 0, 'c'); 393 | ck_assert(string_get(&str, 0) == 'c'); 394 | ck_assert(strcmp(string_data(&str), end) == 0); 395 | 396 | string_free_resources(&str); 397 | } 398 | END_TEST 399 | 400 | START_TEST(string_set_mixed) { 401 | char* start = "aんb"; 402 | char* middle = "yんb"; 403 | char* end = "yんz"; 404 | 405 | String str = string_create(start); 406 | ck_assert(string_size(&str) == 5); 407 | 408 | string_set(&str, 0, 'y'); 409 | ck_assert(string_get(&str, 0) == 'y'); 410 | ck_assert(strcmp(string_data(&str), middle) == 0); 411 | 412 | string_set(&str, 4, 'z'); 413 | ck_assert(string_get(&str, 4) == 'z'); 414 | ck_assert(strcmp(string_data(&str), end) == 0); 415 | string_free_resources(&str); 416 | } 417 | END_TEST 418 | 419 | START_TEST(string_u8_set_ascii) { 420 | char* start = "abc"; 421 | char* middle = "aba"; 422 | char* end = "cba"; 423 | 424 | String str = string_create(start); 425 | 426 | string_u8_set(&str, 2, 'a'); 427 | ck_assert(string_u8_get(&str, 2) == 'a'); 428 | ck_assert(strcmp(string_data(&str), middle) == 0); 429 | 430 | string_u8_set(&str, 0, 'c'); 431 | ck_assert(string_u8_get(&str, 0) == 'c'); 432 | ck_assert(strcmp(string_data(&str), end) == 0); 433 | 434 | string_free_resources(&str); 435 | } 436 | END_TEST 437 | 438 | START_TEST(string_u8_set_smaller) { 439 | char expected[] = "こnにちは"; 440 | 441 | String str = string_create(KANA); 442 | string_u8_set(&str, 3, 'n'); 443 | 444 | ck_assert(string_size(&str) == 13); 445 | ck_assert(string_u8_get(&str, 3) == 'n'); 446 | ck_assert(strcmp(string_data(&str), expected) == 0); 447 | string_free_resources(&str); 448 | } 449 | END_TEST 450 | 451 | START_TEST(string_u8_set_bigger) { 452 | char* expected = "koんnichiwa"; 453 | 454 | String str = string_create("konnichiwa"); 455 | string_u8_set(&str, 2, L'\u3093'); 456 | ck_assert(string_size(&str) == 12); 457 | ck_assert(string_u8_get(&str, 2) == L'\u3093'); 458 | ck_assert(strcmp(string_data(&str), expected) == 0); 459 | string_free_resources(&str); 460 | } 461 | END_TEST 462 | 463 | START_TEST(string_empty_empty) { 464 | String str = string_create(""); 465 | ck_assert(string_empty(&str)); 466 | string_free_resources(&str); 467 | } 468 | END_TEST 469 | 470 | START_TEST(string_empty_whitespace) { 471 | String str = string_create(" "); 472 | ck_assert(!string_empty(&str)); 473 | string_free_resources(&str); 474 | } 475 | END_TEST 476 | 477 | START_TEST(string_empty_letters) { 478 | ck_assert(!string_empty(&small)); 479 | ck_assert(!string_empty(&large)); 480 | } 481 | END_TEST 482 | 483 | START_TEST(string_reserve_small_less_than_capacity) { 484 | size_t reserve = string_capacity(&small) - 1; 485 | ck_assert(string_reserve(&small, reserve)); 486 | ck_assert(string_capacity(&small) > reserve); 487 | } 488 | END_TEST 489 | 490 | START_TEST(string_reserve_small_greater_than_capacity) { 491 | size_t reserve = string_capacity(&small) + 1; 492 | ck_assert(string_reserve(&small, reserve)); 493 | ck_assert(string_capacity(&small) > reserve); 494 | } 495 | END_TEST 496 | 497 | START_TEST(string_reserve_large_less_than_capacity) { 498 | size_t reserve = string_capacity(&large) - 1; 499 | ck_assert(string_reserve(&large, reserve)); 500 | ck_assert(string_capacity(&large) > reserve); 501 | } 502 | END_TEST 503 | 504 | START_TEST(string_reserve_large_greater_than_capacity) { 505 | size_t reserve = string_capacity(&large) + 1; 506 | ck_assert(string_reserve(&large, reserve)); 507 | ck_assert(string_capacity(&large) > reserve); 508 | } 509 | END_TEST 510 | 511 | START_TEST(string_shrink_small_to_small) { 512 | size_t capacity = string_capacity(&small); 513 | string_shrink_to_fit(&small); 514 | ck_assert(capacity == string_capacity(&small)); 515 | } 516 | END_TEST 517 | 518 | START_TEST(string_shrink_large_to_large) { 519 | if(string_size(&large) == string_capacity(&large)) 520 | string_push_back(&large, 'a'); 521 | 522 | ck_assert(string_size(&large) != string_capacity(&large)); 523 | string_shrink_to_fit(&large); 524 | ck_assert(string_size(&large) == string_capacity(&large)); 525 | } 526 | END_TEST 527 | 528 | START_TEST(string_shrink_large_to_small) { 529 | string_clear(&large); 530 | ck_assert(sso_string_is_long(&large)); 531 | string_shrink_to_fit(&large); 532 | ck_assert(!sso_string_is_long(&large)); 533 | 534 | // Make sure the string is functional after switching sizes 535 | string_append_cstr(&large, HELLO); 536 | ck_assert(strcmp(string_data(&large), HELLO) == 0); 537 | } 538 | END_TEST 539 | 540 | START_TEST(string_clear_small) { 541 | ck_assert(string_size(&small) != 0); 542 | size_t capacity = string_capacity(&small); 543 | string_clear(&small); 544 | ck_assert(string_size(&small) == 0); 545 | ck_assert(string_capacity(&small) == capacity); 546 | } 547 | END_TEST 548 | 549 | START_TEST(string_clear_large) { 550 | ck_assert(string_size(&large) != 0); 551 | size_t capacity = string_capacity(&large); 552 | string_clear(&large); 553 | ck_assert(string_size(&large) == 0); 554 | ck_assert(string_capacity(&large) == capacity); 555 | } 556 | END_TEST 557 | 558 | START_TEST(string_insert_cstr_ascii) { 559 | String str = string_create("lexd"); 560 | ck_assert(string_insert_cstr(&str, "a", 0)); 561 | ck_assert(strcmp(string_data(&str), "alexd") == 0); 562 | 563 | ck_assert(string_insert_cstr(&str, "an", 4)); 564 | ck_assert(strcmp(string_data(&str), "alexand") == 0); 565 | 566 | ck_assert(string_insert_cstr(&str, "er", 7)); 567 | ck_assert(strcmp(string_data(&str), "alexander") == 0); 568 | 569 | string_free_resources(&str); 570 | } 571 | END_TEST 572 | 573 | START_TEST(string_insert_cstr_utf8) { 574 | String str = string_create("んち"); 575 | 576 | ck_assert(string_insert_cstr(&str, "こ", 0)); 577 | ck_assert(strcmp(string_data(&str), "こんち") == 0); 578 | 579 | ck_assert(string_insert_cstr(&str, "に", 6)); 580 | ck_assert(strcmp(string_data(&str), "こんにち") == 0); 581 | 582 | ck_assert(string_insert_cstr(&str, "は。", 12)); 583 | ck_assert(strcmp(string_data(&str), "こんにちは。") == 0); 584 | 585 | } 586 | END_TEST 587 | 588 | START_TEST(string_insert_string_ascii) { 589 | String str = string_create("lexd"); 590 | String a = string_create("a"); 591 | String an = string_create("an"); 592 | String er = string_create("er"); 593 | 594 | ck_assert(string_insert_string(&str, &a, 0)); 595 | ck_assert(strcmp(string_data(&str), "alexd") == 0); 596 | 597 | ck_assert(string_insert_string(&str, &an, 4)); 598 | ck_assert(strcmp(string_data(&str), "alexand") == 0); 599 | 600 | ck_assert(string_insert_string(&str, &er, 7)); 601 | ck_assert(strcmp(string_data(&str), "alexander") == 0); 602 | 603 | string_free_resources(&str); 604 | } 605 | END_TEST 606 | 607 | START_TEST(string_insert_string_utf8) { 608 | String str = string_create("んち"); 609 | String ko = string_create("こ"); 610 | String ni = string_create("に"); 611 | String wa = string_create("は。"); 612 | 613 | ck_assert(string_insert_string(&str, &ko, 0)); 614 | ck_assert(strcmp(string_data(&str), "こんち") == 0); 615 | 616 | ck_assert(string_insert_string(&str, &ni, 6)); 617 | ck_assert(strcmp(string_data(&str), "こんにち") == 0); 618 | 619 | ck_assert(string_insert_string(&str, &wa, 12)); 620 | ck_assert(strcmp(string_data(&str), "こんにちは。") == 0); 621 | 622 | string_free_resources(&str); 623 | } 624 | END_TEST 625 | 626 | START_TEST(string_insert_cstr_part_ascii) { 627 | String str = string_create("lexd"); 628 | char* result = "alexander"; 629 | 630 | ck_assert(string_insert_cstr_part(&str, result, 0, 0, 1)); 631 | ck_assert(strcmp(string_data(&str), "alexd") == 0); 632 | 633 | ck_assert(string_insert_cstr_part(&str, result, 4, 4, 2)); 634 | ck_assert(strcmp(string_data(&str), "alexand") == 0); 635 | 636 | ck_assert(string_insert_cstr_part(&str, result, 7, 7, 2)); 637 | ck_assert(strcmp(string_data(&str), result) == 0); 638 | 639 | string_free_resources(&str); 640 | } 641 | END_TEST 642 | 643 | START_TEST(string_insert_cstr_part_utf8) { 644 | String str = string_create("んち"); 645 | char* result = "こんにちは。"; 646 | 647 | ck_assert(string_insert_cstr_part(&str, result, 0, 0, 3)); 648 | ck_assert(strcmp(string_data(&str), "こんち") == 0); 649 | 650 | ck_assert(string_insert_cstr_part(&str, result, 6, 6, 3)); 651 | ck_assert(strcmp(string_data(&str), "こんにち") == 0); 652 | 653 | ck_assert(string_insert_cstr_part(&str, result, 12, 12, 6)); 654 | ck_assert(strcmp(string_data(&str), "こんにちは。") == 0); 655 | 656 | string_free_resources(&str); 657 | } 658 | END_TEST 659 | 660 | START_TEST(string_insert_string_part_ascii) { 661 | String str = string_create("lexd"); 662 | String result = string_create("alexander"); 663 | 664 | ck_assert(string_insert_string_part(&str, &result, 0, 0, 1)); 665 | ck_assert(strcmp(string_data(&str), "alexd") == 0); 666 | 667 | ck_assert(string_insert_string_part(&str, &result, 4, 4, 2)); 668 | ck_assert(strcmp(string_data(&str), "alexand") == 0); 669 | 670 | ck_assert(string_insert_string_part(&str, &result, 7, 7, 2)); 671 | ck_assert(strcmp(string_data(&str), "alexander") == 0); 672 | 673 | string_free_resources(&str); 674 | string_free_resources(&result); 675 | } 676 | END_TEST 677 | 678 | START_TEST(string_insert_string_part_utf8) { 679 | String str = string_create("んち"); 680 | String result = string_create("こんにちは。"); 681 | 682 | ck_assert(string_insert_string_part(&str, &result, 0, 0, 3)); 683 | ck_assert(strcmp(string_data(&str), "こんち") == 0); 684 | 685 | ck_assert(string_insert_string_part(&str, &result, 6, 6, 3)); 686 | ck_assert(strcmp(string_data(&str), "こんにち") == 0); 687 | 688 | ck_assert(string_insert_string_part(&str, &result, 12, 12, 6)); 689 | ck_assert(strcmp(string_data(&str), "こんにちは。") == 0); 690 | 691 | string_free_resources(&str); 692 | string_free_resources(&result); 693 | } 694 | END_TEST 695 | 696 | START_TEST(string_erase_small_start) { 697 | string_erase(&small, 0, 1); 698 | ck_assert(string_equals_cstr(&small, "ello")); 699 | } 700 | END_TEST 701 | 702 | START_TEST(string_erase_small_end) { 703 | string_erase(&small, 3, 2); 704 | ck_assert(string_equals_cstr(&small, "hel")); 705 | } 706 | END_TEST 707 | 708 | START_TEST(string_erase_small_middle) { 709 | string_erase(&small, 1, 3); 710 | ck_assert(string_equals_cstr(&small, "ho")); 711 | } 712 | END_TEST 713 | 714 | START_TEST(string_erase_large_start) { 715 | string_erase(&large, 0, 1); 716 | ck_assert(string_equals_cstr(&large, "bcdefghijklmnopqrstuvwxyz")); 717 | } 718 | END_TEST 719 | 720 | START_TEST(string_erase_large_end) { 721 | string_erase(&large, 23, 3); 722 | ck_assert(string_equals_cstr(&large, "abcdefghijklmnopqrstuvw")); 723 | } 724 | END_TEST 725 | 726 | START_TEST(string_erase_large_middle) { 727 | string_erase(&large, 1, 24); 728 | ck_assert(string_equals_cstr(&large, "az")); 729 | } 730 | END_TEST 731 | 732 | START_TEST(string_push_back_succeeds) { 733 | String str = string_create(""); 734 | ck_assert(string_push_back(&str, 'h')); 735 | ck_assert(string_equals_cstr(&str, "h")); 736 | 737 | ck_assert(string_push_back(&str, 'e')); 738 | ck_assert(string_equals_cstr(&str, "he")); 739 | 740 | string_free_resources(&str); 741 | } 742 | END_TEST 743 | 744 | START_TEST(string_u8_push_back_ascii) { 745 | String str = string_create(""); 746 | ck_assert(string_u8_push_back(&str, 'h')); 747 | ck_assert(string_equals_cstr(&str, "h")); 748 | 749 | ck_assert(string_u8_push_back(&str, 'e')); 750 | ck_assert(string_equals_cstr(&str, "he")); 751 | 752 | string_free_resources(&str); 753 | } 754 | END_TEST 755 | 756 | START_TEST(string_u8_push_back_utf8) { 757 | String str = string_create(""); 758 | wchar_t kana[] = KANA_WIDE; 759 | string_u8_push_back(&str, kana[0]); 760 | ck_assert(string_equals_cstr(&str, "こ")); 761 | 762 | string_u8_push_back(&str, kana[1]); 763 | ck_assert(string_equals_cstr(&str, "こん")); 764 | 765 | string_free_resources(&str); 766 | } 767 | END_TEST 768 | 769 | START_TEST(string_u8_push_back_all) { 770 | Char32 all_codepoints[] = ALL_CODEPOINTS; 771 | String str = string_create(""); 772 | 773 | for(int i = 0; i < ARRAY_SIZE(all_codepoints); i++) 774 | ck_assert(string_u8_push_back(&str, all_codepoints[i])); 775 | 776 | int index = 0; 777 | int count = 0; 778 | int size; 779 | Char32 current; 780 | do { 781 | current = string_u8_get_with_size(&str, index, &size); 782 | index += size; 783 | if(current) { 784 | ck_assert(current == all_codepoints[count]); 785 | count++; 786 | } 787 | } 788 | while(current); 789 | 790 | ck_assert(count == ARRAY_SIZE(all_codepoints)); 791 | string_free_resources(&str); 792 | } 793 | END_TEST 794 | 795 | START_TEST(string_pop_back_succeeds) { 796 | char* hello = HELLO; 797 | for(int i = HELLO_SIZE - 1; i >= 0; i--) { 798 | char letter = string_pop_back(&small); 799 | ck_assert(letter = hello[i]); 800 | } 801 | } 802 | END_TEST 803 | 804 | START_TEST(string_u8_pop_back_ascii) { 805 | char* hello = HELLO; 806 | for(int i = HELLO_SIZE - 1; i >= 0; i--) { 807 | Char32 letter = string_u8_pop_back(&small); 808 | ck_assert(letter = hello[i]); 809 | } 810 | } 811 | END_TEST 812 | 813 | START_TEST(string_u8_pop_back_utf8) { 814 | String str = string_create(KANA); 815 | String result = string_create(KANA); 816 | int codepoints = string_u8_codepoints(&result); 817 | int size = string_size(&str); 818 | 819 | for(int i = codepoints - 1; i >= 0; i--) { 820 | Char32 kana = string_u8_pop_back(&str); 821 | Char32 expected = string_u8_get(&result, i * 3); 822 | ck_assert(kana == expected); 823 | size_t new_size = string_size(&str); 824 | ck_assert(new_size == size - 3); 825 | size = new_size; 826 | } 827 | 828 | string_free_resources(&str); 829 | string_free_resources(&result); 830 | } 831 | END_TEST 832 | 833 | START_TEST(string_u8_pop_back_all) { 834 | Char32 all_codepoints[] = ALL_CODEPOINTS; 835 | String str = string_create(""); 836 | 837 | for(int i = 0; i < ARRAY_SIZE(all_codepoints); i++) 838 | ck_assert(string_u8_push_back(&str, all_codepoints[i])); 839 | 840 | String result = string_create(string_data(&str)); 841 | 842 | int expected_size = 4; 843 | int offset = 4; 844 | size_t size = string_size(&result); 845 | 846 | for(int i = ARRAY_SIZE(all_codepoints) - 1; i >= 0; i--) { 847 | Char32 letter = string_u8_pop_back(&str); 848 | Char32 expected_letter = string_u8_get(&result, size - offset); 849 | 850 | ck_assert(letter == expected_letter); 851 | 852 | if(i % 2 == 0) 853 | expected_size--; 854 | 855 | offset += expected_size; 856 | } 857 | 858 | string_free_resources(&str); 859 | string_free_resources(&result); 860 | } 861 | END_TEST 862 | 863 | START_TEST(string_append_cstr_small_to_small) { 864 | String str = string_create("hel"); 865 | ck_assert(string_append_cstr(&str, "lo")); 866 | ck_assert(string_equals_cstr(&str, HELLO)); 867 | 868 | string_free_resources(&str); 869 | } 870 | END_TEST 871 | 872 | START_TEST(string_append_cstr_large_to_large) { 873 | String str = string_create(ALPHABET); 874 | ck_assert(string_append_cstr(&str, ALPHABET)); 875 | ck_assert(string_equals_cstr(&str, ALPHABET ALPHABET)); 876 | 877 | string_free_resources(&str); 878 | } 879 | END_TEST 880 | 881 | START_TEST(string_append_cstr_small_to_large) { 882 | String str = string_create(""); 883 | ck_assert(!sso_string_is_long(&str)); 884 | ck_assert(string_append_cstr(&str, ALPHABET)); 885 | ck_assert(string_equals_cstr(&str, ALPHABET)); 886 | ck_assert(sso_string_is_long(&str)); 887 | 888 | string_free_resources(&str); 889 | } 890 | END_TEST 891 | 892 | START_TEST(string_append_string_small_to_small) { 893 | String str = string_create("hel"); 894 | String value = string_create("lo"); 895 | ck_assert(string_append_string(&str, &value)); 896 | ck_assert(string_equals_cstr(&str, HELLO)); 897 | 898 | string_free_resources(&str); 899 | string_free_resources(&value); 900 | } 901 | END_TEST 902 | 903 | START_TEST(string_append_string_large_to_large) { 904 | String str = string_create(ALPHABET); 905 | String value = string_create(ALPHABET); 906 | ck_assert(string_append_string(&str, &value)); 907 | ck_assert(string_equals_cstr(&str, ALPHABET ALPHABET)); 908 | 909 | string_free_resources(&str); 910 | string_free_resources(&value); 911 | } 912 | END_TEST 913 | 914 | START_TEST(string_append_string_small_to_large) { 915 | String str = string_create(""); 916 | String value = string_create(ALPHABET); 917 | ck_assert(!sso_string_is_long(&str)); 918 | ck_assert(string_append_string(&str, &value)); 919 | ck_assert(string_equals_cstr(&str, ALPHABET)); 920 | ck_assert(sso_string_is_long(&str)); 921 | 922 | string_free_resources(&str); 923 | string_free_resources(&value); 924 | } 925 | END_TEST 926 | 927 | START_TEST(string_append_cstr_part_succeeds) { 928 | String str = string_create("hel"); 929 | ck_assert(string_append_cstr_part(&str, "Hello!", 3, 2)); 930 | ck_assert(string_equals_cstr(&str, HELLO)); 931 | 932 | string_free_resources(&str); 933 | } 934 | END_TEST 935 | 936 | START_TEST(string_append_string_part_succeeds) { 937 | String str = string_create("hel"); 938 | String value = string_create("Hello!"); 939 | ck_assert(string_append_string_part(&str, &value, 3, 2)); 940 | ck_assert(string_equals_cstr(&str, HELLO)); 941 | 942 | string_free_resources(&str); 943 | } 944 | END_TEST 945 | 946 | START_TEST(string_compare_cstr_less_than) { 947 | String str = string_create("a"); 948 | ck_assert(string_compare_cstr(&str, "b") == -1); 949 | string_free_resources(&str); 950 | } 951 | END_TEST 952 | 953 | START_TEST(string_compare_cstr_greater_than) { 954 | String str = string_create("b"); 955 | ck_assert(string_compare_cstr(&str, "a") == 1); 956 | string_free_resources(&str); 957 | } 958 | END_TEST 959 | 960 | START_TEST(string_compare_cstr_equals) { 961 | String str = string_create("a"); 962 | ck_assert(string_compare_cstr(&str, "a") == 0); 963 | string_free_resources(&str); 964 | } 965 | END_TEST 966 | 967 | START_TEST(string_compare_string_less_than) { 968 | String str = string_create("a"); 969 | String value = string_create("b"); 970 | ck_assert(string_compare_string(&str, &value) == -1); 971 | string_free_resources(&str); 972 | string_free_resources(&value); 973 | } 974 | END_TEST 975 | 976 | START_TEST(string_compare_string_greater_than) { 977 | String str = string_create("b"); 978 | String value = string_create("a"); 979 | ck_assert(string_compare_string(&str, &value) == 1); 980 | string_free_resources(&str); 981 | string_free_resources(&value); 982 | } 983 | END_TEST 984 | 985 | START_TEST(string_compare_string_equals) { 986 | String str = string_create("a"); 987 | String value = string_create("a"); 988 | ck_assert(string_compare_string(&str, &value) == 0); 989 | string_free_resources(&str); 990 | string_free_resources(&value); 991 | } 992 | END_TEST 993 | 994 | START_TEST(string_equals_cstr_true) { 995 | String str = string_create(HELLO); 996 | ck_assert(string_equals_cstr(&str, HELLO)); 997 | string_free_resources(&str); 998 | } 999 | END_TEST 1000 | 1001 | START_TEST(string_equals_cstr_false) { 1002 | String str = string_create(ALPHABET); 1003 | ck_assert(!string_equals_cstr(&str, HELLO)); 1004 | string_free_resources(&str); 1005 | } 1006 | END_TEST 1007 | 1008 | START_TEST(string_equals_string_true) { 1009 | String str = string_create(HELLO); 1010 | String value = string_create(HELLO); 1011 | ck_assert(string_equals_string(&str, &value)); 1012 | string_free_resources(&str); 1013 | string_free_resources(&value); 1014 | } 1015 | END_TEST 1016 | 1017 | START_TEST(string_equals_string_false) { 1018 | String str = string_create(ALPHABET); 1019 | String value = string_create(HELLO); 1020 | ck_assert(!string_equals_string(&str, &value)); 1021 | string_free_resources(&str); 1022 | string_free_resources(&value); 1023 | } 1024 | END_TEST 1025 | 1026 | START_TEST(string_starts_with_cstr_true) { 1027 | ck_assert(string_starts_with_cstr(&large, "abc")); 1028 | } 1029 | END_TEST 1030 | 1031 | START_TEST(string_starts_with_cstr_false) { 1032 | ck_assert(!string_starts_with_cstr(&large, "xyz")); 1033 | } 1034 | END_TEST 1035 | 1036 | START_TEST(string_starts_with_string_true) { 1037 | String value = string_create("abc"); 1038 | ck_assert(string_starts_with_string(&large, &value)); 1039 | string_free_resources(&value); 1040 | } 1041 | END_TEST 1042 | 1043 | START_TEST(string_starts_with_string_false) { 1044 | String value = string_create("xyz"); 1045 | ck_assert(!string_starts_with_string(&large, &value)); 1046 | string_free_resources(&value); 1047 | } 1048 | END_TEST 1049 | 1050 | START_TEST(string_ends_with_cstr_true) { 1051 | ck_assert(string_ends_with_cstr(&large, "xyz")); 1052 | } 1053 | END_TEST 1054 | 1055 | START_TEST(string_ends_with_cstr_false) { 1056 | ck_assert(!string_ends_with_cstr(&large, "abc")); 1057 | } 1058 | END_TEST 1059 | 1060 | START_TEST(string_ends_with_string_true) { 1061 | String value = string_create("xyz"); 1062 | ck_assert(string_ends_with_string(&large, &value)); 1063 | string_free_resources(&value); 1064 | } 1065 | END_TEST 1066 | 1067 | START_TEST(string_ends_with_string_false) { 1068 | String value = string_create("abc"); 1069 | ck_assert(!string_ends_with_string(&large, &value)); 1070 | string_free_resources(&value); 1071 | } 1072 | END_TEST 1073 | 1074 | START_TEST(string_replace_cstr_shrink) { 1075 | ck_assert(string_replace_cstr(&small, 1, 3, "b")); 1076 | ck_assert(string_equals(&small, "hbo")); 1077 | } 1078 | END_TEST 1079 | 1080 | START_TEST(string_replace_cstr_shrink_end) { 1081 | ck_assert(string_replace_cstr(&small, 3, 2, "p")); 1082 | ck_assert(string_equals(&small, "help")); 1083 | } 1084 | END_TEST 1085 | 1086 | START_TEST(string_replace_cstr_same_size) { 1087 | ck_assert(string_replace_cstr(&small, 2, 1, "n")); 1088 | ck_assert(string_equals(&small, "henlo")); 1089 | } 1090 | END_TEST 1091 | 1092 | START_TEST(string_replace_cstr_grow_small_to_small) { 1093 | String str = string_create("hbo"); 1094 | ck_assert(string_replace_cstr(&str, 1, 1, "ell")); 1095 | ck_assert(string_equals_cstr(&str, HELLO)); 1096 | string_free_resources(&str); 1097 | } 1098 | END_TEST 1099 | 1100 | START_TEST(string_replace_cstr_grow_small_to_large) { 1101 | String str = string_create("Helero"); 1102 | char* value = " runs heroically into the burning building like a he"; 1103 | ck_assert(string_replace_cstr(&str, 2, 2, value)); 1104 | ck_assert(string_equals_cstr(&str, "He runs heroically into the burning building like a hero")); 1105 | 1106 | string_free_resources(&str); 1107 | } 1108 | END_TEST 1109 | 1110 | START_TEST(string_replace_string_shrink) { 1111 | String b = string_create("b"); 1112 | ck_assert(string_replace_string(&small, 1, 3, &b)); 1113 | ck_assert(string_equals(&small, "hbo")); 1114 | string_free_resources(&b); 1115 | } 1116 | END_TEST 1117 | 1118 | START_TEST(string_replace_string_shrink_end) { 1119 | String p = string_create("p"); 1120 | ck_assert(string_replace_string(&small, 3, 2, &p)); 1121 | ck_assert(string_equals(&small, "help")); 1122 | string_free_resources(&p); 1123 | } 1124 | END_TEST 1125 | 1126 | START_TEST(string_replace_string_same_size) { 1127 | String n = string_create("n"); 1128 | ck_assert(string_replace_string(&small, 2, 1, &n)); 1129 | ck_assert(string_equals(&small, "henlo")); 1130 | string_free_resources(&n); 1131 | } 1132 | END_TEST 1133 | 1134 | START_TEST(string_replace_string_grow_small_to_small) { 1135 | String ell = string_create("ell"); 1136 | String str = string_create("hbo"); 1137 | ck_assert(string_replace_string(&str, 1, 1, &ell)); 1138 | ck_assert(string_equals(&str, HELLO)); 1139 | string_free_resources(&ell); 1140 | string_free_resources(&str); 1141 | } 1142 | END_TEST 1143 | 1144 | START_TEST(string_replace_string_grow_small_to_large) { 1145 | String hero = string_create(" runs heroically into the burning building like a he"); 1146 | String str = string_create("Helero"); 1147 | ck_assert(string_replace_string(&str, 2, 2, &hero)); 1148 | ck_assert(string_equals(&str, "He runs heroically into the burning building like a hero")); 1149 | 1150 | string_free_resources(&str); 1151 | string_free_resources(&hero); 1152 | } 1153 | END_TEST 1154 | 1155 | START_TEST(string_substring_part) { 1156 | String hell; 1157 | ck_assert(string_substring(&small, 0, 4, &hell)); 1158 | ck_assert(string_equals(&hell, "hell")); 1159 | 1160 | String ello; 1161 | ck_assert(string_substring(&small, 1, 4, &ello)); 1162 | ck_assert(string_equals(&ello, "ello")); 1163 | 1164 | string_free_resources(&hell); 1165 | string_free_resources(&ello); 1166 | } 1167 | END_TEST 1168 | 1169 | START_TEST(string_substring_whole) { 1170 | String hello; 1171 | ck_assert(string_substring(&small, 0, 5, &hello)); 1172 | ck_assert(string_equals_string(&hello, &small)); 1173 | string_free_resources(&hello); 1174 | } 1175 | END_TEST 1176 | 1177 | START_TEST(string_copy_small) { 1178 | String hello; 1179 | ck_assert(string_copy(&small, &hello)); 1180 | ck_assert(string_equals_string(&small, &hello)); 1181 | string_free_resources(&hello); 1182 | } 1183 | END_TEST 1184 | 1185 | START_TEST(string_copy_large) { 1186 | String alphabet; 1187 | ck_assert(string_copy(&large, &alphabet)); 1188 | ck_assert(string_equals_string(&alphabet, &large)); 1189 | string_free_resources(&alphabet); 1190 | } 1191 | END_TEST 1192 | 1193 | START_TEST(string_copy_to_short) { 1194 | char buffer[6]; 1195 | string_copy_to(&small, buffer, 0, 5); 1196 | buffer[5] = '\0'; 1197 | ck_assert(string_equals(&small, buffer)); 1198 | } 1199 | END_TEST 1200 | 1201 | START_TEST(string_copy_to_large) { 1202 | char buffer[27]; 1203 | string_copy_to(&large, buffer, 0, 26); 1204 | buffer[26] = '\0'; 1205 | ck_assert(string_equals(&large, buffer)); 1206 | } 1207 | END_TEST 1208 | 1209 | START_TEST(string_resize_shrink) { 1210 | ck_assert(string_resize(&small, 4, ' ')); 1211 | ck_assert(string_equals(&small, "hell")); 1212 | ck_assert(string_size(&small) == 4); 1213 | } 1214 | END_TEST 1215 | 1216 | START_TEST(string_resize_same_size) { 1217 | size_t old_size = string_size(&small); 1218 | ck_assert(string_resize(&small, 5, ' ')); 1219 | ck_assert(string_equals(&small, "hello")); 1220 | ck_assert(string_size(&small) == old_size); 1221 | } 1222 | END_TEST 1223 | 1224 | START_TEST(string_resize_grow) { 1225 | ck_assert(string_resize(&small, 8, ' ')); 1226 | ck_assert(string_equals(&small, "hello ")); 1227 | ck_assert(string_size(&small) == 8); 1228 | } 1229 | END_TEST 1230 | 1231 | START_TEST(string_swap_small_and_large) { 1232 | size_t small_size = string_size(&small); 1233 | size_t large_size = string_size(&large); 1234 | size_t small_cap = string_capacity(&small); 1235 | size_t large_cap = string_capacity(&large); 1236 | 1237 | string_swap(&small, &large); 1238 | 1239 | ck_assert(string_equals(&small, ALPHABET)); 1240 | ck_assert(string_equals(&large, HELLO)); 1241 | ck_assert(string_size(&small) == large_size); 1242 | ck_assert(string_size(&large) == small_size); 1243 | ck_assert(string_capacity(&small) == large_cap); 1244 | ck_assert(string_capacity(&large) == small_cap); 1245 | } 1246 | END_TEST 1247 | 1248 | START_TEST(string_find_cstr_first) { 1249 | String str = string_create(HELLO HELLO); 1250 | ck_assert(string_find_cstr(&str, 0, HELLO) == 0); 1251 | string_free_resources(&str); 1252 | } 1253 | END_TEST 1254 | 1255 | START_TEST(string_find_cstr_second) { 1256 | String str = string_create(HELLO HELLO); 1257 | ck_assert(string_find_cstr(&str, 1, HELLO) == 5); 1258 | string_free_resources(&str); 1259 | } 1260 | END_TEST 1261 | 1262 | START_TEST(string_find_cstr_none) { 1263 | ck_assert(string_find_cstr(&small, 0, "moo") == SIZE_MAX); 1264 | } 1265 | END_TEST 1266 | 1267 | START_TEST(string_find_string_first) { 1268 | String str = string_create(HELLO HELLO); 1269 | ck_assert(string_find_string(&str, 0, &small) == 0); 1270 | string_free_resources(&str); 1271 | } 1272 | END_TEST 1273 | 1274 | START_TEST(string_find_string_second) { 1275 | String str = string_create(HELLO HELLO); 1276 | ck_assert(string_find_string(&str, 1, &small) == 5); 1277 | string_free_resources(&str); 1278 | } 1279 | END_TEST 1280 | 1281 | START_TEST(string_find_string_none) { 1282 | String moo = string_create("moo"); 1283 | ck_assert(string_find_string(&small, 0, &moo) == SIZE_MAX); 1284 | string_free_resources(&moo); 1285 | } 1286 | END_TEST 1287 | 1288 | START_TEST(string_rfind_cstr_first) { 1289 | String str = string_create(HELLO HELLO); 1290 | ck_assert(string_rfind_cstr(&str, 0, HELLO) == 5); 1291 | string_free_resources(&str); 1292 | } 1293 | END_TEST 1294 | 1295 | START_TEST(string_rfind_cstr_second) { 1296 | String str = string_create(HELLO HELLO); 1297 | ck_assert(string_rfind_cstr(&str, 6, HELLO) == 0); 1298 | string_free_resources(&str); 1299 | } 1300 | END_TEST 1301 | 1302 | START_TEST(string_rfind_cstr_none) { 1303 | String str = string_create(HELLO HELLO); 1304 | ck_assert(string_rfind_cstr(&str, 0, "moo") == SIZE_MAX); 1305 | string_free_resources(&str); 1306 | } 1307 | END_TEST 1308 | 1309 | START_TEST(string_rfind_string_first) { 1310 | String str = string_create(HELLO HELLO); 1311 | ck_assert(string_rfind_string(&str, 0, &small) == 5); 1312 | string_free_resources(&str); 1313 | } 1314 | END_TEST 1315 | 1316 | START_TEST(string_rfind_string_second) { 1317 | String str = string_create(HELLO HELLO); 1318 | ck_assert(string_rfind_string(&str, 6, &small) == 0); 1319 | string_free_resources(&str); 1320 | } 1321 | END_TEST 1322 | 1323 | START_TEST(string_rfind_string_none) { 1324 | String str = string_create(HELLO HELLO); 1325 | String moo = string_create("moo"); 1326 | ck_assert(string_rfind_string(&str, 0, &moo) == SIZE_MAX); 1327 | string_free_resources(&str); 1328 | string_free_resources(&moo); 1329 | } 1330 | END_TEST 1331 | 1332 | START_TEST(string_is_null_or_empty_null) { 1333 | ck_assert(string_is_null_or_empty(NULL)); 1334 | } 1335 | END_TEST 1336 | 1337 | START_TEST(string_is_null_or_empty_empty) { 1338 | String str = string_create(""); 1339 | ck_assert(string_is_null_or_empty(&str)); 1340 | string_free_resources(&str); 1341 | } 1342 | END_TEST 1343 | 1344 | START_TEST(string_is_null_or_empty_filled) { 1345 | ck_assert(!string_is_null_or_empty(&small)); 1346 | } 1347 | END_TEST 1348 | 1349 | START_TEST(string_is_null_or_whitespace_null) { 1350 | ck_assert(string_u8_is_null_or_whitespace(NULL)); 1351 | } 1352 | END_TEST 1353 | 1354 | START_TEST(string_is_null_or_whitespace_empty) { 1355 | String str = string_create(""); 1356 | ck_assert(string_u8_is_null_or_whitespace(&str)); 1357 | string_free_resources(&str); 1358 | } 1359 | END_TEST 1360 | 1361 | START_TEST(string_is_null_or_whitespace_space) { 1362 | String str = string_create(" "); 1363 | ck_assert(string_u8_is_null_or_whitespace(&str)); 1364 | string_free_resources(&str); 1365 | } 1366 | END_TEST 1367 | 1368 | START_TEST(string_is_null_or_whitespace_u8_space) { 1369 | String str = string_create(""); 1370 | string_u8_push_back(&str, ' '); // ascii space 1371 | string_u8_push_back(&str, L'\xA0'); // no-break space 1372 | string_u8_push_back(&str, L'\x180E'); // mongolian vowel separator 1373 | ck_assert(string_u8_is_null_or_whitespace(&str)); 1374 | string_free_resources(&str); 1375 | } 1376 | END_TEST 1377 | 1378 | START_TEST(string_is_null_or_whitespace_filled) { 1379 | String str = string_create(" a "); 1380 | ck_assert(!string_u8_is_null_or_whitespace(&str)); 1381 | string_free_resources(&str); 1382 | } 1383 | END_TEST 1384 | 1385 | START_TEST(string_reverse_bytes_ascii) { 1386 | string_reverse_bytes(&small); 1387 | ck_assert(string_equals(&small, "olleh")); 1388 | } 1389 | END_TEST 1390 | 1391 | START_TEST(string_reverse_bytes_utf8) { 1392 | char result[] = KANA; 1393 | size_t index = ARRAY_SIZE(result) - 2; 1394 | String kana = string_create(KANA); 1395 | string_reverse_bytes(&kana); 1396 | 1397 | for(int i = 0; i < string_size(&kana); i++, index--) { 1398 | ck_assert(string_get(&kana, i) == result[index]); 1399 | } 1400 | 1401 | string_free_resources(&kana); 1402 | } 1403 | END_TEST 1404 | 1405 | START_TEST(string_u8_reverse_codepoints_ascii) { 1406 | string_u8_reverse_codepoints(&small); 1407 | ck_assert(string_equals(&small, "olleh")); 1408 | } 1409 | END_TEST 1410 | 1411 | START_TEST(string_u8_reverse_codepoints_utf8) { 1412 | String kana = string_create(KANA); 1413 | string_u8_reverse_codepoints(&kana); 1414 | 1415 | ck_assert(string_equals(&kana, "はちにんこ")); 1416 | string_free_resources(&kana); 1417 | } 1418 | END_TEST 1419 | 1420 | START_TEST(string_join_none) { 1421 | ck_assert(string_join(NULL, NULL, NULL, 0)); 1422 | } 1423 | END_TEST 1424 | 1425 | START_TEST(string_join_two) { 1426 | String segments[2]; 1427 | String comma = string_create(", "); 1428 | String result = string_create(""); 1429 | ck_assert(string_init(&segments[0], "Hello")); 1430 | ck_assert(string_init(&segments[1], "my name is ...")); 1431 | ck_assert(string_join(&result, &comma, segments, 2)); 1432 | ck_assert(string_equals(&result, "Hello, my name is ...")); 1433 | string_free_resources(&segments[0]); 1434 | string_free_resources(&segments[1]); 1435 | string_free_resources(&comma); 1436 | string_free_resources(&result); 1437 | } 1438 | END_TEST 1439 | 1440 | START_TEST(string_join_many) { 1441 | String segments[5]; 1442 | String comma = string_create(", "); 1443 | String result = string_create("List: "); 1444 | ck_assert(string_init(&segments[0], "Milk")); 1445 | ck_assert(string_init(&segments[1], "Apples")); 1446 | ck_assert(string_init(&segments[2], "Bananas")); 1447 | ck_assert(string_init(&segments[3], "Sandwich Meat")); 1448 | ck_assert(string_init(&segments[4], "Bread")); 1449 | ck_assert(string_join(&result, &comma, segments, 5)); 1450 | ck_assert(string_equals(&result, "List: Milk, Apples, Bananas, Sandwich Meat, Bread")); 1451 | 1452 | for(int i = 0; i < 5; i++) 1453 | string_free_resources(&segments[i]); 1454 | 1455 | string_free_resources(&comma); 1456 | string_free_resources(&result); 1457 | } 1458 | END_TEST 1459 | 1460 | START_TEST(string_join_refs_none) { 1461 | ck_assert(string_join_refs(NULL, NULL, NULL, 0)); 1462 | } 1463 | END_TEST 1464 | 1465 | START_TEST(string_join_refs_two) { 1466 | String* segments[2]; 1467 | String comma = string_create(", "); 1468 | String result = string_create(""); 1469 | ck_assert((segments[0] = string_create_ref("Hello"))); 1470 | ck_assert((segments[1] = string_create_ref("my name is ..."))); 1471 | ck_assert(string_join_refs(&result, &comma, segments, 2)); 1472 | ck_assert(string_equals(&result, "Hello, my name is ...")); 1473 | string_free(segments[0]); 1474 | string_free(segments[1]); 1475 | string_free_resources(&comma); 1476 | string_free_resources(&result); 1477 | } 1478 | END_TEST 1479 | 1480 | START_TEST(string_join_refs_many) { 1481 | String* segments[5]; 1482 | String comma = string_create(", "); 1483 | String result = string_create("List: "); 1484 | ck_assert((segments[0] = string_create_ref("Milk"))); 1485 | ck_assert((segments[1] = string_create_ref("Apples"))); 1486 | ck_assert((segments[2] = string_create_ref("Bananas"))); 1487 | ck_assert((segments[3] = string_create_ref("Sandwich Meat"))); 1488 | ck_assert((segments[4] = string_create_ref("Bread"))); 1489 | ck_assert(string_join_refs(&result, &comma, segments, 5)); 1490 | ck_assert(string_equals(&result, "List: Milk, Apples, Bananas, Sandwich Meat, Bread")); 1491 | 1492 | for(int i = 0; i < 5; i++) 1493 | string_free(segments[i]); 1494 | 1495 | string_free_resources(&comma); 1496 | string_free_resources(&result); 1497 | } 1498 | END_TEST 1499 | 1500 | START_TEST(string_split_init_into_existing_less_than_size) { 1501 | String results[4]; 1502 | String to_split = string_create("moo, caw, meow"); 1503 | String separator = string_create(", "); 1504 | int filled; 1505 | String* result = string_split(&to_split, &separator, results, 4, &filled, false, true); 1506 | ck_assert(result); 1507 | ck_assert(result == results); 1508 | ck_assert(filled == 3); 1509 | ck_assert(string_equals_cstr(results + 0, "moo")); 1510 | ck_assert(string_equals_cstr(results + 1, "caw")); 1511 | ck_assert(string_equals_cstr(results + 2, "meow")); 1512 | 1513 | for(int i = 0; i < filled; i++) { 1514 | string_free_resources(results + i); 1515 | } 1516 | 1517 | string_free_resources(&to_split); 1518 | } 1519 | END_TEST 1520 | 1521 | START_TEST(string_split_init_into_existing_equal_size) { 1522 | String results[3]; 1523 | String to_split = string_create("Who am I"); 1524 | String separator = string_create(" "); 1525 | int filled; 1526 | String* result = string_split(&to_split, &separator, results, 3, &filled, false, true); 1527 | ck_assert(result); 1528 | ck_assert(result == results); 1529 | ck_assert(filled == 3); 1530 | ck_assert(string_equals_cstr(results + 0, "Who")); 1531 | ck_assert(string_equals_cstr(results + 1, "am")); 1532 | ck_assert(string_equals_cstr(results + 2, "I")); 1533 | 1534 | for(int i = 0; i < filled; i++) { 1535 | string_free_resources(results + i); 1536 | } 1537 | 1538 | string_free_resources(&to_split); 1539 | } 1540 | END_TEST 1541 | 1542 | START_TEST(string_split_init_into_existing_greater_than_size) { 1543 | String results[2]; 1544 | String to_split = string_create("What is your name my friend"); 1545 | String separator = string_create(" "); 1546 | int filled; 1547 | String* result = string_split(&to_split, &separator, results, 2, &filled, false, true); 1548 | ck_assert(result); 1549 | ck_assert(result == results); 1550 | ck_assert(filled == 2); 1551 | ck_assert(string_equals_cstr(results + 0, "What")); 1552 | ck_assert(string_equals_cstr(results + 1, "is")); 1553 | 1554 | for(int i = 0; i < filled; i++) { 1555 | string_free_resources(results + i); 1556 | } 1557 | 1558 | string_free_resources(&to_split); 1559 | } 1560 | END_TEST 1561 | 1562 | START_TEST(string_split_no_init_into_existing_less_than_size) { 1563 | String results[4]; 1564 | for(int i = 0; i < 4; i++) { 1565 | ck_assert(string_init(results + i, "init")); 1566 | } 1567 | String to_split = string_create("moo, caw, meow"); 1568 | String separator = string_create(", "); 1569 | int filled; 1570 | String* result = string_split(&to_split, &separator, results, 4, &filled, false, false); 1571 | ck_assert(result); 1572 | ck_assert(result == results); 1573 | ck_assert(filled == 3); 1574 | ck_assert(string_equals_cstr(results + 0, "initmoo")); 1575 | ck_assert(string_equals_cstr(results + 1, "initcaw")); 1576 | ck_assert(string_equals_cstr(results + 2, "initmeow")); 1577 | 1578 | for(int i = 0; i < filled; i++) { 1579 | string_free_resources(results + i); 1580 | } 1581 | 1582 | string_free_resources(&to_split); 1583 | } 1584 | END_TEST 1585 | 1586 | START_TEST(string_split_no_init_into_existing_equal_size) { 1587 | String results[3]; 1588 | for(int i = 0; i < 3; i++) { 1589 | ck_assert(string_init(results + i, "init")); 1590 | } 1591 | String to_split = string_create("Who am I"); 1592 | String separator = string_create(" "); 1593 | int filled; 1594 | String* result = string_split(&to_split, &separator, results, 3, &filled, false, false); 1595 | ck_assert(result); 1596 | ck_assert(result == results); 1597 | ck_assert(filled == 3); 1598 | ck_assert(string_equals_cstr(results + 0, "initWho")); 1599 | ck_assert(string_equals_cstr(results + 1, "initam")); 1600 | ck_assert(string_equals_cstr(results + 2, "initI")); 1601 | 1602 | for(int i = 0; i < filled; i++) { 1603 | string_free_resources(results + i); 1604 | } 1605 | 1606 | string_free_resources(&to_split); 1607 | } 1608 | END_TEST 1609 | 1610 | START_TEST(string_split_no_init_into_existing_greater_than_size) { 1611 | String results[2]; 1612 | for(int i = 0; i < 2; i++) { 1613 | ck_assert(string_init(results + i, "init")); 1614 | } 1615 | String to_split = string_create("What is your name my friend"); 1616 | String separator = string_create(" "); 1617 | int filled; 1618 | String* result = string_split(&to_split, &separator, results, 2, &filled, false, false); 1619 | ck_assert(result); 1620 | ck_assert(result == results); 1621 | ck_assert(filled == 2); 1622 | ck_assert(string_equals_cstr(results + 0, "initWhat")); 1623 | ck_assert(string_equals_cstr(results + 1, "initis")); 1624 | 1625 | for(int i = 0; i < filled; i++) { 1626 | string_free_resources(results + i); 1627 | } 1628 | 1629 | string_free_resources(&to_split); 1630 | } 1631 | END_TEST 1632 | 1633 | START_TEST(string_split_allocate_init) { 1634 | String to_split = string_create("moo, caw, meow"); 1635 | String separator = string_create(", "); 1636 | int filled; 1637 | String* result = string_split(&to_split, &separator, NULL, STRING_SPLIT_ALLOCATE, &filled, false, true); 1638 | ck_assert(result); 1639 | ck_assert(filled == 3); 1640 | ck_assert(string_equals_cstr(result + 0, "moo")); 1641 | ck_assert(string_equals_cstr(result + 1, "caw")); 1642 | ck_assert(string_equals_cstr(result + 2, "meow")); 1643 | 1644 | for(int i = 0; i < filled; i++) { 1645 | string_free_resources(result + i); 1646 | } 1647 | 1648 | free(result); 1649 | string_free_resources(&to_split); 1650 | } 1651 | END_TEST 1652 | 1653 | START_TEST(string_split_allocate_no_init) { 1654 | // This should work exactly the same as string_split_allocate_init, since 1655 | // it should be set to init no matter what if results_count < 0. 1656 | 1657 | String to_split = string_create("moo, caw, meow"); 1658 | String separator = string_create(", "); 1659 | int filled; 1660 | String* result = string_split(&to_split, &separator, NULL, STRING_SPLIT_ALLOCATE, &filled, false, false); 1661 | ck_assert(result); 1662 | ck_assert(filled == 3); 1663 | ck_assert(string_equals_cstr(result + 0, "moo")); 1664 | ck_assert(string_equals_cstr(result + 1, "caw")); 1665 | ck_assert(string_equals_cstr(result + 2, "meow")); 1666 | 1667 | for(int i = 0; i < filled; i++) { 1668 | string_free_resources(result + i); 1669 | } 1670 | 1671 | free(result); 1672 | string_free_resources(&to_split); 1673 | } 1674 | END_TEST 1675 | 1676 | START_TEST(string_split_skip_empty) { 1677 | String to_split = string_create("double space"); 1678 | String separator = string_create(" "); 1679 | int filled; 1680 | String* result = string_split(&to_split, &separator, NULL, STRING_SPLIT_ALLOCATE, &filled, true, true); 1681 | 1682 | ck_assert(result); 1683 | ck_assert(filled == 2); 1684 | ck_assert(string_equals_cstr(result + 0, "double")); 1685 | ck_assert(string_equals_cstr(result + 1, "space")); 1686 | 1687 | for(int i = 0; i < filled; i++) { 1688 | string_free_resources(result + i); 1689 | } 1690 | 1691 | free(result); 1692 | string_free_resources(&to_split); 1693 | 1694 | } 1695 | END_TEST 1696 | 1697 | START_TEST(string_split_dont_skip_empty) { 1698 | String to_split = string_create("double space"); 1699 | String separator = string_create(" "); 1700 | int filled; 1701 | String* result = string_split(&to_split, &separator, NULL, STRING_SPLIT_ALLOCATE, &filled, false, true); 1702 | 1703 | ck_assert(result); 1704 | ck_assert(filled == 3); 1705 | ck_assert(string_equals_cstr(result + 0, "double")); 1706 | ck_assert(string_equals_cstr(result + 1, "")); 1707 | ck_assert(string_equals_cstr(result + 2, "space")); 1708 | 1709 | for(int i = 0; i < filled; i++) { 1710 | string_free_resources(result + i); 1711 | } 1712 | 1713 | free(result); 1714 | string_free_resources(&to_split); 1715 | 1716 | } 1717 | END_TEST 1718 | 1719 | START_TEST(string_format_string_new) { 1720 | String format = string_create("%d"); 1721 | String* result = string_format_string(NULL, &format, 152); 1722 | ck_assert(result != NULL); 1723 | ck_assert(string_equals(result, "152")); 1724 | string_free(result); 1725 | } 1726 | END_TEST 1727 | 1728 | START_TEST(string_format_string_existing) { 1729 | String format = string_create("%u"); 1730 | String result = string_create(""); 1731 | ck_assert(string_format_string(&result, &format, UINT_MAX) != NULL); 1732 | ck_assert(string_equals(&result, "4294967295")); 1733 | string_free_resources(&result); 1734 | } 1735 | END_TEST 1736 | 1737 | START_TEST(string_format_string_append) { 1738 | String format = string_create("%d"); 1739 | String result = string_create("My age is "); 1740 | ck_assert(string_format_string(&result, &format, 23) != NULL); 1741 | ck_assert(string_equals(&result, "My age is 23")); 1742 | string_free_resources(&result); 1743 | } 1744 | END_TEST 1745 | 1746 | START_TEST(string_format_cstr_new) { 1747 | String* result = string_format_cstr(NULL, "%d", 152); 1748 | ck_assert(result != NULL); 1749 | ck_assert(string_equals(result, "152")); 1750 | string_free(result); 1751 | } 1752 | END_TEST 1753 | 1754 | START_TEST(string_format_cstr_existing) { 1755 | String result = string_create(""); 1756 | ck_assert(string_format_cstr(&result, "%u", UINT_MAX) != NULL); 1757 | ck_assert(string_equals(&result, "4294967295")); 1758 | string_free_resources(&result); 1759 | } 1760 | END_TEST 1761 | 1762 | START_TEST(string_format_cstr_append) { 1763 | String result = string_create("My age is "); 1764 | ck_assert(string_format_cstr(&result, "%d", 23) != NULL); 1765 | ck_assert(string_equals(&result, "My age is 23")); 1766 | string_free_resources(&result); 1767 | } 1768 | END_TEST 1769 | 1770 | START_TEST(string_hash_verify) { 1771 | String str = string_create("Hello world, it is I, your master."); 1772 | size_t hash_result = string_hash(&str); 1773 | 1774 | #if SSO_STRING_SHIFT == 24 1775 | ck_assert(hash_result == 0x7d8ee846); 1776 | #elif SSO_STRING_SHIFT == 56 1777 | ck_assert(hash_result == 0xe288df2017a06dc6); 1778 | #endif 1779 | 1780 | string_free_resources(&str); 1781 | } 1782 | END_TEST 1783 | 1784 | int main(void) { 1785 | int number_failed; 1786 | 1787 | Suite* s = suite_create("String"); 1788 | TCase* tc = tcase_create("String"); 1789 | 1790 | tcase_add_checked_fixture(tc, string_start, string_reset); 1791 | 1792 | tcase_add_test(tc, string_small_has_small_flag); 1793 | tcase_add_test(tc, string_long_has_long_flag); 1794 | tcase_add_test(tc, string_switches_size); 1795 | tcase_add_test(tc, string_is_correct_size); 1796 | tcase_add_test(tc, string_init_copies_cstr); 1797 | tcase_add_test(tc, string_small_cstr); 1798 | tcase_add_test(tc, string_large_cstr); 1799 | tcase_add_test(tc, string_small_size); 1800 | tcase_add_test(tc, string_large_size); 1801 | tcase_add_test(tc, string_size_dynamic); 1802 | tcase_add_test(tc, string_size_utf8); 1803 | tcase_add_test(tc, string_u8_codepoints_ascii); 1804 | tcase_add_test(tc, string_u8_codepoints_utf8); 1805 | tcase_add_test(tc, string_u8_codepoints_all); 1806 | tcase_add_test(tc, string_get_all); 1807 | tcase_add_test(tc, string_u8_get_ascii); 1808 | tcase_add_test(tc, string_u8_get_utf8); 1809 | tcase_add_test(tc, string_u8_get_all); 1810 | tcase_add_test(tc, string_u8_get_with_size_ascii); 1811 | tcase_add_test(tc, string_u8_get_with_size_utf8); 1812 | tcase_add_test(tc, string_u8_get_with_size_all); 1813 | tcase_add_test(tc, string_u8_codepoint_size_ascii); 1814 | tcase_add_test(tc, string_u8_codepoint_size_utf8); 1815 | tcase_add_test(tc, string_u8_codepoint_size_all); 1816 | tcase_add_test(tc, string_set_ascii); 1817 | tcase_add_test(tc, string_set_mixed); 1818 | tcase_add_test(tc, string_u8_set_ascii); 1819 | tcase_add_test(tc, string_u8_set_smaller); 1820 | tcase_add_test(tc, string_u8_set_bigger); 1821 | tcase_add_test(tc, string_empty_empty); 1822 | tcase_add_test(tc, string_empty_whitespace); 1823 | tcase_add_test(tc, string_empty_letters); 1824 | tcase_add_test(tc, string_reserve_small_less_than_capacity); 1825 | tcase_add_test(tc, string_reserve_small_greater_than_capacity); 1826 | tcase_add_test(tc, string_reserve_large_less_than_capacity); 1827 | tcase_add_test(tc, string_reserve_large_greater_than_capacity); 1828 | tcase_add_test(tc, string_shrink_small_to_small); 1829 | tcase_add_test(tc, string_shrink_large_to_large); 1830 | tcase_add_test(tc, string_shrink_large_to_small); 1831 | tcase_add_test(tc, string_clear_small); 1832 | tcase_add_test(tc, string_clear_large); 1833 | tcase_add_test(tc, string_insert_cstr_ascii); 1834 | tcase_add_test(tc, string_insert_cstr_utf8); 1835 | tcase_add_test(tc, string_insert_string_ascii); 1836 | tcase_add_test(tc, string_insert_string_utf8); 1837 | tcase_add_test(tc, string_insert_cstr_part_ascii); 1838 | tcase_add_test(tc, string_insert_cstr_part_utf8); 1839 | tcase_add_test(tc, string_insert_string_part_ascii); 1840 | tcase_add_test(tc, string_insert_string_part_utf8); 1841 | tcase_add_test(tc, string_erase_small_start); 1842 | tcase_add_test(tc, string_erase_small_end); 1843 | tcase_add_test(tc, string_erase_small_middle); 1844 | tcase_add_test(tc, string_erase_large_start); 1845 | tcase_add_test(tc, string_erase_large_end); 1846 | tcase_add_test(tc, string_erase_large_middle); 1847 | tcase_add_test(tc, string_push_back_succeeds); 1848 | tcase_add_test(tc, string_u8_push_back_ascii); 1849 | tcase_add_test(tc, string_u8_push_back_utf8); 1850 | tcase_add_test(tc, string_u8_push_back_all); 1851 | tcase_add_test(tc, string_pop_back_succeeds); 1852 | tcase_add_test(tc, string_u8_pop_back_ascii); 1853 | tcase_add_test(tc, string_u8_pop_back_utf8); 1854 | tcase_add_test(tc, string_u8_pop_back_all); 1855 | tcase_add_test(tc, string_append_cstr_small_to_small); 1856 | tcase_add_test(tc, string_append_cstr_large_to_large); 1857 | tcase_add_test(tc, string_append_cstr_small_to_large); 1858 | tcase_add_test(tc, string_append_string_small_to_small); 1859 | tcase_add_test(tc, string_append_string_large_to_large); 1860 | tcase_add_test(tc, string_append_string_small_to_large); 1861 | tcase_add_test(tc, string_append_cstr_part_succeeds); 1862 | tcase_add_test(tc, string_append_string_part_succeeds); 1863 | tcase_add_test(tc, string_compare_cstr_less_than); 1864 | tcase_add_test(tc, string_compare_cstr_greater_than); 1865 | tcase_add_test(tc, string_compare_cstr_equals); 1866 | tcase_add_test(tc, string_compare_string_less_than); 1867 | tcase_add_test(tc, string_compare_string_greater_than); 1868 | tcase_add_test(tc, string_compare_string_equals); 1869 | tcase_add_test(tc, string_equals_cstr_true); 1870 | tcase_add_test(tc, string_equals_cstr_false); 1871 | tcase_add_test(tc, string_equals_string_true); 1872 | tcase_add_test(tc, string_equals_string_false); 1873 | tcase_add_test(tc, string_starts_with_cstr_true); 1874 | tcase_add_test(tc, string_starts_with_cstr_false); 1875 | tcase_add_test(tc, string_starts_with_string_true); 1876 | tcase_add_test(tc, string_starts_with_string_false); 1877 | tcase_add_test(tc, string_ends_with_cstr_true); 1878 | tcase_add_test(tc, string_ends_with_cstr_false); 1879 | tcase_add_test(tc, string_ends_with_string_true); 1880 | tcase_add_test(tc, string_ends_with_string_false); 1881 | tcase_add_test(tc, string_replace_cstr_shrink); 1882 | tcase_add_test(tc, string_replace_cstr_shrink_end); 1883 | tcase_add_test(tc, string_replace_cstr_same_size); 1884 | tcase_add_test(tc, string_replace_cstr_grow_small_to_small); 1885 | tcase_add_test(tc, string_replace_cstr_grow_small_to_large); 1886 | tcase_add_test(tc, string_replace_string_shrink); 1887 | tcase_add_test(tc, string_replace_string_shrink_end); 1888 | tcase_add_test(tc, string_replace_string_same_size); 1889 | tcase_add_test(tc, string_replace_string_grow_small_to_small); 1890 | tcase_add_test(tc, string_replace_string_grow_small_to_large); 1891 | tcase_add_test(tc, string_substring_part); 1892 | tcase_add_test(tc, string_substring_whole); 1893 | tcase_add_test(tc, string_copy_small); 1894 | tcase_add_test(tc, string_copy_large); 1895 | tcase_add_test(tc, string_copy_to_short); 1896 | tcase_add_test(tc, string_copy_to_large); 1897 | tcase_add_test(tc, string_resize_shrink); 1898 | tcase_add_test(tc, string_resize_same_size); 1899 | tcase_add_test(tc, string_resize_grow); 1900 | tcase_add_test(tc, string_swap_small_and_large); 1901 | tcase_add_test(tc, string_find_cstr_first); 1902 | tcase_add_test(tc, string_find_cstr_second); 1903 | tcase_add_test(tc, string_find_cstr_none); 1904 | tcase_add_test(tc, string_find_string_first); 1905 | tcase_add_test(tc, string_find_string_second); 1906 | tcase_add_test(tc, string_find_string_none); 1907 | tcase_add_test(tc, string_rfind_cstr_first); 1908 | tcase_add_test(tc, string_rfind_cstr_second); 1909 | tcase_add_test(tc, string_rfind_cstr_none); 1910 | tcase_add_test(tc, string_rfind_string_first); 1911 | tcase_add_test(tc, string_rfind_string_second); 1912 | tcase_add_test(tc, string_rfind_string_none); 1913 | tcase_add_test(tc, string_is_null_or_empty_null); 1914 | tcase_add_test(tc, string_is_null_or_empty_empty); 1915 | tcase_add_test(tc, string_is_null_or_empty_filled); 1916 | tcase_add_test(tc, string_is_null_or_whitespace_empty); 1917 | tcase_add_test(tc, string_is_null_or_whitespace_space); 1918 | tcase_add_test(tc, string_is_null_or_whitespace_u8_space); 1919 | tcase_add_test(tc, string_is_null_or_whitespace_filled); 1920 | tcase_add_test(tc, string_reverse_bytes_ascii); 1921 | tcase_add_test(tc, string_reverse_bytes_utf8); 1922 | tcase_add_test(tc, string_u8_reverse_codepoints_ascii); 1923 | tcase_add_test(tc, string_u8_reverse_codepoints_utf8); 1924 | tcase_add_test(tc, string_join_none); 1925 | tcase_add_test(tc, string_join_two); 1926 | tcase_add_test(tc, string_join_many); 1927 | tcase_add_test(tc, string_join_refs_none); 1928 | tcase_add_test(tc, string_join_refs_two); 1929 | tcase_add_test(tc, string_join_refs_many); 1930 | tcase_add_test(tc, string_format_string_new); 1931 | tcase_add_test(tc, string_format_string_existing); 1932 | tcase_add_test(tc, string_format_string_append); 1933 | tcase_add_test(tc, string_format_cstr_new); 1934 | tcase_add_test(tc, string_format_cstr_existing); 1935 | tcase_add_test(tc, string_format_cstr_append); 1936 | tcase_add_test(tc, string_split_init_into_existing_less_than_size); 1937 | tcase_add_test(tc, string_split_init_into_existing_equal_size); 1938 | tcase_add_test(tc, string_split_init_into_existing_greater_than_size); 1939 | tcase_add_test(tc, string_split_no_init_into_existing_less_than_size); 1940 | tcase_add_test(tc, string_split_no_init_into_existing_equal_size); 1941 | tcase_add_test(tc, string_split_no_init_into_existing_greater_than_size); 1942 | tcase_add_test(tc, string_split_allocate_init); 1943 | tcase_add_test(tc, string_split_skip_empty); 1944 | tcase_add_test(tc, string_split_dont_skip_empty); 1945 | tcase_add_test(tc, string_hash_verify); 1946 | 1947 | 1948 | suite_add_tcase(s, tc); 1949 | 1950 | SRunner* sr = srunner_create(s); 1951 | 1952 | srunner_run_all(sr, CK_NORMAL); 1953 | number_failed = srunner_ntests_failed(sr); 1954 | srunner_free(sr); 1955 | 1956 | return number_failed == 0 ? EXIT_SUCCESS : EXIT_FAILURE; 1957 | } --------------------------------------------------------------------------------