aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMikhail Romanko <me@blankhex.com>2024-04-13 14:52:29 +0300
committerMikhail Romanko <me@blankhex.com>2024-04-13 14:52:29 +0300
commitac5df0ebe9a6ac67b9be4cc319f5cd0625b50178 (patch)
tree5532f9fd219691476fb3be127f228f3a07325e4b
downloadbhlib-old-ac5df0ebe9a6ac67b9be4cc319f5cd0625b50178.tar.gz
Initial commit
-rw-r--r--.gitignore79
-rw-r--r--CMakeLists.txt45
-rw-r--r--LICENSE12
-rw-r--r--README2
-rw-r--r--include/bh/bh.h22
-rw-r--r--include/bh/hashmap.h237
-rw-r--r--include/bh/internal/hashmap.h30
-rw-r--r--include/bh/internal/queue.h35
-rw-r--r--include/bh/queue.h159
-rw-r--r--main.c83
-rwxr-xr-xscripts/coverage.sh14
-rwxr-xr-xscripts/trim-whitespace.sh2
-rw-r--r--src/hashmap.c343
-rw-r--r--src/queue.c176
-rw-r--r--tests/CMakeLists.txt20
-rw-r--r--tests/src/hashmap.c278
-rw-r--r--tests/src/queue.c183
-rw-r--r--unit/CMakeLists.txt22
-rw-r--r--unit/include/bh/unit.h24
-rw-r--r--unit/src/unit.c55
20 files changed, 1821 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..04ee206
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,79 @@
+# CMake
+CMakeLists.txt.user
+CMakeCache.txt
+CMakeFiles
+CMakeScripts
+Testing
+Makefile
+cmake_install.cmake
+install_manifest.txt
+compile_commands.json
+CTestTestfile.cmake
+_deps
+
+# Prerequisites
+*.d
+
+# Object files
+*.o
+*.ko
+*.obj
+*.elf
+
+# Linker output
+*.ilk
+*.map
+*.exp
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+
+# Debug files
+*.dSYM/
+*.su
+*.idb
+*.pdb
+
+# Build directories
+[Bb]uild/
+[Dd]ebug/
+[Rr]elease/
+[Bb]in/
+[Oo]bj/
+x86/
+x64/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+
+!platform/*
+
+# Visual Studio Code
+.vscode/
+
+# Doxygen
+[Dd]ocs/[Hh]tml
+
+# Coverage
+[Cc]overage
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..51d4cd1
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,45 @@
+cmake_minimum_required(VERSION 3.10)
+
+# Project and C standard configuration
+project(bhlib LANGUAGES C)
+set(CMAKE_C_STANDARD 90)
+set(CMAKE_C_STANDARD_REQUIRED ON)
+
+# Check for IPO/LTO
+include(CheckIPOSupported)
+check_ipo_supported(RESULT supported)
+
+if(supported)
+ message(STATUS "IPO/LTO enabled")
+ set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
+endif()
+
+# Enable testing
+include(CTest)
+enable_testing()
+
+# Set library code
+set(BHLIB_SOURCE
+ src/hashmap.c
+ src/queue.c
+)
+
+set(BHLIB_HEADER
+ include/bh/hashmap.h
+ include/bh/queue.h
+)
+
+# Library
+add_library(bhlib STATIC ${BHLIB_SOURCE} ${BHLIB_HEADER})
+target_include_directories(bhlib PUBLIC include)
+
+# Runtime definition
+add_executable(main
+ main.c
+)
+
+target_link_libraries(main bhlib)
+
+# Tests
+add_subdirectory(unit)
+add_subdirectory(tests)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..2cac726
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,12 @@
+Copyright (c) 2024 by Mikhail Romanko
+
+Permission to use, copy, modify, and/or distribute this software for
+any purpose with or without fee is hereby granted.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE
+FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
+DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/README b/README
new file mode 100644
index 0000000..fa158bb
--- /dev/null
+++ b/README
@@ -0,0 +1,2 @@
+BHLib
+
diff --git a/include/bh/bh.h b/include/bh/bh.h
new file mode 100644
index 0000000..f9df864
--- /dev/null
+++ b/include/bh/bh.h
@@ -0,0 +1,22 @@
+#ifndef BHLIB_H
+#define BHLIB_H
+
+#include <stddef.h>
+
+#define BH_INT_TO_PTR(x) \
+ ((void *)((long)(x)))
+
+#define BH_UINT_TO_PTR(x) \
+ ((void *)((unsigned long)(x)))
+
+#define BH_PTR_TO_INT(x) \
+ ((long)(x))
+
+#define BH_PTR_TO_UINT(x) \
+ ((unsigned long)(x))
+
+typedef int (*bh_equal_cb_t)(const void *, const void *);
+typedef size_t (*bh_hash_cb_t)(const void *);
+
+#endif /* BHLIB_H */
+
diff --git a/include/bh/hashmap.h b/include/bh/hashmap.h
new file mode 100644
index 0000000..b8c2dc5
--- /dev/null
+++ b/include/bh/hashmap.h
@@ -0,0 +1,237 @@
+#ifndef BHLIB_HASHMAP_H
+#define BHLIB_HASHMAP_H
+
+#include <bh/bh.h>
+
+typedef struct bh_hashmap_s bh_hashmap_t;
+
+/**
+ * @brief Create new hashmap object.
+ *
+ * @param equal Function used for comparing keys
+ * @param hash Function used to calculate hash value of the key
+ *
+ * @return If the function succeeds, the return value is a pointer to the new
+ * hashmap object.
+ * @return If the function fails, the return value is NULL.
+ *
+ * @warning The quality of the supplied hash function will affect performance
+ * of the hashmap.
+ *
+ * @sa bh_hashmap_free, bh_hashmap_reserve, bh_hashmap_insert
+ */
+bh_hashmap_t *bh_hashmap_new(bh_equal_cb_t equal,
+ bh_hash_cb_t hash);
+
+/**
+ * @brief Free hashmap object.
+ *
+ * @param hashmap Valid pointer to the hashmap object.
+ *
+ * @sa bh_hashmap_clear
+ */
+void bh_hashmap_free(bh_hashmap_t *hashmap);
+
+/**
+ * @brief Clear the hashmap.
+ *
+ * @param hashmap Valid pointer to the hashmap object.
+ *
+ * @warning Clearing the hashmap does invalidate iterators.
+ *
+ * @sa bh_hashmap_remove
+ */
+void bh_hashmap_clear(bh_hashmap_t *hashmap);
+
+/**
+ * @brief Reserve space for the specified amount of elements.
+ *
+ * @param hashmap Valid pointer to the hashmap object.
+ * @param size The amount of elements.
+ *
+ * @return If the function succeeds, the return value is zero.
+ * @return If the function fails, the return value is non-zero.
+ *
+ * @warning Reserving hashmap space does invalidate iterators.
+ *
+ * @sa bh_hashmap_capacity, bh_hashmap_insert
+ */
+int bh_hashmap_reserve(bh_hashmap_t *hashmap,
+ size_t size);
+
+/**
+ * @brief Insert key/value into the hashmap.
+ *
+ * @param hashmap Valid pointer to the hashmap object.
+ * @param key Key
+ * @param value Value
+ *
+ * @return If the function succeeds, the return value is zero.
+ * @return If the function fails, the return value is non-zero.
+ *
+ * @warning Inserted element is owned by the caller of the function.
+ * @warning Inserting elements into the hashmap does invalidate iterators.
+ *
+ * @sa bbh_hashmap_remove, bh_hashmap_at
+ */
+int bh_hashmap_insert(bh_hashmap_t *hashmap,
+ void *key,
+ void *value);
+
+/**
+ * @brief Remove element from the hashmap.
+ *
+ * @param hashmap Valid pointer to the hashmap object.
+ * @param key Key.
+ *
+ * @warning Removing elements from the hashmap does invalidate iterators.
+ *
+ * @sa bh_hashmap_insert, bh_hashmap_at
+ */
+void bh_hashmap_remove(bh_hashmap_t *hashmap,
+ void *key);
+
+/**
+ * @brief Return element value by the key from the hashmap.
+ *
+ * @param hashmap Valid pointer to the hashmap.
+ * @param key Key.
+ * @param exists Pointer to the exists flag (optional).
+ *
+ * @return If the function succeeds, the return value is a valid pointer to
+ * the element value.
+ * @return If the function fails, the return value is NULL.
+ *
+ * @note If the hashmap does not contain any element with the key, the
+ * function will fail.
+ *
+ * @sa bh_hashmap_empty, bh_hashmap_insert
+ */
+void *bh_hashmap_at(bh_hashmap_t *hashmap,
+ void *key,
+ int *exists);
+
+/**
+ * @brief Check if the hashmap is empty.
+ *
+ * @param hashmap Valid pointer to the hashmap object.
+ *
+ * @return The return value is non-zero if the hashmap is empty, otherwise
+ * zero.
+ *
+ * @sa bh_hashmap_size
+ */
+int bh_hashmap_empty(bh_hashmap_t *hashmap);
+
+/**
+ * @brief Return hashmap size.
+ *
+ * @param hashmap Valid pointer to the hashmap object.
+ *
+ * @return The return value is current hashmap size.
+ *
+ * @sa bh_hashmap_empty, bh_hashmap_capacity
+ */
+size_t bh_hashmap_size(bh_hashmap_t *hashmap);
+
+/**
+ * @brief Return hashmap capacity.
+ *
+ * @param hashmap Valid pointer to the hashmap object.
+ *
+ * @return The return value is current hashmap capacity.
+ *
+ * @sa bh_hashmap_reserve, bh_hashmap_size
+ */
+size_t bh_hashmap_capacity(bh_hashmap_t *hashmap);
+
+/**
+ * @brief Return hashmap load factor.
+ *
+ * @param hashmap Valid pointer to the hashmap object.
+ *
+ * @return The return value is current hashmap load factor.
+ *
+ * @sa bh_hashmap_set_factor, bh_hashmap_capacity
+ */
+float bh_hashmap_factor(bh_hashmap_t *hashmap);
+
+/**
+ * @brief Set hashmap load factor.
+ *
+ * @param hashmap Valid pointer to the hashmap object.
+ * @param factor Load factor.
+ *
+ * @sa bh_hashmap_factor
+ */
+void bh_hashmap_set_factor(bh_hashmap_t *hashmap,
+ float factor);
+
+/**
+ * @brief Return iterator for the element by the key.
+ *
+ * @param hashmap Valid pointer to the hashmap object.
+ * @param key Key
+ *
+ * @return The return value is the valid iterator for the element in the
+ * hashmap.
+ * @return The return value is the NULL iterator if there is no element with
+ * specified key.
+ *
+ * @sa bh_hashmap_iter_key, bh_hashmap_iter_value
+ */
+void *bh_hashmap_iter_at(bh_hashmap_t *hashmap,
+ void *key);
+
+/**
+ * @brief Return iterator for the next element in the hashmap.
+ *
+ * @param hashmap Valid pointer to the hashmap object.
+ * @param iter Valid or NULL iterator.
+ *
+ * @return The return value is the valid iterator for the next element in the
+ * hashmap.
+ * @return The return value is the NULL iterator if there is no more elements
+ * in the hashmap.
+ *
+ * @sa bh_hashmap_iter_key, bh_hashmap_iter_value
+ */
+void *bh_hashmap_iter_next(bh_hashmap_t *hashmap,
+ void *iter);
+
+/**
+ * @brief Remove element from the hashmap by the iterator.
+ *
+ * @param hashmap Valid pointer to the hashmap object.
+ * @param key Valid iterator.
+ *
+ * @warning Removing elements from the hashmap does invalidate iterators.
+ *
+ * @sa bh_hashmap_insert, bh_hashmap_at
+ */
+void bh_hashmap_iter_remove(bh_hashmap_t *hashmap,
+ void *iter);
+
+/**
+ * @brief Return pointer to the element's key.
+ *
+ * @param iter Valid iterator.
+ *
+ * @return The return value is the stored element key.
+ *
+ * @sa bh_hashmap_iter_value, bh_hashmap_iter_next
+ */
+void *bh_hashmap_iter_key(void *iter);
+
+/**
+ * @brief Return pointer to the element's value.
+ *
+ * @param iter Valid iterator.
+ *
+ * @return The return value is the stored element value.
+ *
+ * @sa bh_hashmap_iter_key, bh_hashmap_iter_next
+ */
+void *bh_hashmap_iter_value(void *iter);
+
+#endif /* BHLIB_HASHMAP_H */
diff --git a/include/bh/internal/hashmap.h b/include/bh/internal/hashmap.h
new file mode 100644
index 0000000..8d62c16
--- /dev/null
+++ b/include/bh/internal/hashmap.h
@@ -0,0 +1,30 @@
+#ifndef BHLIB_INTERNAL_HASHMAP_H
+#define BHLIB_INTERNAL_HASHMAP_H
+
+#include <bh/hashmap.h>
+
+typedef struct bh_hashmap_node_s
+{
+ void *key;
+ void *value;
+} bh_hashmap_node_t;
+
+struct bh_hashmap_s
+{
+ bh_hashmap_node_t *data;
+ size_t *psls;
+ size_t size;
+ size_t capacity;
+ size_t threshold;
+ bh_equal_cb_t equal;
+ bh_hash_cb_t hash;
+ float factor;
+};
+
+void bh_hashmap_init(bh_hashmap_t *hashmap,
+ bh_equal_cb_t equal,
+ bh_hash_cb_t hash);
+
+void bh_hashmap_destroy(bh_hashmap_t *hashmap);
+
+#endif /* BHLIB_INTERNAL_HASHMAP_H */
diff --git a/include/bh/internal/queue.h b/include/bh/internal/queue.h
new file mode 100644
index 0000000..13a6cad
--- /dev/null
+++ b/include/bh/internal/queue.h
@@ -0,0 +1,35 @@
+#ifndef BHLIB_INTERNAL_QUEUE_H
+#define BHLIB_INTERNAL_QUEUE_H
+
+#include <bh/queue.h>
+
+struct bh_queue_s
+{
+ void **data;
+ size_t size;
+ size_t head;
+ size_t tail;
+ size_t capacity;
+};
+
+/**
+ * @internal
+ * @brief Initialize embedded queue object.
+ *
+ * @param queue Valid pointer to the queue object.
+ *
+ * @sa bh_queue_destroy
+ */
+void bh_queue_init(bh_queue_t *queue);
+
+/**
+ * @internal
+ * @brief Destroy embedded queue object.
+ *
+ * @param queue Valid pointer to the queue object.
+ *
+ * @sa bh_queue_init
+ */
+void bh_queue_destroy(bh_queue_t *queue);
+
+#endif /* BHLIB_INTERNAL_QUEUE_H */
diff --git a/include/bh/queue.h b/include/bh/queue.h
new file mode 100644
index 0000000..c5185ef
--- /dev/null
+++ b/include/bh/queue.h
@@ -0,0 +1,159 @@
+#ifndef BH_QUEUE_H
+#define BH_QUEUE_H
+
+#include <bh/bh.h>
+
+typedef struct bh_queue_s bh_queue_t;
+
+/**
+ * @brief Create new queue object.
+ *
+ * @return If the function succeeds, the return value is a pointer to the new
+ * queue object.
+ * @return If the function fails, the return value is NULL.
+ *
+ * @sa bh_queue_free, bh_queue_reserve, bh_queue_insert
+ */
+bh_queue_t *bh_queue_new(void);
+
+/**
+ * @brief Free queue object.
+ *
+ * @param queue Valid pointer to the queue object.
+ *
+ * @sa bh_queue_clear
+ */
+void bh_queue_free(bh_queue_t *queue);
+
+/**
+ * @brief Clear the queue.
+ *
+ * @param queue Valid pointer to the queue object.
+ *
+ * @warning Clearing the queue does invalidate iterators.
+ *
+ * @sa bh_queue_remove
+ */
+void bh_queue_clear(bh_queue_t *queue);
+
+/**
+ * @brief Reserve space for the specified amount of elements.
+ *
+ * @param queue Valid pointer to the queue object.
+ * @param size The amount of elements.
+ *
+ * @return If the function succeeds, the return value is zero.
+ * @return If the function fails, the return value is non-zero.
+ *
+ * @warning Reserving queue space does invalidate iterators.
+ *
+ * @sa bh_queue_capacity, bh_queue_insert
+ */
+int bh_queue_reserve(bh_queue_t *queue,
+ size_t size);
+
+/**
+ * @brief Insert element at the end of the queue.
+ *
+ * @param queue Valid pointer to the queue object.
+ * @param value Element.
+ *
+ * @return If the function succeeds, the return value is zero.
+ * @return If the function fails, the return value is non-zero.
+ *
+ * @warning Inserted element is owned by the caller of the function.
+ * @warning Inserting elements into the queue does invalidate iterators.
+ *
+ * @sa bh_queue_remove, bh_queue_front
+ */
+int bh_queue_insert(bh_queue_t *queue,
+ void *value);
+
+/**
+ * @brief Remove element from the front of the queue.
+ *
+ * @param queue Valid pointer to the queue object.
+ *
+ * @warning Removing elements from the queue does invalidate iterators.
+ *
+ * @sa bh_queue_insert, bh_queue_front
+ */
+void bh_queue_remove(bh_queue_t *queue);
+
+/**
+ * @brief Return element from the front of the queue.
+ *
+ * @param queue Valid pointer to the queue object.
+ *
+ * @return If the function succeeds, the return value is a valid pointer to
+ * the element.
+ * @return If the function fails, the return value is NULL.
+ *
+ * @note If the queue is empty, function will fail to return element from
+ * the queue.
+ *
+ * @sa bh_queue_empty, bh_queue_insert
+ */
+void *bh_queue_front(bh_queue_t *queue);
+
+/**
+ * @brief Check if the queue is empty.
+ *
+ * @param queue Valid pointer to the queue object.
+ *
+ * @return The return value is non-zero if the queue is empty, otherwise zero.
+ *
+ * @sa bh_queue_size
+ */
+int bh_queue_empty(bh_queue_t *queue);
+
+/**
+ * @brief Return queue size.
+ *
+ * @param queue Valid pointer to the queue object.
+ *
+ * @return The return value is current queue size.
+ *
+ * @sa bh_queue_empty, bh_queue_capacity
+ */
+size_t bh_queue_size(bh_queue_t *queue);
+
+/**
+ * @brief Return queue capacity.
+ *
+ * @param queue Valid pointer to the queue object.
+ *
+ * @return The return value is current queue capacity.
+ *
+ * @sa bh_queue_reserve, bh_queue_size
+ */
+size_t bh_queue_capacity(bh_queue_t *queue);
+
+/**
+ * @brief Return iterator for the next element in the queue.
+ *
+ * @param queue Valid pointer to the queue object.
+ * @param iter Valid or NULL iterator.
+ *
+ * @return The return value is the valid iterator for the next element in the
+ * queue.
+ * @return The return value is the NULL iterator if there is no more elements
+ * in the queue.
+ *
+ * @sa bh_queue_iter_value
+ */
+void *bh_queue_iter_next(bh_queue_t *queue,
+ void *iter);
+
+/**
+ * @brief Return pointer to the element's value.
+ *
+ * @param iter Valid iterator.
+ *
+ * @return The return value is the stored element.
+ *
+ * @sa bh_queue_iter_next
+ */
+void *bh_queue_iter_value(void *iter);
+
+#endif /* BH_QUEUE_H */
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..8cd7e5a
--- /dev/null
+++ b/main.c
@@ -0,0 +1,83 @@
+#include <bh/queue.h>
+#include <bh/hashmap.h>
+#include <stdio.h>
+#include <stdint.h>
+
+#define BH_INT_TO_PTR(x) \
+ ((void *)((long)(x)))
+
+#define BH_UINT_TO_PTR(x) \
+ ((void *)((unsigned long)(x)))
+
+#define BH_PTR_TO_INT(x) \
+ ((long)(x))
+
+#define BH_PTR_TO_UINT(x) \
+ ((unsigned long)(x))
+
+size_t ptr_hash(const void *item)
+{
+ return BH_PTR_TO_INT(item);
+}
+
+int ptr_equal(const void *lhs, const void *rhs)
+{
+ return BH_PTR_TO_INT(lhs) - BH_PTR_TO_INT(rhs);
+}
+
+void foo()
+{
+ bh_hashmap_t *hashmap;
+ size_t i;
+ void *iter;
+
+ hashmap = bh_hashmap_new((bh_equal_cb_t)ptr_equal, (bh_hash_cb_t)ptr_hash);
+
+ for (i = 0; i < 16; i++)
+ bh_hashmap_insert(hashmap, (void*)i, (void*)(i * 4));
+
+ iter = bh_hashmap_iter_next(hashmap, NULL);
+ while (iter)
+ {
+ printf("%zu: %zu\n", BH_PTR_TO_INT(bh_hashmap_iter_key(iter)), BH_PTR_TO_INT(bh_hashmap_iter_value(iter)));
+ iter = bh_hashmap_iter_next(hashmap, iter);
+ }
+
+ bh_hashmap_free(hashmap);
+}
+
+int main()
+{
+ bh_queue_t *queue;
+ void *iter;
+ size_t i, j;
+
+ foo();
+
+ queue = bh_queue_new();
+
+ for (j = 0; j < 32; j++)
+ {
+ printf("%zu %zu\n", bh_queue_size(queue), bh_queue_capacity(queue));
+
+ for (i = 0; i < 4; i++)
+ bh_queue_insert(queue, (void *)(j * 4 + i));
+
+ printf("%zu %zu\n", bh_queue_size(queue), bh_queue_capacity(queue));
+
+ for (i = 0; i < 2; i++)
+ bh_queue_remove(queue);
+ }
+
+ printf("%zu %zu\n", bh_queue_size(queue), bh_queue_capacity(queue));
+
+ iter = bh_queue_iter_next(queue, NULL);
+ while (iter)
+ {
+ printf("%d\n", (int)bh_queue_iter_value(iter));
+ iter = bh_queue_iter_next(queue, iter);
+ }
+
+ bh_queue_free(queue);
+ return 0;
+}
diff --git a/scripts/coverage.sh b/scripts/coverage.sh
new file mode 100755
index 0000000..9597cf6
--- /dev/null
+++ b/scripts/coverage.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+ulimit -Sv 320000
+rm -r build coverage
+mkdir build coverage
+cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS_DEBUG="-g -fprofile-arcs -ftest-coverage"
+cmake --build build
+cd build
+#ctest -T Test -T Coverage
+ctest
+cd ..
+#echo Report > coverage.txt
+#find . -iname "*.gcda" -exec gcov {} \;
+#find . -iname "*.gcno" -exec echo {} >> coverage.txt \; -exec gcov -t {} >> coverage.txt \;
+gcovr --html-details coverage/index.html
diff --git a/scripts/trim-whitespace.sh b/scripts/trim-whitespace.sh
new file mode 100755
index 0000000..34a9c58
--- /dev/null
+++ b/scripts/trim-whitespace.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+find . \( -iname "*.h" -o -iname "*.c" \) -exec sed -i "s/[ \t]*$//" {} \; \ No newline at end of file
diff --git a/src/hashmap.c b/src/hashmap.c
new file mode 100644
index 0000000..c9fb315
--- /dev/null
+++ b/src/hashmap.c
@@ -0,0 +1,343 @@
+#include <bh/internal/hashmap.h>
+#include <stdlib.h>
+#include <string.h>
+
+bh_hashmap_t *bh_hashmap_new(bh_equal_cb_t equal,
+ bh_hash_cb_t hash)
+{
+ bh_hashmap_t *result;
+
+ result = malloc(sizeof(*result));
+ if (result)
+ bh_hashmap_init(result, equal, hash);
+
+ return result;
+}
+
+void bh_hashmap_free(bh_hashmap_t *hashmap)
+{
+ bh_hashmap_destroy(hashmap);
+ free(hashmap);
+}
+
+void bh_hashmap_init(bh_hashmap_t *hashmap,
+ bh_equal_cb_t equal,
+ bh_hash_cb_t hash)
+{
+ memset(hashmap, 0, sizeof(*hashmap));
+ hashmap->factor = 0.75f;
+ hashmap->equal = equal;
+ hashmap->hash = hash;
+}
+
+void bh_hashmap_destroy(bh_hashmap_t *hashmap)
+{
+ if (hashmap->capacity)
+ {
+ free(hashmap->data);
+ free(hashmap->psls);
+ }
+}
+
+void bh_hashmap_clear(bh_hashmap_t *hashmap)
+{
+ if (hashmap->capacity)
+ memset(hashmap->psls, 0, hashmap->capacity * sizeof(size_t));
+ hashmap->size = 0;
+}
+
+int bh_hashmap_reserve(bh_hashmap_t *hashmap,
+ size_t size)
+{
+ bh_hashmap_t other;
+ size_t capacity, max_capacity, threshold;
+
+ /* Calculate hashmap max capacity and capacity threshold */
+ capacity = hashmap->capacity;
+ threshold = hashmap->capacity * hashmap->factor;
+ max_capacity = ((size_t)-1) / sizeof(bh_hashmap_node_t);
+
+ /* New capacity can't be smaller then current hashmap size */
+ if (size < hashmap->size)
+ size = hashmap->size;
+
+ /* Find new hashmap capacity */
+ if (!size)
+ {
+ /* No capacity needed */
+ capacity = 0;
+ threshold = 0;
+ }
+ else if (size > threshold)
+ {
+ /* Bigger capacity needed - grow the hashmap */
+ while (size > threshold)
+ {
+ capacity = (capacity) ? (capacity * 2) : (16);
+ threshold = capacity * hashmap->factor;
+
+ /* Capacity can't be bigger than max capacity and overflow */
+ if (capacity > max_capacity || capacity < 16)
+ return -1;
+ }
+ }
+ else
+ {
+ /* Smaller capacity needed - shrink the hashmap */
+ while (size <= threshold / 2 && capacity > 16)
+ {
+ capacity /= 2;
+ threshold = capacity * hashmap->factor;
+ }
+ }
+
+ /* Prevent same size reallocation */
+ if (capacity == hashmap->capacity)
+ return 0;
+
+ /* Initialize new hashmap */
+ bh_hashmap_init(&other, hashmap->equal, hashmap->hash);
+ other.factor = hashmap->factor;
+
+ if (capacity)
+ {
+ void *iter;
+
+ /* Allocate new memory for the hashmap */
+ other.data = malloc(sizeof(*other.data) * capacity);
+
+ other.psls = malloc(sizeof(size_t) * capacity);
+ other.threshold = threshold;
+ other.capacity = capacity;
+
+ /* Check for allocations failure */
+ if (!other.data || !other.psls)
+ {
+ if (other.data)
+ free(other.data);
+ if (other.psls)
+ free(other.psls);
+ return -1;
+ }
+
+ /* Reset probe sequence lengths */
+ memset(other.psls, 0, sizeof(size_t) * other.capacity);
+
+ /* Iterate and insert data into hashmap */
+ iter = bh_hashmap_iter_next(hashmap, NULL);
+ while (iter)
+ {
+ void *key, *value;
+
+ key = bh_hashmap_iter_key(iter);
+ value = bh_hashmap_iter_value(iter);
+ bh_hashmap_insert(&other, key, value);
+
+ iter = bh_hashmap_iter_next(hashmap, iter);
+ }
+ }
+
+ /* Swap hashmaps */
+ bh_hashmap_destroy(hashmap);
+ *hashmap = other;
+ return 0;
+}
+
+int bh_hashmap_insert(bh_hashmap_t *hashmap,
+ void *key,
+ void *value)
+{
+ size_t bucket, psl, tmp_psl;
+ bh_hashmap_node_t item, tmp;
+
+ /* Try to stay below hashmap threshold */
+ if (hashmap->size + 1 > hashmap->threshold)
+ if (bh_hashmap_reserve(hashmap, hashmap->size + 1))
+ if (hashmap->size >= hashmap->capacity)
+ return -1;
+
+ /* Prepare inserted data and set PSL to 1 */
+ item.key = key;
+ item.value = value;
+ psl = 1;
+
+ /* Calculate prefered bucket index */
+ bucket = hashmap->hash(key) & (hashmap->capacity - 1);
+
+ /* Find empty bucket */
+ while (hashmap->psls[bucket])
+ {
+ /* Current bucket is richer then us - swap elements */
+ if (psl > hashmap->psls[bucket])
+ {
+ tmp = hashmap->data[bucket];
+ tmp_psl = hashmap->psls[bucket];
+ hashmap->data[bucket] = item;
+ hashmap->psls[bucket] = psl;
+ item = tmp;
+ psl = tmp_psl;
+ }
+
+ bucket = (bucket + 1) & (hashmap->capacity - 1);
+ psl++;
+ }
+
+ /* Found empty bucket - place item here */
+ hashmap->data[bucket] = item;
+ hashmap->psls[bucket] = psl;
+ hashmap->size++;
+
+ return 0;
+}
+
+void bh_hashmap_remove(bh_hashmap_t *hashmap,
+ void *key)
+{
+ void *iter;
+
+ iter = bh_hashmap_iter_at(hashmap, key);
+ if (iter)
+ bh_hashmap_iter_remove(hashmap, iter);
+}
+
+void *bh_hashmap_at(bh_hashmap_t *hashmap,
+ void *key,
+ int *exists)
+{
+ void *iter;
+
+ iter = bh_hashmap_iter_at(hashmap, key);
+ if (iter)
+ {
+ /* If exists flag passed - set to 1 */
+ if (exists)
+ *exists = 1;
+ return bh_hashmap_iter_value(iter);
+ }
+
+ /* If exists flag passed - set to 0 */
+ if (exists)
+ *exists = 0;
+ return NULL;
+}
+
+int bh_hashmap_empty(bh_hashmap_t *hashmap)
+{
+ return !hashmap->size;
+}
+
+size_t bh_hashmap_size(bh_hashmap_t *hashmap)
+{
+ return hashmap->size;
+}
+
+size_t bh_hashmap_capacity(bh_hashmap_t *hashmap)
+{
+ return hashmap->capacity;
+}
+
+float bh_hashmap_factor(bh_hashmap_t *hashmap)
+{
+ return hashmap->factor;
+}
+
+void bh_hashmap_set_factor(bh_hashmap_t *hashmap,
+ float factor)
+{
+ factor = (factor > 1.0f) ? (1.0f) : (factor);
+ factor = (factor < 0.15f) ? (0.15f) : (factor);
+
+ hashmap->factor = factor;
+ hashmap->threshold = hashmap->capacity * factor;
+}
+
+void *bh_hashmap_iter_at(bh_hashmap_t *hashmap,
+ void *key)
+{
+ size_t bucket, psl;
+
+ /* Nothing can be in empty map */
+ if (!hashmap->size)
+ return NULL;
+
+ /* Calculate prefered bucket index and set PSL to 1 */
+ bucket = hashmap->hash(key) & (hashmap->capacity - 1);
+ psl = 1;
+
+ /* Iterate hashmap until we find element or find richer bucket */
+ while (hashmap->psls[bucket] >= psl && hashmap->equal(hashmap->data[bucket].key, key))
+ {
+ bucket = (bucket + 1) & (hashmap->capacity - 1);
+ psl++;
+ }
+
+ /* If bucket is poorer or equal to us - we found our element */
+ if (hashmap->psls[bucket] >= psl)
+ return hashmap->data + bucket;
+
+ return NULL;
+}
+
+void *bh_hashmap_iter_next(bh_hashmap_t *hashmap,
+ void *iter)
+{
+ bh_hashmap_node_t *item;
+
+ item = (bh_hashmap_node_t *)iter;
+ while (1)
+ {
+ /* Advance or set iterator to the first element */
+ if (item)
+ item++;
+ else
+ item = hashmap->data;
+
+ /* Check iterator for validity */
+ if (item >= hashmap->data + hashmap->capacity)
+ return NULL;
+
+ /* Check iterator's item PSL */
+ if (hashmap->psls[item - hashmap->data])
+ return item;
+ }
+}
+
+void bh_hashmap_iter_remove(bh_hashmap_t *hashmap,
+ void *iter)
+{
+ size_t bucket, next_bucket;
+
+ /* Check if hashmap is empty or we are given NULL iterator */
+ if (!iter || !hashmap->size)
+ return;
+
+ /* Adjust hashmap size, calculate current and next bucket index */
+ hashmap->size--;
+ bucket = (bh_hashmap_node_t *)iter - hashmap->data;
+ next_bucket = (bucket + 1) & (hashmap->capacity - 1);
+
+ /* Shift all elements toward their preffered place */
+ while (hashmap->psls[next_bucket] > 1)
+ {
+ /* Copy item and adjust PSL */
+ hashmap->data[bucket] = hashmap->data[next_bucket];
+ hashmap->psls[bucket] = hashmap->psls[next_bucket] - 1;
+
+ /* Calculate next bucket index */
+ bucket = next_bucket;
+ next_bucket = (bucket + 1) & (hashmap->capacity - 1);
+ }
+
+ /* Reset bucket's PSL (mark empty) */
+ hashmap->psls[bucket] = 0;
+}
+
+void *bh_hashmap_iter_key(void *iter)
+{
+ return ((bh_hashmap_node_t *)iter)->key;
+}
+
+void *bh_hashmap_iter_value(void *iter)
+{
+ return ((bh_hashmap_node_t *)iter)->value;
+}
diff --git a/src/queue.c b/src/queue.c
new file mode 100644
index 0000000..8d48401
--- /dev/null
+++ b/src/queue.c
@@ -0,0 +1,176 @@
+#include <bh/internal/queue.h>
+#include <stdlib.h>
+#include <string.h>
+
+bh_queue_t *bh_queue_new(void)
+{
+ bh_queue_t *result;
+
+ result = malloc(sizeof(*result));
+ if (result)
+ bh_queue_init(result);
+
+ return result;
+}
+
+void bh_queue_free(bh_queue_t *queue)
+{
+ bh_queue_destroy(queue);
+ free(queue);
+}
+
+void bh_queue_init(bh_queue_t *queue)
+{
+ memset(queue, 0, sizeof(*queue));
+}
+
+void bh_queue_destroy(bh_queue_t *queue)
+{
+ if (queue->capacity)
+ free(queue->data);
+}
+
+void bh_queue_clear(bh_queue_t *queue)
+{
+ queue->head = 0;
+ queue->tail = 0;
+ queue->size = 0;
+}
+
+int bh_queue_reserve(bh_queue_t *queue,
+ size_t size)
+{
+ bh_queue_t other;
+
+ /* New capacity should be great or equal to current size */
+ if (size < queue->size)
+ size = queue->size;
+
+ /* New capacity should not exceed maximum capacity */
+ if (size > ((size_t)-1) / sizeof(void *))
+ return -1;
+
+ /* Prevent same size memory reallocation */
+ if (size == queue->capacity)
+ return 0;
+
+ /* Prepare new empty queue */
+ bh_queue_init(&other);
+ if (size)
+ {
+ void *iter;
+
+ /* Allocate new capacity for the queue */
+ other.data = malloc(size * sizeof(void *));
+ other.capacity = size;
+ if (!other.data)
+ return -1;
+
+ /* Iterate over old queue and insert data into new queue */
+ iter = bh_queue_iter_next(queue, NULL);
+ while (iter)
+ {
+ bh_queue_insert(&other, bh_queue_iter_value(iter));
+ iter = bh_queue_iter_next(queue, iter);
+ }
+ }
+
+ /* If old queue had allocated data - free it */
+ if (queue->capacity)
+ free(queue->data);
+
+ /* Copy queue information */
+ *queue = other;
+ return 0;
+}
+
+int bh_queue_insert(bh_queue_t *queue,
+ void *value)
+{
+ /* Check if queue can contain new element */
+ if (queue->size + 1 > queue->capacity)
+ {
+ size_t capacity;
+
+ /* Check potential size overflow and reserve capacity */
+ capacity = (queue->capacity) ? (queue->capacity * 2) : (16);
+ if (capacity < queue->capacity || bh_queue_reserve(queue, capacity))
+ return -1;
+ }
+
+ /* Increase queue size and advance tail index */
+ queue->data[queue->tail] = value;
+ queue->size++;
+ if (++queue->tail >= queue->capacity)
+ queue->tail = 0;
+
+ return 0;
+}
+
+void bh_queue_remove(bh_queue_t *queue)
+{
+ /* Do nothing if queue is empty */
+ if (!queue->size)
+ return;
+
+ /* Decrease queue size and advance head index */
+ queue->size--;
+ if (++queue->head >= queue->capacity)
+ queue->head = 0;
+}
+
+void *bh_queue_front(bh_queue_t *queue)
+{
+ /* Do nothing if queue is empty */
+ if (!queue->size)
+ return NULL;
+
+ /* Return front element */
+ return queue->data[queue->head];
+}
+
+int bh_queue_empty(bh_queue_t *queue)
+{
+ return !queue->size;
+}
+
+size_t bh_queue_size(bh_queue_t *queue)
+{
+ return queue->size;
+}
+
+size_t bh_queue_capacity(bh_queue_t *queue)
+{
+ return queue->capacity;
+}
+
+void *bh_queue_iter_next(bh_queue_t *queue,
+ void *iter)
+{
+ void **element = (void **)iter;
+
+ /* Do nothing if queue is empty */
+ if (!queue->size)
+ return NULL;
+
+ /* Advance or set iterator */
+ if (element)
+ {
+ element++;
+ if (element == queue->data + queue->capacity)
+ element = queue->data;
+
+ /* Check if we reached the end */
+ if (element == queue->data + queue->tail)
+ return NULL;
+ }
+ else
+ element = queue->data + queue->head;
+
+ return element;
+}
+
+void *bh_queue_iter_value(void *iter)
+{
+ return *(void **)iter;
+}
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
new file mode 100644
index 0000000..e111e33
--- /dev/null
+++ b/tests/CMakeLists.txt
@@ -0,0 +1,20 @@
+set(CMAKE_C_STANDARD 90)
+set(CMAKE_C_STANDARD_REQUIRED ON)
+set(CMAKE_C_EXTENSIONS OFF)
+
+# Enable warnings and pedantics
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -pedantic")
+
+# Enable testing
+include(CTest)
+enable_testing()
+
+# Search files
+file(GLOB TEST_FILES "src/*.c")
+
+foreach(TEST_FILENAME ${TEST_FILES})
+ get_filename_component(TEST_NAME ${TEST_FILENAME} NAME_WE)
+ add_executable("${TEST_NAME}" ${TEST_FILENAME})
+ target_link_libraries("${TEST_NAME}" bhlib bhunit)
+ add_test(NAME "${TEST_NAME}" COMMAND "${TEST_NAME}")
+endforeach()
diff --git a/tests/src/hashmap.c b/tests/src/hashmap.c
new file mode 100644
index 0000000..7fda5fc
--- /dev/null
+++ b/tests/src/hashmap.c
@@ -0,0 +1,278 @@
+#include <bh/internal/hashmap.h>
+#include <bh/unit.h>
+
+static size_t direct_hash(const void *ptr)
+{
+ return BH_PTR_TO_INT(ptr);
+}
+
+static int direct_equal(const void *lhs, const void *rhs)
+{
+ return BH_PTR_TO_INT(lhs) - BH_PTR_TO_INT(rhs);
+}
+
+static int init_destroy(void)
+{
+ bh_hashmap_t hashmap;
+
+ bh_hashmap_init(&hashmap, direct_equal, direct_hash);
+
+ /* Check internal state is valid and empty */
+ bh_unit_assert(hashmap.data == NULL);
+ bh_unit_assert(hashmap.psls == NULL);
+ bh_unit_assert(hashmap.size == 0);
+ bh_unit_assert(hashmap.capacity == 0);
+ bh_unit_assert(hashmap.threshold == 0);
+ bh_unit_assert(hashmap.factor >= 0.15f && hashmap.factor <= 1.0f);
+ bh_unit_assert(hashmap.equal == direct_equal);
+ bh_unit_assert(hashmap.hash == direct_hash);
+
+ bh_hashmap_destroy(&hashmap);
+ return 0;
+}
+
+static int new_free(void)
+{
+ bh_hashmap_t *hashmap;
+
+ hashmap = bh_hashmap_new(direct_equal, direct_hash);
+ bh_unit_assert(hashmap != NULL);
+
+ /* Check internal state is valid and empty */
+ bh_unit_assert(hashmap->data == NULL);
+ bh_unit_assert(hashmap->psls == NULL);
+ bh_unit_assert(hashmap->size == 0);
+ bh_unit_assert(hashmap->capacity == 0);
+ bh_unit_assert(hashmap->threshold == 0);
+ bh_unit_assert(hashmap->factor >= 0.15f && hashmap->factor <= 1.0f);
+ bh_unit_assert(hashmap->equal == direct_equal);
+ bh_unit_assert(hashmap->hash == direct_hash);
+
+ bh_hashmap_free(hashmap);
+ return 0;
+}
+
+static int grow_shrink(void)
+{
+ bh_hashmap_t *hashmap;
+ bh_hashmap_node_t *old_data;
+ size_t *old_psls;
+ void *iter;
+
+ hashmap = bh_hashmap_new(direct_equal, direct_hash);
+ bh_unit_assert(hashmap != NULL);
+
+ /* Allocate space for 1024 entries and insert 1 element */
+ bh_unit_assert(bh_hashmap_reserve(hashmap, 1024) == 0);
+ bh_unit_assert(bh_hashmap_insert(hashmap, BH_INT_TO_PTR(1337), BH_INT_TO_PTR(80085)) == 0);
+
+ /* Check hashmap contents */
+ iter = bh_hashmap_iter_next(hashmap, NULL);
+ bh_unit_assert(iter != NULL);
+ bh_unit_assert(BH_PTR_TO_INT(bh_hashmap_iter_key(iter)) == 1337);
+ bh_unit_assert(BH_PTR_TO_INT(bh_hashmap_iter_value(iter)) == 80085);
+ bh_unit_assert(bh_hashmap_iter_next(hashmap, iter) == NULL);
+
+ bh_unit_assert(hashmap->data != NULL);
+ bh_unit_assert(hashmap->psls != NULL);
+ bh_unit_assert(hashmap->capacity >= 1024);
+ bh_unit_assert(hashmap->threshold == (size_t)(hashmap->capacity * hashmap->factor));
+ bh_unit_assert(hashmap->size == 1);
+
+ /* Save old pointers */
+ old_data = hashmap->data;
+ old_psls = hashmap->psls;
+
+ /* Change factor and grow */
+ bh_hashmap_set_factor(hashmap, 0.35f);
+ bh_unit_assert(bh_hashmap_reserve(hashmap, 8192) == 0);
+
+ bh_unit_assert(hashmap->data != NULL);
+ bh_unit_assert(hashmap->psls != NULL);
+ bh_unit_assert(hashmap->capacity >= 8192);
+ bh_unit_assert(hashmap->data != old_data);
+ bh_unit_assert(hashmap->psls != old_psls);
+ bh_unit_assert(hashmap->factor == 0.35f);
+ bh_unit_assert(hashmap->threshold == (size_t)(hashmap->capacity * hashmap->factor));
+ bh_unit_assert(hashmap->size == 1);
+
+ old_data = hashmap->data;
+ old_psls = hashmap->psls;
+
+ /* Check hashmap contents */
+ iter = bh_hashmap_iter_next(hashmap, NULL);
+ bh_unit_assert(iter != NULL);
+ bh_unit_assert(BH_PTR_TO_INT(bh_hashmap_iter_key(iter)) == 1337);
+ bh_unit_assert(BH_PTR_TO_INT(bh_hashmap_iter_value(iter)) == 80085);
+ bh_unit_assert(bh_hashmap_iter_next(hashmap, iter) == NULL);
+
+ /* Shrink */
+ bh_unit_assert(bh_hashmap_reserve(hashmap, 0) == 0);
+
+ bh_unit_assert(hashmap->data != NULL);
+ bh_unit_assert(hashmap->psls != NULL);
+ bh_unit_assert(hashmap->capacity < 8192);
+ bh_unit_assert(hashmap->data != old_data);
+ bh_unit_assert(hashmap->psls != old_psls);
+ bh_unit_assert(hashmap->factor == 0.35f);
+ bh_unit_assert(hashmap->threshold == (size_t)(hashmap->capacity * hashmap->factor));
+ bh_unit_assert(hashmap->size == 1);
+
+ /* Check hashmap contents */
+ iter = bh_hashmap_iter_next(hashmap, NULL);
+ bh_unit_assert(iter != NULL);
+ bh_unit_assert(BH_PTR_TO_INT(bh_hashmap_iter_key(iter)) == 1337);
+ bh_unit_assert(BH_PTR_TO_INT(bh_hashmap_iter_value(iter)) == 80085);
+ bh_unit_assert(bh_hashmap_iter_next(hashmap, iter) == NULL);
+
+ /* Shrink to 0 (deallocate) */
+ bh_hashmap_clear(hashmap);
+ bh_unit_assert(hashmap->size == 0);
+
+ bh_unit_assert(bh_hashmap_reserve(hashmap, 0) == 0);
+ bh_unit_assert(hashmap->capacity == 0);
+ bh_unit_assert(hashmap->size == 0);
+
+ /* Check hashmap contents */
+ iter = bh_hashmap_iter_next(hashmap, NULL);
+ bh_unit_assert(iter == NULL);
+
+ bh_hashmap_free(hashmap);
+ return 0;
+}
+
+static int insert_remove(void)
+{
+ bh_hashmap_t *hashmap;
+ size_t i, added, removed;
+ void *iter;
+
+ hashmap = bh_hashmap_new(direct_equal, direct_hash);
+ bh_unit_assert(hashmap != NULL);
+ bh_hashmap_set_factor(hashmap, 1.0f);
+
+ /* Insert elements into hashmap */
+ added = 0;
+ for (i = 1024; i > 0; i--)
+ {
+ added += (i - 1) / 4;
+ bh_unit_assert(bh_hashmap_insert(hashmap, BH_INT_TO_PTR((i - 1) / 4), BH_INT_TO_PTR(i)) == 0);
+ }
+
+ /* Remove elements */
+ iter = bh_hashmap_iter_next(hashmap, NULL);
+ removed = 0;
+ while (iter)
+ {
+ removed += BH_PTR_TO_INT(bh_hashmap_iter_key(iter));
+ bh_hashmap_iter_remove(hashmap, iter);
+
+ iter = bh_hashmap_iter_next(hashmap, NULL);
+ }
+
+ /* Check inserted elements are equal to removed */
+ bh_unit_assert(added == removed);
+
+ bh_hashmap_free(hashmap);
+ return 0;
+}
+
+static int lookup(void)
+{
+ bh_hashmap_t *hashmap;
+ size_t i;
+
+ hashmap = bh_hashmap_new(direct_equal, direct_hash);
+ bh_unit_assert(hashmap != NULL);
+
+ /* Insert elements into hashmap */
+ for (i = 0; i < 256; i++)
+ bh_unit_assert(bh_hashmap_insert(hashmap, BH_INT_TO_PTR(i * 4), BH_INT_TO_PTR(i)) == 0);
+
+ /* Lookup inserted elements */
+ for (i = 0; i < 256; i++)
+ {
+ int exists;
+
+ bh_unit_assert(BH_PTR_TO_INT(bh_hashmap_at(hashmap, BH_INT_TO_PTR(i * 4), NULL)) == i);
+ bh_hashmap_at(hashmap, BH_INT_TO_PTR(i * 4), &exists);
+ bh_unit_assert(exists == 1);
+ }
+
+ /* Lookup non-existing elements */
+ for (i = 256; i < 512; i++)
+ {
+ int exists;
+
+ bh_unit_assert(bh_hashmap_at(hashmap, BH_INT_TO_PTR(i * 4), NULL) == NULL);
+ bh_hashmap_at(hashmap, BH_INT_TO_PTR(i * 4), &exists);
+ bh_unit_assert(exists == 0);
+ }
+
+ bh_hashmap_free(hashmap);
+ return 0;
+}
+
+static int clear(void)
+{
+ bh_hashmap_t *hashmap;
+ size_t i;
+
+ hashmap = bh_hashmap_new(direct_equal, direct_hash);
+ bh_unit_assert(hashmap != NULL);
+
+ /* Insert elements into hashmap */
+ for (i = 0; i < 128; i++)
+ bh_unit_assert(bh_hashmap_insert(hashmap, BH_INT_TO_PTR(i), 0) == 0);
+
+ bh_hashmap_clear(hashmap);
+
+ /* Remove non-existing elements */
+ for (i = 0; i < 128; i++)
+ bh_hashmap_remove(hashmap, BH_INT_TO_PTR(i));
+
+ bh_hashmap_free(hashmap);
+ return 0;
+}
+
+static int fields(void)
+{
+ bh_hashmap_t *hashmap;
+ size_t i;
+
+ hashmap = bh_hashmap_new(direct_equal, direct_hash);
+ bh_unit_assert(hashmap != NULL);
+ bh_unit_assert(bh_hashmap_empty(hashmap) == 1);
+
+ /* Insert elements into hashmap */
+ for (i = 0; i < 14; i++)
+ bh_unit_assert(bh_hashmap_insert(hashmap, BH_INT_TO_PTR(i), NULL) == 0);
+
+ /* Check hashmap fields correspond to getter functions */
+ bh_unit_assert(hashmap->size == 14);
+ bh_unit_assert(hashmap->capacity >= 14);
+ bh_unit_assert(bh_hashmap_size(hashmap) == hashmap->size);
+ bh_unit_assert(bh_hashmap_capacity(hashmap) == hashmap->capacity);
+ bh_unit_assert(bh_hashmap_factor(hashmap) == hashmap->factor);
+ bh_unit_assert(bh_hashmap_empty(hashmap) == 0);
+
+ bh_hashmap_free(hashmap);
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ (void)argc;
+ (void)argv;
+
+ /* Add unit tests */
+ bh_unit_add("init_destroy", init_destroy);
+ bh_unit_add("new_free", new_free);
+ bh_unit_add("grow_shrink", grow_shrink);
+ bh_unit_add("insert_remove", insert_remove);
+ bh_unit_add("lookup", lookup);
+ bh_unit_add("clear", clear);
+ bh_unit_add("fields", fields);
+
+ return bh_unit_run();
+}
diff --git a/tests/src/queue.c b/tests/src/queue.c
new file mode 100644
index 0000000..8c805a6
--- /dev/null
+++ b/tests/src/queue.c
@@ -0,0 +1,183 @@
+#include <bh/internal/queue.h>
+#include <bh/unit.h>
+
+static int init_destroy(void)
+{
+ bh_queue_t queue;
+
+ bh_queue_init(&queue);
+
+ bh_unit_assert(queue.size == 0);
+ bh_unit_assert(queue.capacity == 0);
+ bh_unit_assert(queue.data == NULL);
+ bh_unit_assert(queue.head == queue.tail);
+
+ bh_queue_destroy(&queue);
+
+ return 0;
+}
+
+static int new_free(void)
+{
+ bh_queue_t *queue;
+
+ queue = bh_queue_new();
+ bh_unit_assert(queue != NULL);
+ bh_unit_assert(queue->size == 0);
+ bh_unit_assert(queue->capacity == 0);
+ bh_unit_assert(queue->data == NULL);
+ bh_unit_assert(queue->head == queue->tail);
+
+ bh_queue_free(queue);
+ return 0;
+}
+
+static int grow_shrink(void)
+{
+ bh_queue_t *queue;
+ void *old_data;
+
+ queue = bh_queue_new();
+ bh_unit_assert(queue != NULL);
+
+ /* Reserve 1024 elements and insert item into queue */
+ bh_unit_assert(bh_queue_reserve(queue, 1024) == 0);
+ bh_unit_assert(bh_queue_insert(queue, BH_INT_TO_PTR(1337)) == 0);
+ bh_unit_assert(queue->capacity >= 1024);
+ bh_unit_assert(queue->size == 1);
+ bh_unit_assert(bh_queue_empty(queue) == 0);
+ bh_unit_assert(queue->data != NULL);
+
+ /* Check queue content */
+ bh_unit_assert(BH_PTR_TO_INT(bh_queue_front(queue)) == 1337);
+
+ old_data = queue->data;
+
+ /* Grow queue */
+ bh_unit_assert(bh_queue_reserve(queue, 8192) == 0);
+ bh_unit_assert(queue->capacity >= 8192);
+ bh_unit_assert(queue->size == 1);
+ bh_unit_assert(bh_queue_empty(queue) == 0);
+ bh_unit_assert(queue->data != NULL);
+ bh_unit_assert(queue->data != old_data);
+
+ /* Check queue content */
+ bh_unit_assert(BH_PTR_TO_INT(bh_queue_front(queue)) == 1337);
+
+ old_data = queue->data;
+
+ /* Shrink the queue */
+ bh_unit_assert(bh_queue_reserve(queue, 0) == 0);
+ bh_unit_assert(queue->capacity >= 1 && queue->capacity <= 8192);
+ bh_unit_assert(queue->size == 1);
+ bh_unit_assert(bh_queue_empty(queue) == 0);
+ bh_unit_assert(queue->data != NULL);
+ bh_unit_assert(queue->data != old_data);
+
+ /* Check queue content */
+ bh_unit_assert(BH_PTR_TO_INT(bh_queue_front(queue)) == 1337);
+
+ /* Shrink to 0 (deallocate) */
+ bh_queue_clear(queue);
+ bh_unit_assert(queue->size == 0);
+
+ bh_unit_assert(bh_queue_reserve(queue, 0) == 0);
+ bh_unit_assert(queue->capacity == 0);
+ bh_unit_assert(queue->size == 0);
+ bh_unit_assert(bh_queue_empty(queue) == 1);
+ bh_unit_assert(queue->data == NULL);
+
+ bh_queue_free(queue);
+ return 0;
+}
+
+static int insert_remove(void)
+{
+ bh_queue_t *queue;
+ size_t i, added, removed;
+ void *iter;
+
+ queue = bh_queue_new();
+ bh_unit_assert(queue != NULL);
+
+ added = 0;
+ for (i = 0; i < 256; i++)
+ {
+ added += i * 2;
+ bh_unit_assert(bh_queue_insert(queue, BH_INT_TO_PTR(i * 2)) == 0);
+ }
+
+ removed = 0;
+ iter = bh_queue_iter_next(queue, NULL);
+ while (iter)
+ {
+ removed += BH_PTR_TO_INT(bh_queue_front(queue));
+ bh_queue_remove(queue);
+ iter = bh_queue_iter_next(queue, NULL);
+ }
+
+ bh_unit_assert(added == removed);
+ bh_unit_assert(queue->size == 0);
+
+ bh_queue_free(queue);
+ return 0;
+}
+
+static int rollover(void)
+{
+ bh_queue_t *queue;
+ size_t i, j, capacity;
+
+ queue = bh_queue_new();
+ bh_unit_assert(queue != NULL);
+
+ bh_unit_assert(bh_queue_reserve(queue, 128) == 0);
+ capacity = queue->capacity;
+
+ for (i = 0; i < 128; i++)
+ {
+ for (j = 0; j < 3; j++)
+ bh_queue_remove(queue);
+
+ for (j = 0; j < 4 && queue->size < 128; j++)
+ bh_unit_assert(bh_queue_insert(queue, BH_INT_TO_PTR(i * 4 + j)) == 0);
+ }
+
+ bh_unit_assert(queue->size == 128);
+ bh_unit_assert(queue->capacity == capacity);
+
+ bh_queue_free(queue);
+ return 0;
+}
+
+static int fields(void)
+{
+ bh_queue_t *queue;
+
+ queue = bh_queue_new();
+ bh_unit_assert(queue != NULL);
+
+ bh_unit_assert(bh_queue_insert(queue, BH_INT_TO_PTR(1337)) == 0);
+ bh_unit_assert(queue->size == 1);
+ bh_unit_assert(queue->capacity >= 1);
+ bh_unit_assert(bh_queue_size(queue) == queue->size);
+ bh_unit_assert(bh_queue_capacity(queue) == queue->capacity);
+
+ bh_queue_free(queue);
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ (void)argc;
+ (void)argv;
+
+ bh_unit_add("init_destroy", init_destroy);
+ bh_unit_add("new_free", new_free);
+ bh_unit_add("grow_shrink", grow_shrink);
+ bh_unit_add("insert_remove", insert_remove);
+ bh_unit_add("rollover", rollover);
+ bh_unit_add("fields", fields);
+
+ return bh_unit_run();
+}
diff --git a/unit/CMakeLists.txt b/unit/CMakeLists.txt
new file mode 100644
index 0000000..f1eef3e
--- /dev/null
+++ b/unit/CMakeLists.txt
@@ -0,0 +1,22 @@
+cmake_minimum_required(VERSION 3.10)
+
+# Project and C standard configuration
+project(bhunit LANGUAGES C)
+set(CMAKE_C_STANDARD 90)
+set(CMAKE_C_STANDARD_REQUIRED ON)
+
+# Disable extensions
+set(CMAKE_C_EXTENSIONS OFF)
+
+# Library code
+set(BHUNIT_SOURCE
+ src/unit.c
+)
+
+set(BHUNIT_HEADER
+ include/bh/unit.h
+)
+
+# Library
+add_library(bhunit STATIC ${BHUNIT_SOURCE} ${BHUNIT_HEADER})
+target_include_directories(bhunit PUBLIC include)
diff --git a/unit/include/bh/unit.h b/unit/include/bh/unit.h
new file mode 100644
index 0000000..42d41cd
--- /dev/null
+++ b/unit/include/bh/unit.h
@@ -0,0 +1,24 @@
+#ifndef BHLIB_UNIT_H
+#define BHLIB_UNIT_H
+
+#include <stdio.h>
+
+typedef int (*bh_unit_cb_t)(void);
+
+#define bh_unit_assert(e) \
+ if (!(e)) { \
+ printf("%s:%d\t%s", __FILE__, __LINE__, #e); \
+ return -1; \
+ }
+
+
+#define bh_unit_assert_delta(x, y, e) \
+ if ((((x)>(y))?((x)-(y)):((y)-(x)))>(e)) { \
+ printf("%s:%d\t%s", __FILE__, __LINE__, #x " == " #y); \
+ return -1; \
+ }
+
+void bh_unit_add(const char *name, bh_unit_cb_t func);
+int bh_unit_run(void);
+
+#endif /* BHLIB_UNIT_H */
diff --git a/unit/src/unit.c b/unit/src/unit.c
new file mode 100644
index 0000000..95de080
--- /dev/null
+++ b/unit/src/unit.c
@@ -0,0 +1,55 @@
+#include <bh/unit.h>
+#include <stdlib.h>
+
+typedef struct bh_unit_s
+{
+ struct bh_unit_s *next;
+ const char *name;
+ bh_unit_cb_t func;
+} bh_unit_t;
+
+static bh_unit_t *root = NULL;
+
+void bh_unit_add(const char *name, bh_unit_cb_t func)
+{
+ bh_unit_t *unit, *current;
+
+ unit = malloc(sizeof(*unit));
+ if (!unit)
+ return;
+
+ unit->name = name;
+ unit->func = func;
+ unit->next = NULL;
+
+ current = root;
+ while (current && current->next)
+ current = current->next;
+
+ if (current)
+ current->next = unit;
+ else
+ root = unit;
+}
+
+int bh_unit_run(void)
+{
+ bh_unit_t *current;
+ printf("Running tests...\n");
+
+ current = root;
+ while (current)
+ {
+ printf("%s\n", current->name);
+ if (current->func())
+ {
+ printf("\tFAIL\n");
+ return -1;
+ }
+ printf("\tPASS\n");
+ current = current->next;
+ }
+
+ return 0;
+}
+