Compare commits
14 Commits
dev
...
657b0d1ab1
| Author | SHA1 | Date | |
|---|---|---|---|
| 657b0d1ab1 | |||
|
|
4bd6843ba5 | ||
|
|
657d05341c | ||
|
|
9cefc52568 | ||
|
|
2d413764f0 | ||
|
|
50532459ce | ||
| 2d5a7316f1 | |||
| cf45aaa39a | |||
| def8d0b6b8 | |||
| f7daa58b38 | |||
| 1208f1f59f | |||
| 93a0b6cf58 | |||
| cae7fecb51 | |||
| 13f37a81b6 |
@@ -68,12 +68,12 @@ public:
|
||||
BoxedObject(const BoxedObject&) = delete;
|
||||
BoxedObject(BoxedObject&&) = delete;
|
||||
|
||||
#if MIJIN_BOXED_OBJECT_DEBUG
|
||||
constexpr ~BoxedObject() noexcept
|
||||
{
|
||||
#if MIJIN_BOXED_OBJECT_DEBUG
|
||||
MIJIN_ASSERT(!constructed, "BoxedObject::~BoxedObject(): Object has not been destroyed prior to destructor!");
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
BoxedObject& operator=(const BoxedObject&) = delete;
|
||||
BoxedObject& operator=(BoxedObject&&) = delete;
|
||||
|
||||
@@ -7,317 +7,56 @@
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include "../internal/common.hpp"
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "../debug/assert.hpp"
|
||||
#include "../util/align.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
namespace impl
|
||||
{
|
||||
struct ObjectPoolGap
|
||||
{
|
||||
std::uint16_t nextGapOffset;
|
||||
std::uint16_t remainingObjects;
|
||||
|
||||
ObjectPoolGap* nextGap() noexcept {
|
||||
return nextGapOffset == 0 ? nullptr : reinterpret_cast<ObjectPoolGap*>(reinterpret_cast<std::byte*>(this) + nextGapOffset);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename TObject, std::size_t OBJECTS_PER_PAGE>
|
||||
struct ObjectPoolPage
|
||||
{
|
||||
ObjectPoolGap* firstGap;
|
||||
ObjectPoolPage* nextPage = nullptr;
|
||||
std::array<std::byte, OBJECTS_PER_PAGE * MIJIN_STRIDEOF(TObject)> data;
|
||||
|
||||
ObjectPoolPage() noexcept
|
||||
{
|
||||
firstGap = reinterpret_cast<ObjectPoolGap*>(data.data());
|
||||
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)
|
||||
{
|
||||
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>
|
||||
template<typename TObject, std::size_t OBJECTS_PER_PAGE = 1024>
|
||||
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));
|
||||
struct GapInfo
|
||||
{
|
||||
std::uint32_t objectCount;
|
||||
std::uint32_t nextGap;
|
||||
};
|
||||
static constexpr std::size_t ALIGN = std::max(alignof(TObject), alignof(GapInfo));
|
||||
|
||||
[[no_unique_address]] TAllocator<page_t> allocator_;
|
||||
page_t* firstPage = nullptr;
|
||||
struct alignas(ALIGN) ObjectData
|
||||
{
|
||||
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:
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
[[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>
|
||||
TObject* ObjectPool<TObject, OBJECTS_PER_PAGE>::allocate(std::size_t count) noexcept
|
||||
{
|
||||
MIJIN_ASSERT(count <= OBJECTS_PER_PAGE, "Cannot allocate more than OBJECTS_PER_PAGE elements at once!");
|
||||
// first try to find a free spot in the existing pages
|
||||
|
||||
for (std::unique_ptr<Page>& page : pages)
|
||||
{
|
||||
std::uint32_t offset = page->freeOffset;
|
||||
while (offset < OBJECTS_PER_PAGE)
|
||||
{
|
||||
GapInfo& gapInfo = *std::bit_cast<GapInfo*>(&page->data[offset]);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace mijin
|
||||
|
||||
#endif // !defined(MIJIN_MEMORY_OBJECT_POOL_HPP_INCLUDED)
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
|
||||
#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
|
||||
@@ -3,7 +3,6 @@
|
||||
#if !defined(MIJIN_UTIL_ITERATORS_HPP_INCLUDED)
|
||||
#define MIJIN_UTIL_ITERATORS_HPP_INCLUDED 1
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
|
||||
@@ -39,9 +39,9 @@ namespace mijin
|
||||
// internal variables
|
||||
//
|
||||
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
|
||||
namespace
|
||||
{
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
|
||||
std::mutex gDlErrorMutex; // dlerror may not be thread-safe
|
||||
|
||||
const std::uint64_t gCPUClockResolution = []()
|
||||
@@ -52,16 +52,8 @@ const std::uint64_t gCPUClockResolution = []()
|
||||
MIJIN_ASSERT(time.tv_sec == 0, "What kind of cpu clock is this?");
|
||||
return static_cast<std::uint64_t>(time.tv_nsec);
|
||||
}();
|
||||
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
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
|
||||
}
|
||||
};
|
||||
#endif // MIJIN_TARGET_OS == MIJIN_OS_LINUX
|
||||
|
||||
//
|
||||
// internal functions
|
||||
@@ -204,10 +196,8 @@ std::uint64_t getCPUTicks() MIJIN_NOEXCEPT
|
||||
MIJIN_ASSERT(result == 0, "Error getting cpu time.");
|
||||
return (static_cast<std::uint64_t>(time.tv_sec) * 1e9 + time.tv_nsec) / gCPUClockResolution;
|
||||
#else
|
||||
LARGE_INTEGER ticks;
|
||||
[[maybe_unused]] const BOOL result = QueryPerformanceCounter(&ticks);
|
||||
MIJIN_ASSERT(result, "Error getting cpu time.");
|
||||
return static_cast<std::uint64_t>(ticks.QuadPart);
|
||||
MIJIN_ERROR("TODO");
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -216,7 +206,8 @@ std::uint64_t getCPUTicksPerSecond() MIJIN_NOEXCEPT
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
|
||||
return 1e9 / gCPUClockResolution;
|
||||
#else
|
||||
return gCPUTickFrequency;
|
||||
MIJIN_ERROR("TODO");
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
} // namespace mijin
|
||||
|
||||
@@ -33,16 +33,6 @@ namespace mijin
|
||||
// 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
|
||||
//
|
||||
@@ -108,361 +98,6 @@ struct SplitOptions
|
||||
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
|
||||
//
|
||||
@@ -638,6 +273,13 @@ std::basic_string_view<TChar, TTraits> trimImpl(std::basic_string_view<TChar, TT
|
||||
{
|
||||
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>
|
||||
@@ -654,61 +296,6 @@ template<std::size_t count, typename TLeft, typename TRight>
|
||||
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>
|
||||
[[nodiscard]]
|
||||
auto trimPrefix(TString&& string, TChars&& chars)
|
||||
@@ -720,7 +307,7 @@ template<typename TString>
|
||||
[[nodiscard]]
|
||||
auto trimPrefix(TString&& string)
|
||||
{
|
||||
return trimPrefix(string, DEFAULT_TRIM_CHARS<str_char_type_t<TString>>);
|
||||
return trimPrefix(string, detail::DEFAULT_TRIM_CHARS<str_char_type_t<TString>>);
|
||||
}
|
||||
|
||||
template<typename TString, typename TChars>
|
||||
@@ -734,7 +321,7 @@ template<typename TString>
|
||||
[[nodiscard]]
|
||||
auto trimSuffix(TString&& string)
|
||||
{
|
||||
return trimSuffix(string, DEFAULT_TRIM_CHARS<str_char_type_t<TString>>);
|
||||
return trimSuffix(string, detail::DEFAULT_TRIM_CHARS<str_char_type_t<TString>>);
|
||||
}
|
||||
|
||||
template<typename TString, typename TChars>
|
||||
@@ -748,7 +335,7 @@ template<typename TString>
|
||||
[[nodiscard]]
|
||||
auto trim(TString&& string)
|
||||
{
|
||||
return trim(string, DEFAULT_TRIM_CHARS<str_char_type_t<TString>>);
|
||||
return trim(string, detail::DEFAULT_TRIM_CHARS<str_char_type_t<TString>>);
|
||||
}
|
||||
|
||||
template<typename TLeft, typename TRight>
|
||||
@@ -900,6 +487,21 @@ auto operator|(TIterable&& iterable, const Join& joiner)
|
||||
}
|
||||
} // 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>
|
||||
ConvertCharTypeResult convertCharType(const TFrom* chrFrom, std::size_t numFrom, TTo* outTo, std::size_t numTo, std::mbstate_t& mbstate) MIJIN_NOEXCEPT
|
||||
{
|
||||
|
||||
@@ -26,7 +26,3 @@
|
||||
#if defined(DEBUG)
|
||||
#undef DEBUG
|
||||
#endif
|
||||
|
||||
#if defined(VOID)
|
||||
#undef VOID
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user