commit 83bd4a3492cc213f2061e11b22d091f63a5351ec Author: SoraKatadzuma Date: Mon Aug 17 04:16:46 2026 -0500 Initial Commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c6c8b36 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b08dc48 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.chinook/ +.environ/ diff --git a/chinookfile b/chinookfile new file mode 100644 index 0000000..973acb4 --- /dev/null +++ b/chinookfile @@ -0,0 +1,30 @@ +name: containers +gpid: malunal +semv: 1.0.0 + +requires: +- remote: git@git.erasit.com:malunal/allocators + branch: v1.0.0 +- remote: git@git.erasit.com:malunal/types + branch: v1.0.0 + +targets: +- name: malunal.containers + type: archive + deps: + - malunal.allocators + - malunal.types + srcs: + - ./sources/container.c + - ./sources/vector.c + +tests: +- name: vector_tests + type: program + deps: + - malunal.containers + srcs: + - ./tests/vector_container.c + +exports: +- malunal.containers diff --git a/include/malunal/container.h b/include/malunal/container.h new file mode 100644 index 0000000..319630d --- /dev/null +++ b/include/malunal/container.h @@ -0,0 +1,215 @@ +/** + * @file container.h + * @brief Contains the base definition of a container, to include its structures + * and function pointers. + * @author John Christman (sorakatadzuma@gmail.com) + * @copyright Malunal Studios, LLC. + */ +#include "malunal/allocators.h" +#include "malunal/types.h" + +#ifndef MALUNAL_CONTAINERS_HEADER +#define MALUNAL_CONTAINERS_HEADER + + +#ifdef CONTAINER_SIZE +#undef CONTAINER_SIZE +#endif /* CONTAINER_SIZE */ + +/** + * @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. + */ +#define CONTAINER_SIZE sizeof(malunal_size_t) * 6 + + +/** + * @brief Defines an abstract class type for containers. + * @details Containers are objects which simply store data. They provide ways to + * @c append, @c remove, @c find, and @c clear the container. Being an + * abstract class like object, it provides some useful member variables + * and a virtual function table for container specific functions. + */ +typedef struct { + malunal_uint8_t __opaque[CONTAINER_SIZE]; +} container_t; + +/** + * @brief A pointer to a mutable container. + * @details This is provided to simplify type declarations for functions + * requiring containers that are meant to be mutable. + */ +typedef container_t* container_mptr_t; + +/** + * @brief A pointer to an immutable container. + * @details This is provided to simplify type declarations for functions + * requiring containers that are meant to be immutable. + */ +typedef const container_t* container_iptr_t; + + +/** + * @brief Defines a set of exception 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; + + +/** + * @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. + */ +container_exception_t +container_allocator( + container_iptr_t container, + allocator_mptr_t* out +); + +/** + * @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. + */ +container_exception_t +container_stride( + container_iptr_t container, + malunal_size_t* out +); + +/** + * @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. + */ +container_exception_t +container_count( + container_iptr_t container, + malunal_size_t* out +); + +/** + * @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. + */ +container_exception_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. + */ +container_exception_t +container_append( + container_mptr_t container, + malunal_iptr_t element +); + +/** + * @brief Removes the specified element from the container. + * @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. + */ +container_exception_t +container_remove( + container_mptr_t container, + malunal_iptr_t element +); + +/** + * @brief Checks the container for the given element. + * @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. + */ +container_exception_t +container_contains( + container_iptr_t container, + malunal_iptr_t element +); + +#endif /* MALUNAL_CONTAINERS_HEADER */ diff --git a/include/malunal/containers/vector.h b/include/malunal/containers/vector.h new file mode 100644 index 0000000..3621028 --- /dev/null +++ b/include/malunal/containers/vector.h @@ -0,0 +1,129 @@ +/** + * @file vector.h + * @brief Contains the structures and functions necessary to utilize a vector + * container implementation. + * @author John Christman (sorakatadzuma@gmail.com) + * @copyright Malunal Studios, LLC. + */ +#include "../container.h" + +#ifndef MALUNAL_VECTOR_HEADER +#define MALUNAL_VECTOR_HEADER + +/** + * @brief Defines an class type for vectors. + * @details Vectors store their data contiguously in memory. They extend the + * functionality of containers, so they maybe used in container + * functions. + * @remarks The purpose of defining a separate type definition from @c container + * is to make it physically distinct from a container. This also makes + * it so other containers cannot accidentally be used in operations + * meant for vectors. + */ +typedef struct { + malunal_uint8_t __opaque[sizeof(container_t)]; +} vector_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; + +/** + * @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; + + +/** + * @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. + */ +container_exception_t +vector_get( + vector_iptr_t vector, + malunal_size_t index, + malunal_mptr_t outptr +); + +/** + * @brief Sets the element at the given index into the given vector, copying + * the data from the memory region of the @c element. + * @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. + */ +container_exception_t +vector_set( + vector_mptr_t vector, + malunal_size_t index, + malunal_iptr_t element +); + +/** + * @brief Inserts the given element at the given index of the given vector, + * 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. + */ +container_exception_t +vector_insert_at( + vector_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. + */ +container_exception_t +vector_remove_at( + vector_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 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. + */ +container_exception_t +vector_index_of( + vector_mptr_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 new file mode 100644 index 0000000..75cbd1d --- /dev/null +++ b/sources/container.c @@ -0,0 +1,182 @@ +#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 +); + + +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; + +typedef virtual_table_t* base_vtable_mptr_t; +typedef const virtual_table_t* vtable_iptr_t; + + +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 +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; +} + +container_exception_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; +} + +container_exception_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; +} + +container_exception_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; +} + +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 +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; +} + +container_exception_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; +} + +container_exception_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; +} diff --git a/sources/vector.c b/sources/vector.c new file mode 100644 index 0000000..bd3b8d6 --- /dev/null +++ b/sources/vector.c @@ -0,0 +1,500 @@ +#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; + + 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; + + +static +container_exception_t +vector_initialize_impl( + impl_mptr_t vector, + allocator_mptr_t allocator, + malunal_size_t stride, + malunal_size_t capacity +) { + 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; + } + + 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; + } +} + +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 + ); + + switch (error) { + case ALLOCATION_ERROR_SUCCESS: + return CONTAINER_ERROR_SUCCESS; + + 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; + } +} + +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 +) { + if (vector->context == NULL_ADDRESS) + return CONTAINER_ERROR_NULL_CONTEXT; + + 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 + ); + + if (!result.threw) { + vector->context = result.address; + vector->capacity = doubled; + goto copy_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; + } + +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; +} + +static +container_exception_t +vector_remove_impl( + impl_mptr_t vector, + malunal_iptr_t element +) { + if (vector->context == NULL_ADDRESS) + return CONTAINER_ERROR_NULL_CONTEXT; + + 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; +} + +static +container_exception_t +vector_contains_impl( + impl_iptr_t vector, + malunal_iptr_t element +) { + if (vector->context == NULL_ADDRESS) + return CONTAINER_ERROR_NULL_CONTEXT; + + 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; +} + +static +container_exception_t +vector_get_def_impl( + impl_iptr_t vector, + malunal_size_t index, + malunal_mptr_t outptr +) { + if (vector->context == NULL_ADDRESS) + return CONTAINER_ERROR_NULL_CONTEXT; + + 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; +} + +static +container_exception_t +vector_set_def_impl( + impl_mptr_t vector, + malunal_size_t index, + malunal_iptr_t element +) { + if (vector->context == NULL_ADDRESS) + return CONTAINER_ERROR_NULL_CONTEXT; + + 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; +} + +static +container_exception_t +vector_insert_at_def_impl( + impl_mptr_t vector, + malunal_size_t index, + malunal_iptr_t element +) { + if (vector->context == NULL_ADDRESS) + return CONTAINER_ERROR_NULL_CONTEXT; + + if (index < 0 || index > vector->count) + return CONTAINER_ERROR_OUT_OF_BOUNDS; + + 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; +} + +static +container_exception_t +vector_remove_at_def_impl( + impl_mptr_t vector, + malunal_size_t index +) { + if (vector->context == NULL_ADDRESS) + return CONTAINER_ERROR_NULL_CONTEXT; + + if (index < 0 || index >= vector->count) + return CONTAINER_ERROR_OUT_OF_BOUNDS; + + 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; +} + +static +container_exception_t +vector_index_of_def_impl( + impl_mptr_t vector, + malunal_iptr_t element, + malunal_size_t* outidx +) { + if (vector->context == NULL_ADDRESS) + return CONTAINER_ERROR_NULL_CONTEXT; + + malunal_size_t index = 0; + malunal_uint8_t* buffer = vector->context; + while (index < vector->count) { + if (memcmp(buffer, element, vector->stride) == 0) { + *outidx = index; + return CONTAINER_ERROR_SUCCESS; + } + + index += 1; + buffer += vector->stride; + } + + *outidx = -1; + return CONTAINER_ERROR_SUCCESS; +} + + +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 new file mode 100644 index 0000000..c570da5 --- /dev/null +++ b/tests/containers.c @@ -0,0 +1,7 @@ +#include +#include "malunal/vector.h" + +int main(int argc, char** argv) { + printf("Hello, World!\n"); + return 0; +} diff --git a/tests/vector_container.c b/tests/vector_container.c new file mode 100644 index 0000000..164b366 --- /dev/null +++ b/tests/vector_container.c @@ -0,0 +1,634 @@ +#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" + +/* ------------------------------------------------------------------ */ +/* Minimal assertion + test-registration harness (no framework) */ +/* ------------------------------------------------------------------ */ + +#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) + +#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) + +#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) + +typedef void (*test_fn_t)(void); + +typedef struct { + const char* name; + test_fn_t fn; +} test_case_t; + +#define MAX_TESTS 64 +static test_case_t g_tests[MAX_TESTS]; +static int g_test_count = 0; + +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++; +} + +#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) + +/* 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_size_t count = 0; + container_count(container, &count); + CHECK_EQ_SZ(count, 2, "count should track number of appends"); + + 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"); + + container_finalize(container); +} + +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); + + 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_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"); + } + + container_finalize(container); +} + +/* ------------------------------------------------------------------ */ +/* container_clear */ +/* ------------------------------------------------------------------ */ + +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); + + 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"); + + malunal_size_t count = 0; + container_count(container, &count); + CHECK_EQ_SZ(count, 2, "count should drop by one after removal"); + + 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"); + + container_finalize(container); +} + +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); + + 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, 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_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); +} + +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); + + int a = 1; + container_append(container, &a); + + 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"); + + container_finalize(container); +} + +/* ------------------------------------------------------------------ */ +/* container_contains */ +/* ------------------------------------------------------------------ */ + +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); + + int a = 1, b = 2; + container_append(container, &a); + container_append(container, &b); + + 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); +} + +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); + + int a = 1, b = 2; + container_append(container, &a); + container_append(container, &b); /* [1, 2], no adjacent duplicates */ + + 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"); + + container_finalize(container); +} + +/* ------------------------------------------------------------------ */ +/* runner */ +/* ------------------------------------------------------------------ */ + +int main(void) { + int passed = 0; + + printf("running %d tests\n\n", g_test_count); + + 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; + } + + printf("---------------------------------------------\n"); + printf("%d / %d tests passed\n", passed, g_test_count); + + return passed == g_test_count ? 0 : 1; +}