summaryrefslogtreecommitdiff
path: root/include
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 /include
parent1d357adfa19725ee69fb267a363f1fd217b1272f (diff)
Add hash map.
Diffstat (limited to 'include')
-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
15 files changed, 671 insertions, 62 deletions
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;