diff options
author | Dominick Allen <djallen@librehumanitas.org> | 2025-03-30 23:08:43 -0500 |
---|---|---|
committer | Dominick Allen <djallen@librehumanitas.org> | 2025-03-30 23:08:43 -0500 |
commit | cb9fa588ba8144fcdd52ba4b83d69d93fb18066f (patch) | |
tree | 214574ca68c1551ec76e7fbb9e0263793180231d | |
parent | 1d357adfa19725ee69fb267a363f1fd217b1272f (diff) |
Add hash map.
-rw-r--r-- | .clang-tidy | 2 | ||||
-rw-r--r-- | CMakeLists.txt | 6 | ||||
-rw-r--r-- | README.org | 4 | ||||
-rw-r--r-- | include/fud_algorithm.hpp | 13 | ||||
-rw-r--r-- | include/fud_allocator.hpp | 16 | ||||
-rw-r--r-- | include/fud_array.hpp | 7 | ||||
-rw-r--r-- | include/fud_assert.hpp | 2 | ||||
-rw-r--r-- | include/fud_drain.hpp | 2 | ||||
-rw-r--r-- | include/fud_format.hpp | 8 | ||||
-rw-r--r-- | include/fud_hash.hpp | 77 | ||||
-rw-r--r-- | include/fud_hash_map.hpp | 389 | ||||
-rw-r--r-- | include/fud_option.hpp | 39 | ||||
-rw-r--r-- | include/fud_result.hpp | 21 | ||||
-rw-r--r-- | include/fud_sqlite.hpp | 55 | ||||
-rw-r--r-- | include/fud_string.hpp | 6 | ||||
-rw-r--r-- | include/fud_string_view_format.hpp | 31 | ||||
-rw-r--r-- | include/fud_utf8.hpp | 5 | ||||
-rw-r--r-- | include/fud_vector.hpp | 62 | ||||
-rw-r--r-- | source/fud_hash.cpp | 53 | ||||
-rw-r--r-- | source/fud_sqlite.cpp | 175 | ||||
-rw-r--r-- | source/fud_string.cpp | 11 | ||||
-rw-r--r-- | test/.clang-tidy | 2 | ||||
-rw-r--r-- | test/CMakeLists.txt | 1 | ||||
-rw-r--r-- | test/test_directory.cpp | 4 | ||||
-rw-r--r-- | test/test_file.cpp | 4 | ||||
-rw-r--r-- | test/test_format.cpp | 160 | ||||
-rw-r--r-- | test/test_hash_map.cpp | 146 | ||||
-rw-r--r-- | test/test_option.cpp | 8 | ||||
-rw-r--r-- | test/test_vector.cpp | 32 |
29 files changed, 1157 insertions, 184 deletions
diff --git a/.clang-tidy b/.clang-tidy index d6fa925..60c819b 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,5 +1,5 @@ --- -Checks: 'clang-diagnostic-*,clang-analyzer-*,readability*,-readability-use-anyofallof,bugprone*,-bugprone-easily-swappable-parameters,deadcode,cppcoreguidelines*,-cppcoreguidelines-avoid-magic-numbers,-cppcoreguidelines-avoid-do-while,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-cppcoreguidelines-owning-memory,-cppcoreguidelines-non-private-member-variables-in-classes,modernize-*,-modernize-pass-by-value,-modernize-use-trailing-return-type,-modernize-avoid-c-arrays,performance*,-cppcoreguidelines-pro-bounds-constant-array-index,-cppcoreguidelines-pro-type-union-access,-clang-diagnostic-unknown-warning-option,-clang-analyzer-valist*,-modernize-use-designated-initializers,-readability-redundant-member-init' +Checks: 'clang-diagnostic-*,clang-analyzer-*,readability*,-readability-use-anyofallof,bugprone*,-bugprone-easily-swappable-parameters,deadcode,cppcoreguidelines*,-cppcoreguidelines-avoid-magic-numbers,-cppcoreguidelines-avoid-do-while,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-cppcoreguidelines-owning-memory,-cppcoreguidelines-non-private-member-variables-in-classes,modernize-*,-modernize-pass-by-value,-modernize-use-trailing-return-type,-modernize-avoid-c-arrays,performance*,-cppcoreguidelines-pro-bounds-constant-array-index,-cppcoreguidelines-pro-type-union-access,-clang-diagnostic-unknown-warning-option,-clang-analyzer-valist*,-modernize-use-designated-initializers,-readability-redundant-member-init,-cppcoreguidelines-use-default-member-init,-modernize-use-default-member-init' WarningsAsErrors: '' HeaderFileExtensions: - '' diff --git a/CMakeLists.txt b/CMakeLists.txt index 465e35d..7084262 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,7 @@ add_library(fud SHARED source/fud_directory.cpp source/fud_file.cpp source/fud_format.cpp + source/fud_hash.cpp source/fud_memory.cpp source/fud_print.cpp source/fud_sqlite.cpp @@ -112,8 +113,9 @@ set(FUD_HEADERS "include/fud_drain.hpp" "include/fud_file.hpp" "include/fud_fixed_vector.hpp" - "include/fud_type_traits.hpp" "include/fud_format.hpp" + "include/fud_hash.hpp" + "include/fud_hash_map.hpp" "include/fud_memory.hpp" "include/fud_option.hpp" "include/fud_permissions.hpp" @@ -124,8 +126,10 @@ set(FUD_HEADERS "include/fud_string.hpp" "include/fud_string_convert.hpp" "include/fud_string_view.hpp" + "include/fud_string_view_format.hpp" "include/fud_sqlite.hpp" "include/fud_text.hpp" + "include/fud_type_traits.hpp" "include/fud_unique_array.hpp" "include/fud_utf8.hpp" "include/fud_utf8_iterator.hpp" @@ -29,3 +29,7 @@ user controlled allocators. + Formatting à la =std::format= + Wrappers around C files + CSV parsing + +** TODO list: ++ Convert from cmake to meson ++ Convert from gtest to Catch2 diff --git a/include/fud_algorithm.hpp b/include/fud_algorithm.hpp index 01cc5d3..82e98cb 100644 --- a/include/fud_algorithm.hpp +++ b/include/fud_algorithm.hpp @@ -23,7 +23,6 @@ #include <concepts> #include <limits> -#include <type_traits> namespace fud { @@ -36,11 +35,23 @@ concept LessThanComparable = template <LessThanComparable T> inline const T& min(const T& lhs, const T& rhs) { if (lhs < rhs) { + // NOLINTNEXTLINE(bugprone-return-const-ref-from-parameter) return lhs; } + // NOLINTNEXTLINE(bugprone-return-const-ref-from-parameter) return rhs; } +template <LessThanComparable T> +inline const T& max(const T& lhs, const T& rhs) { + if (lhs < rhs) { + // NOLINTNEXTLINE(bugprone-return-const-ref-from-parameter) + return rhs; + } + // NOLINTNEXTLINE(bugprone-return-const-ref-from-parameter) + return lhs; +} + template <std::integral T> class Iota { public: diff --git a/include/fud_allocator.hpp b/include/fud_allocator.hpp index 99b33ce..8940dcd 100644 --- a/include/fud_allocator.hpp +++ b/include/fud_allocator.hpp @@ -79,34 +79,30 @@ extern NullAllocator globalNullAllocator; template <size_t Size> class SimpleStackAllocator final : public Allocator { private: + size_t m_allocated{0}; // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) std::byte m_memory[Size]{}; - size_t m_allocated{0}; public: ~SimpleStackAllocator() final = default; Result<std::byte*, FudStatus> allocate(size_t bytes, size_t alignment = alignof(std::max_align_t)) final { - using RetType = Result<std::byte*, FudStatus>; static_cast<void>(alignment); - if (bytes > Size - m_allocated) { - return RetType::error(FudStatus::AllocFailure); + if (bytes > (Size - m_allocated)) { + return Error{FudStatus::AllocFailure}; } auto* data = m_memory + m_allocated; m_allocated += bytes; - return RetType::okay(data); + return Okay{data}; } void deallocate(std::byte* pointer, size_t bytes) final { - if (pointer + bytes != m_memory + m_allocated) { - m_allocated = Size; - return; - } - m_allocated -= bytes; + static_cast<void>(pointer); + static_cast<void>(bytes); } [[nodiscard]] bool isEqual(const Allocator& rhs) const final diff --git a/include/fud_array.hpp b/include/fud_array.hpp index dcbd54a..506ab27 100644 --- a/include/fud_array.hpp +++ b/include/fud_array.hpp @@ -104,7 +104,7 @@ struct Array { return m_data[index]; } - constexpr bool operator==(const Array<T, Size>&) const noexcept = default; + bool operator==(const Array<T, Size>&) const noexcept = default; constexpr auto operator<=>(const Array<T, Size>& other) const noexcept = default; @@ -112,6 +112,11 @@ struct Array { { return Span<T, Size>{data(), Size}; } + + Span<T> dynSpan() + { + return Span<T>{data(), Size}; + } }; } // namespace fud diff --git a/include/fud_assert.hpp b/include/fud_assert.hpp index 1b5fe9f..cceb58b 100644 --- a/include/fud_assert.hpp +++ b/include/fud_assert.hpp @@ -29,7 +29,7 @@ namespace fud { const char* assertion, std::source_location sourceLocation = std::source_location::current()); -#define fudAssert(expr) ((expr) ? static_cast<void>(0) : assertFail(#expr, std::source_location::current())) +#define fudAssert(expr) ((expr) ? static_cast<void>(0) : fud::assertFail(#expr, std::source_location::current())) namespace impl { void assertFailMessage(const char* assertion, std::source_location sourceLocation); diff --git a/include/fud_drain.hpp b/include/fud_drain.hpp index 13c878a..eb1c1e7 100644 --- a/include/fud_drain.hpp +++ b/include/fud_drain.hpp @@ -28,7 +28,7 @@ namespace fud { struct DrainResult { size_t bytesDrained; FudStatus status; - [[nodiscard]] constexpr bool isOkay() + [[nodiscard]] constexpr bool isOkay() const { return status == FudStatus::Success; } diff --git a/include/fud_format.hpp b/include/fud_format.hpp index e80d96e..0dff3c1 100644 --- a/include/fud_format.hpp +++ b/include/fud_format.hpp @@ -261,6 +261,9 @@ struct FormatArguments { template <typename... Args> static auto makeFormatArguments(Args&&... args) -> FormatArguments { + static_assert(Size == sizeof...(Args)); + constexpr size_t MaxStackFormatArgSize = 2048U; + static_assert(sizeof(FormatArguments) < MaxStackFormatArgSize); return FormatArguments{Array<FormatArgument, Size>{{FormatArgument{std::forward<Args>(args)}...}}}; } }; @@ -440,6 +443,10 @@ FormatResult vFormat(Sink& sink, FormatCharMode formatMode, FormatString fmt, co } formatArgIndex = formatSpec.position; } + if (formatArgIndex >= Size) { + result.status = FudStatus::FormatInvalid; + return result; + } auto argResult{std::visit( [&](const auto& arg) -> FormatResult { return format(sink, formatMode, formatSpec, arg); }, args[formatArgIndex])}; @@ -451,7 +458,6 @@ FormatResult vFormat(Sink& sink, FormatCharMode formatMode, FormatString fmt, co if (!formatSpec.takesPosition()) { argIndex++; argIndex += static_cast<uint8_t>(formatSpec.takesWidth); - argIndex += static_cast<uint8_t>(formatSpec.takesPrecision); } } diff --git a/include/fud_hash.hpp b/include/fud_hash.hpp new file mode 100644 index 0000000..57b5619 --- /dev/null +++ b/include/fud_hash.hpp @@ -0,0 +1,77 @@ +/* + * libfud + * Copyright 2025 Dominick Allen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef FUD_HASH_HPP +#define FUD_HASH_HPP + +#include "fud_string.hpp" +#include "fud_string_view.hpp" +#include "fud_utf8.hpp" + +#include <type_traits> + +/* +namespace fud { +template <typename Key> +concept Hashable = requires(Key key) { + { sink.drain(source) } -> std::same_as<DrainResult>; +}; +} // namespace fud +*/ + +namespace fud::detail { + +constexpr uint64_t roundToNearest2(uint64_t inputValue) noexcept +{ + uint64_t outputValue = inputValue - 1; + constexpr uint8_t max2PowerShift = 32; + for (uint8_t shift = 1; shift <= max2PowerShift; shift *= 2) { + outputValue |= outputValue >> shift; + } + outputValue++; + return outputValue; +} + +/** \brief The djb2 algorithm by Dan Bernstein. See http://www.cse.yorku.ca/~oz/hash.html + * + * If passed a null pointer for data, returns the initial hash value. + */ +size_t djb2(const utf8* data, size_t length); + +template <typename T> +struct DefaultHash { + static_assert(std::is_integral_v<T> || std::is_enum_v<T>); + size_t operator()(const T& value, size_t seed) const + { + static_cast<void>(seed); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + return djb2(reinterpret_cast<const utf8*>(&value), sizeof(value)); + } +}; + +template <> +struct DefaultHash<String> { + size_t operator()(const String& value, size_t seed) const; +}; + +template <> +struct DefaultHash<StringView> { + size_t operator()(const StringView& value, size_t seed) const; +}; + +} // namespace fud::detail + +#endif diff --git a/include/fud_hash_map.hpp b/include/fud_hash_map.hpp new file mode 100644 index 0000000..4333df7 --- /dev/null +++ b/include/fud_hash_map.hpp @@ -0,0 +1,389 @@ +/* + * libfud + * Copyright 2025 Dominick Allen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FUD_HASH_MAP_HPP +#define FUD_HASH_MAP_HPP + +#include "fud_allocator.hpp" +#include "fud_hash.hpp" +#include "fud_option.hpp" +#include "fud_result.hpp" +#include "fud_status.hpp" + +#include <algorithm> + +namespace fud { + +constexpr double hashMapMaxLoadFactor = 0.6; + +/** \brief An open-address hash map using quadratic probing. + * + * Templates on a Hash object to facilitate a variety of hashing function implementations. */ +template <typename Key, typename Value, typename Hash = detail::DefaultHash<Key>> +class HashMap { + public: + struct KeyValuePair { + Key m_key; + Value m_value; + }; + using Node = Option<KeyValuePair>; + static constexpr size_t NodeSize = sizeof(Node); + static constexpr size_t Alignment = alignof(Node); + + constexpr HashMap() noexcept = default; + HashMap(const HashMap&) = delete; + HashMap(HashMap&&) = delete; + HashMap& operator=(const HashMap&) = delete; + HashMap& operator=(HashMap&&) = delete; + constexpr ~HashMap() noexcept + { + static_cast<void>(cleanup()); + } + /** \brief Construct a HashMap explicitly using the specified allocator. */ + constexpr explicit HashMap(Allocator& allocator) noexcept : m_allocator{&allocator} + { + } + + [[nodiscard]] size_t size() noexcept + { + return m_count; + } + + [[nodiscard]] size_t capacity() + { + return m_buckets; + } + + [[nodiscard]] bool contains(const Key& key) const; + [[nodiscard]] bool contains(Key&& key) const; + + FudStatus insert(const Key& key, const Value& value) + { + auto growStatus = checkGrow(); + if (growStatus != FudStatus::Success) { + return growStatus; + } + + auto hashIndexResult = findEmptyBucket(key, m_buckets, m_data); + if (hashIndexResult.isError()) { + return hashIndexResult.takeError(); + } + auto hashIndex{hashIndexResult.takeOkay()}; + m_data[hashIndex].~Node(); + auto* ptr = new (m_data + hashIndex) Node{{key, value}}; + m_count++; + return FudStatus::Success; + } + + FudStatus insert(const Key& key, Value&& value) + { + auto growStatus = checkGrow(); + if (growStatus != FudStatus::Success) { + return growStatus; + } + + auto hashIndexResult = findEmptyBucket(key, m_buckets, m_data); + if (hashIndexResult.isError()) { + return hashIndexResult.takeError(); + } + auto hashIndex{hashIndexResult.takeOkay()}; + m_data[hashIndex].~Node(); + auto* ptr = new (m_data + hashIndex) Node{{key, std::move(value)}}; + m_count++; + return FudStatus::Success; + } + + FudStatus insert(Key&& key, const Value& value) + { + auto growStatus = checkGrow(); + if (growStatus != FudStatus::Success) { + return growStatus; + } + + auto hashIndexResult = findEmptyBucket(key, m_buckets, m_data); + if (hashIndexResult.isError()) { + return hashIndexResult.takeError(); + } + auto hashIndex{hashIndexResult.takeOkay()}; + m_data[hashIndex].~Node(); + auto* ptr = new (m_data + hashIndex) Node{{std::move(key), value}}; + m_count++; + return FudStatus::Success; + } + + FudStatus insert(Key&& key, Value&& value) + { + auto growStatus = checkGrow(); + if (growStatus != FudStatus::Success) { + return growStatus; + } + + auto hashIndexResult = findEmptyBucket(key, m_buckets, m_data); + if (hashIndexResult.isError()) { + return hashIndexResult.takeError(); + } + auto hashIndex{hashIndexResult.takeOkay()}; + m_data[hashIndex].~Node(); + auto* ptr = new (m_data + hashIndex) Node{{std::move(key), std::move(value)}}; + m_count++; + return FudStatus::Success; + } + + FudStatus replace(const Key& key, const Value& value); + FudStatus replace(const Key& key, Value&& value); + FudStatus replace(Key&& key, const Value& value); + FudStatus replace(Key&& key, Value&& value); + + FudStatus remove(const Key& key); + FudStatus remove(Key&& key); + + Option<Value> extract(const Key& key); + Option<Value> extract(Key& key); + Option<KeyValuePair> extractPair(const Key& key); + Option<KeyValuePair> extractPair(Key& key); + + Option<Value> get(const Key& key) const; + Option<Value&> getRef(const Key& key); + + Option<const Value&> getConstRef(const Key& key) const + { + auto hashIndexOption = lookup(key); + if (hashIndexOption.isNone()) { + return NullOpt; + } + + return m_data[hashIndexOption.value()].value().m_value; + } + + [[nodiscard]] bool empty() const; + + FudStatus clear() + { + if (m_allocator == nullptr || m_data == nullptr) { + if (m_buckets > 0 || m_count > 0) { + return FudStatus::ObjectInvalid; + } + return FudStatus::Success; + } + for (size_t index = 0; index < m_buckets; ++index) { + m_data[index].~Node(); + } + m_count = 0; + return FudStatus::Success; + } + + FudStatus reserve(size_t count) + { + if (count <= m_buckets) { + return FudStatus::Success; + } + + if (m_allocator == nullptr) { + return FudStatus::ObjectInvalid; + } + + if (count > SIZE_MAX / NodeSize) { + return FudStatus::ArgumentInvalid; + } + + size_t requestedSize = count * NodeSize; + auto dataPtrResult = m_allocator->allocate(requestedSize, Alignment); + if (dataPtrResult.isError()) { + return dataPtrResult.takeError(); + } + + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + auto* dataPtr = reinterpret_cast<Node*>(dataPtrResult.takeOkay()); + for (size_t index = 0; index < count; ++index) { + const auto* ptr = new (dataPtr + index) Node(NullOpt); + fudAssert(ptr != nullptr); + } + + for (size_t index = 0; index < m_buckets; ++index) { + if (m_data[index].hasValue()) { + const auto& key = m_data[index].value().m_key; + const auto hash = m_hasher(key, m_seed); + auto newHashIndexResult = findEmptyBucket(key, count, dataPtr); + if (newHashIndexResult.isError()) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + m_allocator->deallocate(reinterpret_cast<std::byte*>(dataPtr), requestedSize); + return FudStatus::Failure; + } + const auto newHashIndex{newHashIndexResult.takeOkay()}; + dataPtr[newHashIndex].~Node(); + const auto* ptr = new (dataPtr + newHashIndex) Node(std::move(m_data[index])); + fudAssert(ptr != nullptr); + m_data[index].~Node(); + } + } + + auto status = FudStatus::Success; + if (m_buckets > 0) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + m_allocator->deallocate(reinterpret_cast<std::byte*>(m_data), m_buckets * NodeSize); + } + + m_data = dataPtr; + m_buckets = count; + + return status; + } + + Result<Value, FudStatus> exchange(const Key& key, const Value& value); + + private: + [[nodiscard]] double loadFactor() const + { + if (m_buckets == 0) { + return hashMapMaxLoadFactor + 1.0; + } + return static_cast<double>(m_count) / static_cast<double>(m_buckets); + } + + FudStatus checkGrow() + { + if (loadFactor() > hashMapMaxLoadFactor) { + auto growStatus = grow(); + if (growStatus != FudStatus::Success) { + return growStatus; + } + } + return FudStatus::Success; + } + + FudStatus grow() + { + size_t additional = m_buckets < 3 ? 3 : m_buckets / 2; + constexpr auto maxSize = std::numeric_limits<size_t>::max(); + if (maxSize - additional * NodeSize < m_buckets * NodeSize) { + additional = maxSize - m_buckets * NodeSize / 2; + } + while (additional > 0) { + auto reserveStatus = reserve(additional + m_buckets); + if (reserveStatus == FudStatus::Success) { + break; + } + if (reserveStatus == FudStatus::AllocFailure) { + additional /= 2; + } else { + return reserveStatus; + } + } + + if (loadFactor() >= hashMapMaxLoadFactor) { + return FudStatus::AllocFailure; + } + + return FudStatus::Success; + } + + FudStatus cleanup() noexcept + { + auto status = clear(); + if (m_data != nullptr && m_allocator != nullptr) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + m_allocator->deallocate(reinterpret_cast<std::byte*>(m_data), m_buckets); + } + + m_allocator = nullptr; + m_data = nullptr; + m_count = 0; + m_buckets = 0; + + return status; + } + + [[nodiscard]] constexpr size_t calculateHashIndex(size_t hash, size_t probe) const + { + return hash + ((probe + probe * probe) / 2); + } + + Option<size_t> lookup(const Key& key) const + { + const auto hash = m_hasher(key, m_seed); + const auto nearestPowerOf2 = detail::roundToNearest2(m_buckets); + size_t probe = 0; + size_t hashIndex{}; + auto collision = true; + + while (collision and probe < m_buckets) { + hashIndex = calculateHashIndex(hash, probe) % nearestPowerOf2; + if (hashIndex >= m_buckets) { + probe++; + continue; + } + collision = m_data[hashIndex].hasValue(); + if (collision and m_data[hashIndex].value().m_key == key) { + break; + } + if (collision) { + probe++; + } + } + + if (collision and m_data[hashIndex].value().m_key == key) { + return hashIndex; + } + + return NullOpt; + } + + Result<size_t, FudStatus> findEmptyBucket(const Key& key, size_t buckets, Node* data) + { + const auto hash = m_hasher(key, m_seed); + const auto nearestPowerOf2 = detail::roundToNearest2(buckets); + size_t hashIndex{}; + auto collision = true; + size_t probe = 0; + + while (collision and probe < buckets) { + hashIndex = calculateHashIndex(hash, probe) % nearestPowerOf2; + if (hashIndex >= buckets) { + probe++; + continue; + } + collision = data[hashIndex].hasValue(); + if (collision and data[hashIndex].value().m_key == key) { + break; + } + if (collision) { + probe++; + } + } + + if (collision and data[hashIndex].value().m_key == key) { + return Error{FudStatus::Exists}; + } + + if (collision) { + return Error{FudStatus::Failure}; + } + + return Okay{hashIndex}; + } + + Allocator* m_allocator{&globalFudAllocator}; + Node* m_data{nullptr}; + size_t m_count{0}; + size_t m_buckets{0}; + size_t m_seed{0}; + Hash m_hasher{}; +}; + +} // namespace fud + +#endif diff --git a/include/fud_option.hpp b/include/fud_option.hpp index 9d3068c..5a5611f 100644 --- a/include/fud_option.hpp +++ b/include/fud_option.hpp @@ -89,7 +89,16 @@ class Option { static_cast<void>(nullOpt); } - constexpr Option(T value) noexcept : m_engaged{true} + constexpr Option(const Option& rhs) noexcept : m_data(rhs.m_data), m_engaged(rhs.m_engaged) + { + } + + constexpr Option(Option&& rhs) noexcept : m_data(std::move(rhs.m_data)), m_engaged(rhs.m_engaged) + { + rhs.cleanup(); + } + + constexpr Option(const T& value) noexcept : m_engaged{true} { if constexpr (IsRef) { new (m_data.data()) std::reference_wrapper<ValueType>(std::ref(value)); @@ -98,30 +107,30 @@ class Option { } } - constexpr static Option take(T&& value) noexcept + constexpr Option(T&& value) noexcept requires (not IsRef) : m_engaged{true} { - Option option{}; - option.m_engaged = true; if constexpr (IsRef) { - new (option.m_data.data()) std::reference_wrapper<ValueType>(std::ref(value)); + new (m_data.data()) std::reference_wrapper<ValueType>(std::ref(value)); } else { - new (option.m_data.data()) ValueType(std::move(value)); + new (m_data.data()) ValueType(std::move(value)); } - return option; } - constexpr Option(const Option& rhs) noexcept : m_data(rhs.m_data), m_engaged(rhs.m_engaged) - { - } - - constexpr Option(Option&& rhs) noexcept : m_data(std::move(rhs.m_data)), m_engaged(rhs.m_engaged) + constexpr ~Option() noexcept { - rhs.cleanup(); + destroy(); } - constexpr ~Option() noexcept + constexpr static Option take(T&& value) noexcept { - destroy(); + Option option{}; + option.m_engaged = true; + if constexpr (IsRef) { + new (option.m_data.data()) std::reference_wrapper<ValueType>(std::ref(value)); + } else { + new (option.m_data.data()) ValueType(std::move(value)); + } + return option; } Option& operator=(const Option& rhs) noexcept diff --git a/include/fud_result.hpp b/include/fud_result.hpp index 0f501e8..88d0dc4 100644 --- a/include/fud_result.hpp +++ b/include/fud_result.hpp @@ -60,7 +60,7 @@ class [[nodiscard]] Result { constexpr Result(Okay<T>&& value) : m_data{}, m_discriminant{Discriminant::Okay} { - auto ptrValue = new (m_data.data()) T(std::move(value.value)); + auto ptrValue = new (m_data.data()) T(std::move(value).value); fudAssert(ptrValue != nullptr); } @@ -72,7 +72,7 @@ class [[nodiscard]] Result { constexpr Result(E&& value) : m_data{}, m_discriminant{Discriminant::Error} { - auto ptrValue = new (m_data.data()) E(std::move(value.value)); + auto ptrValue = new (m_data.data()) E(std::move(value).value); fudAssert(ptrValue != nullptr); } @@ -170,7 +170,7 @@ class [[nodiscard]] Result { template <typename F> static constexpr ResultType okay(Result<T, F>&& okayRes) { - return ResultType::okay(okayRes.takeOkay()); + return ResultType::okay(std::move(okayRes).takeOkay()); } template <typename U> @@ -182,7 +182,7 @@ class [[nodiscard]] Result { template <typename U> static constexpr ResultType error(Result<U, E>&& errorRes) { - return ResultType::error(errorRes.takeError()); + return ResultType::error(std::move(errorRes).takeError()); } [[nodiscard]] constexpr bool isOkay() const @@ -200,34 +200,41 @@ class [[nodiscard]] Result { [[nodiscard]] constexpr const T& getOkay() const& { fudAssert(isOkay()); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) return *reinterpret_cast<const T*>(m_data.data()); } [[nodiscard]] constexpr const T& getOkayOr(const T& alternative) const& { if (!isOkay()) { + // NOLINTNEXTLINE(bugprone-return-const-ref-from-parameter) return alternative; } + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) return *reinterpret_cast<const T*>(m_data.data()); } [[nodiscard]] constexpr const E& getError() const& { fudAssert(isError()); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) return *reinterpret_cast<const E*>(m_data.data()); } [[nodiscard]] constexpr const E& getErrorOr(const E& alternative) const& { if (!isError()) { + // NOLINTNEXTLINE(bugprone-return-const-ref-from-parameter) return alternative; } + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) return *reinterpret_cast<const E*>(m_data.data()); } [[nodiscard]] constexpr T&& takeOkay() { fudAssert(isOkay()); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) return std::move(*reinterpret_cast<T*>(m_data.data())); } @@ -236,12 +243,14 @@ class [[nodiscard]] Result { if (!isOkay()) { return std::move(alternative); } + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) return std::move(*reinterpret_cast<T*>(m_data.data())); } [[nodiscard]] constexpr E&& takeError() { fudAssert(isError()); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) return std::move(*reinterpret_cast<E*>(m_data.data())); } @@ -250,6 +259,7 @@ class [[nodiscard]] Result { if (!isError()) { return std::move(alternative); } + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) return std::move(*reinterpret_cast<E*>(m_data.data())); } @@ -297,9 +307,11 @@ class [[nodiscard]] Result { constexpr void destroy() noexcept { if (m_discriminant == Discriminant::Okay) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) reinterpret_cast<T*>(m_data.data())->~T(); m_discriminant = Discriminant::Invalid; } else if (m_discriminant == Discriminant::Error) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) reinterpret_cast<E*>(m_data.data())->~E(); m_discriminant = Discriminant::Invalid; } @@ -317,6 +329,7 @@ class [[nodiscard]] Result { } m_discriminant{Discriminant::Invalid}; }; +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define M_TakeOrReturn(HYGIENE_RESULT_TYPE, HYGIENE_EXPRESSION) \ ({ \ auto HYGIENE_RESULT{(HYGIENE_EXPRESSION)}; \ diff --git a/include/fud_sqlite.hpp b/include/fud_sqlite.hpp index 7ab3ef8..4f4c60f 100644 --- a/include/fud_sqlite.hpp +++ b/include/fud_sqlite.hpp @@ -21,7 +21,6 @@ #include <fud_result.hpp> #include <fud_status.hpp> #include <fud_string.hpp> -#include <memory> #include <sqlite3.h> namespace fud { @@ -66,11 +65,23 @@ class SqliteDb { bool revalidate(); + Result<SqliteStatement, FudStatus> prepare(const String& sql); + FudStatus exec( const String& statement, int (*callback)(void*, int, char**, char**), void* context, - std::unique_ptr<SqliteErrorMsg> errorMessage); + Option<SqliteErrorMsg&>& errorMessage); + + template <typename T> + FudStatus bind(SqliteStatement& statement, int index, T value); + + FudStatus step(SqliteStatement& statement); + + Result<int32_t, FudStatus> columnInt32(SqliteStatement& statement, int index); + Result<int64_t, FudStatus> columnInt64(SqliteStatement& statement, int index); + Result<double, FudStatus> columnDouble(SqliteStatement& statement, int index); + Result<StringView, FudStatus> columnText(SqliteStatement& statement, int index); [[nodiscard]] constexpr int errorCode() const { @@ -90,8 +101,6 @@ class SqliteDb { [[nodiscard]] int open(); - Result<SqliteStatement, FudStatus> prepare(const String& sql); - // private data members String m_name{}; @@ -124,8 +133,6 @@ class SqliteStatement { sqlite3_stmt* statement(); - int step(); - FudStatus reset(); [[nodiscard]] constexpr int errorCode() const @@ -139,7 +146,6 @@ class SqliteStatement { } private: - String m_input{}; const char* m_tail{nullptr}; FudStatus m_status{FudStatus::ObjectInvalid}; int m_errorCode{0}; @@ -158,10 +164,45 @@ class SqliteErrorMsg { SqliteErrorMsg& operator=(const SqliteErrorMsg&) = delete; SqliteErrorMsg& operator=(SqliteErrorMsg&& rhs) noexcept; + void setMessage(char* newMessage); + + [[nodiscard]] const char* message() const + { + return m_errorMsg; + } + private: char* m_errorMsg{nullptr}; }; +template <typename T> +FudStatus SqliteDb::bind(SqliteStatement& statement, int index, T value) +{ + static_assert(std::is_same_v<T, int32_t>|| std::is_same_v<T, int64_t> || std::is_same_v<T, double> || std::is_same_v<T, StringView>); + if (index < 0) { + return FudStatus::ArgumentInvalid; + } + if constexpr (std::is_same_v<T, int32_t>) { + m_errorCode = sqlite3_bind_int(statement.statement(), index, value); + } else if constexpr (std::is_same_v<T, int64_t>) { + m_errorCode = sqlite3_bind_int64(statement.statement(), index, value); + } else if constexpr (std::is_same_v<T, double>) { + m_errorCode = sqlite3_bind_double(statement.statement(), index, value); + } else if constexpr (std::is_same_v<T, StringView>) { + if (value.length() > std::numeric_limits<int>::max()) { + return FudStatus::ArgumentInvalid; + } + int stringLength = static_cast<int>(value.length()); + m_errorCode = ::sqlite3_bind_text(statement.statement(), index, value.c_str(), stringLength, nullptr); + } else { + return FudStatus::Failure; + } + if (m_errorCode != SQLITE_OK) { + return FudStatus::Failure; + } + return FudStatus::Success; +} + } // namespace fud #endif diff --git a/include/fud_string.hpp b/include/fud_string.hpp index 4b3dbe2..1d724f0 100644 --- a/include/fud_string.hpp +++ b/include/fud_string.hpp @@ -294,11 +294,11 @@ class String { /** \brief Returns the first character in the sequence if the length is * greater than zero. */ - [[nodiscard]] Option<utf8> front(); + [[nodiscard]] Option<utf8> front() const; /** \brief Returns the last character in the sequence if the length is * greater than zero. */ - [[nodiscard]] Option<utf8> back(); + [[nodiscard]] Option<utf8> back() const; /** \brief Append a character to the back of the string, growing it if necessary. */ FudStatus pushBack(char letter); @@ -338,7 +338,7 @@ class String { /** @copydoc String::drain(const char* source) */ DrainResult drain(StringView source); - [[nodiscard]] bool compare(const String& rhs) const; + [[nodiscard]] bool operator==(const String& rhs) const; FudStatus clear(); diff --git a/include/fud_string_view_format.hpp b/include/fud_string_view_format.hpp new file mode 100644 index 0000000..eb03958 --- /dev/null +++ b/include/fud_string_view_format.hpp @@ -0,0 +1,31 @@ +/* + * libfud + * Copyright 2024 Dominick Allen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FUD_STRING_VIEW_FORMAT_HPP +#define FUD_STRING_VIEW_FORMAT_HPP + +#include "fud_string_view.hpp" + +template <> +struct fmt::formatter<fud::StringView> : fmt::formatter<std::string> { + auto format(fud::StringView view, format_context& ctx) const -> decltype(ctx.out()) + { + return fmt::format_to(ctx.out(), "{}", view.as_string_view()); + } +}; + +#endif diff --git a/include/fud_utf8.hpp b/include/fud_utf8.hpp index 030164d..bdc6720 100644 --- a/include/fud_utf8.hpp +++ b/include/fud_utf8.hpp @@ -290,6 +290,7 @@ static_assert(Utf8TypeSet.m_values[3] == static_cast<uint8_t>(Utf8Type::Utf84Byt | U+10000 | U+10FFFF | 11110xxx | 10xxxxxx | 10xxxxxx | 10xxxxxx */ constexpr Utf8Type utf8TypeFromByte(utf8 input) { + // NOLINTBEGIN(readability-magic-numbers) if ((input >> 7) == 0) { return Utf8Type::Ascii; } @@ -302,7 +303,7 @@ constexpr Utf8Type utf8TypeFromByte(utf8 input) { if ((input >> 3) == 0b11110) { return Utf8Type::Utf84Byte; } - + // NOLINTEND(readability-magic-numbers) return Utf8Type::Invalid; } @@ -527,7 +528,7 @@ struct Utf8 { return std::strong_ordering::greater; } - Option<Ascii> getAscii() const + [[nodiscard]] Option<Ascii> getAscii() const { if (m_variant.index() == static_cast<size_t>(Utf8Type::Ascii)) { return std::get<Ascii>(m_variant); diff --git a/include/fud_vector.hpp b/include/fud_vector.hpp index 0a9abf3..fd01cd0 100644 --- a/include/fud_vector.hpp +++ b/include/fud_vector.hpp @@ -33,14 +33,20 @@ namespace fud { +/** \brief Vector storing elements of T using a custom allocator. */ template <typename T> class Vector { static constexpr size_t ElementSize = sizeof(T); static constexpr size_t Alignment = alignof(T); public: + /** \brief Construct a vector implictly using the globalFudAllocator. */ constexpr Vector() noexcept = default; - constexpr explicit Vector(Allocator& allocator) noexcept : m_allocator{&allocator} {} + + /** \brief Construct a vector explicitly using the specified allocator. */ + constexpr explicit Vector(Allocator& allocator) noexcept : m_allocator{&allocator} + { + } constexpr Vector(const Vector<T>& rhs) = delete; constexpr Vector(Vector<T>&& rhs) noexcept : m_allocator(rhs.m_allocator), m_data(rhs.m_data), m_length{rhs.m_length}, m_capacity{rhs.m_capacity} @@ -77,7 +83,8 @@ class Vector { return *this; } - static constexpr Vector<T> NullVector() noexcept { + static constexpr Vector<T> NullVector() noexcept + { Vector<T> output{}; output.m_allocator = &globalNullAllocator; return output; @@ -134,10 +141,7 @@ class Vector { return Okay<Vector<T>>{std::move(output)}; } - static FudStatus initializeWithSize( - Vector<T>& output, - size_t count, - Allocator* allocator = &globalFudAllocator) + static FudStatus initializeWithSize(Vector<T>& output, size_t count, Allocator* allocator = &globalFudAllocator) { if (output.m_data != nullptr) { return FudStatus::AlreadyInitialized; @@ -224,7 +228,7 @@ class Vector { if (status != FudStatus::Success) { return status; } - return output; + return Okay{std::move(output)}; } template <size_t Size> @@ -235,7 +239,7 @@ class Vector { if (status != FudStatus::Success) { return status; } - return output; + return Okay{std::move(output)}; } template <size_t Size> @@ -251,6 +255,30 @@ class Vector { } } + template <typename... Args> + static Result<Vector<T>, FudStatus> from(Option<Allocator*> allocatorOpt, Args&&... args) + { + constexpr size_t size = sizeof...(args); + Vector<T> output{}; + Allocator* allocator = allocatorOpt.hasValue() ? allocatorOpt.value() : &globalFudAllocator; + auto status = Vector::initializeWithCapacity(output, size, allocator); + if (status != FudStatus::Success) { + return Error{status}; + } + output.m_length = size; + size_t index = 0; + /* + for (size_t index = 0; index < output.m_length; ++index) { + + } + */ + ([&]() { + output.m_data[index] = std::forward<T>(args); + ++index; + } (), ...); + return Okay{std::move(output)}; + } + static Vector<T> move(Vector<T>&& rhs) noexcept { return Vector<T>{std::move(rhs)}; @@ -270,6 +298,11 @@ class Vector { return m_capacity; } + [[nodiscard]] bool empty() const + { + return m_length == 0; + } + Result<Span<const T>, FudStatus> span() const { using RetType = Result<Span<const T>, FudStatus>; @@ -367,7 +400,7 @@ class Vector { auto status = FudStatus::Success; if (m_capacity > 0) { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - m_allocator->deallocate(reinterpret_cast<std::byte*>(m_data), m_capacity); + m_allocator->deallocate(reinterpret_cast<std::byte*>(m_data), m_capacity * ElementSize); } m_data = dataPtr; @@ -647,8 +680,7 @@ class Vector { if (std::numeric_limits<size_t>::max() - Size < m_length) { return FudStatus::Failure; } - if (m_length + Size > m_capacity) - { + if (m_length + Size > m_capacity) { size_t currentLength = m_length; auto status = resize(m_length + Size); m_length = currentLength; @@ -674,8 +706,7 @@ class Vector { if (std::numeric_limits<size_t>::max() - span.size() < m_length) { return FudStatus::Failure; } - if (m_length + span.size() > m_capacity) - { + if (m_length + span.size() > m_capacity) { size_t currentLength = m_length; auto status = resize(m_length + span.size()); m_length = currentLength; @@ -700,8 +731,7 @@ class Vector { } m_data[index].~T(); - for (size_t fwdIndex = index; fwdIndex + 1 < m_length; fwdIndex++) - { + for (size_t fwdIndex = index; fwdIndex + 1 < m_length; fwdIndex++) { m_data[fwdIndex] = std::move(m_data[fwdIndex + 1]); } m_data[m_length - 1].~T(); @@ -743,7 +773,7 @@ class Vector { if (m_data != nullptr && m_allocator != nullptr) { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - m_allocator->deallocate(reinterpret_cast<std::byte*>(m_data), m_capacity); + m_allocator->deallocate(reinterpret_cast<std::byte*>(m_data), m_capacity * ElementSize); } m_allocator = nullptr; diff --git a/source/fud_hash.cpp b/source/fud_hash.cpp new file mode 100644 index 0000000..faa62ff --- /dev/null +++ b/source/fud_hash.cpp @@ -0,0 +1,53 @@ +/* + * libfud + * Copyright 2025 Dominick Allen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "fud_hash.hpp" + +namespace fud::detail { + +size_t djb2(const utf8* data, size_t length) +{ + /* Allegedly djb chose 5381 after testing showed fewer collisions and better avalanching: see + * https://stackoverflow.com/questions/10696223/reason-for-the-number-5381-in-the-djb-hash-function */ + constexpr size_t hashInit = 5381; + constexpr size_t mulBy = 33; + size_t hash = hashInit; + if (data == nullptr) { + return hash; + } + + for (size_t index = 0; index < length; ++index) { + hash = (hash * mulBy) ^ data[index]; + } + + return hash; +} + + +size_t DefaultHash<String>::operator()(const String& value, size_t seed) const +{ + static_cast<void>(seed); + return djb2(value.data(), value.length()); +} + +size_t DefaultHash<StringView>::operator()(const StringView& value, size_t seed) const +{ + static_cast<void>(seed); + return djb2(value.data(), value.length()); +} + +} diff --git a/source/fud_sqlite.cpp b/source/fud_sqlite.cpp index 4a7187f..fccbc1e 100644 --- a/source/fud_sqlite.cpp +++ b/source/fud_sqlite.cpp @@ -37,7 +37,7 @@ SqliteDbResult SqliteDb::make(StringView name, SqliteOpenMode mode, int extraFla return SqliteDbResult::error(nameResult.takeError()); } sqlDb.m_name = nameResult.takeOkay(); - return finishMake(mode, extraFlags, std::move(sqlDb)); + return finishMake(mode, extraFlags, std::move(sqlDb)); } SqliteDbResult SqliteDb::make(const char* cStrName, SqliteOpenMode mode, int extraFlags) @@ -48,7 +48,7 @@ SqliteDbResult SqliteDb::make(const char* cStrName, SqliteOpenMode mode, int ext return SqliteDbResult::error(nameResult); } sqlDb.m_name = nameResult.takeOkay(); - return finishMake(mode, extraFlags, std::move(sqlDb)); + return finishMake(mode, extraFlags, std::move(sqlDb)); } SqliteDbResult SqliteDb::finishMake(SqliteOpenMode& mode, int& extraFlags, SqliteDb&& sqlDb) @@ -104,7 +104,7 @@ SqliteDb& SqliteDb::operator=(SqliteDb&& rhs) noexcept bool SqliteDb::valid() const { - return m_nameValid && m_dbHandle != nullptr && m_errorCode == SQLITE_OK; + return m_nameValid && m_dbHandle != nullptr && (m_errorCode == SQLITE_OK || m_errorCode == SQLITE_DONE); } bool SqliteDb::revalidate() @@ -116,11 +116,34 @@ bool SqliteDb::revalidate() return false; } +FudStatus SqliteDb::initialize() +{ + m_nameValid = m_name.utf8Valid(); + + if (!m_nameValid) { + return FudStatus::ArgumentInvalid; + } + + m_errorCode = open(); + if (m_errorCode != SQLITE_OK) { + return FudStatus::Failure; + } + + return FudStatus::Success; +} + +int SqliteDb::open() +{ + // use default vfs + return sqlite3_open_v2(m_name.c_str(), &m_dbHandle, static_cast<int>(m_mode) | m_extraFlags, nullptr); +} + FudStatus SqliteDb::exec( const String& statement, int (*callback)(void*, int, char**, char**), void* context, - std::unique_ptr<SqliteErrorMsg> errorMessage) + // NOLINTNEXTLINE(performance-copy-param-value) + Option<SqliteErrorMsg&>& errorMessage) { if (!valid()) { return FudStatus::ObjectInvalid; @@ -131,53 +154,135 @@ FudStatus SqliteDb::exec( } char* errorMsgPtr = nullptr; - char** errorMsgPtrAddress = nullptr; - if (errorMessage != nullptr) { - errorMsgPtrAddress = &errorMsgPtr; - } + char** errorMsgPtrAddress = errorMessage.isNone() ? nullptr : &errorMsgPtr; m_errorCode = sqlite3_exec(m_dbHandle, statement.c_str(), callback, context, errorMsgPtrAddress); - if (errorMessage != nullptr) { - errorMessage = std::make_unique<SqliteErrorMsg>(errorMsgPtr); + if (errorMessage.hasValue()) { + errorMessage.value().setMessage(errorMsgPtr); } return m_errorCode == SQLITE_OK ? FudStatus::Success : FudStatus::Failure; } -FudStatus SqliteDb::initialize() +FudStatus SqliteDb::step(SqliteStatement& statement) { - m_nameValid = m_name.utf8Valid(); - - if (!m_nameValid) { - return FudStatus::ArgumentInvalid; + if (!statement.valid()) { + m_errorCode = SQLITE_MISUSE; + } else { + m_errorCode = sqlite3_step(statement.statement()); } - m_errorCode = open(); - if (m_errorCode != SQLITE_OK) { + switch (m_errorCode) { + case SQLITE_OK: + case SQLITE_ROW: + return FudStatus::Success; + case SQLITE_DONE: + return FudStatus::NotFound; + default: return FudStatus::Failure; } +} - return FudStatus::Success; +Result<int32_t, FudStatus> SqliteDb::columnInt32(SqliteStatement& statement, int index) +{ + if (!statement.valid() || statement.status() != FudStatus::Success) { + m_errorCode = statement.errorCode(); + return Error{statement.status()}; + } + + if (index < 0) { + return Error{FudStatus::ArgumentInvalid}; + } + if (sqlite3_column_type(statement.statement(), index) != SQLITE_INTEGER) { + return Error{FudStatus::ArgumentInvalid}; + } + + auto output = sqlite3_column_int(statement.statement(), index); + + return Okay{output}; } -int SqliteDb::open() +Result<int64_t, FudStatus> SqliteDb::columnInt64(SqliteStatement& statement, int index) { - // use default vfs - return sqlite3_open_v2(m_name.c_str(), &m_dbHandle, static_cast<int>(m_mode) | m_extraFlags, nullptr); + if (!statement.valid() || statement.status() != FudStatus::Success) { + m_errorCode = statement.errorCode(); + return Error{statement.status()}; + } + + if (index < 0) { + return Error{FudStatus::ArgumentInvalid}; + } + if (sqlite3_column_type(statement.statement(), index) != SQLITE_INTEGER) { + return Error{FudStatus::ArgumentInvalid}; + } + + auto output = sqlite3_column_int64(statement.statement(), index); + static_assert(std::is_convertible_v<decltype(output), int64_t>); + + return Okay{static_cast<int64_t>(output)}; +} + +Result<double, FudStatus> SqliteDb::columnDouble(SqliteStatement& statement, int index) +{ + if (!statement.valid() || statement.status() != FudStatus::Success) { + m_errorCode = statement.errorCode(); + return Error{statement.status()}; + } + + if (index < 0) { + return Error{FudStatus::ArgumentInvalid}; + } + if (sqlite3_column_type(statement.statement(), index) != SQLITE_FLOAT) { + return Error{FudStatus::ArgumentInvalid}; + } + + auto output = sqlite3_column_double(statement.statement(), index); + + return Okay{output}; +} + +Result<StringView, FudStatus> SqliteDb::columnText(SqliteStatement& statement, int index) +{ + if (!statement.valid() || statement.status() != FudStatus::Success) { + m_errorCode = statement.errorCode(); + return Error{statement.status()}; + } + + if (index < 0) { + return Error{FudStatus::ArgumentInvalid}; + } + + StringView output{}; + if (sqlite3_column_type(statement.statement(), index) != SQLITE_TEXT) { + return Error{FudStatus::ArgumentInvalid}; + } + + auto outputLength = sqlite3_column_bytes(statement.statement(), index); + if (outputLength < 0) { + return Error{FudStatus::Failure}; + } + output.m_length = static_cast<size_t>(outputLength); + + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + output.m_data = reinterpret_cast<const utf8*>(sqlite3_column_text(statement.statement(), index)); + if (output.m_data == nullptr && output.m_length != 0) { + return Error{FudStatus::Failure}; + } + + return Okay{output}; } Result<SqliteStatement, FudStatus> SqliteDb::prepare(const String& sql) { - using RetType = Result<SqliteStatement, FudStatus>; SqliteStatement preparedStatement{*this, sql}; if (!preparedStatement.valid() || preparedStatement.status() != FudStatus::Success) { m_errorCode = preparedStatement.errorCode(); - return RetType::error(preparedStatement.status()); + return Error{preparedStatement.status()}; } - return RetType::okay(std::move(preparedStatement)); + return Okay{std::move(preparedStatement)}; } SqliteStatement::SqliteStatement(const SqliteDb& sqliteDb, const String& input) @@ -200,7 +305,6 @@ SqliteStatement::SqliteStatement(const SqliteDb& sqliteDb, const String& input) } SqliteStatement::SqliteStatement(SqliteStatement&& rhs) noexcept : - m_input{std::move(rhs.m_input)}, m_tail{rhs.m_tail}, m_status{rhs.m_status}, m_errorCode{rhs.m_errorCode}, @@ -229,16 +333,6 @@ sqlite3_stmt* SqliteStatement::statement() return m_preparedStatement; } -int SqliteStatement::step() -{ - if (!valid()) { - m_errorCode = SQLITE_MISUSE; - } else { - m_errorCode = sqlite3_step(m_preparedStatement); - } - return m_errorCode; -} - FudStatus SqliteStatement::reset() { if (!valid()) { @@ -256,7 +350,8 @@ SqliteErrorMsg::SqliteErrorMsg(SqliteErrorMsg&& rhs) noexcept : m_errorMsg{rhs.m rhs.m_errorMsg = nullptr; } -SqliteErrorMsg& SqliteErrorMsg::operator=(SqliteErrorMsg&& rhs) noexcept { +SqliteErrorMsg& SqliteErrorMsg::operator=(SqliteErrorMsg&& rhs) noexcept +{ if (m_errorMsg != nullptr) { sqlite3_free(m_errorMsg); m_errorMsg = nullptr; @@ -275,4 +370,12 @@ SqliteErrorMsg::~SqliteErrorMsg() } } +void SqliteErrorMsg::setMessage(char* newMessage) +{ + if (m_errorMsg != nullptr) { + sqlite3_free(m_errorMsg); + } + m_errorMsg = newMessage; +} + } // namespace fud diff --git a/source/fud_string.cpp b/source/fud_string.cpp index a2a62f4..69df7e4 100644 --- a/source/fud_string.cpp +++ b/source/fud_string.cpp @@ -389,13 +389,13 @@ FudStatus String::reserve(size_t newCapacity) return resize(newCapacity); } -[[nodiscard]] Option<utf8> String::front() +[[nodiscard]] Option<utf8> String::front() const { if (!valid() || length() < 1) { return NullOpt; } - utf8 frontChar = dataMut()[0]; + utf8 frontChar = data()[0]; if (Ascii::valid(frontChar)) { return frontChar; } @@ -403,13 +403,13 @@ FudStatus String::reserve(size_t newCapacity) return NullOpt; } -[[nodiscard]] Option<utf8> String::back() +[[nodiscard]] Option<utf8> String::back() const { if (!valid() || length() < 1) { return NullOpt; } - utf8 backChar = dataMut()[length() - 1]; + utf8 backChar = data()[length() - 1]; if (Ascii::valid(backChar)) { return backChar; } @@ -751,7 +751,7 @@ StringResult String::catenate(const String& rhs) const return StringResult::okay(std::move(output)); } -bool String::compare(const String& rhs) const +bool String::operator==(const String& rhs) const { if (!valid() || !rhs.valid()) { return false; @@ -773,6 +773,7 @@ bool String::compare(const String& rhs) const return diffResult.getOkay() == 0; } + FudStatus String::clear() { if (!valid()) { diff --git a/test/.clang-tidy b/test/.clang-tidy index 97da183..0b184c0 100644 --- a/test/.clang-tidy +++ b/test/.clang-tidy @@ -1,2 +1,2 @@ -Checks: -readability-function-cognitive-complexity +Checks: -readability-function-cognitive-complexity,-readability-magic-numbers InheritParentConfig: true diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index cdc8c6e..9b905c9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -67,6 +67,7 @@ fud_add_test(test_csv SOURCES test_csv.cpp) # fud_add_test(test_c_file SOURCES test_c_file.cpp) fud_add_test(test_directory SOURCES test_directory.cpp) fud_add_test(test_file SOURCES test_file.cpp) +fud_add_test(test_hash_map SOURCES test_hash_map.cpp) fud_add_test(test_format SOURCES test_format.cpp) fud_add_test(test_option SOURCES test_option.cpp) fud_add_test(test_result SOURCES test_result.cpp) diff --git a/test/test_directory.cpp b/test/test_directory.cpp index 7a420ca..905e503 100644 --- a/test/test_directory.cpp +++ b/test/test_directory.cpp @@ -81,7 +81,7 @@ TEST(FudDirectory, Basic) DirectoryEntry{String::from(files[0]).takeOkay(), 0, files[0].size(), 1, 0, DirectoryEntryType::RegularFile}, DirectoryEntry{String::from(files[1]).takeOkay(), 0, files[1].size(), 1, 0, DirectoryEntryType::RegularFile}, }; - ASSERT_TRUE(expectedFiles[0].name.compare(expectedFiles[0].name)); + ASSERT_EQ(expectedFiles[0].name, expectedFiles[0].name); for (auto idx = 0; idx < expectedFiles.size(); ++idx) { debugPrint(u8"On iteration {} - '{}' \n", idx, expectedFiles[idx].name.asView()); @@ -95,7 +95,7 @@ TEST(FudDirectory, Basic) const auto* expected = std::ranges::find_if( expectedFiles, [&dirEntry](const DirectoryEntry& entry) { - return entry.name.compare(dirEntry.name) && entry.entryType == dirEntry.entryType; + return entry.name == dirEntry.name && entry.entryType == dirEntry.entryType; }); EXPECT_NE(expected, nullptr); EXPECT_NE(expected, expectedFiles.end()); diff --git a/test/test_file.cpp b/test/test_file.cpp index c1ce4f1..878c740 100644 --- a/test/test_file.cpp +++ b/test/test_file.cpp @@ -25,8 +25,6 @@ #include <fcntl.h> #include <ftw.h> -// NOLINTBEGIN(readability-magic-numbers) - namespace fud { TEST(FudFile, Basic) @@ -169,5 +167,3 @@ TEST(FudBufferedFile, OpenReadWrite) } } // namespace fud - -// NOLINTEND(readability-magic-numbers) diff --git a/test/test_format.cpp b/test/test_format.cpp index 738b551..0c5c520 100644 --- a/test/test_format.cpp +++ b/test/test_format.cpp @@ -17,10 +17,13 @@ #include "fud_format.hpp" #include "fud_string_view.hpp" +#include "fud_print.hpp" #include "gtest/gtest.h" #include <format> +// NOLINTBEGIN(readability-magic-numbers) + namespace fud { TEST(FormatTest, BasePositionalTest) @@ -506,27 +509,27 @@ TEST(FormatTest, OneBoolFormatTest) TEST(FormatTest, OneFloatFormatUnspecifiedTest) { -/* - String sink{}; - - ASSERT_EQ(sink.clear(), FudStatus::Success); - auto expected = std::format("{:}", 42.0); - auto formatResult = format(sink, FormatCharMode::Unchecked, u8"{:}", 42.0); - EXPECT_TRUE(formatResult.isOkay()); - EXPECT_STREQ(sink.c_str(), expected.c_str()); - - ASSERT_EQ(sink.clear(), FudStatus::Success); - expected = std::format("{:1.0}", 42.0); - formatResult = format(sink, FormatCharMode::Unchecked, u8"{:1.0}", 42.0); - EXPECT_TRUE(formatResult.isOkay()); - EXPECT_STREQ(sink.c_str(), expected.c_str()); - expected = std::format("{:1.0}", 42.0); - expected = std::format("u {:}", 10.0); - expected = std::format("u {:}", 100.0); - expected = std::format("u {:}", 1000.0); - expected = std::format("u {:}", 10000.0); - expected = std::format("u {:}", 100000.0); -*/ + /* + String sink{}; + + ASSERT_EQ(sink.clear(), FudStatus::Success); + auto expected = std::format("{:}", 42.0); + auto formatResult = format(sink, FormatCharMode::Unchecked, u8"{:}", 42.0); + EXPECT_TRUE(formatResult.isOkay()); + EXPECT_STREQ(sink.c_str(), expected.c_str()); + + ASSERT_EQ(sink.clear(), FudStatus::Success); + expected = std::format("{:1.0}", 42.0); + formatResult = format(sink, FormatCharMode::Unchecked, u8"{:1.0}", 42.0); + EXPECT_TRUE(formatResult.isOkay()); + EXPECT_STREQ(sink.c_str(), expected.c_str()); + expected = std::format("{:1.0}", 42.0); + expected = std::format("u {:}", 10.0); + expected = std::format("u {:}", 100.0); + expected = std::format("u {:}", 1000.0); + expected = std::format("u {:}", 10000.0); + expected = std::format("u {:}", 100000.0); + */ } TEST(FormatTest, OneFloatFormatScientificTest) @@ -648,50 +651,50 @@ TEST(FormatTest, OneFloatFormatScientificTest) // Which is: "E#+ +0003.0000000000000000000000000000000000E+00" // Which is: "E#+ +0003.00000000000000000000000000000000000000000000000000E+00" -/* - expected = std::format("E#+ {:Z>+#060.30E}", val); - ASSERT_EQ(sink.clear(), FudStatus::Success); - formatResult = format(sink, FormatCharMode::Unchecked, u8"E#+ {:Z>+#060.30E}", val); - EXPECT_TRUE(formatResult.isOkay()); - EXPECT_STREQ(sink.c_str(), expected.c_str()); - - expected = std::format("E#+ {:Z<+#060.30E}", val); - ASSERT_EQ(sink.clear(), FudStatus::Success); - formatResult = format(sink, FormatCharMode::Unchecked, u8"E#+ {:Z<+#060.30E}", val); - EXPECT_TRUE(formatResult.isOkay()); - EXPECT_STREQ(sink.c_str(), expected.c_str()); - - expected = std::format("E#+ {:Z^+#060.30E}", val); - ASSERT_EQ(sink.clear(), FudStatus::Success); - formatResult = format(sink, FormatCharMode::Unchecked, u8"E#+ {:Z^+#060.30E}", val); - EXPECT_TRUE(formatResult.isOkay()); - EXPECT_STREQ(sink.c_str(), expected.c_str()); -*/ - -/* - double val = 3.0; - expected = std::format("u {:}", val); - expected = std::format("f {:f}", val); - expected = std::format("F {:F}", val); - expected = std::format("a {:a}", val); - expected = std::format("A {:A}", val); - expected = std::format("g {:g}", val); - expected = std::format("G {:G}", val); - expected = std::format("u# {:#}", val); - expected = std::format("f# {:#f}", val); - expected = std::format("F# {:#F}", val); - expected = std::format("a# {:#a}", val); - expected = std::format("A# {:#A}", val); - expected = std::format("g# {:#g}", val); - expected = std::format("G# {:#G}", val); - expected = std::format("u#+ {:+#}", val); - expected = std::format("f#+ {:+#f}", val); - expected = std::format("F#+ {:+#F}", val); - expected = std::format("a#+ {:+#a}", val); - expected = std::format("A#+ {:+#A}", val); - expected = std::format("g#+ {:+#g}", val); - expected = std::format("G#+ {:+#010.3G}", val); -*/ + /* + expected = std::format("E#+ {:Z>+#060.30E}", val); + ASSERT_EQ(sink.clear(), FudStatus::Success); + formatResult = format(sink, FormatCharMode::Unchecked, u8"E#+ {:Z>+#060.30E}", val); + EXPECT_TRUE(formatResult.isOkay()); + EXPECT_STREQ(sink.c_str(), expected.c_str()); + + expected = std::format("E#+ {:Z<+#060.30E}", val); + ASSERT_EQ(sink.clear(), FudStatus::Success); + formatResult = format(sink, FormatCharMode::Unchecked, u8"E#+ {:Z<+#060.30E}", val); + EXPECT_TRUE(formatResult.isOkay()); + EXPECT_STREQ(sink.c_str(), expected.c_str()); + + expected = std::format("E#+ {:Z^+#060.30E}", val); + ASSERT_EQ(sink.clear(), FudStatus::Success); + formatResult = format(sink, FormatCharMode::Unchecked, u8"E#+ {:Z^+#060.30E}", val); + EXPECT_TRUE(formatResult.isOkay()); + EXPECT_STREQ(sink.c_str(), expected.c_str()); + */ + + /* + double val = 3.0; + expected = std::format("u {:}", val); + expected = std::format("f {:f}", val); + expected = std::format("F {:F}", val); + expected = std::format("a {:a}", val); + expected = std::format("A {:A}", val); + expected = std::format("g {:g}", val); + expected = std::format("G {:G}", val); + expected = std::format("u# {:#}", val); + expected = std::format("f# {:#f}", val); + expected = std::format("F# {:#F}", val); + expected = std::format("a# {:#a}", val); + expected = std::format("A# {:#A}", val); + expected = std::format("g# {:#g}", val); + expected = std::format("G# {:#G}", val); + expected = std::format("u#+ {:+#}", val); + expected = std::format("f#+ {:+#f}", val); + expected = std::format("F#+ {:+#F}", val); + expected = std::format("a#+ {:+#a}", val); + expected = std::format("A#+ {:+#A}", val); + expected = std::format("g#+ {:+#g}", val); + expected = std::format("G#+ {:+#010.3G}", val); + */ } TEST(FormatTest, TwoArgFormatTest) @@ -716,6 +719,33 @@ TEST(FormatTest, StringView) auto formatResult = format(sink, FormatCharMode::Unchecked, u8"Test {}", StringView{u8"Hello, World!"}); EXPECT_TRUE(formatResult.isOkay()); EXPECT_STREQ(sink.c_str(), expected.c_str()); + + ASSERT_EQ(sink.clear(), FudStatus::Success); + formatResult = fud::format( + sink, + fud::FormatCharMode::Unchecked, + u8"CREATE TABLE IF NOT EXISTS {} ({} {} {});", + StringView{u8"AtomicSymbol"}.as_string_view(), + StringView{u8"AtomicNumber INTEGER PRIMARY KEY"}.as_string_view(), + StringView{u8"Symbol TEXT NOT NULL"}.as_string_view(), + StringView{u8"Name TEXT NOT NULL"}.as_string_view()); + expected = std::format( + "CREATE TABLE IF NOT EXISTS {} ({} {} {});", + StringView{u8"AtomicSymbol"}.as_string_view(), + StringView{u8"AtomicNumber INTEGER PRIMARY KEY"}.as_string_view(), + StringView{u8"Symbol TEXT NOT NULL"}.as_string_view(), + StringView{u8"Name TEXT NOT NULL"}.as_string_view()); + EXPECT_TRUE(formatResult.isOkay()); + EXPECT_STREQ(sink.c_str(), expected.c_str()); +} + +TEST(FormatTest, NumArgs) +{ + String sink{}; + auto formatResult = format(sink, FormatCharMode::Unchecked, u8"Test {} {} {}", StringView{u8"Hello, World!"}); + ASSERT_EQ(formatResult.status, FudStatus::FormatInvalid); } } // namespace fud + +// NOLINTEND(readability-magic-numbers) diff --git a/test/test_hash_map.cpp b/test/test_hash_map.cpp new file mode 100644 index 0000000..00c4693 --- /dev/null +++ b/test/test_hash_map.cpp @@ -0,0 +1,146 @@ +/* + * libfud + * Copyright 2025 Dominick Allen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "fud_hash_map.hpp" +#include "fud_string.hpp" +#include "fud_vector.hpp" +// #include "test_common.hpp" + +#include "gtest/gtest.h" + +namespace fud { + +TEST(FudHash, InsertMoveKeyMoveValue) +{ + auto stringList{ + Vector<String>::from( + NullOpt, + String::makeFromCString("foo").takeOkay(), + String::makeFromCString("bar").takeOkay(), + String::makeFromCString("baz").takeOkay(), + String::makeFromCString("qux").takeOkay(), + String::makeFromCString("Tom").takeOkay(), + String::makeFromCString("Dick").takeOkay(), + String::makeFromCString("Harry").takeOkay(), + String::makeFromCString("Alice").takeOkay(), + String::makeFromCString("Bob").takeOkay()) + .takeOkay()}; + HashMap<int, String> mapInt2String{}; + for (int index = 0; index < static_cast<int>(stringList.size()); ++index) { + auto insertStatus = mapInt2String.insert(index * 1, String::from(stringList[index]).takeOkay()); + EXPECT_EQ(insertStatus, FudStatus::Success); + } + EXPECT_EQ(mapInt2String.size(), stringList.size()); + EXPECT_GT(mapInt2String.capacity(), mapInt2String.size()); + + const String invalid{String::makeFromCString("Invalid").takeOkay()}; + for (int index = 0; index < static_cast<int>(stringList.size()); ++index) { + EXPECT_EQ(mapInt2String.getConstRef(index).valueOr(invalid), stringList[index]); + } +} + +TEST(FudHash, InsertMoveKeyCopyValue) +{ + auto stringList{ + Vector<String>::from( + NullOpt, + String::makeFromCString("foo").takeOkay(), + String::makeFromCString("bar").takeOkay(), + String::makeFromCString("baz").takeOkay(), + String::makeFromCString("qux").takeOkay(), + String::makeFromCString("Tom").takeOkay(), + String::makeFromCString("Dick").takeOkay(), + String::makeFromCString("Harry").takeOkay(), + String::makeFromCString("Alice").takeOkay(), + String::makeFromCString("Bob").takeOkay()) + .takeOkay()}; + HashMap<String, int> mapString2Int{}; + for (int index = 0; index < static_cast<int>(stringList.size()); ++index) { + auto insertStatus = mapString2Int.insert(String::from(stringList[index]).takeOkay(), index * 1); + EXPECT_EQ(insertStatus, FudStatus::Success); + } + + EXPECT_EQ(mapString2Int.size(), stringList.size()); + EXPECT_GT(mapString2Int.capacity(), mapString2Int.size()); + + for (int index = 0; index < static_cast<int>(stringList.size()); ++index) { + const int invalid = -1; + EXPECT_EQ(mapString2Int.getConstRef(stringList[index]).valueOr(invalid), index); + } +} + +TEST(FudHash, InsertCopyKeyMoveValue) +{ + auto stringList{ + Vector<String>::from( + NullOpt, + String::makeFromCString("foo").takeOkay(), + String::makeFromCString("bar").takeOkay(), + String::makeFromCString("baz").takeOkay(), + String::makeFromCString("qux").takeOkay(), + String::makeFromCString("Tom").takeOkay(), + String::makeFromCString("Dick").takeOkay(), + String::makeFromCString("Harry").takeOkay(), + String::makeFromCString("Alice").takeOkay(), + String::makeFromCString("Bob").takeOkay()) + .takeOkay()}; + HashMap<int, String> mapInt2String{}; + for (int index = 0; index < static_cast<int>(stringList.size()); ++index) { + auto insertStatus = mapInt2String.insert(index, String::from(stringList[index]).takeOkay()); + EXPECT_EQ(insertStatus, FudStatus::Success); + } + + EXPECT_EQ(mapInt2String.size(), stringList.size()); + EXPECT_GT(mapInt2String.capacity(), mapInt2String.size()); + + const String invalid{String::makeFromCString("Invalid").takeOkay()}; + for (int index = 0; index < static_cast<int>(stringList.size()); ++index) { + EXPECT_EQ(mapInt2String.getConstRef(index).valueOr(invalid), stringList[index]); + } +} + +TEST(FudHash, InsertCopyKeyCopyValue) +{ + auto stringList{ + Vector<StringView>::from( + NullOpt, + StringView::makeFromCString("foo"), + StringView::makeFromCString("bar"), + StringView::makeFromCString("baz"), + StringView::makeFromCString("qux"), + StringView::makeFromCString("Tom"), + StringView::makeFromCString("Dick"), + StringView::makeFromCString("Harry"), + StringView::makeFromCString("Alice"), + StringView::makeFromCString("Bob")) + .takeOkay()}; + HashMap<StringView, int> mapView2Int{}; + for (int index = 0; index < static_cast<int>(stringList.size()); ++index) { + auto insertStatus = mapView2Int.insert(stringList[index], index); + EXPECT_EQ(insertStatus, FudStatus::Success); + } + + EXPECT_EQ(mapView2Int.size(), stringList.size()); + EXPECT_GT(mapView2Int.capacity(), mapView2Int.size()); + + for (int index = 0; index < static_cast<int>(stringList.size()); ++index) { + const int invalid = -1; + EXPECT_EQ(mapView2Int.getConstRef(stringList[index]).valueOr(invalid), index); + } +} + +} // namespace fud diff --git a/test/test_option.cpp b/test/test_option.cpp index a503a5f..b900858 100644 --- a/test/test_option.cpp +++ b/test/test_option.cpp @@ -16,6 +16,7 @@ */ #include "fud_option.hpp" +#include "fud_string.hpp" #include "gtest/gtest.h" @@ -51,4 +52,11 @@ TEST(OptionTest, OptionRef) ASSERT_EQ(value, 42); } +TEST(OptionTest, OptionString) +{ + Option<String> optString{std::move(String::makeFromCString("foo").takeOkay())}; + EXPECT_TRUE(optString.hasValue()); +} + + } // namespace fud diff --git a/test/test_vector.cpp b/test/test_vector.cpp index ba0272e..3f4e584 100644 --- a/test/test_vector.cpp +++ b/test/test_vector.cpp @@ -24,9 +24,14 @@ namespace fud { template <size_t Size> struct TestLinearAllocator : public Allocator { - virtual ~TestLinearAllocator() override = default; - - virtual Result<std::byte*, FudStatus> allocate(size_t bytes, size_t alignment = alignof(std::max_align_t)) override + TestLinearAllocator() = default; + TestLinearAllocator(const TestLinearAllocator&) = delete; + TestLinearAllocator(TestLinearAllocator&&) = delete; + TestLinearAllocator& operator=(const TestLinearAllocator&) = delete; + TestLinearAllocator& operator=(TestLinearAllocator&&) = delete; + ~TestLinearAllocator() override = default; + + Result<std::byte*, FudStatus> allocate(size_t bytes, size_t alignment = alignof(std::max_align_t)) override { auto allocIndex = m_next; if (allocIndex % alignment != 0) { @@ -39,13 +44,13 @@ struct TestLinearAllocator : public Allocator { return Okay<std::byte*>{m_backing.data() + allocIndex}; } - virtual void deallocate(std::byte* pointer, size_t bytes) override + void deallocate(std::byte* pointer, size_t bytes) override { static_cast<void>(pointer); static_cast<void>(bytes); } - virtual bool isEqual(const Allocator& rhs) const override { + [[nodiscard]] bool isEqual(const Allocator& rhs) const override { return &rhs == static_cast<const Allocator*>(this); } @@ -82,7 +87,7 @@ struct NonTrivial { counter++; } NonTrivial(const NonTrivial&) = delete; - NonTrivial(NonTrivial&& rhs) : value{rhs.value}, destroyed{rhs.destroyed} + NonTrivial(NonTrivial&& rhs) noexcept : value{rhs.value}, destroyed{rhs.destroyed} { rhs.destroyed = true; } @@ -94,7 +99,7 @@ struct NonTrivial { } } NonTrivial& operator=(const NonTrivial& rhs) = delete; - NonTrivial& operator=(NonTrivial&& rhs) + NonTrivial& operator=(NonTrivial&& rhs) noexcept { value = rhs.value; destroyed = rhs.destroyed; @@ -183,4 +188,17 @@ TEST(VectorTest, NestedVector) // Result<Vector<Vector<FallibleObject>>, FudStatus> } +TEST(VectorTest, WithElements) +{ + auto vectorResult{Vector<int>::from(NullOpt, 1, 2, 3, 42, -100)}; + ASSERT_TRUE(vectorResult.isOkay()); + auto intVector{vectorResult.takeOkay()}; + ASSERT_EQ(intVector.size(), 5); + ASSERT_EQ(intVector[0], 1); + ASSERT_EQ(intVector[1], 2); + ASSERT_EQ(intVector[2], 3); + ASSERT_EQ(intVector[3], 42); + ASSERT_EQ(intVector[4], -100); +} + } // namespace fud |