diff --git a/.gitignore b/.gitignore index 976f52b..c828854 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,4 @@ build .direnv result /debug.c +a.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 93e5b0e..332a9de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,16 @@ cmake_minimum_required(VERSION 3.10) -project(tinyhash VERSION 0.0.1 LANGUAGES C DESCRIPTION "Simple C hash table implementation.") +project(tinyhash VERSION 0.0.1 LANGUAGES C DESCRIPTION "This is a library containing multiple C implementations of hashmap.") + +option(BUILD_TESTS "Build the tests" OFF) +option(BUILD_DOC "Build the documentation" OFF) add_subdirectory(src) if (BUILD_TESTS) add_subdirectory(tests) endif () + +if (BUILD_DOC) + add_subdirectory(doc) +endif () \ No newline at end of file diff --git a/README.md b/README.md index 7433957..b0ab85b 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ The following CMake build arguments are available to enable or disable options. | -- | -- | -- | | `-DBUILD_TESTS` | Compile the test files | **`ON`** | `-DBUILD_STATIC` | Link as a static library (instead of a shared library) | **`OFF`** +| `-DBUILD_DOC` | Build the documentation | **`OFF`** ## 🤝 Contribute @@ -48,6 +49,12 @@ The generated Makefile will contain a special `test` target, so you can run the make test ``` +## 📝 Documentation + +Just make sure you run CMake with the `-DBUILD_DOC=ON` flag. + +The Makefile `all` target will automatically build the documentation. + ## 📎 Some examples Here is a basic example of how you could use the hashmap. diff --git a/default.nix b/default.nix index 9649823..6288c3b 100644 --- a/default.nix +++ b/default.nix @@ -2,9 +2,12 @@ stdenv, lib, cmake, + doxygen, + graphviz, enableStatic ? false, enableShared ? !enableStatic, enaleTests ? true, + enableDoc ? true, }: assert enableShared || enableStatic; @@ -17,16 +20,30 @@ stdenv.mkDerivation (finalAttrs: { cmakeFlags = lib.optional enaleTests "-DBUILD_TESTS=ON" - ++ lib.optional enableStatic "-DBUILD_STATIC=ON"; + ++ lib.optional enableStatic "-DBUILD_STATIC=ON" + ++ lib.optional enableDoc '' + -DBUILD_DOC=ON + -DDOT_BIN_PATH=${graphviz}/bin/dot + ''; nativeBuildInputs = [ cmake ]; - postBuild = '' + buildInputs = [ doxygen ]; + + postBuild = lib.optionals enaleTests '' make test ''; + postInstall = lib.optionals enaleTests '' + mkdir $out/doc + + for dir in "html" "latex"; do + mv doc/$dir $out/doc/$dir + done + ''; + meta = { - description = "Simple C hash table implementation."; + description = "This is a library containing multiple C implementations of hashmap."; homepage = "https://github.com/theobori/${finalAttrs.pname}"; license = lib.licenses.mit; }; diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt new file mode 100644 index 0000000..a141d21 --- /dev/null +++ b/doc/CMakeLists.txt @@ -0,0 +1,41 @@ +find_package(Doxygen) + + +find_program(DOT_BIN NAMES dot) + +if(DOT_BIN) + set(DOT_BIN_PATH "${DOT_BIN}" CACHE PATH "Path to dot") +else() + set(DOT_BIN_PATH "" CACHE PATH "Path to dot") +endif() + +if(DOXYGEN_FOUND) + message(STATUS "Doxygen found: ${DOXYGEN_EXECUTABLE} -- ${DOXYGEN_VERSION}") + + # set Doxygen input and output files. + set(DOXYGEN_INPUT_DIR ${PROJECT_SOURCE_DIR}/src) + set(DOXYGEN_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/doxygen) + set(DOXYGEN_INDEX_FILE ${DOXYGEN_OUTPUT_DIR}/xml/index.xml) + set(DOXYFILE_IN ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in) + set(DOXYFILE_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) + + + # Generate DoxyFile from the input file. + configure_file(${DOXYFILE_IN} ${DOXYFILE_OUT} @ONLY) + + # Create Output directory. + file(MAKE_DIRECTORY ${DOXYGEN_OUTPUT_DIR}) + + + # Command for generating doc from Doxygen config file. + add_custom_command(OUTPUT ${DOXYGEN_INDEX_FILE} + COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE_OUT} + MAIN_DEPENDENCY ${DOXYFILE_OUT} ${DOXYFILE_IN} + COMMENT "Generating Doxygen documentation" + VERBATIM) + + + # Create CMake Target for generating doc. + add_custom_target(docs ALL DEPENDS ${DOXYGEN_INDEX_FILE}) + +endif() diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in new file mode 100644 index 0000000..6a39c51 --- /dev/null +++ b/doc/Doxyfile.in @@ -0,0 +1,35 @@ +PROJECT_NAME = "Tinyhash" +PROJECT_BRIEF = "This is a library containing multiple C implementations of hashmap." +EXTRACT_ALL = YES +DISTRIBUTE_GROUP_DOC = YES +EXTRACT_PRIVATE = YES +EXTRACT_PACKAGE = YES +EXTRACT_STATIC = YES +EXTRACT_ANON_NSPACES = YES +WARN_NO_PARAMDOC = YES +RECURSIVE = YES +SOURCE_BROWSER = YES +REFERENCED_BY_RELATION = YES +REFERENCES_RELATION = YES +REFERENCES_LINK_SOURCE = NO +GENERATE_TREEVIEW = NO + +HAVE_DOT = YES +DOT_GRAPH_MAX_NODES = 50 +MAX_DOT_GRAPH_DEPTH = 0 +DOT_PATH = "@DOT_BIN_PATH@" + +CALL_GRAPH = YES +CALLER_GRAPH = YES +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +GROUP_GRAPHS = YES +UML_LOOK = YES +TEMPLATE_RELATIONS = YES +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +DIRECTORY_GRAPH = YES +DOT_CLEANUP = YES + +INPUT = "@DOXYGEN_INPUT_DIR@" @PROJECT_SOURCE_DIR@/README.md +USE_MDFILE_AS_MAINPAGE = "@PROJECT_SOURCE_DIR@/README.md" diff --git a/flake.nix b/flake.nix index fc157a5..5c93e1f 100644 --- a/flake.nix +++ b/flake.nix @@ -31,6 +31,8 @@ packages = with pkgs; [ cmake clang + doxygen + graphviz ]; }; } diff --git a/src/common/hash.c b/src/common/hash.c index 177e22c..9255f7e 100644 --- a/src/common/hash.c +++ b/src/common/hash.c @@ -1,7 +1,16 @@ #include #include +/** + * @brief Initial value for hash function. + * + */ #define TH_HASH_INITIAL_VALUE 2166136261u + +/** + * @brief Multiplier for hash function. + * + */ #define TH_HASH_MUL_VALUE 16777619 uint32_t th_hash(uint8_t *bytes, size_t size) { diff --git a/src/common/hash.h b/src/common/hash.h index cdedca0..f537da1 100644 --- a/src/common/hash.h +++ b/src/common/hash.h @@ -4,6 +4,13 @@ #include #include +/** + * @brief Compute an unsigned int from bytes. + * + * @param bytes + * @param size + * @return uint32_t + */ uint32_t th_hash(uint8_t *bytes, size_t size); #endif diff --git a/src/common/key.c b/src/common/key.c index 5218541..70e3167 100644 --- a/src/common/key.c +++ b/src/common/key.c @@ -1,8 +1,7 @@ -#include "key.h" - #include #include "hash.h" +#include "key.h" th_key_t th_key_create(th_any_t data, size_t size) { return (th_key_t){ @@ -13,6 +12,9 @@ th_key_t th_key_create(th_any_t data, size_t size) { } bool th_key_is_equal(th_key_t *first, th_key_t *second) { + if (first == NULL || second == NULL) + return false; + if (first->size != second->size) return false; diff --git a/src/common/key.h b/src/common/key.h index f208b2f..cdeebe7 100644 --- a/src/common/key.h +++ b/src/common/key.h @@ -7,14 +7,34 @@ #include "types.h" +/** + * @brief Represent an entry key. + * + */ typedef struct { uint32_t hash; size_t size; th_any_t data; } th_key_t; +/** + * @brief Create a key struct from data and size, it will automatically + * compute its hash. + * + * @param data + * @param size + * @return th_key_t + */ th_key_t th_key_create(th_any_t data, size_t size); +/** + * @brief Key comparator function. + * + * @param first + * @param second + * @return true + * @return false + */ bool th_key_is_equal(th_key_t *first, th_key_t *second); #endif diff --git a/src/common/table.h b/src/common/table.h index c0bf2e0..8d499ad 100644 --- a/src/common/table.h +++ b/src/common/table.h @@ -1,6 +1,10 @@ #ifndef __TINYHASH_COMMON_TABLE_H__ #define __TINYHASH_COMMON_TABLE_H__ +/** + * @brief Generate the next capacity value absed on a previous one. + * + */ #define TH_TABLE_NEXT_CAPACITY(capacity) (capacity) == 0 ? 8 : (capacity) * 2 #endif diff --git a/src/common/types.h b/src/common/types.h index 67e856b..c92b4de 100644 --- a/src/common/types.h +++ b/src/common/types.h @@ -1,8 +1,16 @@ #ifndef __TINYHASH_COMMON_TYPES_H__ #define __TINYHASH_COMMON_TYPES_H__ +/** + * @brief Represent any type of data. + * + */ typedef void *th_any_t; +/** + * @brief Represents any table. + * + */ typedef void *th_generic_table_t; #endif diff --git a/src/open_addressing/entry.h b/src/open_addressing/entry.h index df078d4..219f320 100644 --- a/src/open_addressing/entry.h +++ b/src/open_addressing/entry.h @@ -8,6 +8,10 @@ #include "../common/hash.h" #include "../common/key.h" +/** + * @brief Represent an entry within a bucket. + * + */ typedef struct th_oa_entry_s { th_key_t *key; th_any_t value; diff --git a/src/open_addressing/table.c b/src/open_addressing/table.c index be39646..6d0629a 100644 --- a/src/open_addressing/table.c +++ b/src/open_addressing/table.c @@ -17,6 +17,12 @@ void th_oa_table_init(th_oa_table_t *table) { table->entries = NULL; } +/** + * @brief Allocate then initialize an open addressing table. + * It can return NULL. + * + * @return th_oa_table_t* + */ static th_oa_table_t *_th_oa_table_create() { th_oa_table_t *table = malloc(sizeof(th_oa_table_t)); @@ -32,6 +38,15 @@ th_generic_table_t th_oa_table_create() { return (th_generic_table_t)_th_oa_table_create(); } +/** + * @brief Copy and rehash every value from a table to another. + * Return true on success. + * + * @param dest + * @param src + * @return true + * @return false + */ static bool th_oa_table_copy(th_oa_table_t *dest, th_oa_table_t *src) { bool success; @@ -50,6 +65,13 @@ static bool th_oa_table_copy(th_oa_table_t *dest, th_oa_table_t *src) { return true; } +/** + * @brief Increase the size of a table. + * + * @param table + * @return true + * @return false + */ static bool th_oa_table_increase(th_oa_table_t *table) { th_oa_table_t new_table; bool success; @@ -77,6 +99,14 @@ static bool th_oa_table_increase(th_oa_table_t *table) { return true; } +/** + * @brief Returns a bucket (entry) depending on a key. + * It can return a tomstone or an empty bucket. + * + * @param table + * @param key + * @return th_oa_entry_t* + */ static th_oa_entry_t *th_oa_table_find(th_oa_table_t *table, th_key_t *key) { int index = key->hash % table->capacity; @@ -113,9 +143,18 @@ th_any_t th_oa_table_get(th_generic_table_t generic_table, th_any_t data, return entry->value; } +/** + * @brief Insert a value within the table with an already existing key. + * + * @param table + * @param key + * @param value + * @return true + * @return false + */ static bool th_oa_table_put_with_key(th_oa_table_t *table, th_key_t *key, th_any_t value) { - if (table->count >= (table->capacity * TH_OA_LOAD_FACTOR)) { + if (table->count >= table->capacity * TH_OA_LOAD_FACTOR) { if (th_oa_table_increase(table) == false) return false; } @@ -139,6 +178,9 @@ static bool th_oa_table_put_with_key(th_oa_table_t *table, th_key_t *key, bool th_oa_table_put(th_generic_table_t generic_table, th_any_t data, size_t data_size, th_any_t value) { + if (generic_table == NULL) + return NULL; + th_oa_table_t *table = (th_oa_table_t *)generic_table; th_key_t key = th_key_create(data, data_size); diff --git a/src/open_addressing/table.h b/src/open_addressing/table.h index 5c8a2e4..aac6f75 100644 --- a/src/open_addressing/table.h +++ b/src/open_addressing/table.h @@ -8,6 +8,10 @@ #include "../common/types.h" #include "entry.h" +/** + * @brief Load factor. + * + */ #define TH_OA_LOAD_FACTOR 0.75 typedef struct { @@ -16,18 +20,63 @@ typedef struct { th_oa_entry_t *entries; } th_oa_table_t; +/** + * @brief Initialize open adressing table values. + * + * @param table + */ void th_oa_table_init(th_oa_table_t *table); +/** + * @brief Return an allocated open addressing table struct. + * + * @return th_generic_table_t + */ th_generic_table_t th_oa_table_create(); +/** + * @brief Get a value from an open addressing table. + * Return NULL if it does exist. + * + * @param table + * @param data + * @param data_size + * @return th_any_t + */ th_any_t th_oa_table_get(th_generic_table_t table, th_any_t data, size_t data_size); +/** + * @brief Insert a value within an open addressing table. + * Return true on success. + * + * @param table + * @param data + * @param data_size + * @param value + * @return true + * @return false + */ bool th_oa_table_put(th_generic_table_t table, th_any_t data, size_t data_size, th_any_t value); +/** + * @brief Free an open addressing table. + * + * @param table + */ void th_oa_table_free(th_generic_table_t table); +/** + * @brief Delete a key value pair in an open addressing table. + * Return true on success. + * + * @param table + * @param data + * @param data_size + * @return true + * @return false + */ bool th_oa_table_delete(th_generic_table_t table, th_any_t data, size_t data_size); diff --git a/src/separate_chaining/entry.c b/src/separate_chaining/entry.c index 9721362..57c8fcf 100644 --- a/src/separate_chaining/entry.c +++ b/src/separate_chaining/entry.c @@ -1,5 +1,12 @@ #include "entry.h" +/** + * @brief Allocate then initialize a new entry. + * + * @param key + * @param value + * @return th_sc_entry_t* + */ static th_sc_entry_t *th_sc_entry_new(th_key_t *key, th_any_t value) { th_sc_entry_t *entry = malloc(sizeof(th_sc_entry_t)); diff --git a/src/separate_chaining/entry.h b/src/separate_chaining/entry.h index efcc23e..ed905d3 100644 --- a/src/separate_chaining/entry.h +++ b/src/separate_chaining/entry.h @@ -8,6 +8,10 @@ #include "../common/hash.h" #include "../common/key.h" +/** + * @brief Represents a separate chaining entry. + * + */ typedef struct th_sc_entry_s { th_key_t key; th_any_t value; @@ -15,6 +19,16 @@ typedef struct th_sc_entry_s { struct th_sc_entry_s *next; } th_sc_entry_t; +/** + * @brief Add an a separate chaining entry to the beginning of a linked list. + * Return true on success. + * + * @param root + * @param key + * @param value + * @return true + * @return false + */ bool th_sc_entry_add(th_sc_entry_t **root, th_key_t *key, th_any_t value); #endif diff --git a/src/separate_chaining/table.c b/src/separate_chaining/table.c index 14924ec..b552c5f 100644 --- a/src/separate_chaining/table.c +++ b/src/separate_chaining/table.c @@ -16,6 +16,11 @@ void th_sc_table_init(th_sc_table_t *table) { table->entries = NULL; } +/** + * @brief Allocate then return a new table. + * + * @return th_sc_table_t* + */ static th_sc_table_t *_th_sc_table_create() { th_sc_table_t *table = malloc(sizeof(th_sc_table_t)); @@ -31,6 +36,15 @@ th_generic_table_t th_sc_table_create() { return (th_generic_table_t)_th_sc_table_create(); } +/** + * @brief Copy and rehash every value from a table to another. + * Return true on success. + * + * @param dest + * @param src + * @return true + * @return false + */ static bool th_sc_table_copy(th_sc_table_t *dest, th_sc_table_t *src) { bool success; @@ -50,6 +64,13 @@ static bool th_sc_table_copy(th_sc_table_t *dest, th_sc_table_t *src) { return true; } +/** + * @brief Increase the size of a table. + * + * @param table + * @return true + * @return false + */ static bool th_sc_table_increase(th_sc_table_t *table) { th_sc_table_t new_table; bool success; @@ -77,6 +98,14 @@ static bool th_sc_table_increase(th_sc_table_t *table) { return true; } +/** + * @brief Returns a bucket (entry) depending on a key. + * Return NULL if the entry does not exist. + * + * @param table + * @param key + * @return th_sc_entry_t* + */ static th_sc_entry_t *th_sc_table_find(th_sc_table_t *table, th_key_t *key) { // Avoid division by 0 if (table->capacity == 0) @@ -108,6 +137,15 @@ th_any_t th_sc_table_get(th_generic_table_t generic_table, th_any_t data, return entry->value; } +/** + * @brief Insert a value within the table with an already existing key. + * + * @param table + * @param key + * @param value + * @return true + * @return false + */ static bool th_sc_table_put_with_key(th_sc_table_t *table, th_key_t *key, th_any_t value) { if (table->count >= table->capacity) { diff --git a/src/separate_chaining/table.h b/src/separate_chaining/table.h index 2c38c6e..9244472 100644 --- a/src/separate_chaining/table.h +++ b/src/separate_chaining/table.h @@ -8,24 +8,73 @@ #include "../common/types.h" #include "entry.h" +/** + * @brief Represent a separate chaining table. + * + */ typedef struct { uint32_t count; uint32_t capacity; th_sc_entry_t **entries; } th_sc_table_t; +/** + * @brief Initialize a separate chaining table. + * + * @param table + */ void th_sc_table_init(th_sc_table_t *table); +/** + * @brief Return an allocated separate chaining table. + * + * @return th_generic_table_t + */ th_generic_table_t th_sc_table_create(); +/** + * @brief Get a value from an separate chaining table. + * Return NULL if it does exist. + * + * @param table + * @param data + * @param data_size + * @return th_any_t + */ th_any_t th_sc_table_get(th_generic_table_t table, th_any_t data, size_t data_size); +/** + * @brief Insert a value within an separate chaining table. + * Return true on success. + * + * @param table + * @param data + * @param data_size + * @param value + * @return true + * @return false + */ bool th_sc_table_put(th_generic_table_t table, th_any_t data, size_t data_size, th_any_t value); +/** + * @brief Free an separate chaining table. + * + * @param table + */ void th_sc_table_free(th_generic_table_t table); +/** + * @brief Delete a key value pair in an separate chaining table. + * Return true on success. + * + * @param table + * @param data + * @param data_size + * @return true + * @return false + */ bool th_sc_table_delete(th_generic_table_t table, th_any_t data, size_t data_size); diff --git a/src/tinyhash.c b/src/tinyhash.c index 66f5537..d62c8d7 100644 --- a/src/tinyhash.c +++ b/src/tinyhash.c @@ -3,6 +3,11 @@ #include "./open_addressing/table.h" #include "./separate_chaining/table.h" +/** + * @brief Static array containing functions that accomplish hashmap operation + * associated with an implementation method. + * + */ static th_funcs_t th_funcs[] = { [TH_SEPARATE_CHAINING] = { @@ -23,7 +28,14 @@ static th_funcs_t th_funcs[] = { }, }; +static size_t th_funcs_length = sizeof(th_funcs) / sizeof(th_funcs[0]); + +th_t th_create_default() { return th_create(TH_SEPARATE_CHAINING); } + th_t th_create(th_method_t method) { + if (method < 0 || method >= th_funcs_length) + return th_create_default(); + th_funcs_t funcs = th_funcs[method]; return (th_t){ @@ -33,8 +45,6 @@ th_t th_create(th_method_t method) { }; } -th_t th_create_default() { return th_create(TH_SEPARATE_CHAINING); } - th_any_t th_get(th_t *th, th_any_t data, size_t data_size) { return th->funcs.get(th->table, data, data_size); } diff --git a/src/tinyhash.h b/src/tinyhash.h index 7040f29..9fa1e9b 100644 --- a/src/tinyhash.h +++ b/src/tinyhash.h @@ -6,21 +6,50 @@ #include "./common/types.h" +/** + * @brief Implementation methods. + * + */ typedef enum { TH_SEPARATE_CHAINING, TH_OPEN_ADRESSING, } th_method_t; +/** + * @brief Pointer on generic function to create a table. + * + */ typedef th_generic_table_t (*th_create_func_t)(void); +/** + * @brief Pointer on generic function to get a value from a table. + * + */ typedef th_any_t (*th_get_func_t)(th_generic_table_t, th_any_t, size_t); +/** + * @brief Pointer on generic function to insert a value into a table. + * + */ typedef bool (*th_put_func_t)(th_generic_table_t, th_any_t, size_t, th_any_t); +/** + * @brief Pointer on generic function to delete a value from a table. + * + */ typedef bool (*th_delete_func_t)(th_generic_table_t, th_any_t, size_t); +/** + * @brief Pointer on generic function to free a table. + * + */ typedef void (*th_free_func_t)(th_generic_table_t); +/** + * @brief Centralizing every function associated with + * an unique implementation method. + * + */ typedef struct { th_create_func_t create; th_get_func_t get; @@ -29,22 +58,74 @@ typedef struct { th_free_func_t _free; } th_funcs_t; +/** + * @brief Represent a hashmap controller. + * + */ typedef struct { th_method_t method; th_funcs_t funcs; th_generic_table_t table; } th_t; +/** + * @brief Allocate then initialize a hashmap controller. + * based on the given method. + * + * @param method + * @return th_t + */ th_t th_create(th_method_t method); +/** + * @brief Allocate then initialize a hashmap controller + * with its default values. + * + * @return th_t + */ th_t th_create_default(); +/** + * @brief Returns a value from a hashmap. + * Return NULL if it doest not exist. + * + * @param th + * @param data + * @param data_size + * @return th_any_t + */ th_any_t th_get(th_t *th, th_any_t data, size_t data_size); +/** + * @brief Insert element within the hashmap. + * Return true on success. + * + * @param th + * @param data + * @param data_size + * @param value + * @return true + * @return false + */ bool th_put(th_t *th, th_any_t data, size_t data_size, th_any_t value); +/** + * @brief Delete a key value pair from a hashmap. + * Return true on success. + * + * @param th + * @param data + * @param data_size + * @return true + * @return false + */ bool th_delete(th_t *th, th_any_t data, size_t data_size); +/** + * @brief Free a hashmap. + * + * @param th + */ void th_free(th_t *th); #endif diff --git a/tests/test_table.c b/tests/test_table.c index 05d1f60..eb28876 100644 --- a/tests/test_table.c +++ b/tests/test_table.c @@ -180,12 +180,6 @@ MunitResult test_th_delete(const MunitParameter params[], void *data) { th_put(&th, keys[i], strlen(keys[i]), (th_any_t)(uint64_t)i + 1); } - // Check everything exists - for (int i = 0; i < keys_length; i++) { - value = th_get(&th, keys[i], strlen(keys[i])); - munit_assert_not_null(value); - } - ok = th_delete(&th, "a", strlen("a")); munit_assert_true(ok);