summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDominick Allen <djallen@librehumanitas.org>2025-03-30 23:08:43 -0500
committerDominick Allen <djallen@librehumanitas.org>2025-03-30 23:08:43 -0500
commitcb9fa588ba8144fcdd52ba4b83d69d93fb18066f (patch)
tree214574ca68c1551ec76e7fbb9e0263793180231d
parent1d357adfa19725ee69fb267a363f1fd217b1272f (diff)
Add hash map.
-rw-r--r--.clang-tidy2
-rw-r--r--CMakeLists.txt6
-rw-r--r--README.org4
-rw-r--r--include/fud_algorithm.hpp13
-rw-r--r--include/fud_allocator.hpp16
-rw-r--r--include/fud_array.hpp7
-rw-r--r--include/fud_assert.hpp2
-rw-r--r--include/fud_drain.hpp2
-rw-r--r--include/fud_format.hpp8
-rw-r--r--include/fud_hash.hpp77
-rw-r--r--include/fud_hash_map.hpp389
-rw-r--r--include/fud_option.hpp39
-rw-r--r--include/fud_result.hpp21
-rw-r--r--include/fud_sqlite.hpp55
-rw-r--r--include/fud_string.hpp6
-rw-r--r--include/fud_string_view_format.hpp31
-rw-r--r--include/fud_utf8.hpp5
-rw-r--r--include/fud_vector.hpp62
-rw-r--r--source/fud_hash.cpp53
-rw-r--r--source/fud_sqlite.cpp175
-rw-r--r--source/fud_string.cpp11
-rw-r--r--test/.clang-tidy2
-rw-r--r--test/CMakeLists.txt1
-rw-r--r--test/test_directory.cpp4
-rw-r--r--test/test_file.cpp4
-rw-r--r--test/test_format.cpp160
-rw-r--r--test/test_hash_map.cpp146
-rw-r--r--test/test_option.cpp8
-rw-r--r--test/test_vector.cpp32
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"
diff --git a/README.org b/README.org
index 4fe05c9..6ec3e4d 100644
--- a/README.org
+++ b/README.org
@@ -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