diff --git a/.gitignore b/.gitignore index c22744a..f34a7be 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,10 @@ +# ---> Clion +.idea/ + +# ---> Build files and folders +[Bb]uild/ +cmake-build-*/ + # ---> Eclipse .metadata bin/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..0f0c2b5 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.14 FATAL_ERROR) +project(SkullC) + +set(CMAKE_MODULE_PATH ${CMAKE_BINARY_DIR}) +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") + +option(WITH_TESTS "Enable unit testing." OFF) + +if(WITH_TESTS) + enable_testing() +endif() + +add_subdirectory(Utility) diff --git a/Utility/CMakeLists.txt b/Utility/CMakeLists.txt new file mode 100644 index 0000000..03bac34 --- /dev/null +++ b/Utility/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.14 FATAL_ERROR) + +add_library(utility STATIC + Src/utility_logger.cpp +) + +add_library(skullc::utility ALIAS utility) + +target_include_directories(utility + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/Inc +) + +if(WITH_TESTS) + +find_package(Catch2 REQUIRED) + +add_executable(utility-tests + Tests/main.cpp + Tests/ringbuffer.cpp +) + +target_link_libraries(utility-tests + PUBLIC + skullc::utility + Catch2::Catch2 +) + +include(CTest) +include(Catch) +catch_discover_tests(utility-tests) + +endif() diff --git a/Utility/Inc/utility_ringbuffer.hpp b/Utility/Inc/utility_ringbuffer.hpp new file mode 100644 index 0000000..c75292a --- /dev/null +++ b/Utility/Inc/utility_ringbuffer.hpp @@ -0,0 +1,230 @@ +/* + * utility_ringbuffer.hpp + * + * Created on: Mar 12, 2021 + * Author: erki + */ + +#ifndef UTILITY_RINGBUFFER_HPP_ +#define UTILITY_RINGBUFFER_HPP_ + +#include +#include +#include + +namespace Utility +{ + +template +class Ringbuffer +{ +public: + using value_type = T; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; + + struct iterator + { + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = T; + using pointer = value_type*; + using reference = value_type&; + + iterator(const pointer ptr, const pointer begin, const pointer end) + : _ptr(ptr) + , _arr_begin(begin) + , _arr_end(end) + { } + iterator(const iterator&) = default; + iterator(iterator&&) noexcept = default; + + iterator& operator=(const iterator&) = default; + + reference operator*() const + { + return *_ptr; + } + + pointer operator->() + { + return _ptr; + } + + iterator& operator++() + { + _ptr++; + if (_ptr == _arr_end) + _ptr = _arr_begin; + + return *this; + } + + iterator operator++(int) + { + iterator tmp = *this; + ++(*this); + return tmp; + } + + iterator operator+(const difference_type n) const + { + const pointer naive = _ptr + n; + if (naive < _arr_end) + { + return iterator(naive, _arr_begin, _arr_end); + } + else + { + const pointer remainder = pointer(naive - _arr_end); + return iterator(_arr_begin + difference_type(remainder), _arr_begin, _arr_end); + } + } + + iterator operator-(const difference_type n) const + { + const pointer naive = _ptr - n; + if (naive >= _arr_begin) + { + return iterator(naive, _arr_begin, _arr_end); + } + else + { + const pointer remainder = pointer(_arr_begin - naive); + return iterator(_arr_end - difference_type(remainder), _arr_begin, _arr_end); + } + } + + friend bool operator==(const iterator& a, const iterator& b) + { + return a._ptr == b._ptr; + } + + friend bool operator!=(const iterator& a, const iterator& b) + { + return a._ptr != b._ptr; + } + + friend difference_type operator-(const iterator& a, const iterator& b) + { + return a._ptr - b._ptr; + } + + friend difference_type operator+(const iterator& a, const iterator& b) + { + return a._ptr + b._ptr; + } + + private: + pointer _ptr; + pointer _arr_begin; + pointer _arr_end; + }; + + Ringbuffer() + : _head(&_data[0], &_data[0], &_data[N]) + , _tail(_head) + { } + + Ringbuffer(const Ringbuffer&) = delete; + Ringbuffer(Ringbuffer&&) noexcept = default; + + iterator begin() + { + return _head; + } + + iterator end() + { + return _tail; + } + + void clear() + { + _head = _tail; + } + + void push_back(const T& value) + { + if (size() == N) + return; + + *_tail = value; + ++_tail; + } + + template + void emplace_back(Args&&... args) + { + if (size() == N) + return; + + new (&*_tail)T(std::forward(args)...); + return *(_tail++); + } + + void pop_front() + { + if (empty()) + return; + + ++_head; + } + + reference front() + { + return *_head; + } + + const_reference front() const + { + return *_head; + } + + reference back() + { + return *(_tail - 1); + } + + const_reference back() const + { + return *(_tail - 1); + } + + size_type size() const + { + if (_head == _tail) + return 0; + + const typename Ringbuffer::iterator::difference_type distance = _tail - _head; + + if (distance > 0) + { + return distance; + } + else + { + return _head - _tail; + } + } + + bool empty() const + { + return size() == 0; + } + +private: + std::array _data; + + iterator _head; + iterator _tail; +}; + +} + + +#endif /* UTILITY_RINGBUFFER_HPP_ */ diff --git a/Utility/Src/utility_logger.cpp b/Utility/Src/utility_logger.cpp new file mode 100644 index 0000000..942f9ec --- /dev/null +++ b/Utility/Src/utility_logger.cpp @@ -0,0 +1,10 @@ +/* + * utility_logger.cpp + * + * Created on: Mar 13, 2021 + * Author: erki + */ + + + + diff --git a/Utility/Tests/main.cpp b/Utility/Tests/main.cpp new file mode 100644 index 0000000..d39ef67 --- /dev/null +++ b/Utility/Tests/main.cpp @@ -0,0 +1,6 @@ +// +// Created by erki on 13.03.21. +// + +#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file +#include diff --git a/Utility/Tests/ringbuffer.cpp b/Utility/Tests/ringbuffer.cpp new file mode 100644 index 0000000..ddab506 --- /dev/null +++ b/Utility/Tests/ringbuffer.cpp @@ -0,0 +1,250 @@ +// +// Created by erki on 13.03.21. +// + +#include + +#include "utility_ringbuffer.hpp" + +template +using Ringbuffer = Utility::Ringbuffer; + +TEST_CASE("Ringbuffer iterator", "[ringbuffer],[iterator]") +{ + using iterator = Ringbuffer<10>::iterator; + const auto begin = iterator::pointer(10); + const auto end = iterator::pointer(21); + iterator it = iterator(iterator::pointer(15), begin, end); + + SECTION("Incrementing postfix increases its pointer and returns initial.") + { + iterator it_second = it++; + REQUIRE(it - it_second == iterator::difference_type(1)); + } + + SECTION("Incrementing prefix increases its pointer and returns new.") + { + const iterator original = it; + iterator it_second = ++it; + REQUIRE(it_second == it); + REQUIRE(it - original == iterator::difference_type(1)); + } + + SECTION("Subtracting 1 decreases its pointer.") + { + iterator it_second = it - 1; + REQUIRE(it - it_second == iterator::difference_type(1)); + } + + SECTION("Adding 1 increases its pointer.") + { + iterator it_second = it + 1; + REQUIRE(it_second - it == iterator::difference_type(1)); + } +} + +TEST_CASE("Ringbuffer iterator at the end", "[ringbuffer],[iterator]") +{ + using iterator = Ringbuffer<10>::iterator; + const auto begin = iterator::pointer(10); + const auto end = iterator::pointer(21); + iterator it = iterator(end - 1, begin, end); + + const auto it_begin = iterator(begin, begin, end); + + SECTION("Incrementing postfix sets the pointer to begin().") + { + it++; + REQUIRE(it == it_begin); + } + + SECTION("Incrementing prefix sets the pointer to begin().") + { + ++it; + REQUIRE(it == it_begin); + } + + SECTION("Adding 1 sets the pointer to begin().") + { + const iterator it_second = it + 1; + REQUIRE(it_second == it_begin); + } +} + +TEST_CASE("Ringbuffer iterator at the beginning", "[ringbuffer],[iterator]") +{ + using iterator = Ringbuffer<10>::iterator; + const auto begin = iterator::pointer(10); + const auto end = iterator::pointer(21); + iterator it = iterator(begin, begin, end); + + const auto it_last = iterator(end - 1, begin, end); + + SECTION("Subtracting 1 sets the pointer to last element.") + { + const iterator it_second = it - 1; + REQUIRE(it_last == it_second); + } +} + +TEST_CASE("Constructed buffer is empty.", "[ringbuffer]") +{ + Ringbuffer<10> buffer; + + REQUIRE(buffer.begin() == buffer.end()); + REQUIRE(buffer.size() == 0); + REQUIRE(buffer.empty()); +} + +TEST_CASE("Adding single element.", "[ringbuffer]") +{ + Ringbuffer<10> buffer; + const auto old_end = buffer.end(); + const auto old_begin = buffer.begin(); + + buffer.push_back(1); + + SECTION("Increases size and empty appropriately.") + { + REQUIRE(buffer.size() == 1); + REQUIRE(!buffer.empty()); + } + + SECTION("Updates end() appropriately.") + { + REQUIRE(old_end != buffer.end()); + } + + SECTION("begin() remains the same.") + { + REQUIRE(old_begin == buffer.begin()); + } + + SECTION("Makes begin() refer to the inserted member.") + { + REQUIRE(*old_begin == 1); + REQUIRE(*buffer.begin() == 1); + } + + SECTION("Distance between begin and end is 1.") + { + REQUIRE(buffer.end() - buffer.begin() == 1); + } + + SECTION("Makes back() refer to the first element.") + { + REQUIRE(buffer.back() == 1); + } + + SECTION("Makes front() refer to the first element.") + { + REQUIRE(buffer.front() == 1); + } + + SECTION("Makes front() and back() refer to the same memory address.") + { + REQUIRE(&buffer.front() == &buffer.back()); + } +} + +TEST_CASE("Adding multiple elements.", "[ringbuffer]") +{ + Ringbuffer<10> buffer; + const auto old_begin = buffer.begin(); + + buffer.push_back(1); + buffer.push_back(2); + buffer.push_back(3); + + SECTION("Increases size and empty appropriately.") + { + REQUIRE(buffer.size() == 3); + REQUIRE(!buffer.empty()); + } + + SECTION("Makes front() refer to the first element.") + { + REQUIRE(buffer.front() == 1); + } + + SECTION("Makes back() refer to the last element.") + { + REQUIRE(buffer.back() == 3); + } + + SECTION("Updates begin() and end() appropriately.") + { + REQUIRE(old_begin == buffer.begin()); + REQUIRE(buffer.end() - buffer.begin() == 3); + } +} + +TEST_CASE("Removing elements from the ringbuffer.", "[ringbuffer]") +{ + Ringbuffer<10> buffer; + const auto old_begin = buffer.begin(); + + buffer.push_back(1); + buffer.push_back(2); + buffer.push_back(3); + + buffer.pop_front(); + + SECTION("Updates the front() appropriately.") + { + REQUIRE(buffer.front() == 2); + } + + SECTION("Updates begin() appropriately.") + { + REQUIRE(buffer.begin() - old_begin == 1); + } + + SECTION("Updates size() appropriately.") + { + REQUIRE(buffer.size() == 2); + } + + SECTION("Erasing remaining elements") + { + buffer.pop_front(); + buffer.pop_front(); + + SECTION("Updates empty() appropriately.") + { + REQUIRE(buffer.empty()); + } + + SECTION("Updates begin() and end() appropriately.") + { + REQUIRE(buffer.begin() == buffer.end()); + } + + SECTION("Updates size() appropriately.") + { + REQUIRE(buffer.size() == 0); + } + } +} + +TEST_CASE("Clearing a ringbuffer works.", "[ringbuffer]") +{ + Ringbuffer<10> buffer; + + buffer.push_back(1); + buffer.push_back(2); + buffer.push_back(3); + + buffer.clear(); + + SECTION("Updates size() and empty() appropriately.") + { + REQUIRE(buffer.empty()); + REQUIRE(buffer.size() == 0); + } + + SECTION("Sets begin() and end() pointers appropriately.") + { + REQUIRE(buffer.begin() == buffer.end()); + } +} diff --git a/conanfile.txt b/conanfile.txt new file mode 100644 index 0000000..e13ae86 --- /dev/null +++ b/conanfile.txt @@ -0,0 +1,5 @@ +[requires] +catch2/[>=2.0.0,<3.0.0] + +[generators] +cmake_find_package \ No newline at end of file