From cb9fa588ba8144fcdd52ba4b83d69d93fb18066f Mon Sep 17 00:00:00 2001 From: Dominick Allen Date: Sun, 30 Mar 2025 23:08:43 -0500 Subject: Add hash map. --- include/fud_algorithm.hpp | 13 +- include/fud_allocator.hpp | 16 +- include/fud_array.hpp | 7 +- include/fud_assert.hpp | 2 +- include/fud_drain.hpp | 2 +- include/fud_format.hpp | 8 +- include/fud_hash.hpp | 77 ++++++++ include/fud_hash_map.hpp | 389 +++++++++++++++++++++++++++++++++++++ include/fud_option.hpp | 39 ++-- include/fud_result.hpp | 21 +- include/fud_sqlite.hpp | 55 +++++- include/fud_string.hpp | 6 +- include/fud_string_view_format.hpp | 31 +++ include/fud_utf8.hpp | 5 +- include/fud_vector.hpp | 62 ++++-- 15 files changed, 671 insertions(+), 62 deletions(-) create mode 100644 include/fud_hash.hpp create mode 100644 include/fud_hash_map.hpp create mode 100644 include/fud_string_view_format.hpp (limited to 'include') 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 #include -#include namespace fud { @@ -36,11 +35,23 @@ concept LessThanComparable = template 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 +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 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 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 allocate(size_t bytes, size_t alignment = alignof(std::max_align_t)) final { - using RetType = Result; static_cast(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(pointer); + static_cast(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&) const noexcept = default; + bool operator==(const Array&) const noexcept = default; constexpr auto operator<=>(const Array& other) const noexcept = default; @@ -112,6 +112,11 @@ struct Array { { return Span{data(), Size}; } + + Span dynSpan() + { + return Span{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(0) : assertFail(#expr, std::source_location::current())) +#define fudAssert(expr) ((expr) ? static_cast(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 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{std::forward(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(formatSpec.takesWidth); - argIndex += static_cast(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 + +/* +namespace fud { +template +concept Hashable = requires(Key key) { + { sink.drain(source) } -> std::same_as; +}; +} // 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 +struct DefaultHash { + static_assert(std::is_integral_v || std::is_enum_v); + size_t operator()(const T& value, size_t seed) const + { + static_cast(seed); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + return djb2(reinterpret_cast(&value), sizeof(value)); + } +}; + +template <> +struct DefaultHash { + size_t operator()(const String& value, size_t seed) const; +}; + +template <> +struct DefaultHash { + 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 + +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 > +class HashMap { + public: + struct KeyValuePair { + Key m_key; + Value m_value; + }; + using Node = Option; + 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(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 extract(const Key& key); + Option extract(Key& key); + Option extractPair(const Key& key); + Option extractPair(Key& key); + + Option get(const Key& key) const; + Option getRef(const Key& key); + + Option 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(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(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(m_data), m_buckets * NodeSize); + } + + m_data = dataPtr; + m_buckets = count; + + return status; + } + + Result exchange(const Key& key, const Value& value); + + private: + [[nodiscard]] double loadFactor() const + { + if (m_buckets == 0) { + return hashMapMaxLoadFactor + 1.0; + } + return static_cast(m_count) / static_cast(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::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(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 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 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(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(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(std::ref(value)); + new (m_data.data()) std::reference_wrapper(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(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&& 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 static constexpr ResultType okay(Result&& okayRes) { - return ResultType::okay(okayRes.takeOkay()); + return ResultType::okay(std::move(okayRes).takeOkay()); } template @@ -182,7 +182,7 @@ class [[nodiscard]] Result { template static constexpr ResultType error(Result&& 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(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(m_data.data()); } [[nodiscard]] constexpr const E& getError() const& { fudAssert(isError()); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) return *reinterpret_cast(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(m_data.data()); } [[nodiscard]] constexpr T&& takeOkay() { fudAssert(isOkay()); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) return std::move(*reinterpret_cast(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(m_data.data())); } [[nodiscard]] constexpr E&& takeError() { fudAssert(isError()); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) return std::move(*reinterpret_cast(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(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(m_data.data())->~T(); m_discriminant = Discriminant::Invalid; } else if (m_discriminant == Discriminant::Error) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) reinterpret_cast(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 #include #include -#include #include namespace fud { @@ -66,11 +65,23 @@ class SqliteDb { bool revalidate(); + Result prepare(const String& sql); + FudStatus exec( const String& statement, int (*callback)(void*, int, char**, char**), void* context, - std::unique_ptr errorMessage); + Option& errorMessage); + + template + FudStatus bind(SqliteStatement& statement, int index, T value); + + FudStatus step(SqliteStatement& statement); + + Result columnInt32(SqliteStatement& statement, int index); + Result columnInt64(SqliteStatement& statement, int index); + Result columnDouble(SqliteStatement& statement, int index); + Result columnText(SqliteStatement& statement, int index); [[nodiscard]] constexpr int errorCode() const { @@ -90,8 +101,6 @@ class SqliteDb { [[nodiscard]] int open(); - Result 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 +FudStatus SqliteDb::bind(SqliteStatement& statement, int index, T value) +{ + static_assert(std::is_same_v|| std::is_same_v || std::is_same_v || std::is_same_v); + if (index < 0) { + return FudStatus::ArgumentInvalid; + } + if constexpr (std::is_same_v) { + m_errorCode = sqlite3_bind_int(statement.statement(), index, value); + } else if constexpr (std::is_same_v) { + m_errorCode = sqlite3_bind_int64(statement.statement(), index, value); + } else if constexpr (std::is_same_v) { + m_errorCode = sqlite3_bind_double(statement.statement(), index, value); + } else if constexpr (std::is_same_v) { + if (value.length() > std::numeric_limits::max()) { + return FudStatus::ArgumentInvalid; + } + int stringLength = static_cast(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 front(); + [[nodiscard]] Option front() const; /** \brief Returns the last character in the sequence if the length is * greater than zero. */ - [[nodiscard]] Option back(); + [[nodiscard]] Option 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 : fmt::formatter { + 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(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 getAscii() const + [[nodiscard]] Option getAscii() const { if (m_variant.index() == static_cast(Utf8Type::Ascii)) { return std::get(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 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& rhs) = delete; constexpr Vector(Vector&& 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 NullVector() noexcept { + static constexpr Vector NullVector() noexcept + { Vector output{}; output.m_allocator = &globalNullAllocator; return output; @@ -134,10 +141,7 @@ class Vector { return Okay>{std::move(output)}; } - static FudStatus initializeWithSize( - Vector& output, - size_t count, - Allocator* allocator = &globalFudAllocator) + static FudStatus initializeWithSize(Vector& 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 @@ -235,7 +239,7 @@ class Vector { if (status != FudStatus::Success) { return status; } - return output; + return Okay{std::move(output)}; } template @@ -251,6 +255,30 @@ class Vector { } } + template + static Result, FudStatus> from(Option allocatorOpt, Args&&... args) + { + constexpr size_t size = sizeof...(args); + Vector 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(args); + ++index; + } (), ...); + return Okay{std::move(output)}; + } + static Vector move(Vector&& rhs) noexcept { return Vector{std::move(rhs)}; @@ -270,6 +298,11 @@ class Vector { return m_capacity; } + [[nodiscard]] bool empty() const + { + return m_length == 0; + } + Result, FudStatus> span() const { using RetType = Result, 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(m_data), m_capacity); + m_allocator->deallocate(reinterpret_cast(m_data), m_capacity * ElementSize); } m_data = dataPtr; @@ -647,8 +680,7 @@ class Vector { if (std::numeric_limits::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::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(m_data), m_capacity); + m_allocator->deallocate(reinterpret_cast(m_data), m_capacity * ElementSize); } m_allocator = nullptr; -- cgit v1.2.3