From faa660125ea08c6147cb6488c4ef2c957cd321b2 Mon Sep 17 00:00:00 2001 From: Aaro Saila Date: Sun, 9 Aug 2026 16:29:53 +0300 Subject: [PATCH] First commit --- .gitignore | 1 + include/DynArray.h | 111 ++++++++++++++++++++++ include/DynString.h | 51 ++++++++++ include/scufcont.h | 11 +++ meson.build | 37 ++++++++ meson_options.txt | 1 + src/DynString.c | 198 +++++++++++++++++++++++++++++++++++++++ src/scufcont.c | 3 + tests/cmocka/main.c | 167 +++++++++++++++++++++++++++++++++ tests/cmocka/meson.build | 18 ++++ tests/meson.build | 1 + 11 files changed, 599 insertions(+) create mode 100644 .gitignore create mode 100644 include/DynArray.h create mode 100644 include/DynString.h create mode 100644 include/scufcont.h create mode 100644 meson.build create mode 100644 meson_options.txt create mode 100644 src/DynString.c create mode 100644 src/scufcont.c create mode 100644 tests/cmocka/main.c create mode 100644 tests/cmocka/meson.build create mode 100644 tests/meson.build diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..378eac2 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build diff --git a/include/DynArray.h b/include/DynArray.h new file mode 100644 index 0000000..78cfdca --- /dev/null +++ b/include/DynArray.h @@ -0,0 +1,111 @@ +#ifndef DYN_ARRAY_H_ +#define DYN_ARRAY_H_ + +#include +#include + +// #define SCUFCONT_BOUNDS_CHECK + +#ifdef SCUFCONT_BOUNDS_CHECK + +#include +#include + +#endif // SCUFCONT_BOUNDS_CHECK + +#define DYN_ARRAY_EXPANDER(macro, type, type_name, func_name) macro(type, DynArray_##type_name, DynArray_##type_name##_##func_name); + +#define DYN_ARRAY_IMPL(type, type_name) \ + DYN_ARRAY_EXPANDER(DYN_ARRAY_ALLOC, type, type_name, alloc) \ + DYN_ARRAY_EXPANDER(DYN_ARRAY_IS_NULL, type, type_name, is_null) \ + DYN_ARRAY_EXPANDER(DYN_ARRAY_PUSH_BACK, type, type_name, push_back) \ + DYN_ARRAY_EXPANDER(DYN_ARRAY_AT, type, type_name, at) + +#define DYN_ARRAY_HEADER(type, type_name) \ + DYN_ARRAY_STRUCT(type, type_name) \ + DYN_ARRAY_SIGS(type, type_name) + +#define DYN_ARRAY_SIGS(type, type_name) \ + DYN_ARRAY_EXPANDER(DYN_ARRAY_ALLOC_SIG, type, type_name, alloc) \ + DYN_ARRAY_EXPANDER(DYN_ARRAY_IS_NULL_SIG, type, type_name, is_null) \ + DYN_ARRAY_EXPANDER(DYN_ARRAY_PUSH_BACK_SIG, type, type_name, push_back) \ + DYN_ARRAY_EXPANDER(DYN_ARRAY_AT_SIG, type, type_name, at) + +// DYN_ARRAY_ALLOC_SIG(type, DynArray_##type_name, DynArray_##type_name##_alloc); \ +// DYN_ARRAY_PUSH_BACK_SIG(type, DynArray_##type_name, DynArray_##type_name##_push_back); + +#define DYN_ARRAY_STRUCT(type, type_name) \ + typedef struct { \ + type* data; \ + size_t len; \ + size_t capacity; \ + } DynArray_##type_name; + +// DYN_ARRAY_STRUCT(int); + +#define DYN_ARRAY_ALLOC_SIG(type, type_name, func_name) bool func_name(type_name* darr) + +#define DYN_ARRAY_ALLOC(type, type_name, func_name) \ + DYN_ARRAY_ALLOC_SIG(type, type_name, func_name) { \ + darr->data = (type*) calloc(1, sizeof(type)); \ + if (darr->data == NULL) { \ + return false; \ + } \ + darr->len = 0; \ + darr->capacity = 1; \ + return true; \ + } + +#define DYN_ARRAY_IS_NULL_SIG(type, type_name, func_name) bool func_name(const type_name* darr) + +#define DYN_ARRAY_IS_NULL(type, type_name, func_name) \ + DYN_ARRAY_IS_NULL_SIG(type, type_name, func_name) { \ + return darr->data == NULL; \ + } + +// DYN_ARRAY_ALLOC(int, DynArray_int, DynArray_int_alloc); + +#define DYN_ARRAY_PUSH_BACK_SIG(type, type_name, func_name) int func_name(type_name* darr, const type* new_elem) + +#define DYN_ARRAY_PUSH_BACK(type, type_name, func_name) \ + DYN_ARRAY_PUSH_BACK_SIG(type, type_name, func_name) { \ + if (darr->len + 1 > darr->capacity) { \ + darr->capacity *= 2; \ + darr->data = (type*) realloc(darr->data, darr->capacity * sizeof(type)); \ + if (darr->data == NULL) { \ + return -1; \ + } \ + } \ + memcpy(darr->data + darr->len, new_elem, sizeof(type)); \ + darr->len++; \ + return 1; \ + } + +#define DYN_ARRAY_AT_SIG(type, type_name, func_name) type* func_name(type_name* darr, const size_t i) + +// DYN_ARRAY_PUSH_BACK(int, DynArray_int, DynArray_int_push_back); + +#ifdef SCUFCONT_BOUNDS_CHECK + +#define DYN_ARRAY_AT(type, type_name, func_name) \ + DYN_ARRAY_AT_SIG(type, type_name, func_name) { \ + if (i < 0 || i > darr->len) { \ + fprintf(stderr, #func_name " out-of-bounds. i: %zu, len: %zu", i, darr->len); \ + exit(1); \ + } \ + \ + return darr->data + i; \ + } + +#else + +#define DYN_ARRAY_AT(type, type_name, func_name) \ + DYN_ARRAY_AT_SIG(type, type_name, func_name) { \ + return darr->data + i; \ + } + +#endif // SCUFCONT_BOUNDS_CHECK + +// DYN_ARRAY_AT(char*, DynArray_charp, DynArray_charp_at) + +#endif // DYN_ARRAY_H_ diff --git a/include/DynString.h b/include/DynString.h new file mode 100644 index 0000000..9cd95a8 --- /dev/null +++ b/include/DynString.h @@ -0,0 +1,51 @@ +#ifndef DYN_STRING_H_ +#define DYN_STRING_H_ + +#include +#include + +#include "scufcont.h" + +#define DYN_STRING_FMT(dstr) (int) (dstr).len, (dstr).data + +#define DynString_is_null(dstr) ((dstr)->data == NULL) +#define DynString_c_str(dstr) ((dstr)->data) + +typedef struct { + char* data; + size_t len; + size_t capacity; +} DynString; + +ScufContResult DynString_alloc(DynString* dstr, const char* str, const size_t len); +ScufContResult DynString_reserve(DynString* dstr, const size_t new_capacity); +void DynString_free(DynString* dstr); +bool DynString_equal(const DynString* s1, const DynString* s2); +bool DynString_equal_c_str(const DynString* s1, const char* s2); +bool DynString_equal_next_chars_c_str( + const DynString* dstr, + const char* str, + const size_t str_len, + const size_t s +); +ScufContResult DynString_concat_c_str( + DynString* dstr, + const char* str, + const size_t str_len +); +ScufContResult DynString_alloc_concat_c_strs( + DynString* dstr, + const char* s1, + const size_t s1_len, + const char* s2, + const size_t s2_len +); +ScufContResult DynString_split( + const DynString* dstr, + const char* delim, + DynString** split_arr, + size_t* arr_len +); +void DynString_split_arr_free(DynString** arr, const size_t arr_len); + +#endif // DYN_STRING_H_ diff --git a/include/scufcont.h b/include/scufcont.h new file mode 100644 index 0000000..ff60045 --- /dev/null +++ b/include/scufcont.h @@ -0,0 +1,11 @@ +#ifndef SCUFCONT_H_ +#define SCUFCONT_H_ + +typedef enum { + SCUFCONT_SUCCESS, + SCUFCONT_OUT_OF_MEMORY +} ScufContResult; + +extern const char* SCUFCONT_VERSION; + +#endif // SCUFCONT_H_ diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..020fa65 --- /dev/null +++ b/meson.build @@ -0,0 +1,37 @@ +project( + 'scufcont', + 'c', + version: '0.0.1', + default_options: { + 'buildtype': 'release' + } +) + +c_args = [ + '-std=c99', + '-Wall', + '-Wextra', + '-pedantic' +] + +cmocka_dep = dependency('cmocka') + +scufcont_include = include_directories('include') + +scufcont_lib = library( + 'scufcont', + 'src/DynString.c', + 'src/scufcont.c', + include_directories: [ + 'include' + ], + c_args: [ + c_args, + '-D_SCUFCONT_VERSION="' + meson.project_version() + '"', + ] +) + +if get_option('SCUFCONT_BUILD_TESTS') == true + subdir('tests') +endif + diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..4a1a43f --- /dev/null +++ b/meson_options.txt @@ -0,0 +1 @@ +option('SCUFCONT_BUILD_TESTS', type: 'boolean', value: false) diff --git a/src/DynString.c b/src/DynString.c new file mode 100644 index 0000000..d82ab8a --- /dev/null +++ b/src/DynString.c @@ -0,0 +1,198 @@ +#include +#include +#include +#include + +#include "scufcont.h" +#include "DynString.h" + +ScufContResult DynString_alloc(DynString* dstr, const char* str, const size_t len) { + dstr->data = (char*) malloc(sizeof(char) * (len + 1)); + dstr->len = len; + dstr->capacity = len + 1; + if (dstr->data == NULL) { + return SCUFCONT_OUT_OF_MEMORY; + } + dstr->data = memcpy(dstr->data, str, len); + dstr->data[len] = '\0'; + + return SCUFCONT_SUCCESS; +} + +ScufContResult DynString_reserve(DynString* dstr, const size_t new_capacity) { + if (dstr->capacity < new_capacity) { + errno = 0; + dstr->data = (char*) realloc(dstr->data, sizeof(char) * new_capacity); + if (errno != 0) { + return SCUFCONT_OUT_OF_MEMORY; + } + dstr->capacity = new_capacity; + } + + return SCUFCONT_SUCCESS; +} + +void DynString_free(DynString* dstr) { + if (dstr->data != NULL) { + free(dstr->data); + dstr->data = NULL; + } + dstr->len = 0; + dstr->capacity = 0; +} + +bool DynString_equal(const DynString* s1, const DynString* s2) { + if (s1->data == NULL || s2->data == NULL) { + return false; + } + + if (s1->len != s2->len) { + return false; + } + + for (size_t i = 0; i < s1->len; i++) { + if (s1->data[i] != s2->data[i]) { + return false; + } + } + + return true; +} + +bool DynString_equal_c_str(const DynString* s1, const char* s2) { + if (s1->data == NULL || s2 == NULL) { + return false; + } + + if (s1->len != strlen(s2)) { + return false; + } + + for (size_t i = 0; i < s1->len; i++) { + if (s1->data[i] != s2[i]) { + return false; + } + } + + return true; +} + +bool DynString_equal_next_chars_c_str( + const DynString* dstr, + const char* str, + const size_t str_len, + size_t s +) { + if (dstr->len < s || dstr->len < s + str_len) { + return false; + } + + const size_t e = s + str_len; + for (size_t i = 0; s < e; s++, i++) { + if (dstr->data[s] != str[i]) { + return false; + } + } + + return true; +} + +ScufContResult DynString_concat_c_str( + DynString* dstr, + const char* str, + const size_t str_len +) { + const size_t new_len = dstr->len + str_len; + if (new_len > dstr->capacity) { + ScufContResult res = DynString_reserve(dstr, new_len + 1); + if (res != SCUFCONT_SUCCESS) { + return res; + } + } + + strncpy(dstr->data + dstr->len, str, str_len); + dstr->data[new_len] = '\0'; + dstr->len = new_len; + + return SCUFCONT_SUCCESS; +} + +ScufContResult DynString_alloc_concat_c_strs( + DynString* dstr, + const char* s1, + const size_t s1_len, + const char* s2, + const size_t s2_len +) { + ScufContResult res = DynString_alloc(dstr, s1, s1_len); + if (res != SCUFCONT_SUCCESS) { + return res; + } + res = DynString_concat_c_str(dstr, s2, s2_len); + if (res != SCUFCONT_SUCCESS) { + return res; + } + return SCUFCONT_SUCCESS; +} + +ScufContResult DynString_split( + const DynString* dstr, + const char* delim, + DynString** split_arr, + size_t* arr_len +) { + ScufContResult ret = SCUFCONT_SUCCESS; + *arr_len = 0; + DynString elem; + ScufContResult res = DynString_alloc(&elem, "", 0); + if (res != SCUFCONT_SUCCESS) { + return res; + } + const size_t delim_len = strlen(delim); + for (size_t i = 0; i < dstr->len; i++) { + if (DynString_equal_next_chars_c_str(dstr, delim, delim_len, i)) { + *arr_len += 1; + *split_arr = realloc(*split_arr, sizeof(DynString) * (*arr_len)); + if (split_arr == NULL) { + ret = SCUFCONT_OUT_OF_MEMORY; + break; + } + (*split_arr)[(*arr_len) - 1] = elem; + res = DynString_alloc(&elem, "", 0); + if (res != SCUFCONT_SUCCESS) { + ret = res; + break; + } + i += delim_len - 1; + } else { + DynString_concat_c_str(&elem, dstr->data + i, 1); + } + } + + if (ret == SCUFCONT_SUCCESS) { + *split_arr = realloc(*split_arr, sizeof(DynString) * (*arr_len + 1)); + if (split_arr != NULL) { + (*split_arr)[*arr_len] = elem; + *arr_len += 1; + return ret; + } + ret = SCUFCONT_OUT_OF_MEMORY; + } + + for (size_t i = 0; i < *arr_len; i++) { + DynString_free((*split_arr) + i); + } + free(*split_arr); + *split_arr = NULL; + DynString_free(&elem); + + return ret; +} + +void DynString_split_arr_free(DynString** arr, const size_t arr_len) { + for (size_t i = 0; i < arr_len; i++) { + DynString_free((*arr) + i); + } + free(*arr); + *arr = NULL; +} diff --git a/src/scufcont.c b/src/scufcont.c new file mode 100644 index 0000000..b0072e9 --- /dev/null +++ b/src/scufcont.c @@ -0,0 +1,3 @@ +#include "scufcont.h" + +const char* SCUFCONT_VERSION = _SCUFCONT_VERSION; diff --git a/tests/cmocka/main.c b/tests/cmocka/main.c new file mode 100644 index 0000000..d9c0d8e --- /dev/null +++ b/tests/cmocka/main.c @@ -0,0 +1,167 @@ +#include +#include + +#include + +#include "scufcont.h" +#include "DynString.h" + +void test_DynString_alloc(void** state) { + (void) state; + + const char* c_str = "Hello"; + const size_t c_str_len = strlen(c_str); + DynString dstr; + assert_true(DynString_alloc(&dstr, c_str, c_str_len) == SCUFCONT_SUCCESS); + + assert_memory_equal(dstr.data, c_str, c_str_len + 1); + assert_true(dstr.len == c_str_len); + assert_true(dstr.capacity == c_str_len + 1); + + free(dstr.data); +} + +void test_DynString_free(void** state) { + (void) state; + + DynString dstr; + assert_true(DynString_alloc(&dstr, "Hello", strlen("Hello")) == SCUFCONT_SUCCESS); + + DynString_free(&dstr); + + assert_null(dstr.data); + assert_true(dstr.len == 0); + assert_true(dstr.capacity == 0); +} + +void test_DynString_equal(void** state) { + (void) state; + + DynString s1; + assert_true(DynString_alloc(&s1, "Hello", strlen("Hello")) == SCUFCONT_SUCCESS); + DynString s2; + assert_true(DynString_alloc(&s2, "Hello", strlen("Hello")) == SCUFCONT_SUCCESS); + assert_true(DynString_equal(&s1, &s2)); + DynString_free(&s1); + DynString_free(&s2); + + assert_true(DynString_alloc(&s1, "Hello", strlen("Hello")) == SCUFCONT_SUCCESS); + assert_true(DynString_alloc(&s2, "Hell0", strlen("Hell0")) == SCUFCONT_SUCCESS); + assert_false(DynString_equal(&s1, &s2)); + DynString_free(&s1); + DynString_free(&s2); + + assert_true(DynString_alloc(&s1, "Hello World", strlen("Hello World")) == SCUFCONT_SUCCESS); + assert_true(DynString_alloc(&s2, "Hello", strlen("Hello")) == SCUFCONT_SUCCESS); + assert_false(DynString_equal(&s1, &s2)); + DynString_free(&s1); + DynString_free(&s2); +} + +void test_DynString_equal_c_str(void** state) { + (void) state; + + DynString s1; + assert_true(DynString_alloc(&s1,"Hello", strlen("Hello")) == SCUFCONT_SUCCESS); + char* s2 = "Hello"; + assert_true(DynString_equal_c_str(&s1, s2)); + DynString_free(&s1); + + assert_true(DynString_alloc(&s1, "Hello", strlen("Hello")) == SCUFCONT_SUCCESS); + s2 = "Hell0"; + assert_false(DynString_equal_c_str(&s1, s2)); + DynString_free(&s1); + + assert_true(DynString_alloc(&s1, "Hello World", strlen("Hello World")) == SCUFCONT_SUCCESS); + s2 = "Hello"; + assert_false(DynString_equal_c_str(&s1, s2)); + DynString_free(&s1); +} + +void test_DynString_equal_next_chars_c_str(void** state) { + (void) state; + + DynString dstr; + assert_true(DynString_alloc(&dstr, "Hello", strlen("Hello")) == SCUFCONT_SUCCESS); + char* str = "ll"; + size_t str_len = strlen(str); + assert_true(DynString_equal_next_chars_c_str(&dstr, str, str_len, 2)); + assert_false(DynString_equal_next_chars_c_str(&dstr, str, str_len, 1)); + DynString_free(&dstr); +} + +void test_DynString_concat_c_str(void** state) { + (void) state; + + const char* dstr_c_str = "Hello"; + const char* c_str = " World"; + const size_t combined_length = strlen(dstr_c_str) + strlen(c_str); + + DynString dstr; + assert_true(DynString_alloc(&dstr, dstr_c_str, strlen("Hello")) == SCUFCONT_SUCCESS); + DynString_concat_c_str(&dstr, c_str, strlen(c_str)); + + assert_memory_equal(dstr.data, (void*) "Hello World\0", strlen("Hello World") + 1); + assert_true(dstr.len == combined_length); + assert_true(dstr.capacity == combined_length + 1); + + DynString_free(&dstr); +} + +void test_DynString_c_str(void** state) { + (void) state; + + const char* c_str = "Hello"; + DynString dstr; + assert_true(DynString_alloc(&dstr, c_str, strlen(c_str)) == SCUFCONT_SUCCESS); + + assert_string_equal(c_str, DynString_c_str(&dstr)); + + DynString_concat_c_str(&dstr, " World", strlen(" World")); + + assert_string_equal("Hello World", DynString_c_str(&dstr)); +} + +void test_DynString_split(void** state) { + (void) state; + + DynString dstr; + assert_true(DynString_alloc(&dstr, "Hello World", strlen("Hello World")) == SCUFCONT_SUCCESS); + size_t arr_len = 0; + DynString* arr = NULL; + ScufContResult res = DynString_split(&dstr, " ", &arr, &arr_len); + assert_true(res == SCUFCONT_SUCCESS); + assert_true(arr_len == 2); + assert_string_equal(DynString_c_str(arr), "Hello"); + assert_string_equal(DynString_c_str(arr + 1), "World"); + DynString_free(&dstr); + DynString_split_arr_free(&arr, arr_len); + assert_true(arr == NULL); + + assert_true(DynString_alloc(&dstr, " Hello World", strlen(" Hello World")) == SCUFCONT_SUCCESS); + arr_len = 0; + res = DynString_split(&dstr, " ", &arr, &arr_len); + assert_true(arr_len == 3); + assert_string_equal(DynString_c_str(arr), ""); + assert_string_equal(DynString_c_str(arr + 1), "Hello"); + assert_string_equal(DynString_c_str(arr + 2), "World"); + DynString_free(&dstr); + DynString_split_arr_free(&arr, arr_len); + assert_true(arr == NULL); +} + +int main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_DynString_alloc), + cmocka_unit_test(test_DynString_free), + cmocka_unit_test(test_DynString_equal), + cmocka_unit_test(test_DynString_equal_c_str), + cmocka_unit_test(test_DynString_equal_next_chars_c_str), + cmocka_unit_test(test_DynString_concat_c_str), + cmocka_unit_test(test_DynString_c_str), + cmocka_unit_test(test_DynString_split), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} + diff --git a/tests/cmocka/meson.build b/tests/cmocka/meson.build new file mode 100644 index 0000000..1498781 --- /dev/null +++ b/tests/cmocka/meson.build @@ -0,0 +1,18 @@ + cmocka_tests = executable( + 'cmocka_tests', + 'main.c', + include_directories: [ + scufcont_include + ], + c_args: [ + c_args, + '-ggdb', + '-O0' + ], + dependencies: [ + cmocka_dep + ], + link_with: [ scufcont_lib ] + ) + + test('cmocka', cmocka_tests) diff --git a/tests/meson.build b/tests/meson.build new file mode 100644 index 0000000..3c6c116 --- /dev/null +++ b/tests/meson.build @@ -0,0 +1 @@ +subdir('cmocka')