23 Commits

Author SHA1 Message Date
680acdb332 Fixed compilation of boxed_object.hpp in non-debug builds. 2025-07-25 00:51:53 +02:00
3d507ddef7 Finally implemented object pools. 2025-07-25 00:51:32 +02:00
d35140d278 Added missing template parameter. 2025-07-17 23:47:00 +02:00
933a36d992 Added formattable utility. 2025-07-17 10:56:52 +02:00
d3659f11f7 Implemented getCPUTicks() and getCPUTicksPerSecond() for Windows. 2025-07-17 10:56:39 +02:00
d207774868 Added missing include. 2025-07-17 10:56:15 +02:00
54d9cd327a Added undef for VOID. 2025-07-17 07:51:19 +02:00
93ef90aeb8 Added function_traits. 2025-07-16 21:07:50 +02:00
ba6ffa6c42 Added getCPUTicks() and getCPUTicksPerSeconds() functions. 2025-07-16 21:07:50 +02:00
2cf270df84 Added MIJIN_FUNCNAME() macro. 2025-07-16 21:07:50 +02:00
9808fcf50e Removed unused MIJIN_FUNC() macro. 2025-07-16 21:07:50 +02:00
e91924e049 Added FixedArrayOutputIterator and use it for logging without heap allocations. 2025-07-16 21:07:50 +02:00
79f49829d3 Added utility type to convert variant members via std::visit. 2025-07-16 21:07:50 +02:00
042e0d2465 Added explicit constructor for casting between related not-nullable pointers. 2025-07-16 21:07:50 +02:00
018c75a5ed First implementation of custom path type. 2025-07-16 21:07:50 +02:00
33bc48dd58 Added Path type (WIP). 2025-07-16 21:07:50 +02:00
Patrick Wuttke
51092bb4cb Added splitView() and splitLines() to string utility functions. 2025-07-15 18:21:10 +02:00
Patrick Wuttke
02e99bbc82 Made logger thread-safe and added filters. 2025-07-14 17:16:24 +02:00
Patrick Wuttke
ad627b7c70 Added NotNullable::release() function. 2025-07-14 17:16:09 +02:00
Patrick Wuttke
4a9a60c7f5 Replaced default deleter used in DynamicPointer so it fits better with other functions. 2025-07-14 17:15:25 +02:00
Patrick Wuttke
d8b03893b3 Merge branch 'master' of https://git.mewin.de/mewin/mijin2 2025-07-14 10:08:04 +02:00
Patrick Wuttke
7939d458b3 Added Stream functions for reading and writing 0-terminated strings. 2025-07-14 10:07:57 +02:00
Patrick Wuttke
b5546067a8 Added missing include. 2025-07-14 10:07:37 +02:00
16 changed files with 1095 additions and 77 deletions

View File

@@ -68,12 +68,12 @@ public:
BoxedObject(const BoxedObject&) = delete; BoxedObject(const BoxedObject&) = delete;
BoxedObject(BoxedObject&&) = delete; BoxedObject(BoxedObject&&) = delete;
#if MIJIN_BOXED_OBJECT_DEBUG
constexpr ~BoxedObject() noexcept constexpr ~BoxedObject() noexcept
{ {
#if MIJIN_BOXED_OBJECT_DEBUG
MIJIN_ASSERT(!constructed, "BoxedObject::~BoxedObject(): Object has not been destroyed prior to destructor!"); MIJIN_ASSERT(!constructed, "BoxedObject::~BoxedObject(): Object has not been destroyed prior to destructor!");
}
#endif #endif
}
BoxedObject& operator=(const BoxedObject&) = delete; BoxedObject& operator=(const BoxedObject&) = delete;
BoxedObject& operator=(BoxedObject&&) = delete; BoxedObject& operator=(BoxedObject&&) = delete;

View File

@@ -113,6 +113,42 @@ StreamError Stream::writeBinaryString(std::string_view str)
return writeSpan(str.begin(), str.end()); return writeSpan(str.begin(), str.end());
} }
StreamError Stream::readZString(std::string& outString)
{
char chr = '\0';
std::string result;
while (true)
{
if (isAtEnd())
{
return StreamError::IO_ERROR;
}
if (StreamError error = read(chr); error != StreamError::SUCCESS)
{
return error;
}
if (chr == '\0')
{
outString = std::move(result);
return StreamError::SUCCESS;
}
result.push_back(chr);
}
}
StreamError Stream::writeZString(std::string_view str)
{
static const char ZERO = '\0';
if (StreamError error = writeRaw(str.data(), str.size() * sizeof(char)); error != StreamError::SUCCESS)
{
return error;
}
return write(ZERO);
}
mijin::Task<StreamError> Stream::c_readBinaryString(std::string& outString) mijin::Task<StreamError> Stream::c_readBinaryString(std::string& outString)
{ {
std::uint32_t length; // NOLINT(cppcoreguidelines-init-variables) std::uint32_t length; // NOLINT(cppcoreguidelines-init-variables)

View File

@@ -221,7 +221,7 @@ public:
} }
template<typename T> template<typename T>
StreamError write(const T& value) requires(std::is_trivial_v<T>) StreamError write(const T& value)
{ {
return writeRaw(&value, sizeof(T)); return writeRaw(&value, sizeof(T));
} }
@@ -261,6 +261,9 @@ public:
StreamError readBinaryString(std::string& outString); StreamError readBinaryString(std::string& outString);
StreamError writeBinaryString(std::string_view str); StreamError writeBinaryString(std::string_view str);
StreamError readZString(std::string& outString);
StreamError writeZString(std::string_view str);
mijin::Task<StreamError> c_readBinaryString(std::string& outString); mijin::Task<StreamError> c_readBinaryString(std::string& outString);
mijin::Task<StreamError> c_writeBinaryString(std::string_view str); mijin::Task<StreamError> c_writeBinaryString(std::string_view str);

View File

@@ -0,0 +1,52 @@
#pragma once
#if !defined(MIJIN_LOGGING_FILTERS_HPP_INCLUDED)
#define MIJIN_LOGGING_FILTERS_HPP_INCLUDED 1
#include "./logger.hpp"
namespace mijin
{
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
class BaseLevelFilter : public BaseLogFilter<TChar>
{
public:
using base_t = BaseLogFilter<TChar>;
using typename base_t::char_t;
using typename base_t::message_t;
private:
int mMinLevel = 0;
int mMaxLevel = 0;
public:
explicit BaseLevelFilter(int minLevel, int maxLevel = std::numeric_limits<int>::max()) MIJIN_NOEXCEPT
: mMinLevel(minLevel), mMaxLevel(maxLevel) {}
explicit BaseLevelFilter(const BaseLogLevel<char_t>& minLevel, const BaseLogLevel<char_t>& maxLevel = {nullptr, std::numeric_limits<int>::max()}) MIJIN_NOEXCEPT
: mMinLevel(minLevel.value), mMaxLevel(maxLevel.value) {}
[[nodiscard]]
int getMinLevel() const MIJIN_NOEXCEPT { return mMinLevel; }
[[nodiscard]]
int getMaxLevel() const MIJIN_NOEXCEPT { return mMaxLevel; }
void setMinLevel(int level) MIJIN_NOEXCEPT { mMinLevel = level; }
void setMinLevel(const BaseLogLevel<char_t>& level) MIJIN_NOEXCEPT { mMinLevel = level.value; }
void setMaxLevel(int level) MIJIN_NOEXCEPT { mMaxLevel = level; }
void setMaxLevel(const BaseLogLevel<char_t>& level) MIJIN_NOEXCEPT { mMaxLevel = level.value; }
bool shouldShow(const message_t& message) MIJIN_NOEXCEPT override
{
return message.level->value >= mMinLevel && message.level->value <= mMaxLevel;
}
};
MIJIN_DEFINE_CHAR_VERSIONS(LevelFilter)
} // namespace mijin
#endif // !defined(MIJIN_LOGGING_STDIO_SINK_HPP_INCLUDED)

View File

@@ -6,6 +6,7 @@
#include <cstdint> #include <cstdint>
#include <format> #include <format>
#include <mutex>
#include <source_location> #include <source_location>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -105,6 +106,20 @@ public:
MIJIN_DEFINE_CHAR_VERSIONS(LogSink) MIJIN_DEFINE_CHAR_VERSIONS(LogSink)
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
class BaseLogFilter
{
public:
using char_t = TChar;
using message_t = BaseLogMessage<char_t>;
virtual ~BaseLogFilter() noexcept = default;
virtual bool shouldShow(const message_t& message) MIJIN_NOEXCEPT = 0;
};
MIJIN_DEFINE_CHAR_VERSIONS(LogFilter)
#define LOGGER_COMMON_ARGS(chr_type) template<typename T> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR #define LOGGER_COMMON_ARGS(chr_type) template<typename T> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE, typename TTraits = std::char_traits<TChar>, LOGGER_COMMON_ARGS(TChar)> template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE, typename TTraits = std::char_traits<TChar>, LOGGER_COMMON_ARGS(TChar)>
class BaseLogger class BaseLogger
@@ -115,15 +130,22 @@ public:
using allocator_t = TAllocator<char_t>; using allocator_t = TAllocator<char_t>;
using sink_t = BaseLogSink<char_t>; using sink_t = BaseLogSink<char_t>;
using filter_t = BaseLogFilter<char_t>;
using level_t = BaseLogLevel<char_t>; using level_t = BaseLogLevel<char_t>;
using channel_t = BaseLogChannel<char_t>; using channel_t = BaseLogChannel<char_t>;
using message_t = BaseLogMessage<char_t>; using message_t = BaseLogMessage<char_t>;
using string_t = std::basic_string<char_t, traits_t, allocator_t>; using string_t = std::basic_string<char_t, traits_t, allocator_t>;
private: private:
std::vector<sink_t*, TAllocator<sink_t*>> mSinks; struct SinkEntry
{
sink_t* sink;
filter_t* filter;
};
std::vector<SinkEntry, TAllocator<SinkEntry>> mSinks;
mutable std::mutex mMutex;
public: public:
explicit BaseLogger(TAllocator<sink_t*> allocator = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TAllocator<sink_t*>>) explicit BaseLogger(TAllocator<void> allocator = {}) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<TAllocator<SinkEntry>, TAllocator<void>&&>))
: mSinks(std::move(allocator)) : mSinks(TAllocator<SinkEntry>(std::move(allocator)))
{} {}
BaseLogger(const BaseLogger&) = default; BaseLogger(const BaseLogger&) = default;
@@ -133,14 +155,25 @@ public:
void addSink(sink_t& sink) void addSink(sink_t& sink)
{ {
mSinks.push_back(&sink); std::unique_lock _(mMutex);
mSinks.push_back({&sink, nullptr});
}
void addSink(sink_t& sink, filter_t& filter)
{
std::unique_lock _(mMutex);
mSinks.push_back({&sink, &filter});
} }
void postMessage(const message_t& message) const MIJIN_NOEXCEPT void postMessage(const message_t& message) const MIJIN_NOEXCEPT
{ {
for (sink_t* sink: mSinks) std::unique_lock _(mMutex);
for (const SinkEntry& entry : mSinks)
{ {
sink->handleMessage(message); if (entry.filter != nullptr && !entry.filter->shouldShow(message)) {
continue;
}
entry.sink->handleMessage(message);
} }
} }

View File

@@ -0,0 +1,58 @@
#pragma once
#if !defined(MIJIN_LOGGING_STREAM_SINK_HPP_INCLUDED)
#define MIJIN_LOGGING_STREAM_SINK_HPP_INCLUDED 1
#include "./formatting.hpp"
#include "../io/stream.hpp"
#include "../util/traits.hpp"
namespace mijin
{
template<MIJIN_FORMATTING_SINK_TMPL_ARGS_INIT>
requires(allocator_type<TAllocator<TChar>>)
class BaseStreamSink : public BaseFormattingLogSink<MIJIN_FORMATTING_SINK_TMP_ARG_NAMES>
{
public:
using base_t = BaseFormattingLogSink<MIJIN_FORMATTING_SINK_TMP_ARG_NAMES>;
using typename base_t::char_t;
using typename base_t::allocator_t;
using typename base_t::formatter_ptr_t;
using typename base_t::message_t;
using stream_ptr_t = DynamicPointer<Stream>;
private:
stream_ptr_t mStream;
int mMinStderrLevel = MIJIN_LOG_LEVEL_VALUE_WARNING;
public:
explicit BaseStreamSink(not_null_t<formatter_ptr_t> formatter, allocator_t allocator = {})
MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<allocator_t>)
: base_t(std::move(formatter), std::move(allocator)) {}
explicit BaseStreamSink(not_null_t<stream_ptr_t> stream, not_null_t<formatter_ptr_t> formatter, allocator_t allocator = {})
MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<allocator_t>)
: base_t(std::move(formatter), std::move(allocator)), mStream(std::move(stream)) {}
void setStream(not_null_t<stream_ptr_t> stream) {
mStream = std::move(stream).release();
}
void handleMessageFormatted(const message_t& /* message */, const char_t* formatted) MIJIN_NOEXCEPT override
{
if (!mStream) {
return;
}
(void) mStream->writeSpan(std::basic_string_view(formatted));
(void) mStream->write('\n');
mStream->flush();
}
};
#define SINK_SET_ARGS(chr_type) chr_type, std::char_traits<chr_type>, TAllocator, TDeleter
MIJIN_DEFINE_CHAR_VERSIONS_TMPL(StreamSink, MIJIN_FORMATTING_SINK_COMMON_ARGS, SINK_SET_ARGS)
#undef SINK_SET_ARGS
} // namespace mijin
#endif // !defined(MIJIN_LOGGING_STREAM_SINK_HPP_INCLUDED)

View File

@@ -17,7 +17,7 @@ namespace mijin
{ {
MIJIN_DEFINE_FLAG(Owning); MIJIN_DEFINE_FLAG(Owning);
template<typename T, deleter_type<T> TDeleter = std::default_delete<T>> template<typename T, deleter_type<T> TDeleter = AllocatorDeleter<MIJIN_DEFAULT_ALLOCATOR<T>>>
class DynamicPointer class DynamicPointer
{ {
public: public:

View File

@@ -7,56 +7,317 @@
#include <array> #include <array>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <limits> #include "../internal/common.hpp"
#include <memory>
#include <vector>
#include "../debug/assert.hpp" #include "../debug/assert.hpp"
#include "../util/align.hpp"
namespace mijin namespace mijin
{ {
template<typename TObject, std::size_t OBJECTS_PER_PAGE = 1024> namespace impl
class ObjectPool
{ {
private: struct ObjectPoolGap
struct GapInfo {
{ std::uint16_t nextGapOffset;
std::uint32_t objectCount; std::uint16_t remainingObjects;
std::uint32_t nextGap;
};
static constexpr std::size_t ALIGN = std::max(alignof(TObject), alignof(GapInfo));
struct alignas(ALIGN) ObjectData ObjectPoolGap* nextGap() noexcept {
{ return nextGapOffset == 0 ? nullptr : reinterpret_cast<ObjectPoolGap*>(reinterpret_cast<std::byte*>(this) + nextGapOffset);
std::array<std::byte, sizeof(TObject)> bytes; }
};
struct Page
{
std::uint32_t freeOffset = 0;
std::array<ObjectData, OBJECTS_PER_PAGE> data;
};
std::vector<std::unique_ptr<Page>> pages;
public:
[[nodiscard]] TObject* allocate(std::size_t count = 1) noexcept;
void free(TObject* object, std::size_t count = 1) noexcept;
}; };
template<typename TObject, std::size_t OBJECTS_PER_PAGE> template<typename TObject, std::size_t OBJECTS_PER_PAGE>
TObject* ObjectPool<TObject, OBJECTS_PER_PAGE>::allocate(std::size_t count) noexcept struct ObjectPoolPage
{ {
MIJIN_ASSERT(count <= OBJECTS_PER_PAGE, "Cannot allocate more than OBJECTS_PER_PAGE elements at once!"); ObjectPoolGap* firstGap;
// first try to find a free spot in the existing pages ObjectPoolPage* nextPage = nullptr;
std::array<std::byte, OBJECTS_PER_PAGE * MIJIN_STRIDEOF(TObject)> data;
for (std::unique_ptr<Page>& page : pages) ObjectPoolPage() noexcept
{ {
std::uint32_t offset = page->freeOffset; firstGap = reinterpret_cast<ObjectPoolGap*>(data.data());
while (offset < OBJECTS_PER_PAGE) firstGap->nextGapOffset = 0;
firstGap->remainingObjects = OBJECTS_PER_PAGE;
}
};
}
template<typename TObject, std::size_t OBJECTS_PER_PAGE>
class ObjectPoolIterator
{
public:
using value_type = TObject;
using difference_type = std::ptrdiff_t;
private:
using gap_t = impl::ObjectPoolGap;
using page_t = impl::ObjectPoolPage<TObject, OBJECTS_PER_PAGE>;
page_t* currentPage_ = nullptr;
gap_t* currentGap_ = nullptr;
std::uint16_t currentObject_ = 0;
explicit ObjectPoolIterator(page_t& firstPage) noexcept
{
seekNextOnPage(firstPage);
}
public:
ObjectPoolIterator() noexcept = default;
ObjectPoolIterator(const ObjectPoolIterator&) noexcept = default;
ObjectPoolIterator& operator=(const ObjectPoolIterator&) noexcept = default;
auto operator<=>(const ObjectPoolIterator&) const noexcept = default;
TObject& operator*() const noexcept { return getObject(); }
TObject* operator->() const noexcept { return &getObject(); }
ObjectPoolIterator& operator++() noexcept
{
seekNext();
return *this;
}
ObjectPoolIterator operator++(int) const noexcept
{
ObjectPoolIterator copy(*this);
++(*this);
return copy;
}
private:
TObject& getObject() const noexcept
{
MIJIN_ASSERT(currentGap_ != nullptr, "Attempting to dereference an invalid iterator.");
return *(reinterpret_cast<TObject*>(currentGap_) + currentGap_->remainingObjects + currentObject_);
}
bool seekNextOnPage(page_t& firstPage) noexcept
{
for (page_t* page = &firstPage; page != nullptr; page = page->nextPage)
{ {
GapInfo& gapInfo = *std::bit_cast<GapInfo*>(&page->data[offset]); if (page->firstGap != nullptr && seekNextInGap(*page->firstGap))
{
currentPage_ = page;
return true;
}
}
return false;
}
bool seekNextInGap(gap_t& firstGap) noexcept
{
for (gap_t* gap = &firstGap; gap != nullptr; gap = gap->nextGap())
{
if (gap->remainingObjects < OBJECTS_PER_PAGE)
{
currentGap_ = gap;
currentObject_ = 0;
return true;
}
}
return false;
}
void seekNext() noexcept
{
if (++currentObject_ < OBJECTS_PER_PAGE - currentGap_->remainingObjects)
{
return;
}
if (currentGap_->nextGapOffset != 0 && seekNextInGap(*currentGap_->nextGap())) {
return;
}
if (currentPage_->nextPage != nullptr && seekNextOnPage(*currentPage_->nextPage)) {
return;
}
currentPage_ = nullptr;
currentGap_ = nullptr;
currentObject_ = 0;
}
template<typename TObject2, std::size_t OBJECTS_PER_PAGE2, template<typename> typename TAllocator>
friend class ObjectPool;
};
template<typename TObject, std::size_t OBJECTS_PER_PAGE = 1024, template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR>
class ObjectPool
{
public:
using iterator = ObjectPoolIterator<TObject, OBJECTS_PER_PAGE>;
using const_iterator = iterator;
private:
using gap_t = impl::ObjectPoolGap;
using page_t = impl::ObjectPoolPage<TObject, OBJECTS_PER_PAGE>;
static_assert(sizeof(gap_t) <= sizeof(TObject));
[[no_unique_address]] TAllocator<page_t> allocator_;
page_t* firstPage = nullptr;
public:
explicit ObjectPool(TAllocator<void> allocator = {}) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<TAllocator<page_t>, TAllocator<void>&&>))
: allocator_(std::move(allocator))
{
}
ObjectPool(const ObjectPool&) = delete;
ObjectPool(ObjectPool&&) = default;
ObjectPool& operator=(const ObjectPool&) = delete;
ObjectPool& operator=(ObjectPool&&) = default;
[[nodiscard]]
TObject* allocate(std::size_t count = 1) noexcept
{
MIJIN_ASSERT(count <= OBJECTS_PER_PAGE, "Cannot allocate that many objects at once.");
// first try to find a free spot in the existing pages
for (page_t* page = firstPage; page != nullptr; page = page->nextPage)
{
if (TObject* result = allocateFromPage(*page, count); result != nullptr) {
return result;
}
}
// nothing found in the existing pages, allocate a new one and return memory from there
page_t* newPage = ::new (allocator_.allocate(1)) page_t;
if (firstPage == nullptr)
{
firstPage = newPage;
}
else
{
page_t* lastPage = firstPage;
for (; lastPage->nextPage != nullptr; lastPage = lastPage->nextPage);
lastPage->nextPage = newPage;
}
return allocateFromPage(*newPage, count);
}
void deallocate(TObject* object, std::size_t count = 1) noexcept
{
std::byte* const objectPtr = reinterpret_cast<std::byte*>(object); // for easier comparison
for (page_t* page = firstPage; page != nullptr; page = page->nextPage)
{
// first find the page it's in
std::byte* pageStart = page->data.data();
std::byte* pageEnd = pageStart + (OBJECTS_PER_PAGE * sizeof(TObject));
if (objectPtr >= pageStart && objectPtr <= pageEnd)
{
// then the corresponding gap
if (page->firstGap == nullptr)
{
// everything is used, create a new gap
gap_t* newGap = reinterpret_cast<gap_t*>(objectPtr);
newGap->remainingObjects = count;
newGap->nextGapOffset = 0;
page->firstGap = newGap;
return;
}
for (gap_t* gap = page->firstGap; gap != nullptr; gap = gap->nextGap())
{
std::byte* gapStart = reinterpret_cast<std::byte*>(gap);
std::byte* gapEnd = gap->nextGapOffset == 0 ? pageEnd : gapStart + gap->nextGapOffset;
if (objectPtr > gapStart && objectPtr < gapEnd)
{
if (objectPtr + (count * sizeof(TObject)) == gapEnd)
{
// deallocating right from the end -> just increase the gap remainingObjects
gap->remainingObjects += count;
mergeGaps(gap);
}
else
{
// deallocating from the middle -> create a new gap
gap_t* newGap = reinterpret_cast<gap_t*>(objectPtr);
newGap->remainingObjects = count;
newGap->nextGapOffset = gap->nextGapOffset == 0 ? 0 : (gapEnd - reinterpret_cast<std::byte*>(newGap));
gap->nextGapOffset -= objectPtr - gapStart;
mergeGaps(newGap);
}
return;
}
}
}
} }
} }
}
template<typename... TArgs>
[[nodiscard]]
TObject* create(TArgs&&... args) noexcept
{
TObject* result = allocate();
if (result == nullptr) {
return nullptr;
}
return ::new (result) TObject(std::forward<TArgs>(args)...);
}
[[nodiscard]]
TObject* createMultiple(std::size_t count = 1) noexcept
{
TObject* result = allocate(count);
if (result == nullptr) {
return nullptr;
}
return ::new (result) TObject[count];
}
void destroy(TObject* ptr, std::size_t count = 1) noexcept
{
for (std::size_t idx = 0; idx < count; ++idx) {
ptr[idx].~TObject();
}
deallocate(ptr, count);
}
[[nodiscard]]
iterator begin() const noexcept { return firstPage != nullptr ? iterator(*firstPage) : iterator(); }
[[nodiscard]]
iterator end() const noexcept { return {}; }
private:
TObject* allocateFromPage(page_t& page, std::size_t count)
{
gap_t* previousGap = nullptr;
for (gap_t* gap = page.firstGap; gap != nullptr; previousGap = gap, gap = gap->nextGap())
{
if (gap->remainingObjects == count)
{
// exactly the correct size
if (previousGap != nullptr) {
previousGap->nextGapOffset += gap->nextGapOffset;
}
else {
page.firstGap = gap->nextGap();
}
return reinterpret_cast<TObject*>(gap);
}
else if (gap->remainingObjects > count)
{
// still enough space
TObject* memory = reinterpret_cast<TObject*>(gap) + gap->remainingObjects - count;
gap->remainingObjects -= count;
return reinterpret_cast<TObject*>(memory);
}
}
return nullptr;
}
void mergeGaps(gap_t* start) noexcept
{
while (start->nextGapOffset != 0 && start->remainingObjects == start->nextGapOffset * sizeof(TObject))
{
gap_t& next = *start->nextGap();
start->remainingObjects += start->nextGap()->remainingObjects;
if (next.nextGapOffset == 0) {
start->nextGapOffset = 0;
}
else {
start->nextGapOffset += next.nextGapOffset;
}
}
}
};
} // namespace mijin } // namespace mijin
#endif // !defined(MIJIN_MEMORY_OBJECT_POOL_HPP_INCLUDED) #endif // !defined(MIJIN_MEMORY_OBJECT_POOL_HPP_INCLUDED)

View File

@@ -170,6 +170,9 @@ public:
[[nodiscard]] [[nodiscard]]
constexpr const T& get() const MIJIN_NOEXCEPT { return base_; } constexpr const T& get() const MIJIN_NOEXCEPT { return base_; }
[[nodiscard]]
constexpr T release() && MIJIN_NOEXCEPT { return std::exchange(base_, nullptr); }
template<nullable_type TOther> template<nullable_type TOther>
friend class NotNullable; friend class NotNullable;
}; };

View File

@@ -8,6 +8,8 @@
#include <atomic> #include <atomic>
#include <cstddef> #include <cstddef>
#include "../debug/assert.hpp"
namespace mijin namespace mijin
{ {

View File

@@ -0,0 +1,122 @@
#pragma once
#ifndef MIJIN_UTIL_FORMATTABLE_HPP_INCLUDED
#define MIJIN_UTIL_FORMATTABLE_HPP_INCLUDED 1
#include <format>
#include "../container/boxed_object.hpp"
namespace mijin
{
//
// public concepts
//
template<typename T>
concept parse_result_type = requires
{
typename T::parse_result_t;
};
template<typename T, typename TParseContext>
concept parseable_by_type = parse_result_type<T> &&
requires(const T& object, TParseContext& parseContext)
{
{ T::parseFormat(parseContext) } -> std::convertible_to<std::pair<typename TParseContext::iterator, typename T::parse_result_t>>;
};
template<typename T, typename TFmtContext>
concept simple_formattable_to_type = requires(const T& object, TFmtContext& formatContext)
{
{ object.format(formatContext) } -> std::convertible_to<typename TFmtContext::iterator>;
};
template<typename T, typename TFmtContext, typename TParseContext>
concept complex_formattable_to_type = parseable_by_type<T, TParseContext> &&
requires(const T& object, TParseContext& parseContext, TFmtContext& formatContext, T::parse_result_t& parseResult)
{
{ object.format(formatContext, parseResult) } -> std::convertible_to<typename TFmtContext::iterator>;
};
template<typename T, typename TFmtContext, typename TParseContext>
concept formattable_to_type = simple_formattable_to_type<T, TFmtContext> || complex_formattable_to_type<T, TFmtContext, TParseContext>;
template<typename T>
concept cformattable_type = formattable_to_type<T, std::format_context, std::format_parse_context>;
template<typename T>
concept wformattable_type = formattable_to_type<T, std::wformat_context, std::wformat_parse_context>;
template<typename T>
concept formattable_type = cformattable_type<T> && wformattable_type<T>;
template<typename T>
concept any_formattable_type = cformattable_type<T> || wformattable_type<T>;
//
// internal types
//
namespace impl
{
template<typename T>
struct ParseResult
{
};
template<parse_result_type T>
struct ParseResult<T>
{
BoxedObject<typename T::parse_result_t> value;
};
}
} // namespace mijin
template<mijin::any_formattable_type T>
struct std::formatter<T>
{
[[no_unique_address]] [[maybe_unused]] mijin::impl::ParseResult<T> parseResult;
template<typename TContext>
constexpr TContext::iterator parse(TContext& ctx)
{
if constexpr (mijin::parse_result_type<T>)
{
static_assert(mijin::parseable_by_type<T, TContext>, "Type does not support parsing by this context.");
auto [it, result] = T::parseFormat(ctx);
parseResult.value.construct(std::move(result));
return it;
}
else
{
auto it = ctx.begin();
auto end = ctx.end();
if (it != end && *it != '}')
{
throw std::format_error("invalid format");
}
return it;
}
}
template<typename TContext>
TContext::iterator format(const T& object, TContext& ctx) const
{
if constexpr (mijin::parse_result_type<T>)
{
auto it = object.format(ctx, std::move(*parseResult.value));
parseResult.destroy();
return it;
}
else
{
static_assert(mijin::simple_formattable_to_type<T, TContext>, "Type does not support formatting to this context.");
return object.format(ctx);
}
}
};
#endif // MIJIN_UTIL_FORMATTABLE_HPP_INCLUDED

View File

@@ -3,6 +3,7 @@
#if !defined(MIJIN_UTIL_ITERATORS_HPP_INCLUDED) #if !defined(MIJIN_UTIL_ITERATORS_HPP_INCLUDED)
#define MIJIN_UTIL_ITERATORS_HPP_INCLUDED 1 #define MIJIN_UTIL_ITERATORS_HPP_INCLUDED 1
#include <array>
#include <cstddef> #include <cstddef>
#include <functional> #include <functional>
#include <optional> #include <optional>

View File

@@ -39,9 +39,9 @@ namespace mijin
// internal variables // internal variables
// //
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
namespace namespace
{ {
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
std::mutex gDlErrorMutex; // dlerror may not be thread-safe std::mutex gDlErrorMutex; // dlerror may not be thread-safe
const std::uint64_t gCPUClockResolution = []() const std::uint64_t gCPUClockResolution = []()
@@ -52,8 +52,16 @@ const std::uint64_t gCPUClockResolution = []()
MIJIN_ASSERT(time.tv_sec == 0, "What kind of cpu clock is this?"); MIJIN_ASSERT(time.tv_sec == 0, "What kind of cpu clock is this?");
return static_cast<std::uint64_t>(time.tv_nsec); return static_cast<std::uint64_t>(time.tv_nsec);
}(); }();
}; #elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
#endif // MIJIN_TARGET_OS == MIJIN_OS_LINUX const std::uint64_t gCPUTickFrequency = []()
{
LARGE_INTEGER ticks;
[[maybe_unused]] const BOOL result = QueryPerformanceFrequency(&ticks);
MIJIN_ASSERT(result, "Error getting cpu frequency.");
return static_cast<std::uint64_t>(ticks.QuadPart);
}();
#endif // MIJIN_TARGET_OS == MIJIN_OS_LINUX || MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
}
// //
// internal functions // internal functions
@@ -196,8 +204,10 @@ std::uint64_t getCPUTicks() MIJIN_NOEXCEPT
MIJIN_ASSERT(result == 0, "Error getting cpu time."); MIJIN_ASSERT(result == 0, "Error getting cpu time.");
return (static_cast<std::uint64_t>(time.tv_sec) * 1e9 + time.tv_nsec) / gCPUClockResolution; return (static_cast<std::uint64_t>(time.tv_sec) * 1e9 + time.tv_nsec) / gCPUClockResolution;
#else #else
MIJIN_ERROR("TODO"); LARGE_INTEGER ticks;
return 0; [[maybe_unused]] const BOOL result = QueryPerformanceCounter(&ticks);
MIJIN_ASSERT(result, "Error getting cpu time.");
return static_cast<std::uint64_t>(ticks.QuadPart);
#endif #endif
} }
@@ -206,8 +216,7 @@ std::uint64_t getCPUTicksPerSecond() MIJIN_NOEXCEPT
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX #if MIJIN_TARGET_OS == MIJIN_OS_LINUX
return 1e9 / gCPUClockResolution; return 1e9 / gCPUClockResolution;
#else #else
MIJIN_ERROR("TODO"); return gCPUTickFrequency;
return 0;
#endif #endif
} }
} // namespace mijin } // namespace mijin

View File

@@ -33,6 +33,16 @@ namespace mijin
// public constants // public constants
// //
namespace detail
{
template<typename TChar>
static constexpr std::array DEFAULT_TRIM_CHARS_DATA = {TChar(' '), TChar('\t'), TChar('\r'), TChar('\n')};
}
template<typename TChar>
static const std::basic_string_view<TChar, std::char_traits<TChar>> DEFAULT_TRIM_CHARS
= {detail::DEFAULT_TRIM_CHARS_DATA<TChar>.begin(), detail::DEFAULT_TRIM_CHARS_DATA<TChar>.end()};
// //
// public traits // public traits
// //
@@ -98,6 +108,361 @@ struct SplitOptions
bool ignoreEmpty = true; bool ignoreEmpty = true;
}; };
struct [[nodiscard]] ConvertCharTypeResult
{
unsigned numRead = 0;
unsigned numWritten = 0;
constexpr operator bool() const MIJIN_NOEXCEPT
{
return numRead != 0 || numWritten != 0;
}
constexpr bool operator !() const MIJIN_NOEXCEPT
{
return !static_cast<bool>(*this);
}
};
struct SplitViewOptions
{
bool ignoreEmpty = true;
bool trim = false;
};
template<typename TChar, TChar splitAt, SplitViewOptions options = SplitViewOptions(), typename TCharTraits = std::char_traits<TChar>>
struct SplitStringTraitsCT
{
using char_t = TChar;
using string_view_t = std::basic_string_view<TChar, TCharTraits>;
static constexpr char_t getSplitAt() MIJIN_NOEXCEPT { return splitAt; }
static constexpr bool getIgnoreEmpty() MIJIN_NOEXCEPT { return options.ignoreEmpty; }
static constexpr bool getTrim() MIJIN_NOEXCEPT { return options.trim; }
static constexpr auto getTrimChars() MIJIN_NOEXCEPT { return DEFAULT_TRIM_CHARS<char_t>; }
};
template<typename TChar, typename TCharTraits = std::char_traits<TChar>>
struct SplitStringTraitsRT
{
using char_t = TChar;
using string_view_t = std::basic_string_view<TChar, TCharTraits>;
char_t splitAt;
bool ignoreEmpty;
string_view_t trimChars = {};
constexpr char_t getSplitAt() const MIJIN_NOEXCEPT { return splitAt; }
constexpr bool getIgnoreEmpty() const MIJIN_NOEXCEPT { return ignoreEmpty; }
constexpr bool getTrim() const MIJIN_NOEXCEPT { return !trimChars.empty(); }
constexpr string_view_t getTrimChars() const MIJIN_NOEXCEPT { return trimChars; }
};
template<typename T, typename TChar>
concept SplitStringTraitsType = std::is_copy_constructible_v<T> && requires(const T& object)
{
typename T::char_t;
typename T::string_view_t;
{ object.getSplitAt() } -> std::convertible_to<TChar>;
{ object.getIgnoreEmpty() } -> std::convertible_to<bool>;
{ object.getTrim() } -> std::convertible_to<bool>;
{ object.getTrimChars() } -> std::convertible_to<typename T::string_view_t>;
};
static_assert(SplitStringTraitsType<SplitStringTraitsCT<char, ' '>, char>);
static_assert(SplitStringTraitsType<SplitStringTraitsRT<char>, char>);
template<typename TChar, typename TLine, SplitViewOptions options = SplitViewOptions(), typename TCharTraits = std::char_traits<TChar>>
struct SplitLineTraitsCT : SplitStringTraitsCT<TChar, '\n', options, TCharTraits>
{
using base_t = SplitStringTraitsCT<TChar, '\n', options, TCharTraits>;
using char_t = TChar;
using line_t = TLine;
line_t line = 1;
using base_t::getSplitAt;
using base_t::getIgnoreEmpty;
using base_t::getTrim;
using base_t::getTrimChars;
constexpr void onNext() MIJIN_NOEXCEPT {
++line;
}
constexpr line_t getLine() const MIJIN_NOEXCEPT { return line; }
};
template<typename TChar, typename TLine, typename TCharTraits = std::char_traits<TChar>>
struct SplitLineTraitsRT
{
using char_t = TChar;
using line_t = TLine;
using string_view_t = std::basic_string_view<TChar, TCharTraits>;
line_t line = 1;
bool ignoreEmpty;
string_view_t trimChars = {};
constexpr char_t getSplitAt() const MIJIN_NOEXCEPT { return '\n'; }
constexpr bool getIgnoreEmpty() const MIJIN_NOEXCEPT { return ignoreEmpty; }
constexpr bool getTrim() const MIJIN_NOEXCEPT { return !trimChars.empty(); }
constexpr string_view_t getTrimChars() const MIJIN_NOEXCEPT { return trimChars; }
constexpr line_t getLine() const MIJIN_NOEXCEPT { return line; }
constexpr void onNext() MIJIN_NOEXCEPT {
++line;
}
};
template<typename T, typename TChar, typename TLine>
concept SplitLineTraitsType = SplitStringTraitsType<T, TChar> && requires (const T& object)
{
{ object.getLine() } -> std::convertible_to<TLine>;
};
static_assert(SplitLineTraitsType<SplitLineTraitsCT<char, unsigned>, char, unsigned>);
static_assert(SplitLineTraitsType<SplitLineTraitsRT<char, unsigned>, char, unsigned>);
template<typename TString, typename TChars>
[[nodiscard]]
auto trim(TString&& string, TChars&& chars);
template<typename TChar, SplitStringTraitsType<TChar> TTraits>
class SplitStringIterator
{
public:
using char_t = TChar;
using traits_t = TTraits;
using string_view_t = traits_t::string_view_t;
using base_t = string_view_t::iterator;
using value_type = string_view_t;
private:
[[no_unique_address]] traits_t traits_;
string_view_t full_;
string_view_t::iterator pos_;
string_view_t::iterator next_;
public:
constexpr SplitStringIterator(string_view_t full, base_t pos, traits_t traits = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<traits_t>)
: full_(full), pos_(pos), traits_(std::move(traits))
{
findNext();
}
constexpr explicit SplitStringIterator(traits_t traits = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<traits_t>)
: traits_(std::move(traits)) {}
constexpr SplitStringIterator(const SplitStringIterator&) noexcept(std::is_nothrow_copy_constructible_v<traits_t>) = default;
constexpr SplitStringIterator(SplitStringIterator&&) noexcept(std::is_nothrow_move_constructible_v<traits_t>) = default;
constexpr SplitStringIterator& operator=(const SplitStringIterator&) noexcept(std::is_nothrow_copy_assignable_v<traits_t>) = default;
constexpr SplitStringIterator& operator=(SplitStringIterator&&) noexcept(std::is_nothrow_move_assignable_v<traits_t>) = default;
constexpr bool operator==(const SplitStringIterator& other) const MIJIN_NOEXCEPT { MIJIN_ASSERT(full_ == other.full_, "Comparing unrelated iterators."); return pos_ == other.pos_; }
constexpr bool operator!=(const SplitStringIterator& other) const MIJIN_NOEXCEPT { MIJIN_ASSERT(full_ == other.full_, "Comparing unrelated iterators."); return pos_ != other.pos_; }
constexpr bool operator<(const SplitStringIterator& other) const MIJIN_NOEXCEPT { MIJIN_ASSERT(full_ == other.full_, "Comparing unrelated iterators."); return pos_ < other.pos_; }
constexpr bool operator<=(const SplitStringIterator& other) const MIJIN_NOEXCEPT { MIJIN_ASSERT(full_ == other.full_, "Comparing unrelated iterators."); return pos_ <= other.pos_; }
constexpr bool operator>(const SplitStringIterator& other) const MIJIN_NOEXCEPT { MIJIN_ASSERT(full_ == other.full_, "Comparing unrelated iterators."); return pos_ > other.pos_; }
constexpr bool operator>=(const SplitStringIterator& other) const MIJIN_NOEXCEPT { MIJIN_ASSERT(full_ == other.full_, "Comparing unrelated iterators."); return pos_ >= other.pos_; }
[[nodiscard]]
traits_t& getTraits() MIJIN_NOEXCEPT
{
return traits_;
}
[[nodiscard]]
const traits_t& getTraits() const MIJIN_NOEXCEPT
{
return traits_;
}
constexpr value_type operator*() const MIJIN_NOEXCEPT
{
MIJIN_ASSERT(pos_ != full_.end(), "Dereferencing an invalid iterator.");
string_view_t result{pos_, next_};
if (traits_.getTrim())
{
result = trim(result, traits_.getTrimChars());
}
return result;
}
constexpr SplitStringIterator& operator++() MIJIN_NOEXCEPT
{
MIJIN_ASSERT(pos_ != full_.end(), "Iterating past end.");
if (next_ == full_.end()) {
pos_ = full_.end();
}
else
{
pos_ = std::next(next_);
findNext();
}
return *this;
}
constexpr SplitStringIterator operator++(int) const MIJIN_NOEXCEPT
{
SplitStringIterator copy(*this);
++copy;
return copy;
}
// TODO
// SplitStringIterator& operator--() MIJIN_NOEXCEPT
// {
// MIJIN_ASSERT(pos_ != full_.begin(), "Iterating past begin.");
// next_ = std::prev(pos_);
// pos_ = std::find(std::reverse_iterator(next_), std::reverse_iterator(full_.begin()), separator).base();
// }
private:
constexpr void findNext()
{
while (true)
{
if constexpr (requires{{ traits_.onNext() };}) {
traits_.onNext();
}
next_ = std::find(pos_, full_.end(), traits_.getSplitAt());
if (!traits_.getIgnoreEmpty() || pos_ == full_.end()) {
break;
}
if (traits_.getTrim())
{
const string_view_t trimChars = traits_.getTrimChars();
typename string_view_t::iterator trimmedPos = std::find_if(pos_, next_, [&](char_t chr)
{
return !trimChars.contains(chr);
});
if (trimmedPos == next_)
{
pos_ = next_; // skip this part
}
}
if (pos_ != next_) {
break;
}
pos_ = std::next(pos_);
}
}
};
template<typename TChar, SplitStringTraitsType<TChar> TTraits>
class SplitStringRange
{
public:
using char_t = TChar;
using traits_t = TTraits;
using string_view_t = traits_t::string_view_t;
using iterator = SplitStringIterator<char_t, traits_t>;
private:
[[no_unique_address]] traits_t traits_;
string_view_t stringView_;
public:
constexpr explicit SplitStringRange(string_view_t stringView, traits_t traits = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<traits_t>)
: stringView_(stringView), traits_(std::move(traits)) {}
constexpr SplitStringRange(const SplitStringRange&) noexcept(std::is_nothrow_copy_constructible_v<traits_t>) = default;
constexpr SplitStringRange(SplitStringRange&&) noexcept(std::is_nothrow_move_constructible_v<traits_t>) = default;
constexpr SplitStringRange& operator=(const SplitStringRange&) noexcept(std::is_nothrow_copy_assignable_v<traits_t>) = default;
constexpr SplitStringRange& operator=(SplitStringRange&&) noexcept(std::is_nothrow_move_assignable_v<traits_t>) = default;
constexpr auto operator<=>(const SplitStringRange&) const noexcept = default;
constexpr iterator begin() const MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v<traits_t>)
{
return iterator(stringView_, stringView_.begin(), traits_);
}
constexpr iterator end() const MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v<traits_t>)
{
return iterator(stringView_, stringView_.end(), traits_);
}
};
template<typename TChar, typename TLine = unsigned, SplitLineTraitsType<TChar, TLine> TTraits = SplitLineTraitsCT<TChar, TLine>>
class LineIterator
{
public:
using char_t = TChar;
using line_t = TLine;
using traits_t = TTraits;
using base_t = SplitStringIterator<TChar, traits_t>;
using string_view_t = base_t::string_view_t;
using value_type = std::pair<string_view_t, line_t>;
private:
base_t base_ = {};
public:
constexpr LineIterator(string_view_t full, string_view_t::iterator pos, traits_t traits = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<traits_t>)
: base_(full, pos, std::move(traits)) {}
constexpr explicit LineIterator(traits_t traits = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<traits_t>)
: base_(std::move(traits)) {}
LineIterator(const LineIterator&) noexcept = default;
LineIterator(LineIterator&&) noexcept = default;
LineIterator& operator=(const LineIterator&) noexcept = default;
LineIterator& operator=(LineIterator&&) noexcept = default;
bool operator==(const LineIterator& other) const MIJIN_NOEXCEPT { return base_ == other.base_; }
bool operator!=(const LineIterator& other) const MIJIN_NOEXCEPT { return base_ != other.base_; }
bool operator<(const LineIterator& other) const MIJIN_NOEXCEPT { return base_ < other.base_; }
bool operator>(const LineIterator& other) const MIJIN_NOEXCEPT { return base_ > other.base_; }
bool operator<=(const LineIterator& other) const MIJIN_NOEXCEPT { return base_ <= other.base_; }
bool operator>=(const LineIterator& other) const MIJIN_NOEXCEPT { return base_ >= other.base_; }
constexpr value_type operator*() const MIJIN_NOEXCEPT
{
string_view_t stringView = *base_;
if (!base_.getTraits().getTrim())
{
// always split \r, even if not trimming other whitespace
if (stringView.ends_with('\r')) {
stringView = stringView.substr(0, stringView.size() - 1);
}
}
return {stringView, base_.getTraits().line};
}
constexpr LineIterator& operator++() MIJIN_NOEXCEPT
{
++base_;
return *this;
}
constexpr LineIterator operator++(int) const MIJIN_NOEXCEPT
{
SplitStringIterator copy(*this);
++copy;
return copy;
}
};
template<typename TChar, typename TLine = unsigned, SplitLineTraitsType<TChar, TLine> TTraits = SplitLineTraitsCT<TChar, TLine>>
class LineRange
{
public:
using char_t = TChar;
using line_t = TLine;
using traits_t = TTraits;
using iterator = LineIterator<char_t, line_t, traits_t>;
using string_view_t = iterator::string_view_t;
private:
[[no_unique_address]] traits_t traits_;
string_view_t stringView_;
public:
constexpr explicit LineRange(string_view_t stringView, traits_t traits = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<traits_t>)
: traits_(std::move(traits)), stringView_(stringView) {}
constexpr LineRange(const LineRange&) noexcept(std::is_nothrow_copy_constructible_v<traits_t>) = default;
constexpr LineRange(LineRange&&) noexcept(std::is_nothrow_move_constructible_v<traits_t>) = default;
constexpr LineRange& operator=(const LineRange&) noexcept(std::is_nothrow_copy_assignable_v<traits_t>) = default;
constexpr LineRange& operator=(LineRange&&) noexcept(std::is_nothrow_move_assignable_v<traits_t>) = default;
constexpr auto operator<=>(const LineRange&) const noexcept = default;
constexpr iterator begin() const MIJIN_NOEXCEPT
{
return iterator(stringView_, stringView_.begin(), traits_);
}
constexpr iterator end() const MIJIN_NOEXCEPT
{
return iterator(stringView_, stringView_.end(), traits_);
}
};
// //
// public functions // public functions
// //
@@ -273,13 +638,6 @@ std::basic_string_view<TChar, TTraits> trimImpl(std::basic_string_view<TChar, TT
{ {
return trimPrefixImpl(trimSuffixImpl(stringView, charsToTrim), charsToTrim); return trimPrefixImpl(trimSuffixImpl(stringView, charsToTrim), charsToTrim);
} }
template<typename TChar>
static const std::array DEFAULT_TRIM_CHARS_DATA = {TChar(' '), TChar('\t'), TChar('\r'), TChar('\n')};
template<typename TChar>
static const std::basic_string_view<TChar, std::char_traits<TChar>> DEFAULT_TRIM_CHARS
= {DEFAULT_TRIM_CHARS_DATA<TChar>.begin(), DEFAULT_TRIM_CHARS_DATA<TChar>.end()};
} }
template<typename TLeft, typename TRight> template<typename TLeft, typename TRight>
@@ -296,6 +654,61 @@ template<std::size_t count, typename TLeft, typename TRight>
std::basic_string_view(std::forward<TRight>(separator)), options, outNumResults); std::basic_string_view(std::forward<TRight>(separator)), options, outNumResults);
} }
template<typename TTraits, typename TChar> requires (SplitStringTraitsType<TTraits, TChar>)
[[nodiscard]] SplitStringRange<TChar, TTraits> splitView(typename TTraits::string_view_t stringView, TTraits traits = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TTraits>)
{
return SplitStringRange<TChar, TTraits>(stringView, std::move(traits));
}
template<auto splitAt, SplitViewOptions options = SplitViewOptions(), typename TStringView, typename TChar = decltype(splitAt), typename TCharTraits = std::char_traits<TChar>>
[[nodiscard]] SplitStringRange<TChar, SplitStringTraitsCT<TChar, splitAt, options, TCharTraits>> splitView(TStringView&& stringView) MIJIN_NOEXCEPT
{
return splitView<SplitStringTraitsCT<TChar, splitAt, options, TCharTraits>>(std::basic_string_view<TChar, TCharTraits>(std::forward<TStringView>(stringView)));
}
template<typename TStringView, typename TChar, typename TCharTraits = std::char_traits<TChar>, typename TTrimChars = std::basic_string_view<TChar, TCharTraits>>
[[nodiscard]] auto splitView(TStringView&& stringView, TChar splitAt, bool ignoreEmpty = true, TTrimChars trimChars = {}) MIJIN_NOEXCEPT
{
return splitView(std::basic_string_view<TChar, TCharTraits>(std::forward<TStringView>(stringView)), SplitStringTraitsRT<TChar, TCharTraits>{
.splitAt = splitAt,
.ignoreEmpty = ignoreEmpty,
.trimChars = std::basic_string_view<TChar, TCharTraits>(std::forward<TTrimChars>(trimChars))
});
}
template<typename TTraits, typename TChar, typename TLine = unsigned> requires(SplitLineTraitsType<TTraits, TChar, TLine>)
[[nodiscard]] LineRange<TChar, TLine, TTraits> splitLines(typename TTraits::string_view_t stringView, TTraits traits = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TTraits>)
{
return LineRange<TChar, TLine, TTraits>(stringView, std::move(traits));
}
template<typename TLine = unsigned, SplitViewOptions options = SplitViewOptions{.ignoreEmpty=false},
typename TParam,
typename TStringView = decltype(std::basic_string_view(std::declval<TParam&&>())),
typename TChar = typename TStringView::value_type,
typename TCharTraits = typename TStringView::traits_type,
typename TTraits = SplitLineTraitsCT<TChar, TLine, options, TCharTraits>>
[[nodiscard]]
auto splitLines(TParam&& stringView) MIJIN_NOEXCEPT -> LineRange<TChar, TLine, TTraits>
{
return LineRange<TChar, TLine, TTraits>(std::basic_string_view(std::forward<TParam>(stringView)));
}
template<typename TParam, typename TLine = unsigned,
typename TStringView = decltype(std::basic_string_view(std::declval<TParam&&>())),
typename TChar = typename TStringView::value_type,
typename TCharTraits = typename TStringView::traits_type,
typename TTraits = SplitLineTraitsRT<TChar, TLine, TCharTraits>,
typename TTrimChars = TStringView>
[[nodiscard]]
auto splitLines(TParam&& stringView, bool ignoreEmpty, TTrimChars&& trimChars = {}) MIJIN_NOEXCEPT -> LineRange<TChar, TLine, TTraits>
{
return LineRange<TChar, TLine, TTraits>(std::basic_string_view(std::forward<TParam>(stringView)), TTraits{
.ignoreEmpty = ignoreEmpty,
.trimChars = std::basic_string_view<TChar, TCharTraits>(std::forward<TTrimChars>(trimChars))
});
}
template<typename TString, typename TChars> template<typename TString, typename TChars>
[[nodiscard]] [[nodiscard]]
auto trimPrefix(TString&& string, TChars&& chars) auto trimPrefix(TString&& string, TChars&& chars)
@@ -307,7 +720,7 @@ template<typename TString>
[[nodiscard]] [[nodiscard]]
auto trimPrefix(TString&& string) auto trimPrefix(TString&& string)
{ {
return trimPrefix(string, detail::DEFAULT_TRIM_CHARS<str_char_type_t<TString>>); return trimPrefix(string, DEFAULT_TRIM_CHARS<str_char_type_t<TString>>);
} }
template<typename TString, typename TChars> template<typename TString, typename TChars>
@@ -321,7 +734,7 @@ template<typename TString>
[[nodiscard]] [[nodiscard]]
auto trimSuffix(TString&& string) auto trimSuffix(TString&& string)
{ {
return trimSuffix(string, detail::DEFAULT_TRIM_CHARS<str_char_type_t<TString>>); return trimSuffix(string, DEFAULT_TRIM_CHARS<str_char_type_t<TString>>);
} }
template<typename TString, typename TChars> template<typename TString, typename TChars>
@@ -335,7 +748,7 @@ template<typename TString>
[[nodiscard]] [[nodiscard]]
auto trim(TString&& string) auto trim(TString&& string)
{ {
return trim(string, detail::DEFAULT_TRIM_CHARS<str_char_type_t<TString>>); return trim(string, DEFAULT_TRIM_CHARS<str_char_type_t<TString>>);
} }
template<typename TLeft, typename TRight> template<typename TLeft, typename TRight>
@@ -487,21 +900,6 @@ auto operator|(TIterable&& iterable, const Join& joiner)
} }
} // namespace pipe } // namespace pipe
struct [[nodiscard]] ConvertCharTypeResult
{
unsigned numRead = 0;
unsigned numWritten = 0;
constexpr operator bool() const MIJIN_NOEXCEPT
{
return numRead != 0 || numWritten != 0;
}
constexpr bool operator !() const MIJIN_NOEXCEPT
{
return !static_cast<bool>(*this);
}
};
template<typename TFrom, typename TTo> template<typename TFrom, typename TTo>
ConvertCharTypeResult convertCharType(const TFrom* chrFrom, std::size_t numFrom, TTo* outTo, std::size_t numTo, std::mbstate_t& mbstate) MIJIN_NOEXCEPT ConvertCharTypeResult convertCharType(const TFrom* chrFrom, std::size_t numFrom, TTo* outTo, std::size_t numTo, std::mbstate_t& mbstate) MIJIN_NOEXCEPT
{ {

View File

@@ -253,6 +253,42 @@ struct optional_base<T, false>
template<typename T, bool enable> template<typename T, bool enable>
using optional_base_t = optional_base<T, enable>::type; using optional_base_t = optional_base<T, enable>::type;
namespace impl
{
template<typename TFunc>
struct function_traits_base {};
template<typename TResult, typename... TParams>
struct function_traits_base<TResult (*)(TParams...)>
{
using result_t = TResult;
using params_t = std::tuple<TParams...>;
};
template<typename TResult, typename TType, typename... TParams>
struct function_traits_base<TResult (TType::*)(TParams...)>
{
using result_t = TResult;
using params_t = std::tuple<TType*, TParams...>;
};
template<typename TResult, typename TType, typename... TParams>
struct function_traits_base<TResult (TType::*)(TParams...) const>
{
using result_t = TResult;
using params_t = std::tuple<const TType*, TParams...>;
};
}
template<typename TFunc>
struct function_traits : impl::function_traits_base<TFunc>
{
static constexpr std::size_t NUM_PARAMS = std::tuple_size_v<typename impl::function_traits_base<TFunc>::params_t>;
template<std::size_t pos>
using param_t = std::tuple_element_t<pos, typename impl::function_traits_base<TFunc>::params_t>;
};
// //
// public functions // public functions
// //

View File

@@ -26,3 +26,7 @@
#if defined(DEBUG) #if defined(DEBUG)
#undef DEBUG #undef DEBUG
#endif #endif
#if defined(VOID)
#undef VOID
#endif