From e99d393df26e25ec5f24d114ef4591704b54c373 Mon Sep 17 00:00:00 2001 From: SoraKatadzuma Date: Mon, 24 Aug 2026 05:16:09 -0500 Subject: [PATCH] Rework, no version bump --- chinookfile | 8 +- include/malunal/container.h | 306 +++++++--- include/malunal/containers/vector.h | 214 +++++-- sources/container.c | 224 +++---- sources/vector.c | 816 ++++++++++++------------- tests/containers.c | 9 +- tests/vector_container.c | 884 ++++++++++------------------ 7 files changed, 1164 insertions(+), 1297 deletions(-) diff --git a/chinookfile b/chinookfile index 973acb4..96d5b63 100644 --- a/chinookfile +++ b/chinookfile @@ -5,6 +5,8 @@ semv: 1.0.0 requires: - remote: git@git.erasit.com:malunal/allocators branch: v1.0.0 +- remote: git@git.erasit.com:malunal/microtest + branch: v1.0.0 - remote: git@git.erasit.com:malunal/types branch: v1.0.0 @@ -17,14 +19,18 @@ targets: srcs: - ./sources/container.c - ./sources/vector.c + - ./sources/table.c tests: -- name: vector_tests +- name: malunal.containers.allinone type: program deps: - malunal.containers + - malunal.microtest srcs: - ./tests/vector_container.c + - ./tests/table_container.c + - ./tests/containers.c exports: - malunal.containers diff --git a/include/malunal/container.h b/include/malunal/container.h index 319630d..fd74497 100644 --- a/include/malunal/container.h +++ b/include/malunal/container.h @@ -5,28 +5,190 @@ * @author John Christman (sorakatadzuma@gmail.com) * @copyright Malunal Studios, LLC. */ -#include "malunal/allocators.h" -#include "malunal/types.h" +#include "malunal/allocator.h" +#include "malunal/types/object.h" -#ifndef MALUNAL_CONTAINERS_HEADER -#define MALUNAL_CONTAINERS_HEADER - - -#ifdef CONTAINER_SIZE -#undef CONTAINER_SIZE -#endif /* CONTAINER_SIZE */ +#ifndef MALUNAL_CONTAINER_HEADER +#define MALUNAL_CONTAINER_HEADER /** - * @def CONTAINER_SIZE - * @brief Defines the size of containers such that it can exist on the stack - * without the need to heap allocate it. - * @details This is defined based on the number of bytes of a pointer for the - * system and the number of members within the internal container - * implementation. It is statically asserted during compile to assure - * it is not wrong. + * @brief Imports an external constant for the UUID of a @c container_t. + * @details This is needed to properly be able to cast @c container_t compliant + * types to a @c container_t. */ -#define CONTAINER_SIZE sizeof(malunal_size_t) * 6 +extern +const uuid_t +UUID_CONTAINER_T; +/** + * @brief Imports an external constant for the error domain of @c container_t. + * @details This is needed to detect from what domain an error has come from, + * so that error codes can mean different things based on its domain. + */ +extern +const error_domain_t +ERROR_DOMAIN_CONTAINER_T; + +/** + * @brief A type definition for a pointer for a function which will attempt to + * provided the allocator for a provided @c container into the @c out + * parameter. + * @param container A pointer to the container to get the allocator of. + * @param out A pointer to where the allocator reference will be stored. + * @returns An error code if the container could not provide the allocator. + */ +typedef error_t +(*container_allocator_pfn_t)( + malunal_mptr_t container, + allocator_mptr_t* out +); + +/** + * @brief A type definition for a pointer for a function which will attempt to + * provide the stride for a provided @c container into the @c out + * parameter. + * @param container A pointer to the container to get the stride of. + * @param out A pointer to where the stride will be stored. + * @returns An error code if the container could not provide the stride. + */ +typedef error_t +(*container_stride_pfn_t)( + malunal_mptr_t container, + malunal_size_t* out +); + +/** + * @brief A type definition for a pointer for a function which will attempt to + * provide the count for a provided @c container into the @c out + * parameter. + * @param container A pointer to the container to get the count of. + * @param out A pointer to where the count will be stored. + * @returns An error code if the container could not provide the count. + */ +typedef error_t +(*container_count_pfn_t)( + malunal_mptr_t container, + malunal_size_t* out +); + +/** + * @brief A type definition for a pointer for a function which will attempt to + * provide the capacity for a provided @c container into the @c out + * parameter. + * @param container A pointer to the container to get the capacity of. + * @param out A pointer to where the capacity will be stored. + * @returns An error code if the container could not provide the capacity. + */ +typedef error_t +(*container_capacity_pfn_t)( + malunal_mptr_t container, + malunal_size_t* capacity +); + +/** + * @brief A type definition for a pointer to a function which will attempt to + * append the provided @c element into the provided @c container. + * @param container A pointer to the container to append the element into. + * @param element A pointer to the element to append into the container. + * @returns An error code if the container could not append the element. + */ +typedef error_t +(*container_append_pfn_t)( + malunal_mptr_t container, + malunal_iptr_t element +); + +/** + * @brief A type definition for a pointer to a function which will attempt to + * remove the provided @c element from the provided @c container. + * @param container A pointer to the container to remove the element from. + * @param element A pointer to the element to append from the container. + * @returns An error code if the container could not remove the element. + */ +typedef error_t +(*container_remove_pfn_t)( + malunal_mptr_t container, + malunal_iptr_t element +); + +/** + * @brief A type definition for a pointer to a function which will check the + * provided @c container for the provided @c element. + * @param container A pointer to the container to search for the element of. + * @param element A pointer to the element to search for in the container. + * @returns An error code if the container could not find the element. + */ +typedef error_t +(*container_contains_pfn_t)( + malunal_iptr_t container, + malunal_iptr_t element +); + +/** + * @brief A type definition for a pointer to a function which will clear the + * provided @c container. + * @param container A pointer to the container to clear of all its elements. + * @returns An error code if the container could not clear all its elements. + */ +typedef error_t +(*container_clear_pfn_t)( + malunal_mptr_t container +); + +/** + * @brief Defines the container virtual function table. + * @details Implementing classes of the container interface are expected to + * provided valid pointers to these functions that are implementation + * specific. + */ +typedef struct { + /** + * @brief A pointer for a function which will attempt to provide the + * allocator for a provided @c container into the @c out parameter. + */ + container_allocator_pfn_t const allocator; + + /** + * @brief A pointer for a function which will attempt to provide the stride + * for a provided @c container into the @c out parameter. + */ + container_stride_pfn_t const stride; + + /** + * @brief A pointer for a function which will attempt to provide the count + * for a provided @c container into the @c out parameter. + */ + container_count_pfn_t const count; + + /** + * @brief A pointer for a function which will attempt to provide the + * capacity for a provided @c container into the @c out parameter. + */ + container_capacity_pfn_t const capacity; + + /** + * @brief A pointer to a function which will attempt to append the provided + * @c element into the provided @c container. + */ + container_append_pfn_t const append; + + /** + * @brief A pointer to a function which will attempt to remove the provided + * @c element from the provided @c container. + */ + container_remove_pfn_t const remove; + + /** + * @brief A pointer to a function which will check the provided @c container + * for the provided @c element. + */ + container_contains_pfn_t const contains; + + /** + * @brief A pointer to a function which will clear the provided @c container. + */ + container_clear_pfn_t const clear; +} container_vtable_t; /** * @brief Defines an abstract class type for containers. @@ -36,7 +198,12 @@ * and a virtual function table for container specific functions. */ typedef struct { - malunal_uint8_t __opaque[CONTAINER_SIZE]; + /** + * @brief A pointer to an immutable container virtual function table. + * @details Contains the function pointer to the container specific functions + * that make this container function as one. + */ + const container_vtable_t* vtable; } container_t; /** @@ -55,29 +222,24 @@ typedef const container_t* container_iptr_t; /** - * @brief Defines a set of exception that a container may throw. + * @brief Defines a set of errors that a container may throw. * @details These are extremely useful for debugging an container or catching * runtime issues that can be fixed. */ typedef enum { - CONTAINER_ERROR_SUCCESS, CONTAINER_ERROR_FAILURE, CONTAINER_ERROR_NULL_CONTAINER, - CONTAINER_ERROR_NULL_ALLOCATOR, - CONTAINER_ERROR_NULL_CONTEXT, CONTAINER_ERROR_OUT_OF_BOUNDS, - CONTAINER_ERROR_OUT_OF_MEMORY, -} container_exception_t; +} container_error_t; /** * @brief Obtains the allocator for the given container. * @param container A pointer to the container to obtain the allocator from. * @param out A pointer to where to store the obtained allocator pointer. - * @returns An exception if the given container could not provide the allocator - * pointer; otherwise, @c CONTAINER_ERROR_SUCCESS. + * @returns An error if the given container could not provide the allocator. */ -container_exception_t +error_t container_allocator( container_iptr_t container, allocator_mptr_t* out @@ -87,10 +249,9 @@ container_allocator( * @brief Obtains the stride for the given container. * @param container A pointer to the container to obtain the stride from. * @param out A pointer to where to store the obtained stride. - * @returns An exception if the given container could not provide the stride; - * otherwise, @c CONTAINER_ERROR_SUCCESS. + * @returns An error if the given container could not provide the stride. */ -container_exception_t +error_t container_stride( container_iptr_t container, malunal_size_t* out @@ -100,10 +261,9 @@ container_stride( * @brief Obtains the count for the given container. * @param container A pointer to the container to obtain the count from. * @param out A pointer to where to store the obtained count. - * @returns An exception if the given container could not provide the count; - * otherwise, @c CONTAINER_ERROR_SUCCESS. + * @returns An error if the given container could not provide the count. */ -container_exception_t +error_t container_count( container_iptr_t container, malunal_size_t* out @@ -113,68 +273,22 @@ container_count( * @brief Obtains the capacity for the given container. * @param container A pointer to the container to obtain the capacity from. * @param out A pointer to where to store the obtained capacity. - * @returns An exception if the given container could not provide the capacity; - * otherwise, @c CONTAINER_ERROR_SUCCESS. + * @returns An error if the given container could not provide the capacity. */ -container_exception_t +error_t container_capacity( container_iptr_t container, malunal_size_t* out ); -/** - * @brief Initializes a container, by setting some necessary variables and - * acquiring its memory. - * @param container A pointer to the container to initialize. - * @param allocator A pointer to the allocator to use for initialization. - * @param stride The size of the elements that will be stored in the memory - * region of the container. - * @param capacity The maximum number of elements the container can store in - * the memory region it has acquired. - * @returns An exception if the given container could not be initialized; - * otherwise, @c CONTAINER_ERROR_SUCCESS. - */ -container_exception_t -container_initialize( - container_mptr_t container, - allocator_mptr_t allocator, - malunal_size_t stride, - malunal_size_t capacity -); - -/** - * @brief Finalizes a container, by releasing its acquired memory. - * @param container A pointer to the container to finalize. - * @returns An exception if the given container could not be finalized; - * otherwise, @c CONTAINER_ERROR_SUCCESS. - */ -container_exception_t -container_finalize( - container_mptr_t container -); - -/** - * @brief Clears the container, removing all elements from it. - * @param container A pointer to the container to clear. - * @returns An exception describing if the container could be cleared or an - * exact error for why the container could not be cleared. - * @remarks Generally speaking, if the container can be cleared successfully, - * The most common response should be @c CONTAINER_ERROR_SUCCESS. - */ -container_exception_t -container_clear( - container_mptr_t container -); - /** * @brief Appends a new element to the container. * @param container A pointer to the container to append the element to. * @param element A pointer to the data of the element to append to the * given container. - * @returns An exception describing if the element was appended to the container - * or an exact error for why it could not be appended. + * @returns An error if the container could not append the element. */ -container_exception_t +error_t container_append( container_mptr_t container, malunal_iptr_t element @@ -185,12 +299,9 @@ container_append( * @param container A pointer to the container to remove the element from. * @param element A pointer to the data of the element to remove from the * given container. - * @returns An exception describing if the element was removed from the container - * or an exact error for why it could not be removed. - * @remarks Generally speaking, if an element does not exist in the container, - * the most common response should be @c CONTAINER_ERROR_SUCCESS. + * @returns An error if the container could not remove the element. */ -container_exception_t +error_t container_remove( container_mptr_t container, malunal_iptr_t element @@ -201,15 +312,22 @@ container_remove( * @param container A pointer to the container to find the element within. * @param element A pointer to the data of the element to find within the * given container. - * @returns An exception describing if the element is contained within the - * container or an exact error for why it could not find the element. - * @remarks Generally speaking, if the element does exist in the container, the - * most common response should be @c CONTAINER_ERROR_SUCCESS. + * @returns An error if the container could not find the element. */ -container_exception_t +error_t container_contains( container_iptr_t container, malunal_iptr_t element ); -#endif /* MALUNAL_CONTAINERS_HEADER */ +/** + * @brief Clears the container, removing all elements from it. + * @param container A pointer to the container to clear. + * @returns An error if the container could not clear all of its elements. + */ +error_t +container_clear( + container_mptr_t container +); + +#endif /* MALUNAL_CONTAINER_HEADER */ diff --git a/include/malunal/containers/vector.h b/include/malunal/containers/vector.h index 3621028..3085b9d 100644 --- a/include/malunal/containers/vector.h +++ b/include/malunal/containers/vector.h @@ -21,38 +21,158 @@ * meant for vectors. */ typedef struct { - malunal_uint8_t __opaque[sizeof(container_t)]; -} vector_t; + malunal_size_t __opaque[7]; +} vector_container_t; /** * @brief A pointer to a mutable vector. * @details This is provided to simplify type declarations for functions * requiring vectors that are meant to be mutable. */ -typedef vector_t* vector_mptr_t; +typedef vector_container_t* vector_container_mptr_t; /** * @brief A pointer to an immutable vector. * @details This is provided to simplify type declarations for functions * requiring vectors that are meant to be immutable. */ -typedef const vector_t* vector_iptr_t; +typedef const vector_container_t* vector_container_iptr_t; +/** + * @brief Initializes a vector with the given @c stride, @c capacity, and + * @c allocator. + * @param stride The size of the objects that it stores in bytes. + * @param capacity The initial capacity of objects that it stores. + * @param allocator The allocator it should use to obtain memory. + * @param vector A pointer to the vector to initialize. + * @returns An error if the vector could not be initialized. + */ +error_t +vector_container_init( + malunal_size_t stride, + malunal_size_t capacity, + allocator_mptr_t allocator, + vector_container_mptr_t vector +); + +/** + * @brief Frees the backing memory of the vector. + * @param vector A pointer to the vector to free. + * @returns An error if the vector could not be freed. + */ +error_t +vector_container_free( + vector_container_mptr_t vector +); + +/** + * @brief Obtains the allocator for the given vector. + * @param vector A pointer to the vector to obtain the allocator from. + * @param out A pointer to where to store the obtained allocator pointer. + * @returns An error if the given vector could not provide the allocator. + */ +error_t +vector_container_allocator( + vector_container_iptr_t vector, + allocator_mptr_t* out +); + +/** + * @brief Obtains the stride for the given vector. + * @param vector A pointer to the vector to obtain the stride from. + * @param out A pointer to where to store the obtained stride. + * @returns An error if the given vector could not provide the stride. + */ +error_t +vector_container_stride( + vector_container_iptr_t vector, + malunal_size_t* out +); + +/** + * @brief Obtains the count for the given vector. + * @param vector A pointer to the vector to obtain the count from. + * @param out A pointer to where to store the obtained count. + * @returns An error if the given vector could not provide the count. + */ +error_t +vector_container_count( + vector_container_iptr_t vector, + malunal_size_t* out +); + +/** + * @brief Obtains the capacity for the given vector. + * @param vector A pointer to the vector to obtain the capacity from. + * @param out A pointer to where to store the obtained capacity. + * @returns An error if the given vector could not provide the capacity. + */ +error_t +vector_container_capacity( + vector_container_iptr_t vector, + malunal_size_t* out +); + +/** + * @brief Appends a new element to the vector. + * @param vector A pointer to the vector to append the element to. + * @param element A pointer to the data of the element to append. + * @returns An error if the vector could not append the element. + */ +error_t +vector_container_append( + vector_container_mptr_t vector, + malunal_iptr_t element +); + +/** + * @brief Removes the specified element from the vector. + * @param vector A pointer to the vector to remove the element from. + * @param element A pointer to the data of the element to remove. + * @returns An error if the vector could not remove the element. + */ +error_t +vector_container_remove( + vector_container_mptr_t vector, + malunal_iptr_t element +); + +/** + * @brief Checks the vector for the given element. + * @param vector A pointer to the vector to find the element within. + * @param element A pointer to the data of the element to search for. + * @returns An error if the vector could not find the element. + */ +error_t +vector_container_contains( + vector_container_iptr_t vector, + malunal_iptr_t element +); + +/** + * @brief Clears the vector, removing all elements from it. + * @param vector A pointer to the vector to clear. + * @returns An error if the vector could not clear all its elements. + */ +error_t +vector_container_clear( + vector_container_mptr_t vector +); + /** * @brief Gets the element at the given index from the given vector, copying * the data into the memory region of the @c outptr address. - * @param vector A pointer to the vector to get the element from. - * @param index The index of the element to get from the given vector. - * @param outptr A pointer to the memory region to copy the element to. - * @returns An exception if the given vector can not provide the element at the - * given index; otherwise, @c CONTAINER_ERROR_SUCCESS. + * @param vector A pointer to the vector to get the element from. + * @param index The index of the element to get from the given vector. + * @param element A pointer to the memory region to copy the element to. + * @returns An error if the given vector can not get the element. */ -container_exception_t -vector_get( - vector_iptr_t vector, - malunal_size_t index, - malunal_mptr_t outptr +error_t +vector_container_get( + vector_container_iptr_t vector, + malunal_size_t index, + malunal_mptr_t element ); /** @@ -61,14 +181,13 @@ vector_get( * @param vector A pointer to the vector to set the element into. * @param index The index of the element to set in the given vector. * @param element A pointer to the data of the element to set in the vector. - * @returns An exception if the given vector can not set the element at the - * given index; otherwise, @c CONTAINER_ERROR_SUCCESS. + * @returns An error if the given vector can not set the element. */ -container_exception_t -vector_set( - vector_mptr_t vector, - malunal_size_t index, - malunal_iptr_t element +error_t +vector_container_set( + vector_container_mptr_t vector, + malunal_size_t index, + malunal_iptr_t element ); /** @@ -76,54 +195,41 @@ vector_set( * copying the data from the memory region of the @c element. * @param vector A pointer to the vector to insert the element into. * @param index The index within the vector to insert the element. - * @param element A pointer to the data of the element to insert into the - * vector at the given index; otherwise, @c CONTAINER_ERROR_SUCCESS. - * @returns An exception if the given vector can not insert the element at the - * given index; otherwise, @c CONTAINER_ERROR_SUCCESS. + * @param element A pointer to the data of the element to insert. + * @returns An error if the given vector can not insert the element. */ -container_exception_t -vector_insert_at( - vector_mptr_t vector, - malunal_size_t index, - malunal_iptr_t element +error_t +vector_container_insert_at( + vector_container_mptr_t vector, + malunal_size_t index, + malunal_iptr_t element ); /** * @brief Removes the element at the given index from the given vector. * @param vector A pointer to the vector to remove the element from. * @param index The index of the element to remove from the given vector. - * @returns An exception if the given vector can not remove the element at the - * given index; otherwise, @c CONTAINER_ERROR_SUCCESS. + * @returns An error if the given vector can not remove the element. */ -container_exception_t -vector_remove_at( - vector_mptr_t vector, - malunal_size_t index +error_t +vector_container_remove_at( + vector_container_mptr_t vector, + malunal_size_t index ); /** * @brief Finds the index of the given element within the given vector. * @param vector A pointer to the vector to find the index of the element. - * @param element A pointer to the data of the element to find the index of - * within the vector. + * @param element A pointer to the data of the element to find the index of. * @param outidx A pointer to where the index should be stored. - * @returns An exception if the given vector can not provide the index of the - * element within it; otherwise, @c CONTAINER_ERROR_SUCCESS. + * @returns An error if the given vector can not provide the index of the + * element within it. */ -container_exception_t -vector_index_of( - vector_mptr_t vector, - malunal_iptr_t element, - malunal_size_t* outidx +error_t +vector_container_index_of( + vector_container_iptr_t vector, + malunal_iptr_t element, + malunal_size_t* outidx ); -/** - * @brief Provides an vector container. - * @details This will provide appropriate vector container functions when the - * instance is created. - * @returns An container instance that is populated to be an vector. - */ -vector_t -vector_container(); - #endif /* MALUNAL_VECTOR_HEADER */ diff --git a/sources/container.c b/sources/container.c index 75cbd1d..65d5269 100644 --- a/sources/container.c +++ b/sources/container.c @@ -1,182 +1,136 @@ #include "malunal/container.h" -typedef container_exception_t -(*container_initialize_pfn_t)( - container_mptr_t container, - allocator_mptr_t allocator, - malunal_size_t stride, - malunal_size_t capacity -); - -typedef container_exception_t -(*container_finalize_pfn_t)( - container_mptr_t container -); - -typedef container_exception_t -(*container_clear_pfn_t)( - container_mptr_t container -); - -typedef container_exception_t -(*container_append_pfn_t)( - container_mptr_t container, - malunal_iptr_t element -); - -typedef container_exception_t -(*container_remove_pfn_t)( - container_mptr_t container, - malunal_iptr_t element -); - -typedef container_exception_t -(*container_contains_pfn_t)( - container_iptr_t container, - malunal_iptr_t element -); +const uuid_t UUID_CONTAINER_T = { + .tl = 0xF4784CF5, + .tm = 0xFE95, + .thv = 0x4529, + .csr = 0xBD, + .csl = 0x6D, + .nbs = { + 0x82, 0x53, 0x70, + 0x85, 0xC4, 0xAE + } +}; -typedef struct { - container_initialize_pfn_t const initialize; - container_finalize_pfn_t const finalize; - container_clear_pfn_t const clear; - container_append_pfn_t const append; - container_remove_pfn_t const remove; - container_contains_pfn_t const contains; -} virtual_table_t; +static +malunal_cstr_t +describe(malunal_int32_t code) { + switch (code) { + case CONTAINER_ERROR_FAILURE: + return "Generic container error"; + case CONTAINER_ERROR_NULL_CONTAINER: + return "Container provided was null"; + case CONTAINER_ERROR_OUT_OF_BOUNDS: + return "Index into container was out of bounds"; + } + return "Unknown container error"; +} -typedef virtual_table_t* base_vtable_mptr_t; -typedef const virtual_table_t* vtable_iptr_t; +const error_domain_t ERROR_DOMAIN_CONTAINER_T = { + .describe = &describe, + .name = "malunal.container.error" +}; - -typedef struct { - vtable_iptr_t const vtable; - - allocator_mptr_t allocator; - malunal_mptr_t context; - malunal_size_t stride; - malunal_size_t count; - malunal_size_t capacity; -} implementation_t; - -typedef implementation_t* impl_mptr_t; -typedef const implementation_t* impl_iptr_t; - - -// Verify these are the same. -_Static_assert( - CONTAINER_SIZE == sizeof(implementation_t), - "container_t must be the same size as its implementation" -); - - -container_exception_t +error_t container_allocator( container_iptr_t container, allocator_mptr_t* out ) { - impl_iptr_t implementation = (impl_iptr_t)container; - if (implementation == NULL_ADDRESS) - return CONTAINER_ERROR_NULL_CONTAINER; - *out = implementation->allocator; - return CONTAINER_ERROR_SUCCESS; + return container != null + ? container->vtable->allocator(container, out) + : (error_t) { + .domain = &ERROR_DOMAIN_CONTAINER_T, + .code = CONTAINER_ERROR_NULL_CONTAINER + }; } -container_exception_t +error_t container_stride( container_iptr_t container, malunal_size_t* out ) { - impl_iptr_t implementation = (impl_iptr_t)container; - if (implementation == NULL_ADDRESS) - return CONTAINER_ERROR_NULL_CONTAINER; - *out = implementation->stride; - return CONTAINER_ERROR_SUCCESS; + return container != null + ? container->vtable->stride(container, out) + : (error_t) { + .domain = &ERROR_DOMAIN_CONTAINER_T, + .code = CONTAINER_ERROR_NULL_CONTAINER + }; } -container_exception_t +error_t container_count( container_iptr_t container, malunal_size_t* out ) { - impl_iptr_t implementation = (impl_iptr_t)container; - if (implementation == NULL_ADDRESS) - return CONTAINER_ERROR_NULL_CONTAINER; - *out = implementation->count; - return CONTAINER_ERROR_SUCCESS; + return container != null + ? container->vtable->count(container, out) + : (error_t) { + .domain = &ERROR_DOMAIN_CONTAINER_T, + .code = CONTAINER_ERROR_NULL_CONTAINER + }; } -container_exception_t +error_t container_capacity( container_iptr_t container, malunal_size_t* out ) { - impl_iptr_t implementation = (impl_iptr_t)container; - if (implementation == NULL_ADDRESS) - return CONTAINER_ERROR_NULL_CONTAINER; - *out = implementation->capacity; - return CONTAINER_ERROR_SUCCESS; + return container != null + ? container->vtable->capacity(container, out) + : (error_t) { + .domain = &ERROR_DOMAIN_CONTAINER_T, + .code = CONTAINER_ERROR_NULL_CONTAINER + }; } -container_exception_t -container_initialize( - container_mptr_t container, - allocator_mptr_t allocator, - malunal_size_t stride, - malunal_size_t capacity -) { - impl_mptr_t implementation = (impl_mptr_t)container; - return implementation != NULL_ADDRESS - ? implementation->vtable->initialize(container, allocator, stride, capacity) - : CONTAINER_ERROR_NULL_CONTAINER; -} - -container_exception_t -container_finalize(container_mptr_t container) { - impl_mptr_t implementation = (impl_mptr_t)container; - return implementation != NULL_ADDRESS - ? implementation->vtable->finalize(container) - : CONTAINER_ERROR_NULL_CONTAINER; -} - -container_exception_t -container_clear(container_mptr_t container) { - impl_mptr_t implementation = (impl_mptr_t)container; - return implementation != NULL_ADDRESS - ? implementation->vtable->clear(container) - : CONTAINER_ERROR_NULL_CONTAINER; -} - -container_exception_t +error_t container_append( container_mptr_t container, malunal_iptr_t element ) { - impl_mptr_t implementation = (impl_mptr_t)container; - return implementation != NULL_ADDRESS - ? implementation->vtable->append(container, element) - : CONTAINER_ERROR_NULL_CONTAINER; + return container != null + ? container->vtable->append(container, element) + : (error_t) { + .domain = &ERROR_DOMAIN_CONTAINER_T, + .code = CONTAINER_ERROR_NULL_CONTAINER + }; } -container_exception_t +error_t container_remove( container_mptr_t container, malunal_iptr_t element ) { - impl_mptr_t implementation = (impl_mptr_t)container; - return implementation != NULL_ADDRESS - ? implementation->vtable->remove(container, element) - : CONTAINER_ERROR_NULL_CONTAINER; + return container != null + ? container->vtable->remove(container, element) + : (error_t) { + .domain = &ERROR_DOMAIN_CONTAINER_T, + .code = CONTAINER_ERROR_NULL_CONTAINER + }; } -container_exception_t +error_t container_contains( container_iptr_t container, malunal_iptr_t element ) { - impl_iptr_t implementation = (impl_iptr_t)container; - return implementation != NULL_ADDRESS - ? implementation->vtable->contains(container, element) - : CONTAINER_ERROR_NULL_CONTAINER; + return container != null + ? container->vtable->contains(container, element) + : (error_t) { + .domain = &ERROR_DOMAIN_CONTAINER_T, + .code = CONTAINER_ERROR_NULL_CONTAINER + }; +} + +error_t +container_clear( + container_mptr_t container +) { + return container != null + ? container->vtable->clear(container) + : (error_t) { + .domain = &ERROR_DOMAIN_CONTAINER_T, + .code = CONTAINER_ERROR_NULL_CONTAINER + }; } diff --git a/sources/vector.c b/sources/vector.c index bd3b8d6..0884ae0 100644 --- a/sources/vector.c +++ b/sources/vector.c @@ -1,500 +1,454 @@ #include #include "malunal/containers/vector.h" -typedef container_exception_t -(*vector_initialize_pfn_t)( - vector_mptr_t vector, - allocator_mptr_t allocator, - malunal_size_t stride, - malunal_size_t capacity -); -typedef container_exception_t -(*vector_finalize_pfn_t)( - vector_mptr_t vector -); - -typedef container_exception_t -(*vector_clear_pfn_t)( - vector_mptr_t vector -); - -typedef container_exception_t -(*vector_append_pfn_t)( - vector_mptr_t vector, - malunal_iptr_t element -); - -typedef container_exception_t -(*vector_remove_pfn_t)( - vector_mptr_t vector, - malunal_iptr_t element -); - -typedef container_exception_t -(*vector_contains_pfn_t)( - vector_iptr_t vector, - malunal_iptr_t element -); - -typedef container_exception_t -(*vector_get_pfn_t)( - vector_iptr_t vector, - malunal_size_t index, - malunal_mptr_t outptr -); - -typedef container_exception_t -(*vector_set_pfn_t)( - vector_mptr_t vector, - malunal_size_t index, - malunal_iptr_t element -); - -typedef container_exception_t -(*vector_insert_at_pfn_t)( - vector_mptr_t vector, - malunal_size_t index, - malunal_iptr_t element -); - -typedef container_exception_t -(*vector_remove_at_pfn_t)( - vector_mptr_t vector, - malunal_size_t index -); - -typedef container_exception_t -(*vector_index_of_pfn_t)( - vector_iptr_t vector, - malunal_iptr_t element, - malunal_size_t* outidx -); - - -typedef struct VectorVTable { - // Container generic. - vector_initialize_pfn_t const initialize; - vector_finalize_pfn_t const finalize; - vector_clear_pfn_t const clear; - vector_append_pfn_t const append; - vector_remove_pfn_t const remove; - vector_contains_pfn_t const contains; - - // Vector specific. - vector_get_pfn_t const get; - vector_set_pfn_t const set; - vector_insert_at_pfn_t const insert_at; - vector_remove_at_pfn_t const remove_at; - vector_index_of_pfn_t const index_of; -} virtual_table_t; - -typedef virtual_table_t* vtable_mptr_t; -typedef const virtual_table_t* vtable_iptr_t; - - -// Identical to container. typedef struct { - vtable_iptr_t const vtable; + object_t object; + container_t container; allocator_mptr_t allocator; - malunal_mptr_t context; malunal_size_t stride; malunal_size_t count; malunal_size_t capacity; -} implementation_t; + malunal_mptr_t buffer; +} impl_t; -typedef implementation_t* impl_mptr_t; -typedef const implementation_t* impl_iptr_t; +typedef impl_t* impl_mptr_t; +typedef const impl_t* impl_iptr_t; +_Static_assert( + sizeof(vector_container_t) == sizeof(impl_t), + "Vector container must be the size of its implementation" +); static -container_exception_t -vector_initialize_impl( - impl_mptr_t vector, - allocator_mptr_t allocator, - malunal_size_t stride, - malunal_size_t capacity +error_t +vector_object_cast_impl( + malunal_mptr_t object, + uuid_iptr_t uuid, + malunal_mptr_t* out ) { - malunal_size_t bytes = capacity * stride; - allocation_result_t result = allocator_acquire(allocator, bytes); - if (!result.threw) { - vector->allocator = allocator; - vector->context = result.address; - vector->stride = stride; - vector->capacity = capacity; - return CONTAINER_ERROR_SUCCESS; + if (object == null) + return (error_t) { + .domain = &ERROR_DOMAIN_CONTAINER_T, + .code = CONTAINER_ERROR_NULL_CONTAINER + }; + + impl_mptr_t self = (impl_mptr_t)object; + if (uuid_equals(&UUID_OBJECT_T, uuid)) { + *out = &self->object; + return NO_ERROR; } - switch (result.error) { - case ALLOCATION_ERROR_NULL_ALLOCATOR: - case ALLOCATION_ERROR_NULL_CONTEXT: - case ALLOCATION_ERROR_NULL_UPSTREAM: - return CONTAINER_ERROR_NULL_ALLOCATOR; - - case ALLOCATION_ERROR_OUT_OF_MEMORY: - return CONTAINER_ERROR_OUT_OF_MEMORY; + if (uuid_equals(&UUID_CONTAINER_T, uuid)) { + *out = &self->container; + return NO_ERROR; } + + *out = null; + return (error_t) { + .domain = &ERROR_DOMAIN_CONTAINER_T, + .code = CONTAINER_ERROR_FAILURE + }; } static -container_exception_t -vector_finalize_impl(impl_mptr_t vector) { - malunal_size_t bytes = vector->capacity * vector->stride; - allocation_error_t error = allocator_release( - vector->allocator, - vector->context, - bytes +malunal_uint32_t +vector_object_retain_impl( + malunal_mptr_t object +) { + // TODO: figure out how to handle this. +} + +static +malunal_uint32_t +vector_object_release_impl( + malunal_mptr_t object +) { + // TODO: figure out how to handle this. +} + +static const +object_vtable_t vector_object_vtable = { + .cast = &vector_object_cast_impl, + .retain = &vector_object_retain_impl, + .release = &vector_object_release_impl +}; + +static const +container_vtable_t vector_container_vtable = { + .allocator = (container_allocator_pfn_t)&vector_container_allocator, + .stride = (container_stride_pfn_t)&vector_container_stride, + .count = (container_count_pfn_t)&vector_container_count, + .capacity = (container_capacity_pfn_t)&vector_container_capacity, + .append = (container_append_pfn_t)&vector_container_append, + .remove = (container_remove_pfn_t)&vector_container_remove, + .contains = (container_contains_pfn_t)&vector_container_contains, + .clear = (container_clear_pfn_t)&vector_container_clear +}; + +static +error_t +vector_container_realloc(impl_mptr_t self) { + if ((float)self->count / self->capacity < 0.75f) + return NO_ERROR; + + malunal_mptr_t buffer; + malunal_size_t oldcap = self->stride * self->capacity; + error_t result = allocator_acquire(self->allocator, oldcap * 2, &buffer); + if (result.domain != null) + return result; + + buffer = memmove(buffer, self->buffer, oldcap); + result = allocator_dispose(self->allocator, self->buffer, oldcap); + self->buffer = buffer; + self->capacity = self->capacity * 2; + return NO_ERROR; +} + +error_t +vector_container_init( + malunal_size_t stride, + malunal_size_t capacity, + allocator_mptr_t allocator, + vector_container_mptr_t vector +) { + if (vector == null) + return (error_t) { + .domain = &ERROR_DOMAIN_CONTAINER_T, + .code = CONTAINER_ERROR_NULL_CONTAINER + }; + + impl_mptr_t self = (impl_mptr_t)vector; + error_t result = allocator_acquire( + allocator, + stride * capacity, + &self->buffer ); - switch (error) { - case ALLOCATION_ERROR_SUCCESS: - return CONTAINER_ERROR_SUCCESS; + if (result.domain != null) + return result; - case ALLOCATION_ERROR_NULL_ALLOCATOR: - case ALLOCATION_ERROR_NULL_CONTEXT: - case ALLOCATION_ERROR_NULL_UPSTREAM: - return CONTAINER_ERROR_NULL_ALLOCATOR; - - case ALLOCATION_ERROR_LEAKY_MEMORY: - case ALLOCATION_ERROR_NOT_MY_ADDRESS: - case ALLOCATION_ERROR_FAILURE: - return CONTAINER_ERROR_FAILURE; - } + self->object = (object_t) { &vector_object_vtable }; + self->container = (container_t){ &vector_container_vtable }; + self->allocator = allocator; + self->stride = stride; + self->count = 0; + self->capacity = capacity; + return NO_ERROR; } -static -container_exception_t -vector_clear_impl(impl_mptr_t vector) { - vector->count = 0; - return CONTAINER_ERROR_SUCCESS; -} - -static -container_exception_t -vector_append_impl( - impl_mptr_t vector, - malunal_iptr_t element +error_t +vector_container_free( + vector_container_mptr_t vector ) { - if (vector->context == NULL_ADDRESS) - return CONTAINER_ERROR_NULL_CONTEXT; + if (vector == null) + return (error_t) { + .domain = &ERROR_DOMAIN_CONTAINER_T, + .code = CONTAINER_ERROR_NULL_CONTAINER + }; - if (vector->count < vector->capacity) - goto copy_element; - - malunal_size_t doubled = vector->capacity * 2; - allocation_result_t result = allocator_reacquire( - vector->allocator, - vector->context, - vector->capacity * vector->stride, - doubled * vector->stride + impl_mptr_t self = (impl_mptr_t)vector; + error_t result = allocator_dispose( + self->allocator, + self->buffer, + self->stride * self->capacity ); - if (!result.threw) { - vector->context = result.address; - vector->capacity = doubled; - goto copy_element; - } + if (result.domain != null) + return result; - switch (result.error) { - case ALLOCATION_ERROR_NULL_ALLOCATOR: - case ALLOCATION_ERROR_NULL_UPSTREAM: - case ALLOCATION_ERROR_NULL_CONTEXT: - return CONTAINER_ERROR_NULL_ALLOCATOR; - - case ALLOCATION_ERROR_LEAKY_MEMORY: - case ALLOCATION_ERROR_NOT_MY_ADDRESS: - case ALLOCATION_ERROR_FAILURE: - return CONTAINER_ERROR_FAILURE; - } - -copy_element: - malunal_uint8_t* buffer = vector->context; - buffer = buffer + vector->stride * vector->count; - memcpy(buffer, element, vector->stride); - - vector->count++; - return CONTAINER_ERROR_SUCCESS; + self->stride = 0; + self->count = 0; + self->capacity = 0; + self->buffer = null; + return NO_ERROR; } -static -container_exception_t -vector_remove_impl( - impl_mptr_t vector, - malunal_iptr_t element +error_t +vector_container_allocator( + vector_container_iptr_t vector, + allocator_mptr_t* out ) { - if (vector->context == NULL_ADDRESS) - return CONTAINER_ERROR_NULL_CONTEXT; + if (vector == null) + return (error_t) { + .domain = &ERROR_DOMAIN_CONTAINER_T, + .code = CONTAINER_ERROR_NULL_CONTAINER + }; - malunal_size_t index = 0; - vector_mptr_t erased = (vector_mptr_t)vector; - container_exception_t error = vector_index_of(erased, element, &index); - if (error != CONTAINER_ERROR_SUCCESS) - return error; - - return index != -1 - ? vector_remove_at(erased, index) - : CONTAINER_ERROR_SUCCESS; + impl_iptr_t self = (impl_iptr_t)vector; + *out = self->allocator; + return NO_ERROR; } -static -container_exception_t -vector_contains_impl( - impl_iptr_t vector, - malunal_iptr_t element +error_t +vector_container_stride( + vector_container_iptr_t vector, + malunal_size_t* out ) { - if (vector->context == NULL_ADDRESS) - return CONTAINER_ERROR_NULL_CONTEXT; + if (vector == null) + return (error_t) { + .domain = &ERROR_DOMAIN_CONTAINER_T, + .code = CONTAINER_ERROR_NULL_CONTAINER + }; - malunal_size_t index = 0; - vector_mptr_t erased = (vector_mptr_t)vector; - container_exception_t error = vector_index_of(erased, element, &index); - if (error != CONTAINER_ERROR_SUCCESS) - return error; - - return index != -1 - ? CONTAINER_ERROR_SUCCESS - : CONTAINER_ERROR_FAILURE; + impl_iptr_t self = (impl_iptr_t)vector; + *out = self->stride; + return NO_ERROR; } -static -container_exception_t -vector_get_def_impl( - impl_iptr_t vector, - malunal_size_t index, - malunal_mptr_t outptr +error_t +vector_container_count( + vector_container_iptr_t vector, + malunal_size_t* out ) { - if (vector->context == NULL_ADDRESS) - return CONTAINER_ERROR_NULL_CONTEXT; + if (vector == null) + return (error_t) { + .domain = &ERROR_DOMAIN_CONTAINER_T, + .code = CONTAINER_ERROR_NULL_CONTAINER + }; - if (index < 0 || index >= vector->count) - return CONTAINER_ERROR_OUT_OF_BOUNDS; - - malunal_uint8_t* buffer = vector->context; - buffer = buffer + vector->stride * index; - memcpy(outptr, buffer, vector->stride); - return CONTAINER_ERROR_SUCCESS; + impl_iptr_t self = (impl_iptr_t)vector; + *out = self->count; + return NO_ERROR; } -static -container_exception_t -vector_set_def_impl( - impl_mptr_t vector, - malunal_size_t index, - malunal_iptr_t element +error_t +vector_container_capacity( + vector_container_iptr_t vector, + malunal_size_t* out ) { - if (vector->context == NULL_ADDRESS) - return CONTAINER_ERROR_NULL_CONTEXT; + if (vector == null) + return (error_t) { + .domain = &ERROR_DOMAIN_CONTAINER_T, + .code = CONTAINER_ERROR_NULL_CONTAINER + }; - if (index < 0 || index >= vector->count) - return CONTAINER_ERROR_OUT_OF_BOUNDS; - - malunal_uint8_t* buffer = vector->context; - buffer = buffer + vector->stride * index; - memcpy(buffer, element, vector->stride); - return CONTAINER_ERROR_SUCCESS; + impl_iptr_t self = (impl_iptr_t)vector; + *out = self->capacity; + return NO_ERROR; } -static -container_exception_t -vector_insert_at_def_impl( - impl_mptr_t vector, - malunal_size_t index, - malunal_iptr_t element +error_t +vector_container_append( + vector_container_mptr_t vector, + malunal_iptr_t element ) { - if (vector->context == NULL_ADDRESS) - return CONTAINER_ERROR_NULL_CONTEXT; + if (vector == null) + return (error_t) { + .domain = &ERROR_DOMAIN_CONTAINER_T, + .code = CONTAINER_ERROR_NULL_CONTAINER + }; - if (index < 0 || index > vector->count) - return CONTAINER_ERROR_OUT_OF_BOUNDS; + impl_mptr_t self = (impl_mptr_t)vector; + error_t result = vector_container_realloc(self); + if (result.domain != null) + return result; - if (vector->count < vector->capacity) - goto insert_element; - - malunal_size_t doubled = vector->capacity * 2; - allocation_result_t result = allocator_reacquire( - vector->allocator, - vector->context, - vector->capacity * vector->stride, - doubled * vector->stride - ); - - if (!result.threw) { - vector->context = result.address; - vector->capacity = doubled; - goto insert_element; - } - - switch (result.error) { - case ALLOCATION_ERROR_NULL_ALLOCATOR: - case ALLOCATION_ERROR_NULL_UPSTREAM: - case ALLOCATION_ERROR_NULL_CONTEXT: - return CONTAINER_ERROR_NULL_ALLOCATOR; - - case ALLOCATION_ERROR_LEAKY_MEMORY: - case ALLOCATION_ERROR_NOT_MY_ADDRESS: - case ALLOCATION_ERROR_FAILURE: - return CONTAINER_ERROR_FAILURE; - } - -insert_element: - malunal_size_t offset = vector->stride * index; - malunal_uint8_t* orig = vector->context; - orig = orig + offset; - - malunal_size_t window = vector->count - index; - malunal_size_t bytes = vector->stride * window; - malunal_uint8_t* dest = orig + vector->stride; - memmove(dest, orig, bytes); - memcpy(orig, element, vector->stride); - vector->count++; - return CONTAINER_ERROR_SUCCESS; + malunal_uint8_t* buffer = self->buffer; + buffer += self->stride * self->count; + memcpy(buffer, element, self->stride); + self->count++; + return NO_ERROR; } -static -container_exception_t -vector_remove_at_def_impl( - impl_mptr_t vector, - malunal_size_t index +error_t +vector_container_remove( + vector_container_mptr_t vector, + malunal_iptr_t element ) { - if (vector->context == NULL_ADDRESS) - return CONTAINER_ERROR_NULL_CONTEXT; + if (vector == null) + return (error_t) { + .domain = &ERROR_DOMAIN_CONTAINER_T, + .code = CONTAINER_ERROR_NULL_CONTAINER + }; - if (index < 0 || index >= vector->count) - return CONTAINER_ERROR_OUT_OF_BOUNDS; + malunal_size_t index; + impl_mptr_t self = (impl_mptr_t)vector; + error_t result = vector_container_index_of(vector, element, &index); + if (result.domain != null) + return result; - malunal_size_t offset = vector->stride * index; - malunal_uint8_t* dest = vector->context; - dest = dest + offset; - - malunal_size_t window = vector->count - index; - malunal_size_t bytes = vector->stride * window; - malunal_uint8_t* orig = dest + vector->stride; - memmove(dest, orig, bytes); - vector->count--; - return CONTAINER_ERROR_SUCCESS; + return index != (malunal_size_t)-1 + ? vector_container_remove_at(vector, index) + : NO_ERROR; } -static -container_exception_t -vector_index_of_def_impl( - impl_mptr_t vector, - malunal_iptr_t element, - malunal_size_t* outidx +error_t +vector_container_contains( + vector_container_iptr_t vector, + malunal_iptr_t element ) { - if (vector->context == NULL_ADDRESS) - return CONTAINER_ERROR_NULL_CONTEXT; + if (vector == null) + return (error_t) { + .domain = &ERROR_DOMAIN_CONTAINER_T, + .code = CONTAINER_ERROR_NULL_CONTAINER + }; + + malunal_size_t index; + impl_iptr_t self = (impl_iptr_t)vector; + error_t result = vector_container_index_of(vector, element, &index); + if (result.domain != null) + return result; + + return index == (malunal_size_t)-1 + ? (error_t) { + .domain = &ERROR_DOMAIN_CONTAINER_T, + .code = CONTAINER_ERROR_FAILURE + } + : NO_ERROR; +} + +error_t +vector_container_clear( + vector_container_mptr_t vector +) { + if (vector == null) + return (error_t) { + .domain = &ERROR_DOMAIN_CONTAINER_T, + .code = CONTAINER_ERROR_NULL_CONTAINER + }; + + impl_mptr_t self = (impl_mptr_t)vector; + self->count = 0; + return NO_ERROR; +} + +error_t +vector_container_get( + vector_container_iptr_t vector, + malunal_size_t index, + malunal_mptr_t element +) { + if (vector == null) + return (error_t) { + .domain = &ERROR_DOMAIN_CONTAINER_T, + .code = CONTAINER_ERROR_NULL_CONTAINER + }; + + impl_iptr_t self = (impl_iptr_t)vector; + if (index >= self->count) + return (error_t) { + .domain = &ERROR_DOMAIN_CONTAINER_T, + .code = CONTAINER_ERROR_OUT_OF_BOUNDS + }; + + malunal_uint8_t* buffer = self->buffer; + buffer += self->stride * index; + memcpy(element, buffer, self->stride); + return NO_ERROR; +} + +error_t +vector_container_set( + vector_container_mptr_t vector, + malunal_size_t index, + malunal_iptr_t element +) { + if (vector == null) + return (error_t) { + .domain = &ERROR_DOMAIN_CONTAINER_T, + .code = CONTAINER_ERROR_NULL_CONTAINER + }; + + impl_mptr_t self = (impl_mptr_t)vector; + if (index >= self->count) + return (error_t) { + .domain = &ERROR_DOMAIN_CONTAINER_T, + .code = CONTAINER_ERROR_OUT_OF_BOUNDS + }; + + malunal_uint8_t* buffer = self->buffer; + buffer += self->stride * index; + memcpy(buffer, element, self->stride); + return NO_ERROR; +} + +error_t +vector_container_insert_at( + vector_container_mptr_t vector, + malunal_size_t index, + malunal_iptr_t element +) { + if (vector == null) + return (error_t) { + .domain = &ERROR_DOMAIN_CONTAINER_T, + .code = CONTAINER_ERROR_NULL_CONTAINER + }; + + impl_mptr_t self = (impl_mptr_t)vector; + if (index > self->count) + return (error_t) { + .domain = &ERROR_DOMAIN_CONTAINER_T, + .code = CONTAINER_ERROR_OUT_OF_BOUNDS + }; + + error_t result = vector_container_realloc(self); + if (result.domain != null) + return result; + + malunal_uint8_t* orig = self->buffer; + orig += self->stride * index; + + malunal_uint8_t* dest = orig + self->stride; + memmove(dest, orig, self->stride * self->count - index); + memcpy(orig, element, self->stride); + self->count++; + return NO_ERROR; +} + +error_t +vector_container_remove_at( + vector_container_mptr_t vector, + malunal_size_t index +) { + if (vector == null) + return (error_t) { + .domain = &ERROR_DOMAIN_CONTAINER_T, + .code = CONTAINER_ERROR_NULL_CONTAINER + }; + + impl_mptr_t self = (impl_mptr_t)vector; + if (self->count == 0 || index >= self->count) + return (error_t) { + .domain = &ERROR_DOMAIN_CONTAINER_T, + .code = CONTAINER_ERROR_OUT_OF_BOUNDS + }; + + malunal_uint8_t* dest = self->buffer; + dest += self->stride * index; + + malunal_uint8_t* orig = dest + self->stride; + memmove(dest, orig, self->stride * self->count - index); + self->count--; + return NO_ERROR; +} + +error_t +vector_container_index_of( + vector_container_iptr_t vector, + malunal_iptr_t element, + malunal_size_t* outidx +) { + if (vector == null) + return (error_t) { + .domain = &ERROR_DOMAIN_CONTAINER_T, + .code = CONTAINER_ERROR_NULL_CONTAINER + }; malunal_size_t index = 0; - malunal_uint8_t* buffer = vector->context; - while (index < vector->count) { - if (memcmp(buffer, element, vector->stride) == 0) { + impl_iptr_t self = (impl_iptr_t)vector; + malunal_uint8_t* buffer = self->buffer; + while (index < self->count) { + if (memcmp(buffer, element, self->stride) == 0) { *outidx = index; - return CONTAINER_ERROR_SUCCESS; + return NO_ERROR; } index += 1; - buffer += vector->stride; + buffer += self->stride; } - *outidx = -1; - return CONTAINER_ERROR_SUCCESS; + *outidx = (malunal_size_t)-1; + return NO_ERROR; } - - -container_exception_t -vector_get( - vector_iptr_t vector, - malunal_size_t index, - malunal_mptr_t outptr -) { - impl_iptr_t implementation = (impl_iptr_t)vector; - return implementation != NULL_ADDRESS - ? implementation->vtable->get(vector, index, outptr) - : CONTAINER_ERROR_NULL_CONTAINER; -} - -container_exception_t -vector_set( - vector_mptr_t vector, - malunal_size_t index, - malunal_iptr_t element -) { - impl_iptr_t implementation = (impl_iptr_t)vector; - return implementation != NULL_ADDRESS - ? implementation->vtable->set(vector, index, element) - : CONTAINER_ERROR_NULL_CONTAINER; -} - -container_exception_t -vector_insert_at( - vector_mptr_t vector, - malunal_size_t index, - malunal_iptr_t element -) { - impl_iptr_t implementation = (impl_iptr_t)vector; - return implementation != NULL_ADDRESS - ? implementation->vtable->insert_at(vector, index, element) - : CONTAINER_ERROR_NULL_CONTAINER; -} - -container_exception_t -vector_remove_at( - vector_mptr_t vector, - malunal_size_t index -) { - impl_iptr_t implementation = (impl_iptr_t)vector; - return implementation != NULL_ADDRESS - ? implementation->vtable->remove_at(vector, index) - : CONTAINER_ERROR_NULL_CONTAINER; -} - -container_exception_t -vector_index_of( - vector_mptr_t vector, - malunal_iptr_t element, - malunal_size_t* outidx -) { - impl_iptr_t implementation = (impl_iptr_t)vector; - return implementation != NULL_ADDRESS - ? implementation->vtable->index_of(vector, element, outidx) - : CONTAINER_ERROR_NULL_CONTAINER; -} - - -static const -virtual_table_t vector_vtable = { - .initialize = (vector_initialize_pfn_t)&vector_initialize_impl, - .finalize = (vector_finalize_pfn_t)&vector_finalize_impl, - .clear = (vector_clear_pfn_t)&vector_clear_impl, - .append = (vector_append_pfn_t)&vector_append_impl, - .remove = (vector_remove_pfn_t)&vector_remove_impl, - .contains = (vector_contains_pfn_t)&vector_contains_impl, - .get = (vector_get_pfn_t)&vector_get_def_impl, - .set = (vector_set_pfn_t)&vector_set_def_impl, - .insert_at = (vector_insert_at_pfn_t)&vector_insert_at_def_impl, - .remove_at = (vector_remove_at_pfn_t)&vector_remove_at_def_impl, - .index_of = (vector_index_of_pfn_t)&vector_index_of_def_impl -}; - -vector_t -vector_container() { - implementation_t implementation = { - .vtable = &vector_vtable, - .allocator = NULL_ADDRESS, - .context = NULL_ADDRESS, - .stride = 0, - .count = 0, - .capacity = 0, - }; - - vector_t result; - memcpy( - &result, - &implementation, - sizeof(implementation_t) - ); - - return result; -} - diff --git a/tests/containers.c b/tests/containers.c index c570da5..134b53d 100644 --- a/tests/containers.c +++ b/tests/containers.c @@ -1,7 +1,2 @@ -#include -#include "malunal/vector.h" - -int main(int argc, char** argv) { - printf("Hello, World!\n"); - return 0; -} +#include "malunal/microtest.h" +MICROTEST_MAIN() diff --git a/tests/vector_container.c b/tests/vector_container.c index 164b366..5352c42 100644 --- a/tests/vector_container.c +++ b/tests/vector_container.c @@ -1,634 +1,368 @@ -#define _DEFAULT_SOURCE /* strsignal() is only declared under this with -std=c11 */ -#include -#include -#include -#include -#include - -#include "malunal/container.h" #include "malunal/containers/vector.h" -#include "malunal/allocators.h" +#include "malunal/microtest.h" -/* ------------------------------------------------------------------ */ -/* Minimal assertion + test-registration harness (no framework) */ -/* ------------------------------------------------------------------ */ +MICROTEST(vector_container, can_init_and_free) { + error_t result = {}; + vector_container_t vector = {}; + malunal_size_t temporary = 0; -#define CHECK(cond, msg) \ - do { \ - if (!(cond)) { \ - fprintf(stderr, " assertion failed: %s (%s:%d)\n", \ - (msg), __FILE__, __LINE__); \ - _exit(1); /* fail fast within the child: state is unreliable */ \ - } \ - } while (0) + result = vector_container_init(4, 32, libc_allocator(), &vector); + MICROTEST_EXPECT_NULL(result.domain); -#define CHECK_EQ_SZ(actual, expected, msg) \ - do { \ - malunal_size_t _a = (malunal_size_t)(actual); \ - malunal_size_t _e = (malunal_size_t)(expected); \ - if (_a != _e) { \ - fprintf(stderr, \ - " assertion failed: %s (%s:%d) got=%llu want=%llu\n", \ - (msg), __FILE__, __LINE__, \ - (unsigned long long)_a, (unsigned long long)_e); \ - _exit(1); \ - } \ - } while (0) + allocator_mptr_t allocator; + result = vector_container_allocator(&vector, &allocator); + MICROTEST_EXPECT_NULL(result.domain); + MICROTEST_EXPECT_EQ(allocator, libc_allocator()); -#define CHECK_EQ_INT(actual, expected, msg) \ - do { \ - int _a = (int)(actual); \ - int _e = (int)(expected); \ - if (_a != _e) { \ - fprintf(stderr, " assertion failed: %s (%s:%d) got=%d want=%d\n", \ - (msg), __FILE__, __LINE__, _a, _e); \ - _exit(1); \ - } \ - } while (0) + result = vector_container_stride(&vector, &temporary); + MICROTEST_EXPECT_NULL(result.domain); + MICROTEST_EXPECT_EQ(temporary, 4); -typedef void (*test_fn_t)(void); + result = vector_container_count(&vector, &temporary); + MICROTEST_EXPECT_NULL(result.domain); + MICROTEST_EXPECT_EQ(temporary, 0); -typedef struct { - const char* name; - test_fn_t fn; -} test_case_t; + result = vector_container_capacity(&vector, &temporary); + MICROTEST_EXPECT_NULL(result.domain); + MICROTEST_EXPECT_EQ(temporary, 32); -#define MAX_TESTS 64 -static test_case_t g_tests[MAX_TESTS]; -static int g_test_count = 0; + result = vector_container_free(&vector); + MICROTEST_EXPECT_NULL(result.domain); -static void register_test(const char* name, test_fn_t fn) { - g_tests[g_test_count].name = name; - g_tests[g_test_count].fn = fn; - g_test_count++; + result = vector_container_stride(&vector, &temporary); + MICROTEST_EXPECT_NULL(result.domain); + MICROTEST_EXPECT_EQ(temporary, 0); + + result = vector_container_count(&vector, &temporary); + MICROTEST_EXPECT_NULL(result.domain); + MICROTEST_EXPECT_EQ(temporary, 0); + + result = vector_container_capacity(&vector, &temporary); + MICROTEST_EXPECT_NULL(result.domain); + MICROTEST_EXPECT_EQ(temporary, 0); } -#define TEST(name) \ - static void name(void); \ - static void name##_register(void) __attribute__((constructor)); \ - static void name##_register(void) { register_test(#name, name); } \ - static void name(void) +MICROTEST(vector_container, append_then_get_round_trips_value) { + error_t result = {}; + vector_container_t vector = {}; + vector_container_init(4, 32, libc_allocator(), &vector); -/* Runs a single test in a forked child so a crash can't take out the - * rest of the suite. Returns 1 for pass, 0 for fail/crash. */ -static int run_isolated(test_fn_t fn) { - pid_t pid = fork(); - if (pid < 0) { - perror("fork"); - return 0; - } - - if (pid == 0) { - /* child */ - fn(); - _exit(0); /* reached only if no CHECK failed */ - } - - int status = 0; - waitpid(pid, &status, 0); - - if (WIFEXITED(status) && WEXITSTATUS(status) == 0) - return 1; - - if (WIFSIGNALED(status)) { - int sig = WTERMSIG(status); - fprintf(stderr, " CRASHED: terminated by signal %d (%s)\n", sig, strsignal(sig)); - } - return 0; -} - -/* ------------------------------------------------------------------ */ -/* Shared setup helper */ -/* ------------------------------------------------------------------ */ - -/* Initializes a fresh vector-as-container with a libc-backed allocator. - * `allocator_out` is filled in because callers need to keep the - * allocator alive (by value) for the container's lifetime. */ -static container_mptr_t setup_container( - vector_t* vector, - allocator_t* allocator_out, - malunal_size_t stride, - malunal_size_t capacity -) { - *vector = vector_container(); - *allocator_out = libc_allocator(); - - container_mptr_t container = (container_mptr_t)vector; - container_exception_t rc = container_initialize( - container, allocator_out, stride, capacity); - CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "container_initialize should succeed"); - return container; -} - -/* ------------------------------------------------------------------ */ -/* vector_container() -- construction */ -/* ------------------------------------------------------------------ */ - -TEST(construct_then_uninitialized_accessors_are_zero) { - vector_t vector = vector_container(); - container_iptr_t container = (container_iptr_t)&vector; - - malunal_size_t value = 123; /* sentinel so we know it was actually written */ - container_exception_t rc = container_stride(container, &value); - CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "container_stride should succeed pre-initialize"); - CHECK_EQ_SZ(value, 0, "stride should start at 0"); - - rc = container_count(container, &value); - CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "container_count should succeed pre-initialize"); - CHECK_EQ_SZ(value, 0, "count should start at 0"); - - rc = container_capacity(container, &value); - CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "container_capacity should succeed pre-initialize"); - CHECK_EQ_SZ(value, 0, "capacity should start at 0"); -} - -TEST(vector_t_and_container_t_are_same_size) { - CHECK_EQ_SZ(sizeof(vector_t), sizeof(container_t), "vector_t must match container_t size"); -} - -/* ------------------------------------------------------------------ */ -/* container_initialize / container_finalize */ -/* ------------------------------------------------------------------ */ - -TEST(initialize_allocates_backing_storage) { - vector_t vector = vector_container(); - allocator_t libc_alloc = libc_allocator(); - allocator_mptr_t allocator = &libc_alloc; - malunal_size_t value = 0; - - container_mptr_t container = (container_mptr_t)&vector; - container_exception_t error = container_initialize( - container, allocator, sizeof(int), 4); - - CHECK(error == CONTAINER_ERROR_SUCCESS, "Container failed to initialize"); - - error = container_stride(container, &value); - CHECK(error == CONTAINER_ERROR_SUCCESS, "Container failed to provide stride"); - CHECK(value == sizeof(int), "Stride should match requested element size"); - - error = container_count(container, &value); - CHECK(error == CONTAINER_ERROR_SUCCESS, "Container failed to provide count"); - CHECK(value == 0, "Count should be 0 right after initialize"); - - error = container_capacity(container, &value); - CHECK(error == CONTAINER_ERROR_SUCCESS, "Container failed to provide capacity"); - CHECK(value == 4, "Capacity should match requested capacity"); - - error = container_finalize(container); - CHECK(error == CONTAINER_ERROR_SUCCESS, "Container failed to finalize"); -} - -TEST(allocator_accessor_returns_the_allocator_used) { - vector_t vector = vector_container(); - allocator_t libc_alloc = libc_allocator(); - container_mptr_t container = (container_mptr_t)&vector; - container_initialize(container, &libc_alloc, sizeof(int), 4); - - allocator_mptr_t got = NULL; - container_exception_t rc = container_allocator(container, &got); - CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "container_allocator should succeed"); - CHECK(got == &libc_alloc, "container_allocator should return the allocator passed to initialize"); - - container_finalize(container); -} - -/* ------------------------------------------------------------------ */ -/* container_append + vector_get */ -/* ------------------------------------------------------------------ */ - -TEST(append_then_get_round_trips_value) { - vector_t vector = vector_container(); - allocator_t libc_alloc = libc_allocator(); - container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 4); - - int value = 42; - container_exception_t rc = container_append(container, &value); - CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "container_append should succeed"); - - int out = 0; - rc = vector_get(&vector, 0, &out); - CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "vector_get should succeed for freshly appended element"); - CHECK_EQ_INT(out, 42, "vector_get should return the value that was appended"); - - container_finalize(container); -} - -TEST(append_increments_count) { - vector_t vector = vector_container(); - allocator_t libc_alloc = libc_allocator(); - container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 4); - - int a = 1, b = 2; - container_append(container, &a); - container_append(container, &b); + malunal_int32_t a = 42; + result = vector_container_append(&vector, &a); + MICROTEST_EXPECT_NULL(result.domain); malunal_size_t count = 0; - container_count(container, &count); - CHECK_EQ_SZ(count, 2, "count should track number of appends"); + result = vector_container_count(&vector, &count); + MICROTEST_EXPECT_NULL(result.domain); + MICROTEST_EXPECT_EQ(count, 1); - int out0 = 0, out1 = 0; - vector_get(&vector, 0, &out0); - vector_get(&vector, 1, &out1); - CHECK_EQ_INT(out0, 1, "first appended element preserved"); - CHECK_EQ_INT(out1, 2, "second appended element preserved"); + malunal_int32_t out = 0; + result = vector_container_get(&vector, 0, &out); + MICROTEST_EXPECT_NULL(result.domain); + MICROTEST_EXPECT_EQ(out, 42); - container_finalize(container); + vector_container_free(&vector); } -TEST(append_past_capacity_grows_and_preserves_data) { - vector_t vector = vector_container(); - allocator_t libc_alloc = libc_allocator(); - container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 2); +MICROTEST(vector_container, append_past_capacity_grows_and_preserves) { + error_t result = {}; + vector_container_t vector = {}; + vector_container_init(4, 4, libc_allocator(), &vector); - int vals[5] = { 10, 20, 30, 40, 50 }; - for (int i = 0; i < 5; i++) { - container_exception_t rc = container_append(container, &vals[i]); - CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "append should succeed while growing"); + malunal_int32_t vals[5] = { 10, 20, 30, 40, 50 }; + for (malunal_int32_t index = 0; index < 5; index++) { + result = vector_container_append(&vector, &vals[index]); + MICROTEST_EXPECT_NULL(result.domain); } - malunal_size_t capacity = 0, count = 0; - container_capacity(container, &capacity); - container_count(container, &count); - CHECK(capacity >= 5, "capacity should have grown to fit 5 elements"); - CHECK_EQ_SZ(count, 5, "count should be 5 after 5 appends"); - - for (int i = 0; i < 5; i++) { - int out = 0; - vector_get(&vector, i, &out); - CHECK_EQ_INT(out, vals[i], "element should survive a growth reallocation"); + malunal_size_t capacity = 0; + malunal_size_t count = 0; + vector_container_capacity(&vector, &capacity); + vector_container_count(&vector, &count); + MICROTEST_EXPECT_GT(capacity, 5); + MICROTEST_EXPECT_EQ(count, 5); + for (malunal_int32_t index = 0; index < 5; index++) { + malunal_int32_t out = 0; + vector_container_get(&vector, index, &out); + MICROTEST_EXPECT_EQ(out, vals[index]); } - container_finalize(container); + vector_container_free(&vector); } -/* ------------------------------------------------------------------ */ -/* container_clear */ -/* ------------------------------------------------------------------ */ +MICROTEST(vector_container, remove_first_matching_element) { + error_t result = {}; + vector_container_t vector = {}; + vector_container_init(4, 32, libc_allocator(), &vector); -TEST(clear_resets_count_but_keeps_capacity) { - vector_t vector = vector_container(); - allocator_t libc_alloc = libc_allocator(); - container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 4); + malunal_int32_t vals[3] = { 10, 20, 30 }; + vector_container_append(&vector, &vals[0]); + vector_container_append(&vector, &vals[1]); + vector_container_append(&vector, &vals[2]); - int a = 1, b = 2; - container_append(container, &a); - container_append(container, &b); - - container_exception_t rc = container_clear(container); - CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "container_clear should succeed"); - - malunal_size_t count = 99, capacity = 0; - container_count(container, &count); - container_capacity(container, &capacity); - CHECK_EQ_SZ(count, 0, "clear should reset count to 0"); - CHECK_EQ_SZ(capacity, 4, "clear should not release/shrink capacity"); - - container_finalize(container); -} - -/* ------------------------------------------------------------------ */ -/* vector_set */ -/* ------------------------------------------------------------------ */ - -TEST(set_overwrites_existing_element) { - vector_t vector = vector_container(); - allocator_t libc_alloc = libc_allocator(); - container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 4); - - int a = 1, b = 2, replacement = 99; - container_append(container, &a); - container_append(container, &b); - - container_exception_t rc = vector_set(&vector, 0, &replacement); - CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "vector_set should succeed for an in-range index"); - - int out = 0; - vector_get(&vector, 0, &out); - CHECK_EQ_INT(out, 99, "vector_set should overwrite the element at the given index"); - - container_finalize(container); -} - -TEST(get_set_report_out_of_bounds_at_count) { - /* Per container.h/vector.h, get/set should fail once the index - * reaches or exceeds the number of *live* elements. Capacity may - * still have room, but reading/writing an unset slot is out of - * bounds. */ - vector_t vector = vector_container(); - allocator_t libc_alloc = libc_allocator(); - container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 4); - - int a = 1; - container_append(container, &a); /* count == 1, capacity == 4 */ - - int out = 0; - container_exception_t rc = vector_get(&vector, 1, &out); - CHECK_EQ_INT(rc, CONTAINER_ERROR_OUT_OF_BOUNDS, - "get(index == count) should be out of bounds, not read uninitialized capacity"); - - container_finalize(container); -} - -TEST(get_set_report_out_of_bounds_at_capacity) { - /* index == capacity is one past the last valid slot. */ - vector_t vector = vector_container(); - allocator_t libc_alloc = libc_allocator(); - container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 4); - - int a = 1; - container_append(container, &a); - - int out = 0; - container_exception_t rc = vector_get(&vector, 4, &out); - CHECK_EQ_INT(rc, CONTAINER_ERROR_OUT_OF_BOUNDS, - "get(index == capacity) should be out of bounds"); - - container_finalize(container); -} - -/* ------------------------------------------------------------------ */ -/* vector_index_of */ -/* ------------------------------------------------------------------ */ - -TEST(index_of_finds_present_element) { - vector_t vector = vector_container(); - allocator_t libc_alloc = libc_allocator(); - container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 4); - - int a = 5, b = 6, c = 7; - container_append(container, &a); - container_append(container, &b); - container_append(container, &c); - - malunal_size_t idx = (malunal_size_t)-1; - int needle = 6; - container_exception_t rc = vector_index_of(&vector, &needle, &idx); - CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "index_of should succeed when element is present"); - CHECK_EQ_SZ(idx, 1, "index_of should report the correct index of the matching element"); - - container_finalize(container); -} - -TEST(index_of_reports_missing_element) { - vector_t vector = vector_container(); - allocator_t libc_alloc = libc_allocator(); - container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 4); - - int a = 5; - container_append(container, &a); - - malunal_size_t idx = 0; - int needle = 999; - vector_index_of(&vector, &needle, &idx); - /* Documented contract: outidx should be set to a not-found sentinel - * (-1) when the element isn't found. */ - CHECK_EQ_SZ(idx, (malunal_size_t)-1, "index_of should report a not-found sentinel"); - - container_finalize(container); -} - -TEST(index_of_actually_compares_against_the_search_element) { - /* vector_index_of_def_impl's loop does - * memcmp(buffer, buffer + stride, stride) - * i.e. it compares each element to its *neighbor*, and never touches - * the `element` argument at all. So it doesn't search for the given - * value -- it finds the first position holding two equal adjacent - * elements, whatever the caller was actually looking for. This test - * makes that failure mode concrete and unambiguous: [1, 1, 2], - * searching for 2, should report index 2 -- not index 0. */ - vector_t vector = vector_container(); - allocator_t libc_alloc = libc_allocator(); - container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 4); - - int a = 1, b = 1, c = 2; - container_append(container, &a); - container_append(container, &b); - container_append(container, &c); /* [1, 1, 2] */ - - malunal_size_t idx = 0; - int needle = 2; - vector_index_of(&vector, &needle, &idx); - CHECK_EQ_SZ(idx, 2, "index_of should find the index of the *searched* value, not an adjacent duplicate pair"); - - container_finalize(container); -} - -/* ------------------------------------------------------------------ */ -/* vector_insert_at */ -/* ------------------------------------------------------------------ */ - -TEST(insert_at_end_appends_value) { - vector_t vector = vector_container(); - allocator_t libc_alloc = libc_allocator(); - container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 4); - - int a = 1, b = 2, c = 3; - container_append(container, &a); - container_append(container, &b); - - container_exception_t rc = vector_insert_at(&vector, 2, &c); - CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "insert_at(count) should succeed"); - - int out = 0; - vector_get(&vector, 2, &out); - CHECK_EQ_INT(out, 3, "inserted value should be readable back at the target index"); - - container_finalize(container); -} - -TEST(insert_at_middle_shifts_later_elements) { - vector_t vector = vector_container(); - allocator_t libc_alloc = libc_allocator(); - container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 8); - - int a = 1, b = 3, mid = 2; - container_append(container, &a); - container_append(container, &b); /* [1, 3] */ - - container_exception_t rc = vector_insert_at(&vector, 1, &mid); /* -> [1, 2, 3] */ - CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "insert_at(1) should succeed"); - - int out0 = 0, out1 = 0, out2 = 0; - vector_get(&vector, 0, &out0); - vector_get(&vector, 1, &out1); - vector_get(&vector, 2, &out2); - CHECK_EQ_INT(out0, 1, "element before insertion point unchanged"); - CHECK_EQ_INT(out1, 2, "inserted element lands at requested index"); - CHECK_EQ_INT(out2, 3, "element after insertion point shifted right"); - - container_finalize(container); -} - -/* ------------------------------------------------------------------ */ -/* vector_remove_at */ -/* ------------------------------------------------------------------ */ - -TEST(remove_at_last_leaves_earlier_elements_untouched) { - vector_t vector = vector_container(); - allocator_t libc_alloc = libc_allocator(); - container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 8); - - int a = 1, b = 2, c = 3; - container_append(container, &a); - container_append(container, &b); - container_append(container, &c); /* [1, 2, 3] */ - - container_exception_t rc = vector_remove_at(&vector, 2); /* remove the '3' */ - CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "remove_at(last) should succeed"); + result = vector_container_remove(&vector, &vals[0]); + MICROTEST_EXPECT_NULL(result.domain); malunal_size_t count = 0; - container_count(container, &count); - CHECK_EQ_SZ(count, 2, "count should drop by one after removal"); + vector_container_count(&vector, &count); + MICROTEST_EXPECT_EQ(count, 2); - int out0 = 0, out1 = 0; - vector_get(&vector, 0, &out0); - vector_get(&vector, 1, &out1); - CHECK_EQ_INT(out0, 1, "element 0 should be unaffected by removing the last element"); - CHECK_EQ_INT(out1, 2, "element 1 should be unaffected by removing the last element"); + malunal_int32_t value = 0; + vector_container_get(&vector, 0, &value); + MICROTEST_EXPECT_EQ(value, 20); - container_finalize(container); + vector_container_free(&vector); } -TEST(remove_at_middle_shifts_later_elements_left) { - vector_t vector = vector_container(); - allocator_t libc_alloc = libc_allocator(); - container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 8); +MICROTEST(vector_container, remove_of_absent_element_succeeds) { + error_t result = {}; + vector_container_t vector = {}; + vector_container_init(4, 32, libc_allocator(), &vector); - int a = 1, b = 2, c = 3; - container_append(container, &a); - container_append(container, &b); - container_append(container, &c); /* [1, 2, 3] */ + malunal_int32_t a = 1; + vector_container_append(&vector, &a); - container_exception_t rc = vector_remove_at(&vector, 0); /* -> [2, 3] */ - CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "remove_at(0) should succeed"); - - int out0 = 0, out1 = 0; - vector_get(&vector, 0, &out0); - vector_get(&vector, 1, &out1); - CHECK_EQ_INT(out0, 2, "elements after the removed index should shift left"); - CHECK_EQ_INT(out1, 3, "elements after the removed index should shift left"); - - container_finalize(container); -} - -TEST(remove_at_on_empty_vector_is_out_of_bounds) { - vector_t vector = vector_container(); - allocator_t libc_alloc = libc_allocator(); - container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 4); - - container_exception_t rc = vector_remove_at(&vector, 0); - CHECK_EQ_INT(rc, CONTAINER_ERROR_OUT_OF_BOUNDS, - "remove_at on an empty vector should be rejected as out of bounds"); - - container_finalize(container); -} - -/* ------------------------------------------------------------------ */ -/* container_remove */ -/* ------------------------------------------------------------------ */ - -TEST(container_remove_removes_the_first_matching_element) { - vector_t vector = vector_container(); - allocator_t libc_alloc = libc_allocator(); - container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 8); - - int a = 10, b = 20, c = 30; - container_append(container, &a); - container_append(container, &b); - container_append(container, &c); /* [10, 20, 30] */ - - container_exception_t rc = container_remove(container, &a); /* remove the leading '10' */ - CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "container_remove should succeed for a present element"); + malunal_int32_t missing = 404; + result = vector_container_remove(&vector, &missing); + MICROTEST_EXPECT_NULL(result.domain); malunal_size_t count = 0; - container_count(container, &count); - CHECK_EQ_SZ(count, 2, "count should drop by one after container_remove"); - - int out0 = 0; - vector_get(&vector, 0, &out0); - CHECK_EQ_INT(out0, 20, "removing the first element should shift the rest left"); - - container_finalize(container); + vector_container_count(&vector, &count); + MICROTEST_EXPECT_EQ(count, 1); } -TEST(container_remove_of_absent_element_is_success) { - /* container.h: "if an element does not exist in the container, the - * most common response should be CONTAINER_ERROR_SUCCESS." */ - vector_t vector = vector_container(); - allocator_t libc_alloc = libc_allocator(); - container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 4); +MICROTEST(vector_container, contains_finds_a_present_element) { + error_t result = {}; + vector_container_t vector = {}; + vector_container_init(4, 32, libc_allocator(), &vector); - int a = 1; - container_append(container, &a); + malunal_int32_t a = 1; + malunal_int32_t b = 2; + vector_container_append(&vector, &a); + vector_container_append(&vector, &b); - int missing = 404; - container_exception_t rc = container_remove(container, &missing); - CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, - "removing an absent element should report success, per the documented contract"); + result = vector_container_contains(&vector, &b); + MICROTEST_EXPECT_NULL(result.domain); - container_finalize(container); + vector_container_free(&vector); } -/* ------------------------------------------------------------------ */ -/* container_contains */ -/* ------------------------------------------------------------------ */ +MICROTEST(vector_container, contains_rejects_an_absent_element) { + error_t result = {}; + vector_container_t vector = {}; + vector_container_init(4, 32, libc_allocator(), &vector); -TEST(container_contains_finds_a_present_element) { - vector_t vector = vector_container(); - allocator_t libc_alloc = libc_allocator(); - container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 4); + malunal_int32_t a = 1; + malunal_int32_t b = 2; + vector_container_append(&vector, &a); + vector_container_append(&vector, &b); - int a = 1, b = 2; - container_append(container, &a); - container_append(container, &b); + malunal_int32_t missing = 404; + result = vector_container_contains(&vector, &missing); + MICROTEST_EXPECT_EQ(result.domain, &ERROR_DOMAIN_CONTAINER_T); + MICROTEST_EXPECT_EQ(result.code, CONTAINER_ERROR_FAILURE); - container_exception_t rc = container_contains(container, &b); - CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "contains should succeed for a present element"); - - container_finalize(container); + vector_container_free(&vector); } -TEST(container_contains_rejects_an_absent_element) { - /* vector_contains_impl does `index > 0 ? SUCCESS : FAILURE`. But - * vector_index_of's not-found sentinel is (malunal_size_t)-1, i.e. - * SIZE_MAX, which is also > 0 -- so an absent element is reported as - * *found*. (Combined with index_of's separate bug of comparing - * adjacent elements instead of the search value, this vector has no - * adjacent duplicates, so index_of legitimately reports "not - * found" via the -1 sentinel here -- and contains still says yes.) */ - vector_t vector = vector_container(); - allocator_t libc_alloc = libc_allocator(); - container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 4); +MICROTEST(vector_container, clear_resets_count_but_keeps_capacity) { + error_t result = {}; + vector_container_t vector = {}; + vector_container_init(4, 32, libc_allocator(), &vector); - int a = 1, b = 2; - container_append(container, &a); - container_append(container, &b); /* [1, 2], no adjacent duplicates */ + malunal_int32_t a = 1; + malunal_int32_t b = 2; + vector_container_append(&vector, &a); + vector_container_append(&vector, &b); - int missing = 999; - container_exception_t rc = container_contains(container, &missing); - CHECK_EQ_INT(rc, CONTAINER_ERROR_FAILURE, "contains should report failure for an absent element"); + result = vector_container_clear(&vector); + MICROTEST_EXPECT_NULL(result.domain); - container_finalize(container); + malunal_size_t count = 99; + malunal_size_t capacity = 0; + vector_container_count(&vector, &count); + vector_container_capacity(&vector, &capacity); + MICROTEST_EXPECT_EQ(count, 0); + MICROTEST_EXPECT_EQ(capacity, 32); + + vector_container_free(&vector); } -/* ------------------------------------------------------------------ */ -/* runner */ -/* ------------------------------------------------------------------ */ +MICROTEST(vector_container, set_overwrites_existing_element) { + error_t result = {}; + vector_container_t vector = {}; + vector_container_init(4, 32, libc_allocator(), &vector); -int main(void) { - int passed = 0; + malunal_int32_t vals[3] = { 10, 20, 333 }; + vector_container_append(&vector, &vals[0]); + vector_container_append(&vector, &vals[1]); - printf("running %d tests\n\n", g_test_count); + result = vector_container_set(&vector, 0, &vals[2]); + MICROTEST_EXPECT_NULL(result.domain); - for (int i = 0; i < g_test_count; i++) { - printf("[ RUN ] %s\n", g_tests[i].name); - int ok = run_isolated(g_tests[i].fn); - printf("[%s] %s\n\n", ok ? " PASS " : " FAIL ", g_tests[i].name); - passed += ok; - } + malunal_int32_t out = 0; + vector_container_get(&vector, 0, &out); + MICROTEST_EXPECT_EQ(out, 333); - printf("---------------------------------------------\n"); - printf("%d / %d tests passed\n", passed, g_test_count); - - return passed == g_test_count ? 0 : 1; + vector_container_free(&vector); +} + +MICROTEST(vector_container, get_set_reports_out_of_bounds_at_count) { + error_t result = {}; + vector_container_t vector = {}; + vector_container_init(4, 32, libc_allocator(), &vector); + + malunal_int32_t a = 10; + malunal_int32_t out = 0; + result = vector_container_get(&vector, 4, &out); + MICROTEST_EXPECT_EQ(result.domain, &ERROR_DOMAIN_CONTAINER_T); + MICROTEST_EXPECT_EQ(result.code, CONTAINER_ERROR_OUT_OF_BOUNDS); + + result = NO_ERROR; + result = vector_container_set(&vector, 4, &out); + MICROTEST_EXPECT_EQ(result.domain, &ERROR_DOMAIN_CONTAINER_T); + MICROTEST_EXPECT_EQ(result.code, CONTAINER_ERROR_OUT_OF_BOUNDS); + + vector_container_free(&vector); +} + +MICROTEST(vector_container, insert_at_end_appends_value) { + error_t result = {}; + vector_container_t vector = {}; + vector_container_init(4, 32, libc_allocator(), &vector); + + malunal_int32_t vals[3] = { 10, 20, 30 }; + vector_container_append(&vector, &vals[0]); + vector_container_append(&vector, &vals[1]); + + result = vector_container_insert_at(&vector, 2, &vals[2]); + MICROTEST_EXPECT_NULL(result.domain); + + malunal_int32_t out = 0; + vector_container_get(&vector, 2, &out); + MICROTEST_EXPECT_EQ(out, vals[2]); + + vector_container_free(&vector); +} + +MICROTEST(vector_container, insert_at_middle_shifts_later_elements) { + error_t result = {}; + vector_container_t vector = {}; + vector_container_init(4, 32, libc_allocator(), &vector); + + malunal_int32_t vals[3] = { 10, 20, 30 }; + vector_container_append(&vector, &vals[0]); + vector_container_append(&vector, &vals[2]); + + result = vector_container_insert_at(&vector, 1, &vals[1]); + MICROTEST_EXPECT_NULL(result.domain); + + malunal_int32_t outs[3] = { 0, 0, 0}; + vector_container_get(&vector, 0, &outs[0]); + vector_container_get(&vector, 1, &outs[1]); + vector_container_get(&vector, 2, &outs[2]); + MICROTEST_EXPECT_EQ(outs[0], vals[0]); + MICROTEST_EXPECT_EQ(outs[1], vals[1]); + MICROTEST_EXPECT_EQ(outs[2], vals[2]); + + vector_container_free(&vector); +} + +MICROTEST(vector_container, remove_at_last_preserves_earlier_elements) { + error_t result = {}; + vector_container_t vector = {}; + vector_container_init(4, 32, libc_allocator(), &vector); + + malunal_int32_t vals[3] = { 10, 20, 30 }; + vector_container_append(&vector, &vals[0]); + vector_container_append(&vector, &vals[1]); + vector_container_append(&vector, &vals[2]); + + result = vector_container_remove_at(&vector, 2); + MICROTEST_EXPECT_NULL(result.domain); + + malunal_size_t count = 0; + vector_container_count(&vector, &count); + MICROTEST_EXPECT_EQ(count, 2); + + malunal_int32_t outs[2] = { 0, 0 }; + vector_container_get(&vector, 0, &outs[0]); + vector_container_get(&vector, 1, &outs[1]); + MICROTEST_EXPECT_EQ(outs[0], vals[0]); + MICROTEST_EXPECT_EQ(outs[1], vals[1]); + + vector_container_free(&vector); +} + +MICROTEST(vector_container, remove_at_middle_shifts_later_elements_left) { + error_t result = {}; + vector_container_t vector = {}; + vector_container_init(4, 32, libc_allocator(), &vector); + + malunal_int32_t vals[3] = { 10, 20, 30 }; + vector_container_append(&vector, &vals[0]); + vector_container_append(&vector, &vals[1]); + vector_container_append(&vector, &vals[2]); + + result = vector_container_remove_at(&vector, 0); + MICROTEST_EXPECT_NULL(result.domain); + + malunal_size_t count = 0; + vector_container_count(&vector, &count); + MICROTEST_EXPECT_EQ(count, 2); + + malunal_int32_t outs[2] = { 0, 0 }; + vector_container_get(&vector, 0, &outs[0]); + vector_container_get(&vector, 1, &outs[1]); + MICROTEST_EXPECT_EQ(outs[0], vals[1]); + MICROTEST_EXPECT_EQ(outs[1], vals[2]); + + vector_container_free(&vector); +} + +MICROTEST(vector_container, remove_at_on_empty_vector_is_out_of_bounds) { + error_t result = {}; + vector_container_t vector = {}; + vector_container_init(4, 32, libc_allocator(), &vector); + + result = vector_container_remove_at(&vector, 0); + MICROTEST_EXPECT_EQ(result.domain, &ERROR_DOMAIN_CONTAINER_T); + MICROTEST_EXPECT_EQ(result.code, CONTAINER_ERROR_OUT_OF_BOUNDS); + + vector_container_free(&vector); +} + +MICROTEST(vector_container, index_of_finds_present_element) { + error_t result = {}; + vector_container_t vector = {}; + vector_container_init(4, 32, libc_allocator(), &vector); + + malunal_int32_t vals[3] = { 50, 60, 70 }; + vector_container_append(&vector, &vals[0]); + vector_container_append(&vector, &vals[1]); + vector_container_append(&vector, &vals[2]); + + malunal_size_t index = (malunal_size_t)-1; + result = vector_container_index_of(&vector, &vals[1], &index); + MICROTEST_EXPECT_NULL(result.domain); + MICROTEST_EXPECT_EQ(index, 1); + + vector_container_free(&vector); +} + +MICROTEST(vector_container, index_of_reports_missing_element) { + error_t result = {}; + vector_container_t vector = {}; + vector_container_init(4, 32, libc_allocator(), &vector); + + malunal_int32_t a = 50; + vector_container_append(&vector, &a); + + malunal_size_t index = 0; + malunal_int32_t needle = 333; + result = vector_container_index_of(&vector, &needle, &index); + MICROTEST_EXPECT_NULL(result.domain); + MICROTEST_EXPECT_EQ(index, (malunal_size_t)-1); + + vector_container_free(&vector); }