From 110ccae8b9ad375579e8b4a68a39e4ca2c1d6538 Mon Sep 17 00:00:00 2001 From: Marco Cetica Date: Fri, 13 Mar 2026 10:43:36 +0100 Subject: [PATCH 1/8] Added UTF-8 helper functions --- src/string.c | 152 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/string.h | 67 +++++++++++++++++++++++ 2 files changed, 219 insertions(+) create mode 100644 src/string.c create mode 100644 src/string.h diff --git a/src/string.c b/src/string.c new file mode 100644 index 0000000..4a4d0dc --- /dev/null +++ b/src/string.c @@ -0,0 +1,152 @@ +#define SET_MSG(result, msg) \ + do { \ + snprintf((char *)(result).message, RESULT_MSG_SIZE, "%s", (const char *)msg); \ + } while (0) + +#include +#include +#include +#include + +#include "string.h" + +// Check if a character is a space +static inline bool is_space(unsigned char c) { + return (c == ' ' || c == '\t' || + c == '\n' || c == '\r' || + c == '\f' || c == '\v'); +} + +// Get byte length of an UTF-8 sequence +static inline int utf8_char_len(unsigned char byte) { + if ((byte & 0x80) == 0x00) return 1; + if ((byte & 0xE0) == 0xC0) return 2; + if ((byte & 0xF0) == 0xE0) return 3; + if ((byte & 0xF8) == 0xF0) return 4; + + return -1; +} + +// Validate an UTF-8 symbol +static bool utf8_is_char_valid(const char *utf8_char, int *out_len) { + if (utf8_char == NULL) { + return false; + } + + const size_t len = utf8_char_len((unsigned char)utf8_char[0]); + if (len <= 0) { + return false; + } + + for (size_t idx = 1; idx < len; idx++) { + if ((utf8_char[idx] & 0xC0) != 0x80) { + return false; + } + } + + if (utf8_char[len] != '\0') { + return false; + } + + if (out_len) { + *out_len = len; + } + + return true; +} + +// Validate an UTF-8 symbol and measure byte length and character count +static bool utf8_scan(const char *str, size_t *out_byte_size, size_t *out_char_count) { + size_t b_size = 0; + size_t c_count = 0; + const unsigned char *p = (const unsigned char *)str; + + while (p[b_size] != '\0') { + size_t len = utf8_char_len(p[b_size]); + if (len <= 0) { + return false; + } + + for (size_t idx = 1; idx < len; idx++) { + if (p[b_size + idx] == '\0' || (p[b_size + idx] & 0xC0) != 0x80) { + return false; + } + } + p_size += len; + c_count++; + } + + *out_byte_size = b_size; + *out_char_count = c_count; + + return true; +} + +// Decode an UTF-8 symbol to a codepoint +static uint32_t utf8_decode(const char *str, int *char_len) { + unsigned char byte = (unsigned char)*str; + *char_len = utf8_char_len(byte); + + uint32_t result = 0; + + switch (*char_len) { + case 1: + result = byte; + break; + case 2: + result = ((byte & 0x1F) << 6) | + (str[1] & 0x3F); + break; + case 3: + result = ((byte & 0x0F) << 12) | + ((str[1] & 0x3F) << 6) | + (str[2] & 0x3F); + break; + case 4: + result = ((byte & 0x07) << 18) | + ((str[1] & 0x3F) << 12) | + ((str[2] & 0x3F) << 6) | + (str[3] & 0x3F); + break; + default: + result = 0; + break; + } + + return result; +} + +// Encode a codepoint to an UTF-8 symbol +static int utf8_encode(uint32_t codepoint, char *out) { + if (codepoint <= 0x7F) { + out[0] = (char)codepoint; + + return 1; + } + + if (codepoint <= 0x7FF) { + out[0] = (char)(0xC0 | (codepoint >> 6)); + out[1] = (char)(0x80 | (codepount & 0x3F)); + + return 2; + } + + if (codepoint <= 0xFFFF) { + out[0] = (char)(0xE0 | (codepoint >> 12)); + out[1] = (char)(0x80 | ((codepoint >> 6) & 0x3F)); + out[2] = (char)(0x80 | (codepoint & 0x3F)); + + return 3; + } + + if (codepoint <= 0x10FFFF) { + out[0] = (char)(0xF0 | (codepoint >> 18)); + out[1] = (char)(0x80 | ((codepoint >> 12) & 0x3F)); + out[2] = (char)(0x80 | ((codepoint >> 6) & 0x3F)); + out[3] = (char)(0x80 | (codepoint & 0x3F)); + + return 4; + } + + return 0; +} diff --git a/src/string.h b/src/string.h new file mode 100644 index 0000000..2715cb4 --- /dev/null +++ b/src/string.h @@ -0,0 +1,67 @@ +#ifndef STRING_H +#define STRING_H + +#define RESULT_MSG_SIZE 64 + +#include +#include +#include + +typedef enum { + STRING_OK = 0x0, + STRING_ERR_ALLOCATE, + STRING_ERR_INVALID, + STRING_ERR_INVALID_UTF8, + STRING_ERR_OVERFLOW +} string_status_t; + +typedef struct { + char *data; + size_t byte_size; // Size in bytes minus the NULL terminator + size_t byte_cap; // Total allocated memory + size_t char_count; // Number of symbols +} string_t; + +typedef struct { + string_status_t status; + uint8_t message[RESULT_MSG_SIZE]; + union { + string_t *string; // For new, clone, slice, reverse, trim + char *symbol; // For get_at + bool is_eq; // For comparison + struct { // For split + string_t **strings; + size_t count; + } split; + } value; +} string_result_t; + +#ifdef __cplusplus +#extern "C" { +#endif + +// Public APIs +string_result_t string_new(const char *c_str); +string_result_t string_close(const string_t *str); +string_result_t string_concat(const string_t *x, const string_t *y); +string_result_t string_contains(const string_t *haystack, const string_t *needle); +string_result_t string_slice(const string_t *str, size_t start, size_t end); +string_result_t string_eq(const string_t *x, const string_t *y, bool case_sensitive); +string_result_t string_get_at(const string_t *str, size_t position); +string_result_t string_set_at(const string_t *str, size_t position, const char *utf8_char); +string_result_t string_to_lower(const string_t *str); +string_result_t string_to_upper(const string_t *str); +string_result_t string_reverse(const string_t *str); +string_result_t string_trim(const string_t *str); +string_result_t string_split(const string_t *str, const char *delim); +string_result_t string_destroy(string_t *str); +string_result_t string_split_destroy(string_t **split, size_t count); + +// Inline methods +static inline size_t string_size(const string_t *str) { + return str ? str->char_count : 0; +} + +#ifdef __cplusplus +} +#endif From 55b14ee94902d463f76586d430a2b13fe71e6966 Mon Sep 17 00:00:00 2001 From: Marco Cetica Date: Fri, 13 Mar 2026 11:39:50 +0100 Subject: [PATCH 2/8] Added `string_{new,clone,concat,contains,slice,eq}` --- Makefile | 2 +- src/string.c | 321 ++++++++++++++++++++++++++++++++++++++++++++++++++- src/string.h | 7 +- 3 files changed, 325 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index f145c58..115bc86 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ TEST_M_TARGET = test_map TEST_B_TARGET = test_bigint BENCH_TARGET = benchmark_datum -LIB_OBJS = $(OBJ_DIR)/vector.o $(OBJ_DIR)/map.o $(OBJ_DIR)/bigint.o +LIB_OBJS = $(OBJ_DIR)/vector.o $(OBJ_DIR)/map.o $(OBJ_DIR)/bigint.o $(OBJ_DIR)/string.o PROG_OBJS = $(OBJ_DIR)/usage.o .PHONY: all clean diff --git a/src/string.c b/src/string.c index 4a4d0dc..a69f672 100644 --- a/src/string.c +++ b/src/string.c @@ -72,7 +72,7 @@ static bool utf8_scan(const char *str, size_t *out_byte_size, size_t *out_char_c return false; } } - p_size += len; + b_size += len; c_count++; } @@ -126,7 +126,7 @@ static int utf8_encode(uint32_t codepoint, char *out) { if (codepoint <= 0x7FF) { out[0] = (char)(0xC0 | (codepoint >> 6)); - out[1] = (char)(0x80 | (codepount & 0x3F)); + out[1] = (char)(0x80 | (codepoint & 0x3F)); return 2; } @@ -150,3 +150,320 @@ static int utf8_encode(uint32_t codepoint, char *out) { return 0; } + +/** + * string_new + * @c_str: a C-string + * + * Returns a string_result_t containing a new String data type + */ +string_result_t string_new(const char *c_str) { + string_result_t result = {0}; + + if (c_str == NULL) { + result.status = STRING_ERR_INVALID; + SET_MSG(result, "Invalid input string"); + + return result; + } + + size_t b_size, c_count; + if (utf8_scan(c_str, &b_size, &c_count) == 0) { + result.status = STRING_ERR_INVALID_UTF8; + SET_MSG(result, "Malformed UTF-8 sequence"); + + return result; + } + + string_t *str = malloc(sizeof(string_t)); + if (str == NULL) { + result.status = STRING_ERR_ALLOCATE; + SET_MSG(result, "Cannot allocate memory"); + + return result; + } + + str->data = malloc(b_size + 1); + if (str->data == NULL) { + free(str); + result.status = STRING_ERR_ALLOCATE; + SET_MSG(result, "Cannot allocate memory"); + + return result; + } + + memcpy(str->data, c_str, b_size); + str->data[b_size] = '\0'; + str->byte_size = b_size; + str->byte_capacity = b_size + 1; + str->char_count = c_count; + + result.status = STRING_OK; + result.value.string = str; + SET_MSG(result, "String successfully created"); + + return result; +} + +/** + * string_clone + * @str: a non-null string + * + * Deep copies @str + * + * Returns a string_result_t containing the copied string + */ +string_result_t string_clone(const string_t *str) { + string_result_t result = {0}; + + if (str == NULL) { + result.status = STRING_ERR_INVALID; + SET_MSG(result, "Invalid string"); + + return result; + } + + string_t *str_copy = malloc(sizeof(string_t)); + if (str_copy == NULL) { + result.status = STRING_ERR_ALLOCATE; + SET_MSG(result, "Cannot allocate memory"); + + return result; + } + + str_copy->data = malloc(str->byte_size + 1); + if (str_copy->data == NULL) { + free(str_copy); + result.status = STRING_ERR_ALLOCATE; + SET_MSG(result, "Cannot allocate memory"); + + return result; + } + + memcpy(str_copy->data, str->data, str->byte_size); + str_copy->data[str->byte_size] = '\0'; + str_copy->byte_size = str->byte_size; + str_copy->byte_capacity = str->byte_size + 1; + str_copy->char_count = str->char_count; + + result.status = STRING_OK; + result.value.string = str_copy; + SET_MSG(result, "String successfully copied"); + +return result; +} + +/** + * string_concat + * @x: a non-null string + * @y: a non-null string + * + * Concats @x and @y in a new String + * + * Returns a string_result_t containing the new string + */ +string_result_t string_concat(const string_t *x, const string_t *y) { + string_result_t result = {0}; + + if (x == NULL || y == NULL) { + result.status = STRING_ERR_INVALID; + SET_MSG(result, "Invalid strings"); + + return result; + } + + if (x->byte_size > SIZE_MAX - y->byte_size - 1) { + result.status = STRING_ERR_OVERFLOW; + SET_MSG(result, "Concatenation exceeds size limits"); + + return result; + } + + size_t new_size = x->byte_size + y->byte_size; + char *buf = malloc(new_size + 1); + if (buf == NULL) { + result.status = STRING_ERR_ALLOCATE; + SET_MSG(result, "Cannot allocate memory"); + + return result; + } + + memcpy(buf, x->data, x->byte_size); + memcpy(buf + x->byte_size, y->data, y->byte_size); + buf[new_size] = '\0'; + result = string_new(buf); + free(buf); + + return result; +} + +/** + * string_contains + * @haystack: a non-null string + * @needle: a non-null string + * + * Finds @needle on @haystack + * + * Returns a string_result_t containing the index to the beginning of the located string + * (if the substring has been found) + */ +string_result_t string_contains(const string_t *haystack, const string_t *needle) { + string_result_t result = { + .status = STRING_OK, + .value.idx = -1 + }; + + if (haystack == NULL || needle == NULL || needle->byte_size == 0) { + result.status = STRING_ERR_INVALID; + SET_MSG(result, "Invalid substrings"); + + return result; + } + + const char *found = strstr(haystack->data, needle->data); + if (found) { + size_t char_idx = 0; + const char *ptr = haystack->data; + while (ptr < found) { + ptr += utf8_char_len((unsigned char)*ptr); + char_idx++; + } + + result.value.idx = (int64_t)char_idx; + SET_MSG(result, "Substring found"); + } else { + SET_MSG(result, "Substring not found"); + } + + return result; +} + +/** + * string_slice + * @str: a non-null string + * @start: the lower bound (inclusive) + * @end: the upper bound (inclusive) + * + * Extracts a slice from @str between @start and @end (inclusive) + * + * Returns a string_result_t data type containing the slice + */ +string_result_t string_slice(const string_t *str, size_t start, size_t end) { + string_result_t result = {0}; + + if (str == NULL) { + result.status = STRING_ERR_INVALID; + SET_MSG(result, "Invalid string"); + + return result; + } + + if (start > end || end >= str->char_count) { + result.status = STRING_ERR_OVERFLOW; + SET_MSG(result, "Index out of bounds"); + + return result; + } + + size_t start_byte_offset = 0; + for (size_t idx = 0; idx < start; idx++) { + start_byte_offset += utf8_char_len((unsigned char)str->data[start_byte_offset]); + } + + size_t end_byte_offset = start_byte_offset; + for (size_t idx = start; idx <= end; idx++) { + end_byte_offset += utf8_char_len((unsigned char)str->data[end_byte_offset]); + } + + const size_t slice_byte_size = (end_byte_offset - start_byte_offset); + + string_t *slice = malloc(sizeof(string_t)); + if (slice == NULL) { + result.status = STRING_ERR_ALLOCATE; + SET_MSG(result, "Cannot allocate memory"); + + return result; + } + + slice->data = malloc(slice_byte_size + 1); + if (slice->data == NULL) { + free(slice); + result.status = STRING_ERR_ALLOCATE; + SET_MSG(result, "Cannot allocate memory"); + + return result; + } + + memcpy(slice->data, str->data + start_byte_offset, slice_byte_size); + slice->data[slice_byte_size] = '\0'; + + slice->byte_size = slice_byte_size; + slice->byte_capacity = slice_byte_size + 1; + slice->char_count = end - start + 1; + + result.status = STRING_OK; + result.value.string = slice; + SET_MSG(result, "String sliced successfully"); + + return result; +} + +/** + * string_eq + * @x: a non-null string + * @y: a non-null string + * @case_sensitive: boolean value for case sensitive comparison + * + * Compares two Strings + * + * Returns a string_result_t containing the comparison result + */ +string_result_t string_eq(const string_t *x, const string_t *y, bool case_sensitive) { + string_result_t result = { + .status = STRING_OK, + .value.is_equ = false + }; + + if (x == NULL || y == NULL) { + result.status = STRING_ERR_INVALID; + SET_MSG(result, "Invalid strings"); + + return result; + } + + if (x->char_count != y->char_count) { + result.status = STRING_ERR_INVALID; + SET_MSG(result, "Strings differ in length"); + + return result; + } + + if (case_sensitive) { + result.value.is_equ = (strcmp(x->data, y->data) == 0); + } else { + const char *p1 = x->data, *p2 = y->data; + while (*p1 && *p2) { + int l1, l2; + + const uint32_t codepoint1 = utf8_decode(p1, &l1); + const uint32_t codepoint2 = utf8_decode(p2, &l2); + const uint32_t c1 = (codepoint1 >= 'A' && codepoint1 <= 'Z') ? codepoint1 + 32 : codepoint1; + const uint32_t c2 = (codepoint2 >= 'A' && codepoint2 <= 'Z') ? codepoint2 + 32 : codepoint2; + + if (c1 != c2) { + result.value.is_equ = false; + + return result; + } + + p1 += l1; + p2 += l2; + } + + result.value.is_equ = (*p1 == *p2); + } + + SET_MSG(result, "Comparison completed successfully"); + + return result; +} diff --git a/src/string.h b/src/string.h index 2715cb4..66099e8 100644 --- a/src/string.h +++ b/src/string.h @@ -18,7 +18,7 @@ typedef enum { typedef struct { char *data; size_t byte_size; // Size in bytes minus the NULL terminator - size_t byte_cap; // Total allocated memory + size_t byte_capacity; // Total allocated memory size_t char_count; // Number of symbols } string_t; @@ -28,7 +28,8 @@ typedef struct { union { string_t *string; // For new, clone, slice, reverse, trim char *symbol; // For get_at - bool is_eq; // For comparison + int64_t idx; // For contains + bool is_equ; // For comparison struct { // For split string_t **strings; size_t count; @@ -65,3 +66,5 @@ static inline size_t string_size(const string_t *str) { #ifdef __cplusplus } #endif + +#endif From 086446039bb047121c8de757f6e7617422d00d36 Mon Sep 17 00:00:00 2001 From: Marco Cetica Date: Fri, 13 Mar 2026 11:48:42 +0100 Subject: [PATCH 3/8] Fixed a bug about string comparison --- src/string.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/string.c b/src/string.c index a69f672..bbc2bd4 100644 --- a/src/string.c +++ b/src/string.c @@ -432,8 +432,9 @@ string_result_t string_eq(const string_t *x, const string_t *y, bool case_sensit } if (x->char_count != y->char_count) { - result.status = STRING_ERR_INVALID; - SET_MSG(result, "Strings differ in length"); + result.value.is_equ = false; + result.status = STRING_OK; + SET_MSG(result, "Comparison completed successfully"); return result; } From b4a704f1b416f37908aef13fa07f1aad1733799f Mon Sep 17 00:00:00 2001 From: Marco Cetica Date: Fri, 13 Mar 2026 17:16:02 +0100 Subject: [PATCH 4/8] Added `string_{get_at,set_at,to_lower,to_upper,reverse} --- src/string.c | 283 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 282 insertions(+), 1 deletion(-) diff --git a/src/string.c b/src/string.c index bbc2bd4..17dfb52 100644 --- a/src/string.c +++ b/src/string.c @@ -250,7 +250,7 @@ string_result_t string_clone(const string_t *str) { result.value.string = str_copy; SET_MSG(result, "String successfully copied"); -return result; + return result; } /** @@ -294,6 +294,8 @@ string_result_t string_concat(const string_t *x, const string_t *y) { result = string_new(buf); free(buf); + SET_MSG(result, "String successfully concatenated"); + return result; } @@ -468,3 +470,282 @@ string_result_t string_eq(const string_t *x, const string_t *y, bool case_sensit return result; } + +/** + * string_get_at + * @str: a non-null string + * @position: the position of the symbol to read + * + * Gets symbol indexed by @position from @str + * + * Returns a string_result_t containing the symbol as a C string + */ +string_result_t string_get_at(const string_t *str, size_t position) { + string_result_t result = {0}; + + if (str == NULL) { + result.status = STRING_ERR_INVALID; + SET_MSG(result, "Invalid string"); + + return result; + } + + if (position >= str->char_count) { + result.status = STRING_ERR_OVERFLOW; + SET_MSG(result, "Index out of bounds"); + + return result; + } + + const char *ptr = str->data; + for (size_t idx = 0; idx < position; idx++) { + ptr += utf8_char_len((unsigned char)*ptr); + } + + int char_len = utf8_char_len((unsigned char)*ptr); + char *utf8_char = malloc(char_len + 1); + if (utf8_char == NULL) { + result.status = STRING_ERR_ALLOCATE; + SET_MSG(result, "Cannot allocate memory"); + + return result; + } + + memcpy(utf8_char, ptr, char_len); + utf8_char[char_len] = '\0'; + + result.value.symbol = utf8_char; + result.status = STRING_OK; + SET_MSG(result, "Symbol successfully retrieved"); + + return result; +} + +/** + * string_set_at + * @str: a non-null string + * @position: the position to write into + * @utf8_char: an UTF8 symbol + * + * Writes @utf8_char into @str at index @position + * + * Returns a string_result_t data type + */ +string_result_t string_set_at(const string_t *str, size_t position, const char *utf8_char) { + string_result_t result = {0}; + + if (str == NULL) { + result.status = STRING_ERR_INVALID; + SET_MSG(result, "Invalid string"); + + return result; + } + + int new_char_bytes; + if (utf8_is_char_valid(utf8_char, &new_char_bytes) == 0) { + result.status = STRING_ERR_INVALID_UTF8; + SET_MSG(result, "Invalid UTF-8 character"); + + return result; + } + + if (position >= str->char_count) { + result.status = STRING_ERR_OVERFLOW; + SET_MSG(result, "Index out of bounds"); + + return result; + } + + // Locate the byte offset of the character to replace + const char *pos = str->data; + for (size_t idx = 0; idx < position; idx++) { + pos += utf8_char_len((unsigned char)*pos); + } + + const size_t prefix_len = pos - str->data; + const int old_char_bytes = utf8_char_len((unsigned char)*pos); + const size_t suffix_len = str->byte_size - prefix_len - old_char_bytes; + const size_t new_total_bytes = prefix_len + new_char_bytes + suffix_len; + + string_t *new_str = malloc(sizeof(string_t)); + if (new_str == NULL) { + result.status = STRING_ERR_ALLOCATE; + SET_MSG(result, "Cannot allocate memory"); + + return result; + } + + new_str->data = malloc(new_total_bytes + 1); + if (new_str->data == NULL) { + free(new_str); + result.status = STRING_ERR_ALLOCATE; + SET_MSG(result, "Cannot allocate memory"); + + return result; + } + + // Copy prefix data from original string + memcpy(new_str->data, str->data, prefix_len); + // Copy the new character at requested index + memcpy(new_str->data + prefix_len, utf8_char, new_char_bytes); + // Copy suffix data from the original string by skipping the overwritten character + memcpy(new_str->data + prefix_len + new_char_bytes, pos + old_char_bytes, suffix_len); + new_str->data[new_total_bytes] = '\0'; + + new_str->byte_size = new_total_bytes; + new_str->byte_capacity = new_total_bytes + 1; + new_str->char_count = str->char_count; + + result.status = STRING_OK; + result.value.string = new_str; + SET_MSG(result, "Symbol successfully set"); + + return result; +} + +/** + * string_to_lower + * @str: a non-null string + * + * Converts a String to lowercase + * + * Returns a string_result_t containing a new string + */ +string_result_t string_to_lower(const string_t *str) { + string_result_t result = {0}; + + if (str == NULL) { + result.status = STRING_ERR_INVALID; + SET_MSG(result, "Invalid string"); + + return result; + } + + char *buf = malloc(str->byte_capacity); + if (buf == NULL) { + result.status = STRING_ERR_ALLOCATE; + SET_MSG(result, "Cannot allocate memory"); + + return result; + } + + const char *src = str->data; + char *dst = buf; + + while (*src) { + int len; + uint32_t codepoint = utf8_decode(src, &len); + uint32_t lower = (codepoint >= 'A' && codepoint <= 'Z') ? codepoint + 32 : codepoint; + dst += utf8_encode(lower, dst); + src += len; + } + *dst = '\0'; + result = string_new(buf); + free(buf); + + SET_MSG(result, "String successfully converted to lowercase"); + + return result; +} + +/** + * string_to_upper + * @str: a non-null string + * + * Converts a String to uppercase + * + * Returns a string_result_t containing a new string + */ +string_result_t string_to_upper(const string_t *str) { + string_result_t result = {0}; + + if (str == NULL) { + result.status = STRING_ERR_INVALID; + SET_MSG(result, "Invalid string"); + + return result; + } + + char *buf = malloc(str->byte_capacity); + if (buf == NULL) { + result.status = STRING_ERR_ALLOCATE; + SET_MSG(result, "Cannot allocate memory"); + + return result; + } + + const char *src = str->data; + char *dst = buf; + while (*src) { + int len; + uint32_t codepoint = utf8_decode(src, &len); + uint32_t upper = (codepoint >= 'a' && codepoint <= 'z') ? codepoint - 32 : codepoint; + dst += utf8_encode(upper, dst); + src += len; + } + *dst = '\0'; + result = string_new(buf); +free(buf); + + SET_MSG(result, "String successfully converted to uppercase"); + + return result; +} + +/** + * string_reverse + * @str: a non-null string + * + * Reverses @str + * + * Returns a new string_result_t containing the reversed string + */ +string_result_t string_reverse(const string_t *str) { + string_result_t result = {0}; + + if (str == NULL) { + result.status = STRING_ERR_INVALID; + SET_MSG(result, "Invalid string"); + + return result; + } + + char *buf = malloc(str->byte_capacity); + if (buf == NULL) { + result.status = STRING_ERR_ALLOCATE; + SET_MSG(result, "Cannot allocate memory"); + + return result; + } + + const char **pos = malloc(str->char_count * sizeof(char *)); + if (pos == NULL) { + free(buf); + result.status = STRING_ERR_ALLOCATE; + SET_MSG(result, "Cannot allocate memory"); + + return result; + } + + const char *ptr = str->data; + for (size_t idx = 0; idx < str->char_count; idx++) { + pos[idx] = ptr; + ptr += utf8_char_len((unsigned char)*ptr); + } + + char *dst = buf; + for (int64_t idx = (int64_t)str->char_count - 1; idx >= 0; idx--) { + int len = utf8_char_len((unsigned char)*pos[idx]); + memcpy(dst, pos[idx], len); + dst += len; + } + + *dst = '\0'; + free(pos); + result = string_new(buf); + free(buf); + + SET_MSG(result, "String successfully reversed"); + + return result; +} From 1871035cd6ca05025c6bc38b6d6e2b0b052eb32c Mon Sep 17 00:00:00 2001 From: Marco Cetica Date: Fri, 13 Mar 2026 17:36:37 +0100 Subject: [PATCH 5/8] Added `string_{trim,split,destroy,split_destroy}` --- src/string.c | 192 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 191 insertions(+), 1 deletion(-) diff --git a/src/string.c b/src/string.c index 17dfb52..1c22c54 100644 --- a/src/string.c +++ b/src/string.c @@ -685,7 +685,7 @@ string_result_t string_to_upper(const string_t *str) { } *dst = '\0'; result = string_new(buf); -free(buf); + free(buf); SET_MSG(result, "String successfully converted to uppercase"); @@ -749,3 +749,193 @@ string_result_t string_reverse(const string_t *str) { return result; } + +/** + * string_trim + * @str: a non-null string + * + * Trims whitespace from @str + * + * Returns a string_result_t containing the trimmed string + */ +string_result_t string_trim(const string_t *str) { + string_result_t result = {0}; + + if (str == NULL) { + result.status = STRING_ERR_INVALID; + SET_MSG(result, "Invalid string"); + + return result; + } + + const char *start = str->data; + while (*start && is_space((unsigned char)*start)) { + start++; + } + + if (*start == '\0') { + return string_new(""); + } + + const char *end = str->data + str->byte_size - 1; + while (end > start && is_space((unsigned char)*end)) { + end--; + } + + const size_t len = (end - start) + 1; + char *trimmed = malloc(len + 1); + if (trimmed == NULL) { + result.status = STRING_ERR_ALLOCATE; + SET_MSG(result, "Cannot allocate memory"); + + return result; + } + + memcpy(trimmed, start, len); + trimmed[len] = '\0'; + result = string_new(trimmed); + free(trimmed); + + result.status = STRING_OK; + SET_MSG(result, "String successfully trimmed"); + + return result; +} + +/** + * string_split + * @str: a non-null string + * @delim: delimiter string + * + * Splits @str by @delim + * + * Returns a string_result_t containing an array of String pointers + */ +string_result_t string_split(const string_t *str, const char *delim) { + string_result_t result = {0}; + string_result_t tmp_res = {0}; + + if (str == NULL || delim == NULL || delim[0] == '\0') { + result.status = STRING_ERR_INVALID; + SET_MSG(result, "Invalid strings"); + + return result; + } + + const char *ptr = str->data; + const size_t delim_len = strlen(delim); + size_t count = 1; + + while ((ptr = strstr(ptr, delim))) { + count++; + ptr += delim_len; + } + + string_t **string_array = malloc(count * sizeof(string_t *)); + if (string_array == NULL) { + result.status = STRING_ERR_ALLOCATE; + SET_MSG(result, "Cannot allocate memory"); + + return result; + } + + const char *start = str->data; + size_t idx = 0; + + while ((ptr = strstr(start, delim))) { + const size_t part_len = ptr - start; + char *tmp = malloc(part_len + 1); + if (tmp == NULL) { + result.status = STRING_ERR_ALLOCATE; + SET_MSG(result, "Cannot allocate memory"); + + goto cleanup; + } + + memcpy(tmp, start, part_len); + tmp[part_len] = '\0'; + + tmp_res = string_new(tmp); + free(tmp); + if (tmp_res.status != STRING_OK) { result = tmp_res; goto cleanup; } + + string_array[idx++] = tmp_res.value.string; + start = ptr + delim_len; + } + + tmp_res = string_new(start); + if (tmp_res.status != STRING_OK) { result = tmp_res; goto cleanup; } + + string_array[idx] = tmp_res.value.string; + + result.status = STRING_OK; + result.value.split.strings = string_array; + result.value.split.count = count; + SET_MSG(result, "String successfully split"); + + return result; + +cleanup: + for (size_t j = 0; j < idx; j++) { string_destroy(string_array[j]); } + free(string_array); + + return result; +} + +/** + * string_destroy + * @str: a non-null string + * + * Destroys @str + * + * Returns a string_result_t data type + */ +string_result_t string_destroy(string_t *str) { + string_result_t result = {0}; + + if (str == NULL) { + result.status = STRING_ERR_INVALID; + SET_MSG(result, "Invalid string"); + + return result; + } + + free(str->data); + free(str); + + result.status = STRING_OK; + SET_MSG(result, "String successfully deleted"); + + return result; +} + +/** + * string_split_destory + * @split: an array of pointers of String + * @count: the number of elements + * + * Destroys the @split array of Strings + * + * Returns a string_result_t data type + */ +string_result_t string_split_destroy(string_t **split, size_t count) { + string_result_t result = {0}; + + if (split == NULL) { + result.status = STRING_ERR_INVALID; + SET_MSG(result, "Invalid string"); + + return result; + } + + for (size_t idx = 0; idx < count; idx++) { + string_destroy(split[idx]); + } + + free(split); + + result.status = STRING_OK; + SET_MSG(result, "Array of strings successfully deleted"); + + return result; +} From 6110fc963c30f7c67e44cffc3688ad97ee5efcbd Mon Sep 17 00:00:00 2001 From: Marco Cetica Date: Fri, 13 Mar 2026 18:27:00 +0100 Subject: [PATCH 6/8] Fixed a bug and added unit tests for String type --- .gitea/workflows/clang-build.yml | 4 +- .gitea/workflows/gcc-build.yml | 4 +- Makefile | 8 +- src/string.h | 2 +- tests/test_string.c | 329 +++++++++++++++++++++++++++++++ 5 files changed, 340 insertions(+), 7 deletions(-) create mode 100644 tests/test_string.c diff --git a/.gitea/workflows/clang-build.yml b/.gitea/workflows/clang-build.yml index aea82b1..a404d92 100644 --- a/.gitea/workflows/clang-build.yml +++ b/.gitea/workflows/clang-build.yml @@ -16,8 +16,8 @@ jobs: - name: Run unit tests run: | - ./test_vector && ./test_map && ./test_bigint + ./test_vector && ./test_map && ./test_bigint && ./test_string - name: Run benchmarks run: | - ./benchmark_datum \ No newline at end of file + ./benchmark_datum diff --git a/.gitea/workflows/gcc-build.yml b/.gitea/workflows/gcc-build.yml index cad9781..c35c938 100644 --- a/.gitea/workflows/gcc-build.yml +++ b/.gitea/workflows/gcc-build.yml @@ -13,8 +13,8 @@ jobs: - name: Run unit tests run: | - ./test_vector && ./test_map && ./test_bigint + ./test_vector && ./test_map && ./test_bigint && ./test_string - name: Run benchmarks run: | - ./benchmark_datum \ No newline at end of file + ./benchmark_datum diff --git a/Makefile b/Makefile index 115bc86..808fbbd 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,7 @@ TARGET = usage TEST_V_TARGET = test_vector TEST_M_TARGET = test_map TEST_B_TARGET = test_bigint +TEST_S_TARGET = test_string BENCH_TARGET = benchmark_datum LIB_OBJS = $(OBJ_DIR)/vector.o $(OBJ_DIR)/map.o $(OBJ_DIR)/bigint.o $(OBJ_DIR)/string.o @@ -24,7 +25,7 @@ PROG_OBJS = $(OBJ_DIR)/usage.o .PHONY: all clean -all: $(TARGET) $(TEST_V_TARGET) $(TEST_M_TARGET) $(TEST_B_TARGET) $(BENCH_TARGET) +all: $(TARGET) $(TEST_V_TARGET) $(TEST_M_TARGET) $(TEST_B_TARGET) $(TEST_S_TARGET) $(BENCH_TARGET) bench: $(BENCH_TARGET) $(TARGET): $(PROG_OBJS) $(LIB_OBJS) @@ -39,6 +40,9 @@ $(TEST_M_TARGET): $(OBJ_DIR)/test_map.o $(OBJ_DIR)/map.o $(TEST_B_TARGET): $(OBJ_DIR)/test_bigint.o $(OBJ_DIR)/bigint.o $(OBJ_DIR)/vector.o $(CC) $(CFLAGS) -o $@ $^ +$(TEST_S_TARGET): $(OBJ_DIR)/test_string.o $(OBJ_DIR)/string.o + $(CC) $(CFLAGS) -o $@ $^ + $(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR) $(CC) $(CFLAGS) -c -o $@ $< @@ -65,4 +69,4 @@ $(BENCH_OBJ_DIR): mkdir -p $(BENCH_OBJ_DIR) clean: - rm -rf $(OBJ_DIR) $(BENCH_OBJ_DIR) $(TARGET) $(TEST_V_TARGET) $(TEST_M_TARGET) $(TEST_B_TARGET) $(BENCH_TARGET) + rm -rf $(OBJ_DIR) $(BENCH_OBJ_DIR) $(TARGET) $(TEST_V_TARGET) $(TEST_M_TARGET) $(TEST_B_TARGET) $(TEST_S_TARGET) $(BENCH_TARGET) diff --git a/src/string.h b/src/string.h index 66099e8..b4dde4b 100644 --- a/src/string.h +++ b/src/string.h @@ -43,7 +43,7 @@ typedef struct { // Public APIs string_result_t string_new(const char *c_str); -string_result_t string_close(const string_t *str); +string_result_t string_clone(const string_t *str); string_result_t string_concat(const string_t *x, const string_t *y); string_result_t string_contains(const string_t *haystack, const string_t *needle); string_result_t string_slice(const string_t *str, size_t start, size_t end); diff --git a/tests/test_string.c b/tests/test_string.c new file mode 100644 index 0000000..2bf2c1a --- /dev/null +++ b/tests/test_string.c @@ -0,0 +1,329 @@ +/* +* Unit tests for String data type + */ + +#define TEST(NAME) do { \ + printf("Running test_%s...", #NAME); \ + test_##NAME(); \ + printf(" PASSED\n"); \ +} while(0) + +#include +#include +#include +#include + +#include "../src/string.h" + +// Test string creation +void test_string_new(void) { + string_result_t res = string_new("hello"); + + assert(res.status == STRING_OK); + assert(res.value.string != NULL); + assert(strcmp(res.value.string->data, "hello") == 0); + assert(string_size(res.value.string) == 5); + assert(res.value.string->byte_size == 5); + + string_destroy(res.value.string); +} + +// Test empty string +void test_string_new_empty(void) { + string_result_t res = string_new(""); + + assert(res.status == STRING_OK); + assert(string_size(res.value.string) == 0); + assert(res.value.string->byte_size == 0); + assert(res.value.string->data[0] == '\0'); + + string_destroy(res.value.string); +} + +// Test cloning an existing string +void test_string_clone(void) { + string_t *original = string_new("Original").value.string; + string_result_t res = string_clone(original); + + assert(res.status == STRING_OK); + assert(res.value.string != original); // Different memory address + assert(strcmp(res.value.string->data, original->data) == 0); + assert(res.value.string->byte_size == original->byte_size); + + string_destroy(original); + string_destroy(res.value.string); +} + +// Test string concatenation +void test_string_concat(void) { + string_t *str1 = string_new("Foo").value.string; + string_t *str2 = string_new(" Bar").value.string; + + string_result_t res = string_concat(str1, str2); + assert(res.status == STRING_OK); + assert(strcmp(res.value.string->data, "Foo Bar") == 0); + assert(string_size(res.value.string) == 7); + + string_destroy(str1); + string_destroy(str2); + string_destroy(res.value.string); +} + +// Test if string contains substring +void test_string_contains(void) { + string_t *haystack = string_new("Hello 🌍 World").value.string; + string_t *needle_ascii = string_new("World").value.string; + string_t *needle_utf8 = string_new("🌍").value.string; + string_t *needle_none = string_new("not found").value.string; + + // World starts at symbol 8 + string_result_t res1 = string_contains(haystack, needle_ascii); + assert(res1.status == STRING_OK); + assert(res1.value.idx == 8); + + // 🌍 is at position 6 + string_result_t res2 = string_contains(haystack, needle_utf8); + assert(res2.status == STRING_OK); + assert(res2.value.idx == 6); + + // Not found should return -1 + string_result_t res3 = string_contains(haystack, needle_none); + assert(res3.status == STRING_OK); + assert(res3.value.idx == -1); + + string_destroy(haystack); + string_destroy(needle_ascii); + string_destroy(needle_utf8); + string_destroy(needle_none); +} + +// Test string slicing +void test_string_slice(void) { + // ASCII slice + string_t *str1 = string_new("foobar").value.string; + string_result_t res1 = string_slice(str1, 2, 4); + + assert(res1.status == STRING_OK); + assert(strcmp(res1.value.string->data, "oba") == 0); + assert(res1.value.string->char_count == 3); + + // UTF-8 slice + string_t *str2 = string_new("AB😀🌍").value.string; + string_result_t res2 = string_slice(str2, 2, 2); + + assert(res2.status == STRING_OK); + assert(strcmp(res2.value.string->data, "😀") == 0); + assert(res2.value.string->byte_size == 4); // emoji = 4 bytes + + // UTF-8 + ASCII slice + string_result_t res3 = string_slice(str2, 0, 2); + assert(res3.status == STRING_OK); + assert(strcmp(res3.value.string->data, "AB😀") == 0); + + // Invalid bounds + string_result_t res4 = string_slice(str1, 5, 2); + assert(res4.status == STRING_ERR_OVERFLOW); + + res4 = string_slice(str1, 1, 50); + assert(res4.status == STRING_ERR_OVERFLOW); + + string_destroy(str1); + string_destroy(str2); + string_destroy(res1.value.string); + string_destroy(res2.value.string); + string_destroy(res3.value.string); +} + +// Test case-insensitive and sensitive comparison +void test_string_eq(void) { + string_t *str1 = string_new("Foo").value.string; + string_t *str2 = string_new("foo").value.string; + + // Case sensitive comparison should be false + assert(string_eq(str1, str2, true).value.is_equ == false); + // Case insensitive comparison should be true + assert(string_eq(str1, str2, false).value.is_equ == true); + + string_destroy(str1); + string_destroy(str2); +} + +// Test string reverse using UTF-8 symbols +void test_string_reverse_utf8(void) { + string_t *str = string_new("A🌍Z").value.string; + + string_result_t res = string_reverse(str); + + assert(res.status == STRING_OK); + assert(string_size(res.value.string) == 3); + assert(strcmp(res.value.string->data, "Z🌍A") == 0); + assert(string_size(res.value.string) == 3); + + string_destroy(str); + string_destroy(res.value.string); +} + +// Test string get_at +void test_string_get_at(void) { + string_t *str = string_new("AB😀🌍").value.string; + + // 😀 is at index 2 + string_result_t res1 = string_get_at(str, 2); + assert(res1.status == STRING_OK); + assert(strcmp((char*)res1.value.symbol, "😀") == 0); + free(res1.value.symbol); + + // 🌍 is at index 3 + string_result_t res2 = string_get_at(str, 3); + assert(res2.status == STRING_OK); + assert(strcmp((char*)res2.value.symbol, "🌍") == 0); + free(res2.value.symbol); + + string_destroy(str); +} + +// Test string get_at with invalid index +void test_string_get_at_overflow(void) { + string_t *str = string_new("ABC").value.string; + + string_result_t res = string_get_at(str, 50); + assert(res.status == STRING_ERR_OVERFLOW); + + string_destroy(str); +} + +// Test mutation of UTF-8 symbol +void test_string_set_at(void) { + string_t *str = string_new("ABC").value.string; + + // Replace 'B' with an emoji + string_result_t res = string_set_at(str, 1, "😀"); + string_t *altered = res.value.string; + + assert(res.status == STRING_OK); + assert(strcmp(altered->data, "A😀C") == 0); + assert(string_size(altered) == 3); + assert(altered->byte_size == 6); // that is: A (1B) + emoji (4B) + C (1B) + + string_destroy(str); + string_destroy(altered); +} + +// Test mutation of invalid UTF-8 symbol +void test_string_set_at_invalid_utf8(void) { + string_t *str = string_new("ABC").value.string; + + const char * const invalid_sym1 = "\xFF"; + const char * const invalid_sym2 = "\x80"; + + string_result_t res1 = string_set_at(str, 1, invalid_sym1); + assert(res1.status == STRING_ERR_INVALID_UTF8); + + string_result_t res2 = string_set_at(str, 1, invalid_sym2); + assert(res2.status == STRING_ERR_INVALID_UTF8); + + string_destroy(str); +} + +// Test mutation with overflow +void test_string_set_at_overflow(void) { + string_t *str = string_new("ABC").value.string; + + string_result_t res = string_set_at(str, 10, "a"); + assert(res.status == STRING_ERR_OVERFLOW); + + string_destroy(str); +} + +// Test string to lowercase +void test_string_to_lower(void) { + string_t *str = string_new("AbC").value.string; + string_result_t res = string_to_lower(str); + + assert(res.status == STRING_OK); + assert(strcmp(res.value.string->data, "abc") == 0); + + string_destroy(str); + string_destroy(res.value.string); +} + +// Test string to uppercase +void test_string_to_upper(void) { + string_t *str = string_new("aBc").value.string; + string_result_t res = string_to_upper(str); + + assert(res.status == STRING_OK); + assert(strcmp(res.value.string->data, "ABC") == 0); + + string_destroy(str); + string_destroy(res.value.string); +} + +// Test whitespace trimming +void test_string_trim(void) { + string_t *str = string_new(" \t Foo Bar \n ").value.string; + + string_result_t res = string_trim(str); + assert(res.status == STRING_OK); + assert(strcmp(res.value.string->data, "Foo Bar") == 0); + + string_destroy(str); + string_destroy(res.value.string); +} + +// Test string splitting into an array +void test_string_split(void) { + string_t *str = string_new("Red,Green,Blue").value.string; + + string_result_t res = string_split(str, ","); + assert(res.status == STRING_OK); + assert(res.value.split.count == 3); + + const size_t count = res.value.split.count; + string_t **strings = res.value.split.strings; + + const char *expected[] = { "Red", "Green", "Blue" }; + for (size_t idx = 0; idx < count; idx++) { + assert(strcmp(strings[idx]->data, expected[idx]) == 0); + } + + string_split_destroy(strings, count); + string_destroy(str); +} + +// Test string destroy +void test_string_destroy(void) { + string_t *str = string_new("delete me").value.string; + + string_result_t res = string_destroy(str); + assert(res.status == STRING_OK); + + string_result_t res_null = string_destroy(NULL); + assert(res_null.status == STRING_ERR_INVALID); +} + +int main(void) { +printf("=== Running String unit tests ===\n\n"); + + TEST(string_new); + TEST(string_new_empty); + TEST(string_clone); + TEST(string_concat); + TEST(string_contains); + TEST(string_slice); + TEST(string_eq); + TEST(string_reverse_utf8); + TEST(string_get_at); + TEST(string_get_at_overflow); + TEST(string_set_at); + TEST(string_set_at_overflow); + TEST(string_set_at_invalid_utf8); + TEST(string_to_lower); + TEST(string_to_upper); + TEST(string_trim); + TEST(string_split); + TEST(string_destroy); + + printf("\n=== All tests passed! ===\n"); + return 0; +} From f8a77a6056e4154247ebb79db4907a42cb4d1431 Mon Sep 17 00:00:00 2001 From: Marco Cetica Date: Mon, 16 Mar 2026 09:38:38 +0100 Subject: [PATCH 7/8] Added `String` type documentation --- Makefile | 18 +- README.md | 45 ++- docs/README.md | 3 +- docs/bigint.md | 2 - docs/map.md | 3 +- docs/string.md | 85 ++++++ docs/vector.md | 3 +- examples/Makefile | 37 +++ examples/bigint_operations.c | 118 ++++++++ examples/map_basic.c | 101 +++++++ examples/string_advanced.c | 103 +++++++ examples/string_basic.c | 95 +++++++ examples/vector_basic.c | 78 ++++++ examples/vector_functional.c | 118 ++++++++ examples/vector_sorting.c | 110 ++++++++ src/string.c | 2 +- usage.c | 526 ----------------------------------- 17 files changed, 892 insertions(+), 555 deletions(-) create mode 100644 docs/string.md create mode 100644 examples/Makefile create mode 100644 examples/bigint_operations.c create mode 100644 examples/map_basic.c create mode 100644 examples/string_advanced.c create mode 100644 examples/string_basic.c create mode 100644 examples/vector_basic.c create mode 100644 examples/vector_functional.c create mode 100644 examples/vector_sorting.c delete mode 100644 usage.c diff --git a/Makefile b/Makefile index 808fbbd..da527bd 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,6 @@ BENCH_OBJ_DIR = bench_obj TESTS_SRC = tests -TARGET = usage TEST_V_TARGET = test_vector TEST_M_TARGET = test_map TEST_B_TARGET = test_bigint @@ -21,16 +20,12 @@ TEST_S_TARGET = test_string BENCH_TARGET = benchmark_datum LIB_OBJS = $(OBJ_DIR)/vector.o $(OBJ_DIR)/map.o $(OBJ_DIR)/bigint.o $(OBJ_DIR)/string.o -PROG_OBJS = $(OBJ_DIR)/usage.o -.PHONY: all clean +.PHONY: all clean examples -all: $(TARGET) $(TEST_V_TARGET) $(TEST_M_TARGET) $(TEST_B_TARGET) $(TEST_S_TARGET) $(BENCH_TARGET) +all: $(TEST_V_TARGET) $(TEST_M_TARGET) $(TEST_B_TARGET) $(TEST_S_TARGET) $(BENCH_TARGET) examples bench: $(BENCH_TARGET) -$(TARGET): $(PROG_OBJS) $(LIB_OBJS) - $(CC) $(CFLAGS) -o $@ $^ - $(TEST_V_TARGET): $(OBJ_DIR)/test_vector.o $(OBJ_DIR)/vector.o $(CC) $(CFLAGS) -o $@ $^ @@ -43,10 +38,10 @@ $(TEST_B_TARGET): $(OBJ_DIR)/test_bigint.o $(OBJ_DIR)/bigint.o $(OBJ_DIR)/vector $(TEST_S_TARGET): $(OBJ_DIR)/test_string.o $(OBJ_DIR)/string.o $(CC) $(CFLAGS) -o $@ $^ -$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR) - $(CC) $(CFLAGS) -c -o $@ $< +examples: $(LIB_OBJS) + $(MAKE) -C examples -$(OBJ_DIR)/usage.o: usage.c | $(OBJ_DIR) +$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR) $(CC) $(CFLAGS) -c -o $@ $< $(OBJ_DIR)/%.o: $(TESTS_SRC)/%.c | $(OBJ_DIR) @@ -69,4 +64,5 @@ $(BENCH_OBJ_DIR): mkdir -p $(BENCH_OBJ_DIR) clean: - rm -rf $(OBJ_DIR) $(BENCH_OBJ_DIR) $(TARGET) $(TEST_V_TARGET) $(TEST_M_TARGET) $(TEST_B_TARGET) $(TEST_S_TARGET) $(BENCH_TARGET) + rm -rf $(OBJ_DIR) $(BENCH_OBJ_DIR) $(TEST_V_TARGET) $(TEST_M_TARGET) $(TEST_B_TARGET) $(TEST_S_TARGET) $(BENCH_TARGET) + $(MAKE) -C examples clean diff --git a/README.md b/README.md index 42a0145..3544300 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,8 @@ the standard library. It currently features: - [**Vector**](/docs/vector.md): a growable, contiguous array of homogenous generic data types; - [**Map**](/docs/map.md): an associative array of generic heterogenous data types; -- [**BigInt**](/docs/bigint.md): a data type for arbitrary large integers. +- [**BigInt**](/docs/bigint.md): a data type for arbitrary large integers; +- [**String**](/docs/string.md): an immutable, null-terminated string type with partial UTF-8 support. ## Usage At its simplest, you can use this library as follows: @@ -134,20 +135,44 @@ int main(void) { } ``` -For a more exhaustive example, refer to the `usage.c` file. There, you will find a program with proper error management -and a sample usage for every available method. To run it, first issue the following command: +### `String` usage +```c +#include + +#include "src/string.h" + +/* + * Compile with: gcc main.c src/string.c + * Output: Final string: "Hello,World,😀" Splitted: ["Hello" "World" "😀" ] + */ +int main(void) { + string_t *x = string_new(" Hello, ").value.string; + string_t *x_trm = string_trim(x).value.string; + + string_t *y = string_new("😀,dlroW").value.string; + string_t *y_rev = string_reverse(y).value.string; + + string_t *str = string_concat(x_trm, y_rev).value.string; + string_t **strings = string_split(str, ",").value.split.strings; + + printf("Final string: \"%s\" Splitted: [", str->data); + for (int idx = 0; idx < 3; idx++) { printf("\"%s\" ", strings[idx]->data); } + printf("]\n"); + + string_split_destroy(strings, 3); string_destroy(str); + string_destroy(x); string_destroy(y); + string_destroy(x_trm); string_destroy(y_rev); + + return 0; +} +``` + +For additional usage samples, refer to the [`examples/`](/examples) directory. You can compile these example programs with the following command: ```sh $ make clean all ``` -This will compile the library as well as the `usage.c` file, the unit tests and the benchmark. After that, you can run it by typing `./usage`. - -> [!NOTE] -> This project is primarily developed for learning purposes and was not created with industrial -> or production use in mind. As such, it is not intended to compete with any existing C library such as the -> GNU Multiple Precision Arithmetic Library (GMP). - ## Documentation For additional details about this library (internal design, memory management, data ownership, etc.) go to the [docs folder](/docs). diff --git a/docs/README.md b/docs/README.md index 9968106..1929cf6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -7,4 +7,5 @@ At the time being, this documentation includes the following pages: - [vector.md](vector.md): vector documentation; - [map.md](map.md): map documentation; -- [bigint.md](bigint.md): bigint documentation. +- [bigint.md](bigint.md): bigint documentation; +- [string.md](string.md): string documentation. diff --git a/docs/bigint.md b/docs/bigint.md index bd488e4..06abc9d 100644 --- a/docs/bigint.md +++ b/docs/bigint.md @@ -80,8 +80,6 @@ Each method that returns such type indicates whether the operation was successfu by setting the `status` field and by providing a descriptive message on the `message` field. If the operation was successful (that is, `status == BIGINT_OK`), you can either move on with the rest of the program or read the returned value from the sum data type. -Of course, you can choose to ignore the return value (if you're brave enough :D) as -illustrated on the first part of the README. The sum data type (i.e., the `value` union) defines four different variables. Each of them has an unique scope as described below: diff --git a/docs/map.md b/docs/map.md index c30ac33..7712802 100644 --- a/docs/map.md +++ b/docs/map.md @@ -72,5 +72,4 @@ typedef struct { Each method that returns such type indicates whether the operation was successful or not by setting the `status` field and by providing a descriptive message on the `message` field. If the operation was successful (that is, `status == MAP_OK`), you can either move on with the rest of the program or read -the returned value from the sum data type. Of course, you can choose to ignore the return value (if you're brave enough :D) as illustrated -on the first part of the README. +the returned value from the sum data type. diff --git a/docs/string.md b/docs/string.md new file mode 100644 index 0000000..60f7f51 --- /dev/null +++ b/docs/string.md @@ -0,0 +1,85 @@ +# String Technical Details +In this document you can find a quick overview of the technical aspects (internal design, memory layout, etc.) of the `String` data structure. + +`String` is an immutable, null-terminated string data type with partial UTF-8 support. +This means that methods return a new string instance rather than modifying the string in-place. +Internally, this data structure is represented by the following layout: + +```c +typedef struct { + char *data; + size_t byte_size; + size_t byte_capacity; + size_t char_count; +} string_t; +``` + +where the `data` field represent the actual string, `byte_size` +indicates the actual size (in bytes), `byte_capacity` represents the total number +of allocated memory (in bytes) and `char_count` represents the number of symbols. + +As mentioned earlier, this data type provides partial UTF-8 support. It is able +to recognize UTF-8 byte sequences as individual Unicode code points and has +full support for Unicode symbols such as emojis. However, it does not support +localization. In particular, it does not perform local-aware conversions. For instance, uppercase/lowercase transformations are limited to ASCII characters only. As a result, the German scharfes S (`ß`) is not convert to `SS`, the Spanish `Ñ` is not converted to `ñ` and the Italian `é` (and its variants) is not treated as a single symbol but rather as a base letter combined with an accent. + +At the time being, `String` supports the following methods: + +- `string_result_t string_new(c_str)`: create a new string; +- `string_result_t string_clone(str)`: clone an existing string; +- `string_result_t string_concat(x, y)`: concatenate two strings together; +- `string_result_t string_contains(haystack, needle)`: search whether the `haystack` string contains `needle`; +- `string_result_t string_slice(str, start, end)`: return a slice (a new string) from `str` between `start` and `end` indices (inclusive); +- `string_result_t string_eq(x, y, case_sensitive)`: check whether `x` and `y` are equal; +- `string_result_t string_get_at(str, position)`: get the UTF-8 symbol indexed by `position` from `str`; +- `string_result_t string_set_at(str, position, utf8_char)`: write a UTF-8 symbol into `str` at index `position`; +- `string_result_t string_to_lower(str)`: convert a string to lowercase; +- `string_result_t string_to_upper(str)`: convert a string to uppercase; +- `string_result_t string_reverse(str)`: reverse a string; +- `string_result_t string_trim(str)`: remove leading and trailing white space from a string; +- `string_result_t string_split(str, delim)`: split a string into an array of `string_t` by specifying a separator; +- `string_result_t string_destroy(str)`: remove a string from memory; +- `string_result_t string_split_destroy(split, count)`: remove an array of strings from memory; +- `size_t string_size(str)`: return string character count. + +As you can see from the previous function signatures, most methods that operate on the `String` +data type return a custom type called `string_result_t` which is defined as follows: + +```c +typedef enum { + STRING_OK = 0x0, + STRING_ERR_ALLOCATE, + STRING_ERR_INVALID, + STRING_ERR_INVALID_UTF8, + STRING_ERR_OVERFLOW +} string_status_t; + +typedef struct { + string_status_t status; + uint8_t message[RESULT_MSG_SIZE]; + union { + string_t *string; + char *symbol; + int64_t idx; + bool is_equ; + struct { + string_t **strings; + size_t count; + } split; + } value; +} string_result_t; +``` + +Each method that returns such type indicates whether the operation was successful or not +by setting the `status` field and by providing a descriptive message on the `message` +field. If the operation was successful (that is, `status == STRING_OK`) you can either +move on with the rest of your program or read the returned value from the sum data type. + +The sum data type (i.e., the `value` union) defines five different variables. +Each of them has an unique scope as described below: + +- `string`: result of `new`, `clone`, `slice`, `reverse` and `trim` functions; +- `symbol`: result of `get_at` function; +- `idx`: result of `contains` function; +- `is_eq`: result of `equ` function. It's true when two strings are equal, false otherwise; +- `split`: result of `split` function. It contains an array of `string_t` and its number of elements. \ No newline at end of file diff --git a/docs/vector.md b/docs/vector.md index 6ca7aeb..f99071e 100644 --- a/docs/vector.md +++ b/docs/vector.md @@ -65,8 +65,7 @@ typedef struct { Each method that returns such type indicates whether the operation was successful or not by setting the `status` field and by providing a descriptive message on the `message` field. If the operation was successful (that is, `status == VECTOR_OK`), you can either -move on with the rest of the program or read the returned value from the sum data type. Of course, you can choose to -ignore the return value (if you're brave enough :D) as illustrated on the first part of the README. +move on with the rest of the program or read the returned value from the sum data type. ## Functional methods `Vector` provides three functional methods called `map`, `filter` and `reduce` which allow the caller to apply a computation to the vector, diff --git a/examples/Makefile b/examples/Makefile new file mode 100644 index 0000000..0e33572 --- /dev/null +++ b/examples/Makefile @@ -0,0 +1,37 @@ +CC = gcc +CFLAGS = -Wall -Wextra -Werror -pedantic-errors -fstack-protector-strong \ + -fsanitize=address -fsanitize=undefined -fstack-clash-protection \ + -Wwrite-strings -g -std=c99 + +SRC_DIR = ../src +OBJ_DIR = ../obj + +TARGETS = vector_basic vector_sorting vector_functional map_basic bigint_operations string_basic string_advanced + +.PHONY: all clean + +all: $(TARGETS) + +vector_basic: vector_basic.c $(OBJ_DIR)/vector.o + $(CC) $(CFLAGS) -o $@ $^ + +vector_sorting: vector_sorting.c $(OBJ_DIR)/vector.o + $(CC) $(CFLAGS) -o $@ $^ + +vector_functional: vector_functional.c $(OBJ_DIR)/vector.o + $(CC) $(CFLAGS) -o $@ $^ + +map_basic: map_basic.c $(OBJ_DIR)/map.o + $(CC) $(CFLAGS) -o $@ $^ + +bigint_operations: bigint_operations.c $(OBJ_DIR)/bigint.o $(OBJ_DIR)/vector.o + $(CC) $(CFLAGS) -o $@ $^ + +string_basic: string_basic.c $(OBJ_DIR)/string.o + $(CC) $(CFLAGS) -o $@ $^ + +string_advanced: string_advanced.c $(OBJ_DIR)/string.o + $(CC) $(CFLAGS) -o $@ $^ + +clean: + rm -f $(TARGETS) \ No newline at end of file diff --git a/examples/bigint_operations.c b/examples/bigint_operations.c new file mode 100644 index 0000000..cdcc34b --- /dev/null +++ b/examples/bigint_operations.c @@ -0,0 +1,118 @@ +/* + * Bigint operations example. + */ + +#include +#include +#include + +#include "../src/bigint.h" + +int main(void) { + const char *x_origin = "8036732204560262312865077650774313136023641621894661847778962273940232785242208265819059749867858355"; + const char *y_origin = "7078840479830524979114102683681365071561983635405714511439038016617918064981439736383067887133445937"; + const size_t x_len = strlen(x_origin); + const size_t y_len = strlen(y_origin); + const size_t large_x_size = x_len * 100 + 1; + const size_t large_y_size = y_len * 100 + 1; + + char *large_x = malloc(large_x_size); + char *large_y = malloc(large_y_size); + + if (large_x == NULL || large_y == NULL) { + printf("Error while allocating memory for strings\n"); + free(large_x); + free(large_y); + return 1; + } + + large_x[0] = '\0'; + large_y[0] = '\0'; + + // Concatenate 100 times + for (size_t idx = 0; idx < 100; idx++) { + strcat(large_x, x_origin); + strcat(large_y, y_origin); + } + + // Create two big integers from previous strings + bigint_result_t x_res = bigint_from_string(large_x); + if (x_res.status != BIGINT_OK) { + printf("Error while creating big number: %s\n", x_res.message); + return 1; + } + + bigint_result_t y_res = bigint_from_string(large_y); + if (x_res.status != BIGINT_OK) { + printf("Error while creating big number: %s\n", x_res.message); + return 1; + } + + bigint_t *x = x_res.value.number; + bigint_t *y = y_res.value.number; + + // Sum two big integers + bigint_result_t sum_res = bigint_add(x, y); + if (sum_res.status != BIGINT_OK) { + printf("Error while summing two big numbers: %s\n", sum_res.message); + return 1; + } + + bigint_t *sum = sum_res.value.number; + + // Print result + bigint_printf("Sum result = %B\n", sum); + + // Subtract two big integers + bigint_result_t diff_res = bigint_sub(x, y); + if (diff_res.status != BIGINT_OK) { + printf("Error while subtracting two big numbers: %s\n", diff_res.message); + return 1; + } + + bigint_t *diff = diff_res.value.number; + + // Print result + bigint_printf("difference result = %B\n", diff); + + // Multiply two big integers + bigint_result_t prod_res = bigint_prod(x, y); + if (prod_res.status != BIGINT_OK) { + printf("Error while multiplying two big numbers: %s\n", prod_res.message); + return 1; + } + + bigint_t *prod = prod_res.value.number; + + // Print result + bigint_printf("multiplication result = %B\n", prod); + + bigint_t *a = bigint_from_string(large_x).value.number; + bigint_t *b = bigint_from_string(y_origin).value.number; + + // Divide two big integers + bigint_result_t div_res = bigint_divmod(a, b); + if (div_res.status != BIGINT_OK) { + printf("Error while dividing two big numbers: %s\n", div_res.message); + return 1; + } + + bigint_t *quotient = div_res.value.division.quotient; + bigint_t *remainder = div_res.value.division.remainder; + + // Print result + bigint_printf( + "division result = %B\ + \nmod result = %B\n", + quotient, remainder); + + // Destroy big numbers and strings + bigint_destroy(x); bigint_destroy(y); + bigint_destroy(a); bigint_destroy(b); + bigint_destroy(sum); bigint_destroy(diff); + bigint_destroy(prod); bigint_destroy(quotient); bigint_destroy(remainder); + free(large_x); free(large_y); + + return 0; +} + diff --git a/examples/map_basic.c b/examples/map_basic.c new file mode 100644 index 0000000..dabd5a8 --- /dev/null +++ b/examples/map_basic.c @@ -0,0 +1,101 @@ +/* + * Basic map operations example. + */ + +#include +#include + +#include "../src/map.h" + +int main(void) { + // Create a new map + map_result_t res = map_new(); + if (res.status != MAP_OK) { + printf("Error while creating map: %s\n", res.message); + return 1; + } + + map_t *map = res.value.map; + + // Add some values + const int x = 0xB00B5; + const char *y = "Hello"; + + map_result_t add_res = map_add(map, "x", (void*)&x); + if (add_res.status != MAP_OK) { + printf("Error while adding elements: %s\n", add_res.message); + return 1; + } + + add_res = map_add(map, "y", (void*)y); + if (add_res.status != MAP_OK) { + printf("Error while adding elements: %s\n", add_res.message); + return 1; + } + + // Print size and capacity + printf("Map size (should be 2): %zu\n", map_size(map)); + printf("Map capacity (should be > 2): %zu\n\n", map_capacity(map)); + + // Retrieve keys + map_result_t get_res = map_get(map, "x"); + if (get_res.status != MAP_OK) { + printf("Cannot retrieve map element 'x': %s\n", get_res.message); + return 1; + } else { + const int *val = (const int*)get_res.value.element; + printf("Key 'x' contains (should be 'B00B5'): %X\n", *val); + } + + get_res = map_get(map, "y"); + if (get_res.status != MAP_OK) { + printf("Cannot retrieve map element 'y': %s\n", get_res.message); + return 1; + } else { + const char *val = (const char*)get_res.value.element; + printf("Key 'y' contains (should be 'Hello') : %s\n\n", val); + } + + // Update key + const int new_x = 0xC0FFEE; + map_result_t up_res = map_add(map, "x", (void*)&new_x); + + up_res = map_get(map, "x"); + if (get_res.status != MAP_OK) { + printf("Cannot retrieve map element 'x': %s\n", get_res.message); + return 1; + } else { + const int *val = (const int*)up_res.value.element; + printf("Key 'x' (should be updated to 'C0FFEE'): %X\n\n", *val); + } + + // Remove an element + map_result_t rm_res = map_remove(map, "y"); + if (rm_res.status != MAP_OK) { + printf("Cannot remove map element 'y': %s\n", rm_res.message); + return 1; + } else { + printf("Map element 'y' removed (size should be 1): %zu\n\n", map_size(map)); + } + + // Clear the map + map_result_t clear_res = map_clear(map); + if (clear_res.status != MAP_OK) { + printf("Cannot clear map: %s\n", clear_res.message); + return 1; + } else { + printf("Map cleared (size should be 0): %zu\n", map_size(map)); + } + + printf("\n"); + + // Delete the map + map_result_t del_res = map_destroy(map); + if (del_res.status != MAP_OK) { + printf("Error while destroying the map: %s\n", del_res.message); + return 1; + } + + return 0; +} + diff --git a/examples/string_advanced.c b/examples/string_advanced.c new file mode 100644 index 0000000..4e441cd --- /dev/null +++ b/examples/string_advanced.c @@ -0,0 +1,103 @@ +/* + * Advanced string manipulation example. + */ + +#include +#include + +#include "../src/string.h" + +int main(void) { + // Create a string for manipulation + string_result_t res = string_new("Hello, World! 😜"); + if (res.status != STRING_OK) { + printf("Error: %s\n", res.message); + return 1; + } + + string_t *str = res.value.string; + printf("Original string: \"%s\"\n\n", str->data); + + // Uppercase string + string_result_t res_upper = string_to_upper(str); + if (res_upper.status != STRING_OK) { + printf("Error: %s\n", res_upper.message); + return 1; + } + printf("Uppercase: \"%s\"\n", res_upper.value.string->data); + string_destroy(res_upper.value.string); + + // Lowercase string + string_result_t res_lower = string_to_lower(str); + if (res_lower.status != STRING_OK) { + printf("Error: %s\n", res_lower.message); + return 1; + } + printf("Lowercase: \"%s\"\n\n", res_lower.value.string->data); + string_destroy(res_lower.value.string); + + // Reverse string + string_result_t res_rev = string_reverse(str); + if (res_rev.status != STRING_OK) { + printf("Error: %s\n", res_rev.message); + return 1; + } + printf("Reversed: \"%s\"\n\n", res_rev.value.string->data); + string_destroy(res_rev.value.string); + + // Change first character of the string + string_result_t res_set = string_set_at(str, 0, "J"); + if (res_set.status != STRING_OK) { + printf("Error: %s\n", res_set.message); + return 1; + } + printf("Updated string: \"%s\"\n\n", res_set.value.string->data); + string_destroy(res_set.value.string); + + // Get character from string (the emoji) + string_result_t res_get = string_get_at(str, 14); + if (res_get.status != STRING_OK) { + printf("Error: %s\n", res_get.message); + return 1; + } + printf("Extracted symbol: \"%s\"\n", res_get.value.symbol); + free(res_get.value.symbol); + + // Trim string + string_t *to_trim = string_new(" foo ").value.string; + string_result_t res_trim = string_trim(to_trim); + if (res_trim.status != STRING_OK) { + printf("Error: %s\n", res_trim.message); + return 1; + } + + printf("Trimmed string: \"%s\"\n\n", res_trim.value.string->data); + string_destroy(to_trim); + string_destroy(res_trim.value.string); + + // Split string + string_t *to_split = string_new("foo/bar/biz").value.string; + string_result_t res_split = string_split(to_split, "/"); + if (res_split.status != STRING_OK) { + printf("Error: %s\n", res_split.message); + return 1; + } + + const size_t count = res_split.value.split.count; + string_t **strings = res_split.value.split.strings; + + printf("Original string: \"%s\"\nSplitted string: ", to_split->data); + for (size_t idx = 0; idx < count; idx++) { + printf("\"%s\" ", strings[idx]->data); + } + + printf("\n"); + + string_split_destroy(strings, count); + string_destroy(to_split); + + string_destroy(str); + + return 0; +} + diff --git a/examples/string_basic.c b/examples/string_basic.c new file mode 100644 index 0000000..53eae52 --- /dev/null +++ b/examples/string_basic.c @@ -0,0 +1,95 @@ +/* + * Basic string operations example. + */ + +#include +#include + +#include "../src/string.h" + +int main(void) { + // Create a new string + string_result_t res = string_new("Hello, "); + if (res.status != STRING_OK) { + printf("Error: %s\n", res.message); + return 1; + } + + string_t *str1 = res.value.string; + printf("Created string: \"%s\"\n", str1->data); + printf("Character count: %zu (%zu actual bytes)\n", string_size(str1), str1->byte_size); + + string_result_t res_clone = string_clone(str1); + if (res_clone.status != STRING_OK) { + printf("Error: %s\n", res.message); + return 1; + } + + string_t *cloned = res_clone.value.string; + printf("Cloned string: \"%s\"\n\n", cloned->data); + string_destroy(cloned); + + // Concatenation of strings + string_result_t res_suffix = string_new("World! 😜"); + if (res_suffix.status != STRING_OK) { + printf("Error: %s\n", res.message); + return 1; + } + + string_t *suffix = res_suffix.value.string; + printf("Created another string: \"%s\"\n", suffix->data); + printf("Character count: %zu (%zu actual bytes)\n\n", string_size(suffix), suffix->byte_size); + + string_result_t res_cat = string_concat(str1, suffix); + if (res_cat.status != STRING_OK) { + printf("Error: %s\n", res_cat.message); + return 1; + } + string_destroy(suffix); + + string_t *concat_str = res_cat.value.string; + printf("Concatenation result: \"%s\"\n\n", concat_str->data); + + // String contains + string_t *haystack = string_new("The quick brown fox jumps over the lazy dog.").value.string; + string_t *needle = string_new("brown fox").value.string; + + string_result_t res_contains = string_contains(haystack, needle); + if (res_contains.status != STRING_OK) { + printf("Error: %s\n", res_contains.message); + return 1; + } + + if (res_contains.value.idx != -1) { + printf("Substring found. Starting at index %ld\n\n", res_contains.value.idx); + } + + string_destroy(haystack); + string_destroy(needle); + + // String slicing + string_result_t res_slice = string_slice(concat_str, 7, 14); + if (res_slice.status != STRING_OK) { + printf("Error: %s\n", res_slice.message); + return 1; + } + + printf("Slice of string: \"%s\"\n\n", res_slice.value.string->data); + string_destroy(res_slice.value.string); + + // String equality + string_t *compare = string_new("hello, World! 😜").value.string; + string_result_t res_eq = string_eq(concat_str, compare, true); + if (res_eq.value.is_equ) { + printf("The two strings are equal\n\n"); + } else { + printf("The two strings are not equal\n\n"); + } + + string_destroy(compare); + string_destroy(concat_str); + string_destroy(str1); + + return 0; +} + diff --git a/examples/vector_basic.c b/examples/vector_basic.c new file mode 100644 index 0000000..6b09a4a --- /dev/null +++ b/examples/vector_basic.c @@ -0,0 +1,78 @@ +/* + * Basic vector operations example. + */ + +#include +#include + +#include "../src/vector.h" + +int main(void) { + // Create a vector of 3 integers + vector_result_t res = vector_new(3, sizeof(int)); + if (res.status != VECTOR_OK) { + printf("Error while creating vector: %s\n", res.message); + return 1; + } + + vector_t *vector = res.value.vector; + + // Push some values to trigger reallocation + for (int idx = 0; idx < 5; idx++) { + vector_result_t add_res = vector_push(vector, &idx); + if (add_res.status != VECTOR_OK) { + printf("Error while adding elements: %s\n", add_res.message); + return 1; + } + } + + // Print vector size and capacity + printf("Vector size (should be 5): %zu\n", vector_size(vector)); + printf("Vector capacity (should be > 5): %zu\n\n", vector_capacity(vector)); + + // Print the whole vector + size_t sz = vector_size(vector); + for (size_t idx = 0; idx < sz; idx++) { + vector_result_t get_res = vector_get(vector, idx); + if (get_res.status != VECTOR_OK) { + printf("Cannot retrieve vec[%zu]: %s\n", idx, get_res.message); + return 1; + } else { + int val = *(int *)get_res.value.element; + printf("vec[%zu] (should be '%zu') = %d\n", idx, idx, val); + } + } + + // Set an element at index 2 + int new_val = 0xBABE; + vector_result_t set_res = vector_set(vector, 2, &new_val); + if (set_res.status == VECTOR_OK) { + printf("vec[2] (should be updated to 'BABE'): %X\n\n", new_val); + } + + // Pop last element + vector_result_t pop_res = vector_pop(vector); + if (pop_res.status == VECTOR_OK) { + int val = *(int *)pop_res.value.element; + printf("Popped value (should be 5) : %d\n\n", val); + } + + // Clear vector + vector_result_t clear_res = vector_clear(vector); + if (clear_res.status != VECTOR_OK) { + printf("Cannot clear vector: %s\n", clear_res.message); + return 1; + } else { + printf("Vector cleared (size should be 0): %zu\n\n", vector_size(vector)); + } + + // Free vector + vector_result_t del_res = vector_destroy(vector); + if (del_res.status != VECTOR_OK) { + printf("Error while destroying the vector: %s\n", del_res.message); + return 1; + } + + return 0; +} + diff --git a/examples/vector_functional.c b/examples/vector_functional.c new file mode 100644 index 0000000..0a403e9 --- /dev/null +++ b/examples/vector_functional.c @@ -0,0 +1,118 @@ +/* + * Vector functional operations example. + */ + +#include +#include + +#include "../src/vector.h" + +#define UNUSED(X) (void)(X) + +static void square(void *element, void *env); +static int is_even(const void *element, void *env); +static void adder(void *accumulator, const void *element, void *env); + +int main(void) { + // Create a vector + vector_result_t res = vector_new(1, sizeof(int)); + if (res.status != VECTOR_OK) { + printf("Error while creating vector: %s\n", res.message); + return 1; + } + + vector_t *vector = res.value.vector; + + // Map vector elements + for (size_t idx = 1; idx <= 5; idx++) { + vector_result_t map_push_res = vector_push(vector, &idx); + if (map_push_res.status != VECTOR_OK) { + printf("Error while adding elements: %s\n", map_push_res.message); + return 1; + } + } + + size_t sz = vector_size(vector); + + // Square vector elements: [1, 2, 3, 4, 5] -> [1, 4, 9, 16, 25] + vector_result_t map_res = vector_map(vector, square, NULL); + if (map_res.status != VECTOR_OK) { + printf("Error while mapping vector: %s\n", map_res.message); + return 1; + } + + printf("Squared vector: "); + for (size_t idx = 0; idx < sz; idx++) { + vector_result_t map_get_res = vector_get(vector, idx); + if (map_get_res.status != VECTOR_OK) { + printf("Cannot retrieve vec[%zu]: %s\n", idx, map_get_res.message); + return 1; + } else { + int *val = (int*)map_get_res.value.element; + printf("%d ", *val); + } + } + + printf("\n"); + + // Filter vector elements: [1, 4, 9, 16, 25] -> [4, 16] + vector_result_t filter_res = vector_filter(vector, is_even, NULL); + if (filter_res.status != VECTOR_OK) { + printf("Error while filtering vector: %s\n", filter_res.message); + return 1; + } + + sz = vector_size(vector); + + printf("Filtered vector: "); + for (size_t idx = 0; idx < sz; idx++) { + vector_result_t map_get_res = vector_get(vector, idx); + if (map_get_res.status != VECTOR_OK) { + printf("Cannot retrieve vec[%zu]: %s\n", idx, map_get_res.message); + return 1; + } else { + int *val = (int*)map_get_res.value.element; + printf("%d ", *val); + } + } + + printf("\n"); + + // Reduce vector elements: [4, 16] -> 20 + int sum = 0; + vector_result_t reduce_res = vector_reduce(vector, &sum, adder, NULL); + if (reduce_res.status != VECTOR_OK) { + printf("Error while reducing vector: %s\n", reduce_res.message); + return 1; + } + + printf("Sum of vector: %d\n", sum); + + // Free vector + vector_result_t del_res = vector_destroy(vector); + if (del_res.status != VECTOR_OK) { + printf("Error while destroying the vector: %s\n", del_res.message); + return 1; + } + + return 0; +} + +void square(void *element, void *env) { + UNUSED(env); + int *value = (int*)element; + *value = (*value) * (*value); +} + +int is_even(const void *element, void *env) { + UNUSED(env); + int value = *(int*)element; + + return (value % 2) == 0; +} + +void adder(void *accumulator, const void *element, void *env) { + UNUSED(env); + *(int*)accumulator += *(int*)element; +} + diff --git a/examples/vector_sorting.c b/examples/vector_sorting.c new file mode 100644 index 0000000..81e7124 --- /dev/null +++ b/examples/vector_sorting.c @@ -0,0 +1,110 @@ +/* + * Vector sorting example. + */ + +#include +#include + +#include "../src/vector.h" + +static vector_order_t cmp_int_asc(const void *x, const void *y); +static vector_order_t cmp_int_desc(const void *x, const void *y); + +int main(void) { + // Create a vector + vector_result_t res = vector_new(1, sizeof(int)); + if (res.status != VECTOR_OK) { + printf("Error while creating vector: %s\n", res.message); + return 1; + } + + vector_t *vector = res.value.vector; + + // Sort vector in ascending order + int values[] = {5, 10, -9, 3, 1, 0, 4}; + for (size_t idx = 0; idx < 7; idx++) { + vector_result_t sort_push_res = vector_push(vector, &values[idx]); + if (sort_push_res.status != VECTOR_OK) { + printf("Error while adding elements: %s\n", sort_push_res.message); + return 1; + } + } + + printf("Added new elements. Before sort: "); + + size_t sz = vector_size(vector); + for (size_t idx = 0; idx < sz; idx++) { + vector_result_t sort_get_res = vector_get(vector, idx); + if (sort_get_res.status != VECTOR_OK) { + printf("Cannot retrieve vec[%zu]: %s\n", idx, sort_get_res.message); + return 1; + } else { + const int *val = (int*)sort_get_res.value.element; + printf("%d ", *val); + } + } + printf("\n"); + + vector_result_t sort_asc_res = vector_sort(vector, cmp_int_asc); + if (sort_asc_res.status != VECTOR_OK) { + printf("Cannot sort array: %s\n", sort_asc_res.message); + return 1; + } + + printf("After sort in ascending order: "); + for (size_t idx = 0; idx < sz; idx++) { + vector_result_t sort_get_res = vector_get(vector, idx); + if (sort_get_res.status != VECTOR_OK) { + printf("Cannot retrieve vec[%zu]: %s\n", idx, sort_get_res.message); + return 1; + } else { + int *val = (int*)sort_get_res.value.element; + printf("%d ", *val); + } + } + printf("\n"); + + // Sort vector in descending order + vector_result_t sort_desc_res = vector_sort(vector, cmp_int_desc); + if (sort_desc_res.status != VECTOR_OK) { + printf("Cannot sort array: %s\n", sort_desc_res.message); + return 1; + } + + printf("After sort in descending order: "); + for (size_t idx = 0; idx < sz; idx++) { + vector_result_t sort_get_res = vector_get(vector, idx); + if (sort_get_res.status != VECTOR_OK) { + printf("Cannot retrieve vec[%zu]: %s\n", idx, sort_get_res.message); + return 1; + } else { + int *val = (int*)sort_get_res.value.element; + printf("%d ", *val); + } + } + printf("\n\n"); + + // Free vector + vector_result_t del_res = vector_destroy(vector); + if (del_res.status != VECTOR_OK) { + printf("Error while destroying the vector: %s\n", del_res.message); + return 1; + } + + return 0; +} + +vector_order_t cmp_int_asc(const void *x, const void *y) { + int x_int = *(const int*)x; + int y_int = *(const int*)y; + + if (x_int < y_int) return VECTOR_ORDER_LT; + if (x_int > y_int) return VECTOR_ORDER_GT; + + return VECTOR_ORDER_EQ; +} + +vector_order_t cmp_int_desc(const void *x, const void *y) { + return cmp_int_asc(y, x); +} + diff --git a/src/string.c b/src/string.c index 1c22c54..6a381be 100644 --- a/src/string.c +++ b/src/string.c @@ -910,7 +910,7 @@ string_result_t string_destroy(string_t *str) { } /** - * string_split_destory + * string_split_destroy * @split: an array of pointers of String * @count: the number of elements * diff --git a/usage.c b/usage.c deleted file mode 100644 index 60f2b4a..0000000 --- a/usage.c +++ /dev/null @@ -1,526 +0,0 @@ -/* - * Sample usage of the Datum library. - * - * This program is a complete example on how to use Datum - * with *verbose* error checking. For a more minimal usage, you may want to ignore - * return messages/codes and get straight to the actual result. See the early - * part of the README.md file for such example (use it at your own risk). - * - * Developed by Marco Cetica (c) 2025, - * - */ -#define SEP(SIZE) do { \ - for (size_t i = 0; i < SIZE; i++) { \ - printf("="); \ - }; \ - puts("\n"); \ -} while(0) - -#define UNUSED(X) (void)(X) - -#include -#include -#include - -#include "src/vector.h" -#include "src/map.h" -#include "src/bigint.h" - -static int vector_usage(void); -static int map_usage(void); -static int bigint_usage(void); - -static vector_order_t cmp_int_asc(const void *x, const void *y); -static vector_order_t cmp_int_desc(const void *x, const void *y); -static void square(void *element, void *env); -static int is_even(const void *element, void *env); -static void adder(void *accumulator, const void *element, void *env); - -int main(void) { - int st; - - st = vector_usage(); - if (st) { return st; } - - SEP(50); - - st = map_usage(); - if (st) { return st; } - - SEP(50); - - st = bigint_usage(); - if (st) { return st; } - - return 0; -} - -vector_order_t cmp_int_asc(const void *x, const void *y) { - int x_int = *(const int*)x; - int y_int = *(const int*)y; - - if (x_int < y_int) return VECTOR_ORDER_LT; - if (x_int > y_int) return VECTOR_ORDER_GT; - - return VECTOR_ORDER_EQ; -} - -vector_order_t cmp_int_desc(const void *x, const void *y) { - return cmp_int_asc(y, x); -} - -void square(void *element, void *env) { - UNUSED(env); - int *value = (int*)element; - *value = (*value) * (*value); -} - -int is_even(const void *element, void *env) { - UNUSED(env); - int value = *(int*)element; - - return (value % 2) == 0; -} - -void adder(void *accumulator, const void *element, void *env) { - UNUSED(env); - *(int*)accumulator += *(int*)element; -} - -int vector_usage(void) { - // Create a vector of 3 integers - vector_result_t res = vector_new(3, sizeof(int)); - if (res.status != VECTOR_OK) { - printf("Error while creating vector: %s\n", res.message); - - return 1; - } - - vector_t *vector = res.value.vector; - - // Push some values to trigger reallocation - for (int idx = 0; idx < 5; idx++) { - vector_result_t add_res = vector_push(vector, &idx); - if (add_res.status != VECTOR_OK) { - printf("Error while adding elements: %s\n", add_res.message); - - return 1; - } - } - - // Print vector size and capacity - printf("Vector size (should be 5): %zu\n", vector_size(vector)); - printf("Vector capacity (should be > 5): %zu\n\n", vector_capacity(vector)); - - // Print the whole vector - size_t sz = vector_size(vector); - for (size_t idx = 0; idx < sz; idx++) { - vector_result_t get_res = vector_get(vector, idx); - if (get_res.status != VECTOR_OK) { - printf("Cannot retrieve vec[%zu]: %s\n", idx, get_res.message); - - return 1; - } else { - int val = *(int *)get_res.value.element; - printf("vec[%zu] (should be '%zu') = %d\n", idx, idx, val); - } - } - - // Set an element at index 2 - int new_val = 0xBABE; - vector_result_t set_res = vector_set(vector, 2, &new_val); - if (set_res.status == VECTOR_OK) { - printf("vec[2] (should be updated to 'BABE'): %X\n\n", new_val); - } - - // Pop last element - vector_result_t pop_res = vector_pop(vector); - if (pop_res.status == VECTOR_OK) { - int val = *(int *)pop_res.value.element; - printf("Popped value (should be 5) : %d\n\n", val); - } - - // Clear vector - vector_result_t clear_res = vector_clear(vector); - if (clear_res.status != VECTOR_OK) { - printf("Cannot clear vector: %s\n", clear_res.message); - - return 1; - } else { - printf("Vector cleared (size should be 0): %zu\n\n", vector_size(vector)); - } - - // Sort vector in ascending order - int values[] = {5, 10, -9, 3, 1, 0, 4}; - for (size_t idx = 0; idx < 7; idx++) { - vector_result_t sort_push_res = vector_push(vector, &values[idx]); - if (sort_push_res.status != VECTOR_OK) { - printf("Error while adding elements: %s\n", sort_push_res.message); - - return 1; - } - } - - printf("Added new elements. Before sort: "); - - sz = vector_size(vector); - for (size_t idx = 0; idx < sz; idx++) { - vector_result_t sort_get_res = vector_get(vector, idx); - if (sort_get_res.status != VECTOR_OK) { - printf("Cannot retrieve vec[%zu]: %s\n", idx, sort_get_res.message); - - return 1; - } else { - const int *val = (int*)sort_get_res.value.element; - printf("%d ", *val); - } - } - printf("\n"); - - vector_result_t sort_asc_res = vector_sort(vector, cmp_int_asc); - if (sort_asc_res.status != VECTOR_OK) { - printf("Cannot sort array: %s\n", sort_asc_res.message); - - return 1; - } - - printf("After sort in ascending order: "); - for (size_t idx = 0; idx < sz; idx++) { - vector_result_t sort_get_res = vector_get(vector, idx); - if (sort_get_res.status != VECTOR_OK) { - printf("Cannot retrieve vec[%zu]: %s\n", idx, sort_get_res.message); - - return 1; - } else { - int *val = (int*)sort_get_res.value.element; - printf("%d ", *val); - } - } - printf("\n"); - - // Sort vector in descending order - vector_result_t sort_desc_res = vector_sort(vector, cmp_int_desc); - if (sort_desc_res.status != VECTOR_OK) { - printf("Cannot sort array: %s\n", sort_desc_res.message); - - return 1; - } - - printf("After sort in descending order: "); - for (size_t idx = 0; idx < sz; idx++) { - vector_result_t sort_get_res = vector_get(vector, idx); - if (sort_get_res.status != VECTOR_OK) { - printf("Cannot retrieve vec[%zu]: %s\n", idx, sort_get_res.message); - - return 1; - } else { - int *val = (int*)sort_get_res.value.element; - printf("%d ", *val); - } - } - printf("\n\n"); - - vector_result_t map_clear_res = vector_clear(vector); - if (map_clear_res.status != VECTOR_OK) { - printf("Cannot clear vector: %s\n", map_clear_res.message); - - return 1; - } - - // Map vector elements - for (size_t idx = 1; idx <= 5; idx++) { - vector_result_t map_push_res = vector_push(vector, &idx); - if (map_push_res.status != VECTOR_OK) { - printf("Error while adding elements: %s\n", map_push_res.message); - - return 1; - } - } - - sz = vector_size(vector); - - // Square vector elements: [1, 2, 3, 4, 5] -> [1, 4, 9, 16, 25] - vector_result_t map_res = vector_map(vector, square, NULL); - if (map_res.status != VECTOR_OK) { - printf("Error while mapping vector: %s\n", map_res.message); - - return 1; - } - - printf("Squared vector: "); - for (size_t idx = 0; idx < sz; idx++) { - vector_result_t map_get_res = vector_get(vector, idx); - if (map_get_res.status != VECTOR_OK) { - printf("Cannot retrieve vec[%zu]: %s\n", idx, map_get_res.message); - - return 1; - } else { - int *val = (int*)map_get_res.value.element; - printf("%d ", *val); - } - } - - printf("\n"); - - // Filter vector elements: [1, 4, 9, 16, 25] -> [4, 16] - vector_result_t filter_res = vector_filter(vector, is_even, NULL); - if (filter_res.status != VECTOR_OK) { - printf("Error while filtering vector: %s\n", filter_res.message); - - return 1; - } - - sz = vector_size(vector); - - printf("Filtered vector: "); - for (size_t idx = 0; idx < sz; idx++) { - vector_result_t map_get_res = vector_get(vector, idx); - if (map_get_res.status != VECTOR_OK) { - printf("Cannot retrieve vec[%zu]: %s\n", idx, map_get_res.message); - - return 1; - } else { - int *val = (int*)map_get_res.value.element; - printf("%d ", *val); - } - } - - printf("\n"); - - // Reduce vector elements: [4, 16] -> 20 - int sum = 0; - vector_result_t reduce_res = vector_reduce(vector, &sum, adder, NULL); - if (reduce_res.status != VECTOR_OK) { - printf("Error while reducing vector: %s\n", reduce_res.message); - - return 1; - } - - printf("Sum of vector: %d\n\n", sum); - - // Free vector - vector_result_t del_res = vector_destroy(vector); - if (del_res.status != VECTOR_OK) { - printf("Error while destroying the vector: %s\n", del_res.message); - - return 1; - } - - return 0; -} - -int map_usage(void) { - // Create a new map - map_result_t res = map_new(); - if (res.status != MAP_OK) { - printf("Error while creating map: %s\n", res.message); - - return 1; - } - - map_t *map = res.value.map; - - // Add some values - const int x = 0xB00B5; - const char *y = "Hello"; - - map_result_t add_res = map_add(map, "x", (void*)&x); - if (add_res.status != MAP_OK) { - printf("Error while adding elements: %s\n", add_res.message); - - return 1; - } - - add_res = map_add(map, "y", (void*)y); - if (add_res.status != MAP_OK) { - printf("Error while adding elements: %s\n", add_res.message); - - return 1; - } - - // Print size and capacity - printf("Map size (should be 2): %zu\n", map_size(map)); - printf("Map capacity (should be > 2): %zu\n\n", map_capacity(map)); - - // Retrieve keys - map_result_t get_res = map_get(map, "x"); - if (get_res.status != MAP_OK) { - printf("Cannot retrieve map element 'x': %s\n", get_res.message); - - return 1; - } else { - const int *val = (const int*)get_res.value.element; - printf("Key 'x' contains (should be 'B00B5'): %X\n", *val); - } - - get_res = map_get(map, "y"); - if (get_res.status != MAP_OK) { - printf("Cannot retrieve map element 'y': %s\n", get_res.message); - - return 1; - } else { - const char *val = (const char*)get_res.value.element; - printf("Key 'y' contains (should be 'Hello') : %s\n\n", val); - } - - // Update key - const int new_x = 0xC0FFEE; - map_result_t up_res = map_add(map, "x", (void*)&new_x); - - up_res = map_get(map, "x"); - if (get_res.status != MAP_OK) { - printf("Cannot retrieve map element 'x': %s\n", get_res.message); - - return 1; - } else { - const int *val = (const int*)up_res.value.element; - printf("Key 'x' (should be updated to 'C0FFEE'): %X\n\n", *val); - } - - // Remove an element - map_result_t rm_res = map_remove(map, "y"); - if (rm_res.status != MAP_OK) { - printf("Cannot remove map element 'y': %s\n", rm_res.message); - - return 1; - } else { - printf("Map element 'y' removed (size should be 1): %zu\n\n", map_size(map)); - } - - // Clear the map - map_result_t clear_res = map_clear(map); - if (clear_res.status != MAP_OK) { - printf("Cannot clear map: %s\n", clear_res.message); - - return 1; - } else { - printf("Map cleared (size should be 0): %zu\n", map_size(map)); - } - - printf("\n"); - - // Delete the map - map_result_t del_res = map_destroy(map); - if (del_res.status != MAP_OK) { - printf("Error while destroying the map: %s\n", del_res.message); - - return 1; - } - - return 0; -} - -int bigint_usage(void) { - const char *x_origin = "8036732204560262312865077650774313136023641621894661847778962273940232785242208265819059749867858355"; - const char *y_origin = "7078840479830524979114102683681365071561983635405714511439038016617918064981439736383067887133445937"; - const size_t x_len = strlen(x_origin); - const size_t y_len = strlen(y_origin); - const size_t large_x_size = x_len * 100 + 1; - const size_t large_y_size = y_len * 100 + 1; - - char *large_x = malloc(large_x_size); - char *large_y = malloc(large_y_size); - - if (large_x == NULL || large_y == NULL) { - printf("Error while allocating memory for strings\n"); - free(large_x); - free(large_y); - - return 1; - } - - large_x[0] = '\0'; - large_y[0] = '\0'; - - // Concatenate 100 times - for (size_t idx = 0; idx < 100; idx++) { - strcat(large_x, x_origin); - strcat(large_y, y_origin); - } - - // Create two big integers from previous strings - bigint_result_t x_res = bigint_from_string(large_x); - if (x_res.status != BIGINT_OK) { - printf("Error while creating big number: %s\n", x_res.message); - - return 1; - } - - bigint_result_t y_res = bigint_from_string(large_y); - if (x_res.status != BIGINT_OK) { - printf("Error while creating big number: %s\n", x_res.message); - - return 1; - } - - bigint_t *x = x_res.value.number; - bigint_t *y = y_res.value.number; - - // Sum two big integers - bigint_result_t sum_res = bigint_add(x, y); - if (sum_res.status != BIGINT_OK) { - printf("Error while summing two big numbers: %s\n", sum_res.message); - - return 1; - } - - bigint_t *sum = sum_res.value.number; - - // Print result - bigint_printf("Sum result = %B\n", sum); - - // Subtract two big integers - bigint_result_t diff_res = bigint_sub(x, y); - if (diff_res.status != BIGINT_OK) { - printf("Error while subtracting two big numbers: %s\n", diff_res.message); - - return 1; - } - - bigint_t *diff = diff_res.value.number; - - // Print result - bigint_printf("difference result = %B\n", diff); - - // Multiply two big integers - bigint_result_t prod_res = bigint_prod(x, y); - if (prod_res.status != BIGINT_OK) { - printf("Error while multiplying two big numbers: %s\n", prod_res.message); - - return 1; - } - - bigint_t *prod = prod_res.value.number; - - // Print result - bigint_printf("multiplication result = %B\n", prod); - - bigint_t *a = bigint_from_string(large_x).value.number; - bigint_t *b = bigint_from_string(y_origin).value.number; - - // Divide two big integers - bigint_result_t div_res = bigint_divmod(a, b); - if (div_res.status != BIGINT_OK) { - printf("Error while dividing two big numbers: %s\n", div_res.message); - - return 1; - } - - bigint_t *quotient = div_res.value.division.quotient; - bigint_t *remainder = div_res.value.division.remainder; - - // Print result - bigint_printf( - "division result = %B\ - \nmod result = %B\n", - quotient, remainder); - - // Destroy big numbers and strings - bigint_destroy(x); bigint_destroy(y); - bigint_destroy(a); bigint_destroy(b); - bigint_destroy(sum); bigint_destroy(diff); - bigint_destroy(prod); bigint_destroy(quotient); bigint_destroy(remainder); - free(large_x); free(large_y); - - return 0; -} From de22c706affc311322580edacd75681d594b3ac7 Mon Sep 17 00:00:00 2001 From: Marco Cetica Date: Mon, 16 Mar 2026 09:53:07 +0100 Subject: [PATCH 8/8] Added String benchmark function --- Makefile | 2 +- README.md | 10 ++++++---- benchmark/benchmark.c | 30 ++++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index da527bd..8ea7b2c 100644 --- a/Makefile +++ b/Makefile @@ -51,7 +51,7 @@ $(OBJ_DIR): mkdir -p $(OBJ_DIR) # Benchmark rules -$(BENCH_TARGET): $(BENCH_OBJ_DIR)/bench.o $(BENCH_OBJ_DIR)/vector.o $(BENCH_OBJ_DIR)/map.o $(BENCH_OBJ_DIR)/bigint.o +$(BENCH_TARGET): $(BENCH_OBJ_DIR)/bench.o $(BENCH_OBJ_DIR)/vector.o $(BENCH_OBJ_DIR)/map.o $(BENCH_OBJ_DIR)/bigint.o $(BENCH_OBJ_DIR)/string.o $(CC) $(BENCH_FLAGS) -o $@ $^ $(BENCH_OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(BENCH_OBJ_DIR) diff --git a/README.md b/README.md index 3544300..6391f41 100644 --- a/README.md +++ b/README.md @@ -187,14 +187,16 @@ $ ./test_bigint ``` ## Benchmark -Under the [`benchmark/`](/benchmark/) folder, you can find a simple benchmark program that stress the `Vector` and the `Map` data structures. You can run it by issuing the following command: +Under the [`benchmark/`](/benchmark/) folder, you can find a very simple benchmark program that stress the data structures. +You can run it by issuing the following command: ```sh $ make clean all CC=clang $ ./benchmark_datum -omputing Vector average time...average time: 8 ms -Computing Map average time...average time: 53 ms -Computing BigInt average time...average time: 76 ms +Computing Vector average time...average time: 6 ms +Computing Map average time...average time: 49 ms +Computing BigInt average time...average time: 67 ms +Computing String average time...average time: 13 ms ``` diff --git a/benchmark/benchmark.c b/benchmark/benchmark.c index f083ca1..6bf1746 100644 --- a/benchmark/benchmark.c +++ b/benchmark/benchmark.c @@ -7,6 +7,7 @@ #include "../src/vector.h" #include "../src/map.h" #include "../src/bigint.h" +#include "../src/string.h" typedef void (*test_fn_t)(size_t iterations); @@ -116,6 +117,31 @@ void test_bigint(size_t iterations) { } } +void test_string(size_t iterations) { + volatile size_t total_len = 0; + + for (size_t idx = 0; idx < iterations; idx++) { + string_t *str1 = string_new("hello").value.string; + string_t *str2 = string_new(" World").value.string; + + string_result_t concat = string_concat(str1, str2); + string_result_t upper = string_to_upper(concat.value.string); + total_len += string_size(upper.value.string); + string_result_t needle = string_new("WORLD"); + string_result_t contains = string_contains(upper.value.string, needle.value.string); + + if (contains.value.idx >= 0) { + total_len += contains.value.idx; + } + + string_destroy(str1); + string_destroy(str2); + string_destroy(concat.value.string); + string_destroy(upper.value.string); + string_destroy(needle.value.string); + } +} + static inline uint64_t now_ns(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); @@ -155,5 +181,9 @@ int main(void) { fflush(stdout); printf("average time: %lld ms\n", benchmark(test_bigint, 1e5, 30)); + printf("Computing String average time..."); + fflush(stdout); + printf("average time: %lld ms\n", benchmark(test_string, 1e5, 30)); + return 0; }