diff --git a/src/libraries/kutil/linked_list.h b/src/libraries/kutil/linked_list.h new file mode 100644 index 0000000..affe19d --- /dev/null +++ b/src/libraries/kutil/linked_list.h @@ -0,0 +1,253 @@ +#pragma once +/// \file linked_list.h +/// A generic templatized linked list. + +namespace kutil { + +template class linked_list; + + +/// A list node in a `linked_list` or `sortable_linked_list`. +template +class list_node : + public T +{ +public: + using item_type = T; + using node_type = list_node; + + /// Dereference operator. Helper to cast this node to the contained type. + /// \returns A pointer to the node, cast to T*. + inline item_type & operator*() { return *this; } + + /// Dereference operator. Helper to cast this node to the contained type. + /// \returns A pointer to the node, cast to T*. + inline const item_type & operator*() const { return *this; } + + /// Cast operator. Helper to cast this node to the contained type. + /// \returns A reference to the node, cast to T&. + inline operator item_type& () { return *this; } + + /// Cast operator. Helper to cast this node to the contained type. + /// \returns A reference to the node, cast to const T&. + inline operator const item_type& () { return *this; } + + /// Accessor for the next pointer. + /// \returns The next node in the list + inline node_type * next() { return m_next; } + + /// Accessor for the next pointer. + /// \returns The next node in the list + inline const node_type * next() const { return m_next; } + + /// Accessor for the prev pointer. + /// \returns The prev node in the list + inline node_type * prev() { return m_prev; } + + /// Accessor for the prev pointer. + /// \returns The prev node in the list + inline const node_type * prev() const { return m_prev; } + + /// Insert an item after this one in the list. + /// \arg item The item to insert + void insert_after(node_type *item) + { + if (m_next) m_next->m_prev = item; + item->m_next = m_next; + item->m_prev = this; + m_next = item; + } + + /// Insert an item before this one in the list. + /// \arg item The item to insert + void insert_before(node_type *item) + { + if (m_prev) m_prev->m_next = item; + item->m_prev = m_prev; + item->m_next = this; + m_prev = item; + } + + /// Remove this item from its list. + void remove() + { + if (m_next) m_next->m_prev = m_prev; + if (m_prev) m_prev->m_next = m_next; + m_next = m_prev = nullptr; + } + +private: + friend class linked_list; + + node_type *m_next; + node_type *m_prev; +}; + + +/// An iterator for linked lists +template +class list_iterator +{ +public: + using item_type = list_node; + + list_iterator(item_type *item) : m_item(item) {} + + inline T & operator*() { return *m_item; } + inline const T & operator*() const { return *m_item; } + inline list_iterator & operator++() { m_item = m_item ? m_item->next() : nullptr; return *this; } + inline list_iterator operator++(int) { return list_iterator(m_item ? m_item->next() : nullptr); } + inline bool operator!=(const list_iterator &other) { return m_item != other.m_item; } + +private: + item_type *m_item; +}; + + +/// A templatized doubly-linked list container of `list_node` items. +template +class linked_list +{ +public: + using item_type = list_node; + using iterator = list_iterator; + + /// Constructor. Creates an empty list. + linked_list() : + m_head(nullptr), + m_tail(nullptr) + {} + + /// Count the items in the list. + /// \returns The number of entries in the list. + size_t length() + { + size_t len = 0; + for (item_type *cur = m_head; cur; cur = cur->m_next) ++len; + return len; + } + + /// Get the item at the front of the list, without removing it + /// \returns The first item in the list + inline item_type * front() { return m_head; } + + /// Get the item at the back of the list, without removing it + /// \returns The last item in the list + inline item_type * back() { return m_tail; } + + /// Prepend an item to the front of this list. + /// \arg item The node to insert. + void push_front(item_type *item) + { + if (!item) + return; + + if (!m_head) { + m_head = m_tail = item; + item->m_next = item->m_prev = nullptr; + } else { + m_head->m_prev = item; + item->m_next = m_head; + item->m_prev = nullptr; + m_head = item; + } + } + + /// Append an item to the end of this list. + /// \arg item The node to append. + void push_back(item_type *item) + { + if (!item) + return; + + if (!m_tail) { + m_head = m_tail = item; + item->m_next = item->m_prev = nullptr; + } else { + m_tail->m_next = item; + item->m_prev = m_tail; + item->m_next = nullptr; + m_tail = item; + } + } + + /// Remove an item from the front of this list. + /// \returns The node that was removed + item_type * pop_front() + { + item_type *item = m_head; + if (m_head) { + m_head = item->m_next; + item->m_next = nullptr; + } + return item; + } + + /// Remove an item from the end of this list. + /// \returns The node that was removed + item_type * pop_back() + { + item_type *item = m_tail; + if (m_tail) { + m_tail = item->m_prev; + item->m_prev = nullptr; + } + return item; + } + + /// Append the contents of another list to the end of this list. The other + /// list is emptied, and this list takes ownership of its items. + /// \arg list The other list. + void append(linked_list &list) + { + if (!list.m_head) return; + + if (!m_tail) { + m_head = list.m_head; + m_tail = list.m_tail; + } else { + m_tail->m_next = list.m_head; + m_tail = list.m_tail; + } + + list.m_head = list.m_tail = nullptr; + } + + /// Insert an item into the list in a sorted position. Depends on T + /// having a method `int compare(const T *other)`. + /// \arg item The item to insert + void sorted_insert(item_type *item) + { + if (!item) return; + + item_type *cur = m_head; + while (cur && item->compare(cur) > 0) + cur = cur->m_next; + + if (!cur) + push_back(item); + else if (cur == m_head) + push_front(item); + else + cur->insert_before(item); + } + + /// Range-based for iterator generator. + /// \returns An iterator to the beginning of the list + inline iterator begin() { return iterator(m_head); } + + /// Range-based for iterator generator. + /// \returns A const iterator to the beginning of the list + inline const iterator begin() const { return iterator(m_head); } + + /// Range-based for end-iterator generator. + /// \returns An iterator to the end of the list + inline const iterator end() const { return iterator(nullptr); } + +private: + item_type *m_head; + item_type *m_tail; +}; + + +} // namespace kutil diff --git a/src/libraries/kutil/memory.cpp b/src/libraries/kutil/memory.cpp index 7962573..27cc56c 100644 --- a/src/libraries/kutil/memory.cpp +++ b/src/libraries/kutil/memory.cpp @@ -1,11 +1,12 @@ #include "memory.h" #include "memory_manager.h" +#include "type_macros.h" -void * operator new (size_t, void *p) noexcept { return p; } -void * operator new (size_t n) { return kutil::malloc(n); } -void * operator new[] (size_t n) { return kutil::malloc(n); } -void operator delete (void *p) noexcept { return kutil::free(p); } -void operator delete[] (void *p) noexcept { return kutil::free(p); } +__weak void * operator new (size_t, void *p) noexcept { return p; } +__weak void * operator new (size_t n) { return kutil::malloc(n); } +__weak void * operator new[] (size_t n) { return kutil::malloc(n); } +__weak void operator delete (void *p) noexcept { return kutil::free(p); } +__weak void operator delete[] (void *p) noexcept { return kutil::free(p); } namespace kutil { diff --git a/src/libraries/kutil/type_macros.h b/src/libraries/kutil/type_macros.h new file mode 100644 index 0000000..bce08ae --- /dev/null +++ b/src/libraries/kutil/type_macros.h @@ -0,0 +1,3 @@ +#pragma once + +#define __weak __attribute__ ((weak)) diff --git a/src/tests/linked_list.cpp b/src/tests/linked_list.cpp new file mode 100644 index 0000000..aa75a82 --- /dev/null +++ b/src/tests/linked_list.cpp @@ -0,0 +1,123 @@ +#include +#include +#include +#include +#include +#include "kutil/linked_list.h" +#include "catch.hpp" + +using namespace kutil; + +const int test_list_size = 100; + +struct unsortableT { + int value; +}; + +struct sortableT { + int value; + int compare(const sortableT *other) { + return value - other->value; + } +}; + +template +class ListVectorCompare : + public Catch::MatcherBase>> +{ +public: + using item = list_node; + using vector = std::vector; + + ListVectorCompare(const linked_list &list, bool reversed) : + m_list(list), m_reverse(reversed) {} + + virtual bool match (vector const& vec) const override + { + size_t index = m_reverse ? vec.size() - 1 : 0; + for (const T &i : m_list) { + if (&i != &vec[index]) return false; + index += m_reverse ? -1 : 1; + } + return true; + } + + virtual std::string describe() const override + { + return "is the same as the given linked list"; + } + +private: + const linked_list &m_list; + bool m_reverse; +}; + +template +class IsSorted : + public Catch::MatcherBase> +{ +public: + using item = list_node; + using list = linked_list; + + IsSorted() {} + + virtual bool match (list const& l) const override + { + int big = std::numeric_limits::min(); + for (const T &i : l) { + if (i.value < big) return false; + big = i.value; + } + return true; + } + + virtual std::string describe() const override + { + return "is sorted"; + } +}; + +template +ListVectorCompare IsSameAsList(const linked_list &list, bool reversed = false) +{ + return ListVectorCompare(list, reversed); +} + +TEST_CASE( "Linked list tests", "[containers list]" ) +{ + using clock = std::chrono::system_clock; + unsigned seed = clock::now().time_since_epoch().count(); + std::default_random_engine rng(seed); + std::uniform_int_distribution gen(1, 1000); + + linked_list ulist; + + std::vector> unsortables(test_list_size); + for (auto &i : unsortables) { + i.value = gen(rng); + ulist.push_back(&i); + } + CHECK( ulist.length() == test_list_size ); + CHECK_THAT( unsortables, IsSameAsList(ulist) ); + + linked_list ulist_reversed; + + for (auto &i : unsortables) { + i.remove(); + ulist_reversed.push_front(&i); + } + + CHECK( ulist_reversed.length() == test_list_size ); + CHECK_THAT( unsortables, IsSameAsList(ulist_reversed, true) ); + + linked_list slist; + + std::vector> sortables(test_list_size); + for (auto &i : sortables) { + i.value = gen(rng); + slist.sorted_insert(&i); + } + CHECK( slist.length() == test_list_size ); + CHECK_THAT( slist, IsSorted() ); +} diff --git a/src/tests/main.cpp b/src/tests/main.cpp index 849106b..af78789 100644 --- a/src/tests/main.cpp +++ b/src/tests/main.cpp @@ -1,9 +1,9 @@ #define CATCH_CONFIG_MAIN #include "catch.hpp" -// kutil malloc/free stubs #include -namespace kutil { - void * malloc(size_t n) { return ::malloc(n); } - void free(void *p) { ::free(p); } -} +void * operator new (size_t n) { return ::malloc(n); } +void * operator new[] (size_t n) { return ::malloc(n); } +void operator delete (void *p) noexcept { return ::free(p); } +void operator delete[] (void *p) noexcept { return ::free(p); } + diff --git a/wscript b/wscript index f870669..f2709f7 100644 --- a/wscript +++ b/wscript @@ -203,7 +203,7 @@ def configure(ctx): for mod_path in ctx.env.LIBRARIES: ctx.recurse(mod_path) - for mod_path in ctx.env.LIBRARIES: + for mod_path in ctx.env.TOOLS: ctx.recurse(mod_path) ## Image configuration @@ -225,6 +225,7 @@ def configure(ctx): for mod_path in ctx.env.LIBRARIES: ctx.recurse(mod_path) + ctx.recurse(join("src", "tests")) @@ -358,6 +359,13 @@ def build(bld): copy_part.set_outputs([disk]) bld.add_to_group(copy_part) + ## Tests + # + elif bld.variant == 'tests': + for mod_path in bld.env.LIBRARIES: + bld.recurse(mod_path) + bld.recurse(join("src", "tests")) + def test(bld): from os.path import join