aboutsummaryrefslogtreecommitdiff
path: root/include/cru/base
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2024-10-06 13:57:39 +0800
committercrupest <crupest@outlook.com>2024-10-06 13:57:39 +0800
commitdfe62dcf8bcefc523b466e127c3edc4dc2756629 (patch)
tree1c751a14ba0da07ca2ff805633f97568060aa4c9 /include/cru/base
parentf51eb955e188858272230a990565931e7403f23b (diff)
downloadcru-dfe62dcf8bcefc523b466e127c3edc4dc2756629.tar.gz
cru-dfe62dcf8bcefc523b466e127c3edc4dc2756629.tar.bz2
cru-dfe62dcf8bcefc523b466e127c3edc4dc2756629.zip
Rename common to base.
Diffstat (limited to 'include/cru/base')
-rw-r--r--include/cru/base/Base.h112
-rw-r--r--include/cru/base/Bitmask.h55
-rw-r--r--include/cru/base/Buffer.h161
-rw-r--r--include/cru/base/ClonablePtr.h216
-rw-r--r--include/cru/base/Event.h281
-rw-r--r--include/cru/base/Event2.h200
-rw-r--r--include/cru/base/Exception.h60
-rw-r--r--include/cru/base/Format.h154
-rw-r--r--include/cru/base/Guard.h26
-rw-r--r--include/cru/base/HandlerRegistry.h87
-rw-r--r--include/cru/base/PreConfig.h18
-rw-r--r--include/cru/base/PropertyTree.h63
-rw-r--r--include/cru/base/Range.h42
-rw-r--r--include/cru/base/SelfResolvable.h153
-rw-r--r--include/cru/base/String.h492
-rw-r--r--include/cru/base/StringToNumberConverter.h107
-rw-r--r--include/cru/base/StringUtil.h226
-rw-r--r--include/cru/base/SubProcess.h258
-rw-r--r--include/cru/base/concurrent/ConcurrentQueue.h88
-rw-r--r--include/cru/base/io/AutoReadStream.h72
-rw-r--r--include/cru/base/io/BufferStream.h85
-rw-r--r--include/cru/base/io/CFileStream.h39
-rw-r--r--include/cru/base/io/MemoryStream.h36
-rw-r--r--include/cru/base/io/OpenFileFlag.h56
-rw-r--r--include/cru/base/io/ProxyStream.h42
-rw-r--r--include/cru/base/io/Resource.h8
-rw-r--r--include/cru/base/io/Stream.h126
-rw-r--r--include/cru/base/log/Logger.h88
-rw-r--r--include/cru/base/log/StdioLogTarget.h17
-rw-r--r--include/cru/base/platform/Exception.h12
-rw-r--r--include/cru/base/platform/osx/Convert.h18
-rw-r--r--include/cru/base/platform/osx/Exception.h15
-rw-r--r--include/cru/base/platform/unix/PosixSpawnSubProcess.h50
-rw-r--r--include/cru/base/platform/unix/UnixFileStream.h39
-rw-r--r--include/cru/base/platform/unix/UnixPipe.h70
-rw-r--r--include/cru/base/platform/web/WebException.h16
-rw-r--r--include/cru/base/platform/win/ComAutoInit.h21
-rw-r--r--include/cru/base/platform/win/DebugLogTarget.h25
-rw-r--r--include/cru/base/platform/win/Exception.h56
-rw-r--r--include/cru/base/platform/win/StreamConvert.h14
-rw-r--r--include/cru/base/platform/win/Win32FileStream.h56
-rw-r--r--include/cru/base/platform/win/WinPreConfig.h13
42 files changed, 3773 insertions, 0 deletions
diff --git a/include/cru/base/Base.h b/include/cru/base/Base.h
new file mode 100644
index 00000000..8a6a7634
--- /dev/null
+++ b/include/cru/base/Base.h
@@ -0,0 +1,112 @@
+#pragma once
+#include "PreConfig.h" // IWYU pragma: keep
+
+#include <cassert>
+#include <cstddef>
+#include <functional>
+#include <memory>
+
+#ifdef CRU_PLATFORM_WINDOWS
+#ifdef CRU_BASE_EXPORT_API
+#define CRU_BASE_API __declspec(dllexport)
+#else
+#define CRU_BASE_API __declspec(dllimport)
+#endif
+#else
+#define CRU_BASE_API
+#endif
+
+#define CRU_UNUSED(entity) static_cast<void>(entity);
+
+#define CRU__CONCAT(a, b) a##b
+#define CRU_MAKE_UNICODE_LITERAL(str) CRU__CONCAT(u, #str)
+
+#define CRU_DEFAULT_COPY(classname) \
+ classname(const classname&) = default; \
+ classname& operator=(const classname&) = default;
+
+#define CRU_DEFAULT_MOVE(classname) \
+ classname(classname&&) = default; \
+ classname& operator=(classname&&) = default;
+
+#define CRU_DELETE_COPY(classname) \
+ classname(const classname&) = delete; \
+ classname& operator=(const classname&) = delete;
+
+#define CRU_DELETE_MOVE(classname) \
+ classname(classname&&) = delete; \
+ classname& operator=(classname&&) = delete;
+
+#define CRU_DEFAULT_DESTRUCTOR(classname) ~classname() = default;
+
+#define CRU_DEFAULT_CONSTRUCTOR_DESTRUCTOR(classname) \
+ classname() = default; \
+ ~classname() = default;
+
+#define CRU_DEFINE_COMPARE_OPERATORS(classname) \
+ inline bool operator==(const classname& left, const classname& right) { \
+ return left.Compare(right) == 0; \
+ } \
+ \
+ inline bool operator!=(const classname& left, const classname& right) { \
+ return left.Compare(right) != 0; \
+ } \
+ \
+ inline bool operator<(const classname& left, const classname& right) { \
+ return left.Compare(right) < 0; \
+ } \
+ \
+ inline bool operator<=(const classname& left, const classname& right) { \
+ return left.Compare(right) <= 0; \
+ } \
+ \
+ inline bool operator>(const classname& left, const classname& right) { \
+ return left.Compare(right) > 0; \
+ } \
+ \
+ inline bool operator>=(const classname& left, const classname& right) { \
+ return left.Compare(right) >= 0; \
+ }
+
+namespace cru {
+class CRU_BASE_API Object {
+ public:
+ Object() = default;
+ CRU_DEFAULT_COPY(Object)
+ CRU_DEFAULT_MOVE(Object)
+ virtual ~Object() = default;
+};
+
+struct CRU_BASE_API Interface {
+ Interface() = default;
+ CRU_DELETE_COPY(Interface)
+ CRU_DELETE_MOVE(Interface)
+ virtual ~Interface() = default;
+};
+
+[[noreturn]] void CRU_BASE_API UnreachableCode();
+
+using Index = std::ptrdiff_t;
+
+inline void Expects(bool condition) { assert(condition); }
+inline void Ensures(bool condition) { assert(condition); }
+template <typename T>
+inline void Expects(const std::shared_ptr<T>& ptr) {
+ assert(ptr != nullptr);
+}
+template <typename T>
+inline void Ensures(const std::shared_ptr<T>& ptr) {
+ assert(ptr != nullptr);
+}
+
+// https://www.boost.org/doc/libs/1_54_0/doc/html/hash/reference.html#boost.hash_combine
+template <class T>
+inline void hash_combine(std::size_t& s, const T& v) {
+ std::hash<T> h;
+ s ^= h(v) + 0x9e3779b9 + (s << 6) + (s >> 2);
+}
+
+#define CRU_DEFINE_CLASS_LOG_TAG(tag) \
+ private: \
+ constexpr static const char16_t* kLogTag = tag;
+} // namespace cru
diff --git a/include/cru/base/Bitmask.h b/include/cru/base/Bitmask.h
new file mode 100644
index 00000000..9b6b8957
--- /dev/null
+++ b/include/cru/base/Bitmask.h
@@ -0,0 +1,55 @@
+#pragma once
+
+#include <functional>
+
+namespace cru {
+template <typename Tag, typename TUnderlying = unsigned>
+struct Bitmask final {
+ using Underlying = TUnderlying;
+
+ constexpr Bitmask() : value(0) {}
+ constexpr explicit Bitmask(TUnderlying value) : value(value) {}
+
+ static constexpr Bitmask FromOffset(int offset) {
+ return Bitmask(static_cast<TUnderlying>(1u << offset));
+ }
+
+ constexpr bool Has(Bitmask rhs) const { return (value & rhs.value) != 0; }
+
+ Bitmask operator|(Bitmask rhs) const { return Bitmask(value | rhs.value); }
+ Bitmask operator&(Bitmask rhs) const { return Bitmask(value & rhs.value); }
+ Bitmask operator^(Bitmask rhs) const { return Bitmask(value ^ rhs.value); }
+ Bitmask operator~() const { return Bitmask(~value); }
+ Bitmask& operator|=(Bitmask rhs) {
+ value |= rhs.value;
+ return *this;
+ }
+ Bitmask& operator&=(Bitmask rhs) {
+ value &= rhs.value;
+ return *this;
+ }
+ Bitmask& operator^=(Bitmask rhs) {
+ value ^= rhs.value;
+ return *this;
+ }
+
+ bool operator==(Bitmask rhs) const { return this->value == rhs.value; }
+ bool operator!=(Bitmask rhs) const { return this->value != rhs.value; }
+
+ explicit operator TUnderlying() const { return value; }
+ operator bool() const { return value != 0; }
+
+ TUnderlying value;
+};
+} // namespace cru
+
+namespace std {
+template <typename Tag, typename TUnderlying>
+struct hash<cru::Bitmask<Tag, TUnderlying>> {
+ using Bitmask = cru::Bitmask<Tag, TUnderlying>;
+
+ std::size_t operator()(Bitmask bitmask) const {
+ return std::hash<TUnderlying>{}(bitmask.value);
+ }
+};
+} // namespace std
diff --git a/include/cru/base/Buffer.h b/include/cru/base/Buffer.h
new file mode 100644
index 00000000..bc2e2a26
--- /dev/null
+++ b/include/cru/base/Buffer.h
@@ -0,0 +1,161 @@
+#pragma once
+
+#include "Base.h"
+
+namespace cru {
+class Buffer final {
+ friend void swap(Buffer& left, Buffer& right) noexcept;
+
+ public:
+ Buffer();
+ explicit Buffer(Index size);
+
+ Buffer(const Buffer& other);
+ Buffer(Buffer&& other) noexcept;
+
+ Buffer& operator=(const Buffer& other);
+ Buffer& operator=(Buffer&& other) noexcept;
+
+ ~Buffer();
+
+ public:
+ Index GetBufferSize() const { return size_; }
+ Index GetUsedSize() const { return used_end_ - used_begin_; }
+ bool IsNull() const { return ptr_ == nullptr; }
+ bool IsUsedReachEnd() const { return used_end_ == size_; }
+
+ Index GetFrontFree() const { return used_begin_; }
+ Index GetBackFree() const { return size_ - used_end_; }
+
+ Index GetUsedBegin() const { return used_begin_; }
+ Index GetUsedEnd() const { return used_end_; }
+
+ std::byte* GetPtr() { return GetPtrAt(0); }
+ const std::byte* GetPtr() const { return GetPtrAt(0); }
+
+ std::byte* GetPtrAt(Index index) { return ptr_ + index; }
+ const std::byte* GetPtrAt(Index index) const { return ptr_ + index; }
+
+ std::byte& GetRefAt(Index index) { return *GetPtrAt(index); }
+ const std::byte& GetRefAt(Index index) const { return *GetPtrAt(index); }
+
+ std::byte* GetUsedBeginPtr() { return GetPtrAt(GetUsedBegin()); }
+ const std::byte* GetUsedBeginPtr() const { return GetPtrAt(GetUsedBegin()); }
+ std::byte* GetUsedEndPtr() { return GetPtrAt(GetUsedEnd()); }
+ const std::byte* GetUsedEndPtr() const { return GetPtrAt(GetUsedEnd()); }
+
+ std::byte GetByteAt(Index index) const { return ptr_[index]; }
+ void SetByteAt(Index index, std::byte value) { ptr_[index] = value; }
+
+ void AssignBytes(std::byte* src, Index src_size, bool use_memmove = false) {
+ return AssignBytes(0, src, 0, src_size, use_memmove);
+ }
+ void AssignBytes(Index dst_offset, std::byte* src, Index src_size,
+ bool use_memmove = false) {
+ return AssignBytes(dst_offset, src, 0, src_size, use_memmove);
+ }
+ void AssignBytes(Index dst_offset, std::byte* src, Index src_offset,
+ Index src_size, bool use_memmove = false);
+
+ /**
+ * @brief Change the size of the buffer.
+ *
+ * Unless new size is the same as current size, the buffer is always released
+ * and a new one is allocated. If preserve_used is true, the used size and old
+ * data is copied to the new buffer. If new size is smaller than old used
+ * size, then exceeded data will be lost.
+ */
+ void ResizeBuffer(Index new_size, bool preserve_used);
+
+ /**
+ * @brief Append data to the front of used bytes and increase used size.
+ * @return The actual size of data saved.
+ *
+ * If there is no enough space left for new data, the rest space will be
+ * written and the size of it will be returned, leaving exceeded data not
+ * saved.
+ */
+ Index PushFront(const std::byte* other, Index other_size,
+ bool use_memmove = false);
+
+ bool PushBack(std::byte b);
+
+ /**
+ * @brief Append data to the back of used bytes and increase used size.
+ * @return The actual size of data saved.
+ *
+ * If there is no enough space left for new data, the rest space will be
+ * written and the size of it will be returned, leaving exceeded data not
+ * saved.
+ */
+ Index PushBack(const std::byte* other, Index other_size,
+ bool use_memmove = false);
+
+ void PushBackCount(Index count);
+
+ /**
+ * @brief Move forward the used-begin ptr.
+ * @return The actual size moved forward.
+ *
+ * If given size is bigger than current used size, the used size will be
+ * returned and set to 0.
+ */
+ Index PopFront(Index size);
+
+ /**
+ * @brief Pop front data of used bytes into another buffer.
+ * @return The actual size popped.
+ *
+ * If given size is bigger than current used size, then only current used size
+ * of bytes will be popped. If given size is smaller than current used size,
+ * then only given size of bytes will be popped.
+ */
+ Index PopFront(std::byte* buffer, Index size, bool use_memmove = false);
+
+ /**
+ * @brief Move backward the used-end ptr.
+ * @return The actual size moved backward.
+ *
+ * If given size is bigger than current used size, the used size will be
+ * returned and set to 0.
+ */
+ Index PopEnd(Index size);
+
+ /**
+ * @brief Pop back data of used bytes into another buffer.
+ * @return The actual size popped.
+ *
+ * If given size is bigger than current used size, then only current used size
+ * of bytes will be popped. If given size is smaller than current used size,
+ * then only given size of bytes will be popped.
+ */
+ Index PopEnd(std::byte* buffer, Index size, bool use_memmove = false);
+
+ operator std::byte*() { return GetPtr(); }
+ operator const std::byte*() const { return GetPtr(); }
+
+ /**
+ * @brief Detach internal buffer and return it.
+ * @param size If not null, size of the buffer is written to it.
+ * @return The buffer pointer. May be nullptr.
+ *
+ * After detach, you are responsible to delete[] it.
+ */
+ std::byte* Detach(Index* size = nullptr);
+
+ private:
+ void Copy_(const Buffer& other);
+ void Move_(Buffer&& other) noexcept;
+ void Delete_() noexcept;
+
+ void AssertValid();
+
+ private:
+ std::byte* ptr_;
+ Index size_;
+ Index used_begin_;
+ Index used_end_;
+};
+
+void swap(Buffer& left, Buffer& right) noexcept;
+} // namespace cru
diff --git a/include/cru/base/ClonablePtr.h b/include/cru/base/ClonablePtr.h
new file mode 100644
index 00000000..a2a88758
--- /dev/null
+++ b/include/cru/base/ClonablePtr.h
@@ -0,0 +1,216 @@
+#pragma once
+
+#include <cstddef>
+#include <functional>
+#include <memory>
+#include <type_traits>
+
+namespace cru {
+template <typename TClonable>
+class ClonablePtr {
+ template <typename T>
+ friend class ClonablePtr;
+
+ public:
+ using element_type = typename std::unique_ptr<TClonable>::element_type;
+ using pointer = typename std::unique_ptr<TClonable>::pointer;
+
+ ClonablePtr() = default;
+ ClonablePtr(std::nullptr_t) noexcept : ptr_(nullptr) {}
+ explicit ClonablePtr(pointer p) noexcept : ptr_(p) {}
+ ClonablePtr(std::unique_ptr<element_type>&& p) noexcept
+ : ptr_(std::move(p)) {}
+ template <typename O,
+ std::enable_if_t<std::is_convertible_v<
+ typename ClonablePtr<O>::pointer, pointer>,
+ int> = 0>
+ ClonablePtr(std::unique_ptr<O>&& p) : ptr_(std::move(p)) {}
+ ClonablePtr(const ClonablePtr& other) : ptr_(other.ptr_->Clone()) {}
+ ClonablePtr(ClonablePtr&& other) = default;
+ template <typename O,
+ std::enable_if_t<std::is_convertible_v<
+ typename ClonablePtr<O>::pointer, pointer>,
+ int> = 0>
+ ClonablePtr(const ClonablePtr<O>& other) : ptr_(other.ptr_->Clone()) {}
+ template <typename O,
+ std::enable_if_t<std::is_convertible_v<
+ typename ClonablePtr<O>::pointer, pointer>,
+ int> = 0>
+ ClonablePtr(ClonablePtr<O>&& other) noexcept : ptr_(std::move(other.ptr_)) {}
+ ClonablePtr& operator=(std::nullptr_t) noexcept {
+ ptr_ = nullptr;
+ return *this;
+ }
+ ClonablePtr& operator=(std::unique_ptr<element_type>&& other) noexcept {
+ ptr_ = std::move(other);
+ return *this;
+ }
+ template <typename O,
+ std::enable_if_t<std::is_convertible_v<
+ typename ClonablePtr<O>::pointer, pointer>,
+ int> = 0>
+ ClonablePtr& operator=(std::unique_ptr<O>&& p) noexcept {
+ ptr_ = std::move(p);
+ return *this;
+ }
+ ClonablePtr& operator=(const ClonablePtr& other) {
+ if (this != &other) {
+ ptr_ = std::unique_ptr<element_type>(other.ptr_->Clone());
+ }
+ return *this;
+ }
+ ClonablePtr& operator=(ClonablePtr&& other) = default;
+ template <typename O,
+ std::enable_if_t<std::is_convertible_v<
+ typename ClonablePtr<O>::pointer, pointer>,
+ int> = 0>
+ ClonablePtr& operator=(const ClonablePtr<O>& other) noexcept {
+ if (this != &other) {
+ ptr_ = std::unique_ptr<element_type>(other.ptr_->Clone());
+ }
+ return *this;
+ }
+ template <typename O,
+ std::enable_if_t<std::is_convertible_v<
+ typename ClonablePtr<O>::pointer, pointer>,
+ int> = 0>
+ ClonablePtr& operator=(ClonablePtr<O>&& other) noexcept {
+ ptr_ = std::move(other.ptr_);
+ }
+
+ ~ClonablePtr() = default;
+
+ public:
+ pointer release() noexcept { return ptr_.release(); }
+ void reset(pointer p = pointer()) noexcept { ptr_.reset(p); }
+ void swap(ClonablePtr& other) noexcept { ptr_.swap(other.ptr_); }
+
+ public:
+ pointer get() const noexcept { return ptr_.get(); }
+
+ operator bool() const noexcept { return ptr_ != nullptr; }
+
+ element_type& operator*() const noexcept { return *ptr_; }
+ pointer operator->() const noexcept { return ptr_.get(); }
+
+ int Compare(const ClonablePtr& other) const noexcept {
+ if (ptr_ == other.ptr_) {
+ return 0;
+ } else if (ptr_ < other.ptr_) {
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+
+ int Compare(nullptr_t) const noexcept { return ptr_ ? 1 : 0; }
+
+ private:
+ std::unique_ptr<element_type> ptr_;
+};
+
+template <typename T>
+void swap(ClonablePtr<T>& left, ClonablePtr<T>& right) noexcept {
+ left.swap(right);
+}
+
+template <typename T>
+bool operator==(const ClonablePtr<T>& left, const ClonablePtr<T>& right) {
+ return left.Compare(right) == 0;
+}
+
+template <typename T>
+bool operator!=(const ClonablePtr<T>& left, const ClonablePtr<T>& right) {
+ return left.Compare(right) != 0;
+}
+
+template <typename T>
+bool operator<(const ClonablePtr<T>& left, const ClonablePtr<T>& right) {
+ return left.Compare(right) < 0;
+}
+
+template <typename T>
+bool operator<=(const ClonablePtr<T>& left, const ClonablePtr<T>& right) {
+ return left.Compare(right) <= 0;
+}
+
+template <typename T>
+bool operator>(const ClonablePtr<T>& left, const ClonablePtr<T>& right) {
+ return left.Compare(right) > 0;
+}
+
+template <typename T>
+bool operator>=(const ClonablePtr<T>& left, const ClonablePtr<T>& right) {
+ return left.Compare(right) >= 0;
+}
+
+template <typename T>
+bool operator==(const ClonablePtr<T>& left, std::nullptr_t) {
+ return left.Compare(nullptr) == 0;
+}
+
+template <typename T>
+bool operator!=(const ClonablePtr<T>& left, std::nullptr_t) {
+ return left.Compare(nullptr) != 0;
+}
+
+template <typename T>
+bool operator<(const ClonablePtr<T>& left, std::nullptr_t) {
+ return left.Compare(nullptr) < 0;
+}
+
+template <typename T>
+bool operator<=(const ClonablePtr<T>& left, std::nullptr_t) {
+ return left.Compare(nullptr) <= 0;
+}
+
+template <typename T>
+bool operator>(const ClonablePtr<T>& left, std::nullptr_t) {
+ return left.Compare(nullptr) > 0;
+}
+
+template <typename T>
+bool operator>=(const ClonablePtr<T>& left, std::nullptr_t) {
+ return left.Compare(nullptr) >= 0;
+}
+
+template <typename T>
+bool operator==(std::nullptr_t, const ClonablePtr<T>& right) {
+ return right.Compare(nullptr) == 0;
+}
+
+template <typename T>
+bool operator!=(std::nullptr_t, const ClonablePtr<T>& right) {
+ return right.Compare(nullptr) != 0;
+}
+
+template <typename T>
+bool operator<(std::nullptr_t, const ClonablePtr<T>& right) {
+ return right.Compare(nullptr) > 0;
+}
+
+template <typename T>
+bool operator<=(std::nullptr_t, const ClonablePtr<T>& right) {
+ return right.Compare(nullptr) >= 0;
+}
+
+template <typename T>
+bool operator>(std::nullptr_t, const ClonablePtr<T>& right) {
+ return right.Compare(nullptr) < 0;
+}
+
+template <typename T>
+bool operator>=(std::nullptr_t, const ClonablePtr<T>& right) {
+ return right.Compare(nullptr) <= 0;
+}
+
+} // namespace cru
+
+namespace std {
+template <typename T>
+struct hash<cru::ClonablePtr<T>> {
+ std::size_t operator()(const cru::ClonablePtr<T>& p) const {
+ return std::hash<typename cru::ClonablePtr<T>::pointer>(p.get());
+ }
+};
+} // namespace std
diff --git a/include/cru/base/Event.h b/include/cru/base/Event.h
new file mode 100644
index 00000000..18d2c570
--- /dev/null
+++ b/include/cru/base/Event.h
@@ -0,0 +1,281 @@
+#pragma once
+#include "Base.h"
+
+#include "SelfResolvable.h"
+
+#include <algorithm>
+#include <cassert>
+#include <functional>
+#include <memory>
+#include <utility>
+#include <vector>
+
+namespace cru {
+class EventRevoker;
+
+namespace details {
+template <class>
+inline constexpr bool always_false_v = false;
+
+// Base class of all Event<T...>.
+// It erases event args types and provides a
+// unified form to create event revoker and
+// revoke(remove) handler.
+class EventBase : public SelfResolvable<EventBase> {
+ friend EventRevoker;
+
+ protected:
+ using EventHandlerToken = long;
+
+ EventBase() {}
+ CRU_DELETE_COPY(EventBase)
+ CRU_DEFAULT_MOVE(EventBase)
+ virtual ~EventBase() = default;
+
+ // Remove the handler with the given token. If the token
+ // corresponds to no handler (which might have be revoked
+ // before), then nothing will be done.
+ virtual void RemoveHandler(EventHandlerToken token) = 0;
+
+ // Create a revoker with the given token.
+ inline EventRevoker CreateRevoker(EventHandlerToken token);
+};
+} // namespace details
+
+// A non-copyable and movable event revoker.
+// Call function call operator to revoke the handler.
+class EventRevoker {
+ friend details::EventBase;
+
+ private:
+ EventRevoker(ObjectResolver<details::EventBase>&& resolver,
+ details::EventBase::EventHandlerToken token)
+ : resolver_(std::move(resolver)), token_(token) {}
+
+ public:
+ EventRevoker(const EventRevoker& other) = default;
+ EventRevoker(EventRevoker&& other) = default;
+ EventRevoker& operator=(const EventRevoker& other) = default;
+ EventRevoker& operator=(EventRevoker&& other) = default;
+ ~EventRevoker() = default;
+
+ // Revoke the registered handler. If the event has already
+ // been destroyed, then nothing will be done. If one of the
+ // copies calls this, then other copies's calls will have no
+ // effect. (They have the same token.)
+ void operator()() const {
+ if (const auto event = resolver_.Resolve()) {
+ event->RemoveHandler(token_);
+ }
+ }
+
+ private:
+ ObjectResolver<details::EventBase> resolver_;
+ details::EventBase::EventHandlerToken token_;
+};
+
+inline EventRevoker details::EventBase::CreateRevoker(EventHandlerToken token) {
+ return EventRevoker(CreateResolver(), token);
+}
+
+// int -> int
+// Point -> const Point&
+// int& -> int&
+template <typename TRaw>
+using DeducedEventArgs = std::conditional_t<
+ std::is_lvalue_reference_v<TRaw>, TRaw,
+ std::conditional_t<std::is_scalar_v<TRaw>, TRaw, const TRaw&>>;
+
+struct IBaseEvent {
+ protected:
+ IBaseEvent() = default;
+ CRU_DELETE_COPY(IBaseEvent)
+ CRU_DEFAULT_MOVE(IBaseEvent)
+ ~IBaseEvent() = default; // Note that user can't destroy a Event via IEvent.
+ // So destructor should be protected.
+
+ using SpyOnlyHandler = std::function<void()>;
+
+ public:
+ virtual EventRevoker AddSpyOnlyHandler(SpyOnlyHandler handler) = 0;
+};
+
+// Provides an interface of event.
+// IEvent only allow to add handler but not to raise the event. You may
+// want to create an Event object and expose IEvent only so users won't
+// be able to emit the event.
+template <typename TEventArgs>
+struct IEvent : virtual IBaseEvent {
+ public:
+ using EventArgs = DeducedEventArgs<TEventArgs>;
+ using EventHandler = std::function<void(EventArgs)>;
+ using ShortCircuitHandler = std::function<bool(EventArgs)>;
+
+ protected:
+ IEvent() = default;
+ CRU_DELETE_COPY(IEvent)
+ CRU_DEFAULT_MOVE(IEvent)
+ ~IEvent() = default; // Note that user can't destroy a Event via IEvent. So
+ // destructor should be protected.
+
+ public:
+ virtual EventRevoker AddHandler(EventHandler handler) = 0;
+ virtual EventRevoker AddShortCircuitHandler(ShortCircuitHandler handler) = 0;
+ virtual EventRevoker PrependShortCircuitHandler(
+ ShortCircuitHandler handler) = 0;
+};
+
+// A non-copyable non-movable Event class.
+// It stores a list of event handlers.
+template <typename TEventArgs>
+class Event : public details::EventBase, public IEvent<TEventArgs> {
+ using typename IEvent<TEventArgs>::EventArgs;
+
+ using typename IBaseEvent::SpyOnlyHandler;
+ using typename IEvent<TEventArgs>::EventHandler;
+ using typename IEvent<TEventArgs>::ShortCircuitHandler;
+
+ private:
+ struct HandlerData {
+ HandlerData(EventHandlerToken token, ShortCircuitHandler handler)
+ : token(token), handler(std::move(handler)) {}
+ EventHandlerToken token;
+ ShortCircuitHandler handler;
+ };
+
+ public:
+ Event() = default;
+ CRU_DELETE_COPY(Event)
+ CRU_DEFAULT_MOVE(Event)
+ ~Event() = default;
+
+ EventRevoker AddSpyOnlyHandler(SpyOnlyHandler handler) override {
+ return AddShortCircuitHandler([handler = std::move(handler)](EventArgs) {
+ handler();
+ return false;
+ });
+ }
+
+ EventRevoker AddHandler(EventHandler handler) override {
+ return AddShortCircuitHandler(
+ [handler = std::move(handler)](EventArgs args) {
+ handler(args);
+ return false;
+ });
+ }
+
+ // Handler return true to short circuit following handlers.
+ EventRevoker AddShortCircuitHandler(ShortCircuitHandler handler) override {
+ const auto token = current_token_++;
+ this->handler_data_list_.emplace_back(token, std::move(handler));
+ return CreateRevoker(token);
+ }
+
+ // Handler return true to short circuit following handlers.
+ EventRevoker PrependShortCircuitHandler(
+ ShortCircuitHandler handler) override {
+ const auto token = current_token_++;
+ this->handler_data_list_.emplace(this->handler_data_list_.cbegin(), token,
+ std::move(handler));
+ return CreateRevoker(token);
+ }
+
+ // This method will make a copy of all handlers. Because user might delete a
+ // handler in a handler, which may lead to seg fault as the handler is
+ // deleted while being executed. Thanks to this behavior, all handlers will
+ // be taken a snapshot when Raise is called, so even if you delete a handler
+ // during this period, all handlers in the snapshot will be executed.
+ void Raise(EventArgs args) {
+ std::vector<ShortCircuitHandler> handlers;
+ handlers.reserve(this->handler_data_list_.size());
+ for (const auto& data : this->handler_data_list_) {
+ handlers.push_back(data.handler);
+ }
+ for (const auto& handler : handlers) {
+ auto short_circuit = handler(args);
+ if (short_circuit) return;
+ }
+ }
+
+ protected:
+ void RemoveHandler(EventHandlerToken token) override {
+ const auto find_result = std::find_if(
+ this->handler_data_list_.cbegin(), this->handler_data_list_.cend(),
+ [token](const HandlerData& data) { return data.token == token; });
+ if (find_result != this->handler_data_list_.cend()) {
+ this->handler_data_list_.erase(find_result);
+ }
+ }
+
+ private:
+ std::vector<HandlerData> handler_data_list_;
+ EventHandlerToken current_token_ = 0;
+};
+
+namespace details {
+struct EventRevokerDestroyer {
+ void operator()(EventRevoker* p) {
+ (*p)();
+ delete p;
+ }
+};
+} // namespace details
+
+// A guard class for event revoker. Automatically revoke it when destroyed.
+class EventRevokerGuard {
+ public:
+ EventRevokerGuard() = default;
+ explicit EventRevokerGuard(EventRevoker&& revoker)
+ : revoker_(new EventRevoker(std::move(revoker))) {}
+ EventRevokerGuard(const EventRevokerGuard& other) = delete;
+ EventRevokerGuard(EventRevokerGuard&& other) = default;
+ EventRevokerGuard& operator=(const EventRevokerGuard& other) = delete;
+ EventRevokerGuard& operator=(EventRevokerGuard&& other) = default;
+ ~EventRevokerGuard() = default;
+
+ EventRevoker Get() {
+ // revoker is only null when this is moved
+ // you shouldn't use a moved instance
+ assert(revoker_);
+ return *revoker_;
+ }
+
+ EventRevoker Release() { return std::move(*revoker_.release()); }
+
+ void Reset() { revoker_.reset(); }
+
+ void Reset(EventRevoker&& revoker) {
+ revoker_.reset(new EventRevoker(std::move(revoker)));
+ }
+
+ private:
+ std::unique_ptr<EventRevoker, details::EventRevokerDestroyer> revoker_;
+};
+
+class EventRevokerListGuard {
+ public:
+ EventRevokerListGuard() = default;
+ EventRevokerListGuard(const EventRevokerListGuard& other) = delete;
+ EventRevokerListGuard(EventRevokerListGuard&& other) = default;
+ EventRevokerListGuard& operator=(const EventRevokerListGuard& other) = delete;
+ EventRevokerListGuard& operator=(EventRevokerListGuard&& other) = default;
+ ~EventRevokerListGuard() = default;
+
+ public:
+ void Add(EventRevoker&& revoker) {
+ event_revoker_guard_list_.push_back(EventRevokerGuard(std::move(revoker)));
+ }
+
+ EventRevokerListGuard& operator+=(EventRevoker&& revoker) {
+ this->Add(std::move(revoker));
+ return *this;
+ }
+
+ void Clear() { event_revoker_guard_list_.clear(); }
+
+ bool IsEmpty() const { return event_revoker_guard_list_.empty(); }
+
+ private:
+ std::vector<EventRevokerGuard> event_revoker_guard_list_;
+};
+} // namespace cru
diff --git a/include/cru/base/Event2.h b/include/cru/base/Event2.h
new file mode 100644
index 00000000..891b314f
--- /dev/null
+++ b/include/cru/base/Event2.h
@@ -0,0 +1,200 @@
+#pragma once
+
+#include <cru/base/Bitmask.h>
+#include <cru/base/SelfResolvable.h>
+
+#include <cstddef>
+#include <functional>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+namespace cru {
+class Event2Base {
+ public:
+ virtual ~Event2Base() = default;
+
+ virtual void RevokeHandler(int token_value) = 0;
+};
+
+template <typename TArgument, typename TResult>
+class EventContext {
+ public:
+ EventContext() : argument_(), result_() {}
+ explicit EventContext(TArgument argument)
+ : argument_(std::move(argument)), result_() {}
+ EventContext(TArgument argument, TResult result)
+ : argument_(std::move(argument)), result_(std::move(result)) {}
+
+ TArgument& GetArgument() { return argument_; }
+ const TArgument& GetArgument() const { return argument_; }
+
+ TResult& GetResult() { return result_; }
+ const TResult& GetResult() const { return result_; }
+ void SetResult(const TResult& result) { result_ = result; }
+ void SetResult(TResult&& result) { result_ = std::move(result); }
+ TResult TakeResult() { return std::move(result_); }
+
+ bool GetStopHandling() const { return stop_handling_; }
+ void SetStopHandling(bool stop = true) { stop_handling_ = stop; }
+
+ private:
+ TArgument argument_;
+ TResult result_;
+ bool stop_handling_ = false;
+};
+
+template <typename TArgument, typename TResult>
+class Event2;
+
+template <typename T>
+constexpr bool is_event2_v = false;
+
+template <typename TArgument, typename TResult>
+constexpr bool is_event2_v<Event2<TArgument, TResult>> = true;
+
+class EventHandlerToken {
+ public:
+ EventHandlerToken(ObjectResolver<Event2Base> event_resolver, int token_value)
+ : event_resolver_(std::move(event_resolver)), token_value_(token_value) {}
+
+ ObjectResolver<Event2Base> GetEventResolver() const {
+ return event_resolver_;
+ }
+ int GetTokenValue() const { return token_value_; }
+ inline void RevokeHandler() const;
+
+ private:
+ ObjectResolver<Event2Base> event_resolver_;
+ int token_value_;
+};
+
+namespace details {
+struct Event2BehaviorFlagTag {};
+} // namespace details
+using Event2BehaviorFlag = Bitmask<details::Event2BehaviorFlagTag>;
+
+struct Event2BehaviorFlags {
+ /**
+ * @brief Make a copy of handler list before invoking handlers. So the event
+ * object or its owner can be destroyed during running handlers.
+ */
+ static constexpr Event2BehaviorFlag CopyHandlers =
+ Event2BehaviorFlag::FromOffset(1);
+};
+
+template <typename TArgument = std::nullptr_t,
+ typename TResult = std::nullptr_t>
+class Event2 : public Event2Base,
+ public SelfResolvable<Event2<TArgument, TResult>> {
+ public:
+ using HandlerToken = EventHandlerToken;
+ using Context = EventContext<TArgument, TResult>;
+ using Handler = std::function<void(Context*)>;
+ using SpyOnlyHandler = std::function<void()>;
+
+ template <typename TFunc>
+ static std::enable_if_t<std::invocable<TFunc>, Handler> WrapAsHandler(
+ TFunc&& handler) {
+ return Handler([h = std::forward<TFunc>(handler)](Context*) { h(); });
+ }
+
+ template <typename TFunc>
+ static std::enable_if_t<std::invocable<TFunc, Context*>, Handler>
+ WrapAsHandler(TFunc&& handler) {
+ return Handler(std::forward<TFunc>(handler));
+ }
+
+ private:
+ struct HandlerData {
+ int token_value;
+ Handler handler;
+ };
+
+ public:
+ explicit Event2(Event2BehaviorFlag flags = {}) : flags_(flags) {}
+ Event2(const Event2&) = delete;
+ Event2(Event2&&) = delete;
+ Event2& operator=(const Event2&) = delete;
+ Event2& operator=(Event2&&) = delete;
+ ~Event2() override = default;
+
+ public:
+ template <typename TFunc>
+ HandlerToken AddHandler(TFunc&& handler) {
+ auto token = this->current_token_value_++;
+ auto real_handler = WrapAsHandler(std::forward<TFunc>(handler));
+ HandlerData handler_data{token, std::move(real_handler)};
+ this->handlers_.push_back(std::move(handler_data));
+ return HandlerToken(this->CreateResolver(), token);
+ }
+
+ void RevokeHandler(int token_value) override {
+ auto iter = this->handlers_.cbegin();
+ auto end = this->handlers_.cend();
+ for (; iter != end; ++iter) {
+ if (iter->token_value == token_value) {
+ this->handlers_.erase(iter);
+ break;
+ }
+ }
+ }
+
+ void RevokeHandler(const HandlerToken& token) {
+ return RevokeHandler(token.GetTokenValue());
+ }
+
+ TResult Raise() {
+ Context context;
+ RunInContext(&context);
+ return context.TakeResult();
+ }
+
+ TResult Raise(TArgument argument) {
+ Context context(std::move(argument));
+ RunInContext(&context);
+ return context.TakeResult();
+ }
+
+ TResult Raise(TArgument argument, TResult result) {
+ Context context(std::move(argument), std::move(result));
+ RunInContext(&context);
+ return context.TakeResult();
+ }
+
+ private:
+ void RunInContext(Context* context) {
+ if (this->flags_ & Event2BehaviorFlags::CopyHandlers) {
+ std::vector<Handler> handlers_copy;
+ for (const auto& handler : this->handlers_) {
+ handlers_copy.push_back(handler.handler);
+ }
+ for (const auto& handler : handlers_copy) {
+ if (context->GetStopHandling()) {
+ break;
+ }
+ handler(context);
+ }
+ } else {
+ for (const auto& handler : this->handlers_) {
+ if (context->GetStopHandling()) {
+ break;
+ }
+ handler.handler(context);
+ }
+ }
+ }
+
+ private:
+ int current_token_value_ = 1;
+ std::vector<HandlerData> handlers_;
+ Event2BehaviorFlag flags_;
+};
+
+inline void EventHandlerToken::RevokeHandler() const {
+ auto event = this->event_resolver_.Resolve();
+ if (event) {
+ event->RevokeHandler(this->token_value_);
+ }
+}
+} // namespace cru
diff --git a/include/cru/base/Exception.h b/include/cru/base/Exception.h
new file mode 100644
index 00000000..609fd2c9
--- /dev/null
+++ b/include/cru/base/Exception.h
@@ -0,0 +1,60 @@
+#pragma once
+#include "String.h"
+
+#include <exception>
+#include <optional>
+
+namespace cru {
+#ifdef _MSC_VER
+#pragma warning(disable : 4275)
+#endif
+class CRU_BASE_API Exception : public std::exception {
+ public:
+ explicit Exception(String message = {},
+ std::unique_ptr<std::exception> inner = nullptr);
+
+ ~Exception() override;
+
+ public:
+ String GetMessage() const { return message_; }
+
+ std::exception* GetInner() const noexcept { return inner_.get(); }
+
+ const char* what() const noexcept override;
+
+ protected:
+ void SetMessage(String message) { message_ = std::move(message); }
+
+ void AppendMessage(StringView additional_message);
+ void AppendMessage(std::optional<StringView> additional_message);
+
+ private:
+ String message_;
+ mutable std::string utf8_message_;
+ std::unique_ptr<std::exception> inner_;
+};
+
+class CRU_BASE_API TextEncodeException : public Exception {
+ public:
+ using Exception::Exception;
+};
+
+class ErrnoException : public Exception {
+ public:
+ /**
+ * @brief will retrieve errno automatically.
+ */
+ explicit ErrnoException(String message = {});
+ ErrnoException(String message, int errno_code);
+
+ CRU_DELETE_COPY(ErrnoException)
+ CRU_DELETE_MOVE(ErrnoException)
+
+ ~ErrnoException() override = default;
+
+ int GetErrnoCode() const { return errno_code_; }
+
+ private:
+ int errno_code_;
+};
+} // namespace cru
diff --git a/include/cru/base/Format.h b/include/cru/base/Format.h
new file mode 100644
index 00000000..d5c5ed99
--- /dev/null
+++ b/include/cru/base/Format.h
@@ -0,0 +1,154 @@
+#pragma once
+
+#include "Exception.h"
+#include "String.h"
+
+#include <cassert>
+#include <cstdio>
+#include <type_traits>
+#include <vector>
+
+namespace cru {
+inline String ToString(bool value) {
+ return value ? String(u"true") : String(u"false");
+}
+
+template <typename T>
+inline constexpr std::nullptr_t kPrintfFormatSpecifierOfType = nullptr;
+
+#define CRU_DEFINE_PRINTF_FORMAT_SPECIFIER_OF_TYPE(type, specifier) \
+ template <> \
+ inline constexpr const char* kPrintfFormatSpecifierOfType<type> = specifier;
+
+CRU_DEFINE_PRINTF_FORMAT_SPECIFIER_OF_TYPE(signed char, "%c")
+CRU_DEFINE_PRINTF_FORMAT_SPECIFIER_OF_TYPE(unsigned char, "%c")
+CRU_DEFINE_PRINTF_FORMAT_SPECIFIER_OF_TYPE(signed short, "%hd")
+CRU_DEFINE_PRINTF_FORMAT_SPECIFIER_OF_TYPE(unsigned short, "%hu")
+CRU_DEFINE_PRINTF_FORMAT_SPECIFIER_OF_TYPE(signed int, "%d")
+CRU_DEFINE_PRINTF_FORMAT_SPECIFIER_OF_TYPE(unsigned int, "%u")
+CRU_DEFINE_PRINTF_FORMAT_SPECIFIER_OF_TYPE(signed long, "%ld")
+CRU_DEFINE_PRINTF_FORMAT_SPECIFIER_OF_TYPE(unsigned long, "%lu")
+CRU_DEFINE_PRINTF_FORMAT_SPECIFIER_OF_TYPE(signed long long, "%lld")
+CRU_DEFINE_PRINTF_FORMAT_SPECIFIER_OF_TYPE(unsigned long long, "%llu")
+CRU_DEFINE_PRINTF_FORMAT_SPECIFIER_OF_TYPE(float, "%f")
+CRU_DEFINE_PRINTF_FORMAT_SPECIFIER_OF_TYPE(double, "%f")
+
+#undef CRU_DEFINE_PRINTF_FORMAT_SPECIFIER_OF_TYPE
+
+template <typename T>
+std::enable_if_t<
+ !std::is_null_pointer_v<decltype(kPrintfFormatSpecifierOfType<T>)>, String>
+ToString(T value) {
+ auto size = std::snprintf(nullptr, 0, kPrintfFormatSpecifierOfType<T>, value);
+ assert(size > 0);
+ std::vector<char> buffer(size + 1);
+ size = std::snprintf(buffer.data(), size + 1, kPrintfFormatSpecifierOfType<T>,
+ value);
+ assert(size > 0);
+ return String::FromUtf8(buffer.data(), size);
+}
+
+template <typename T>
+String ToString(const T& value, StringView option) {
+ CRU_UNUSED(option)
+ return ToString(value);
+}
+
+inline String ToString(String value) { return value; }
+
+namespace details {
+enum class FormatTokenType { PlaceHolder, Text };
+enum class FormatPlaceHolderType { None, Positioned, Named };
+
+struct FormatToken {
+ static FormatToken Text() {
+ return FormatToken{FormatTokenType::Text, {}, {}, 0, {}, {}};
+ }
+
+ static FormatToken NonePlaceHolder(String option) {
+ return FormatToken(FormatTokenType::PlaceHolder, {},
+ FormatPlaceHolderType::None, 0, {}, std::move(option));
+ }
+
+ static FormatToken PositionedPlaceHolder(int position, String option) {
+ return FormatToken(FormatTokenType::PlaceHolder, {},
+ FormatPlaceHolderType::Positioned, position, {},
+ std::move(option));
+ }
+
+ static FormatToken NamedPlaceHolder(String name, String option) {
+ return FormatToken(FormatTokenType::PlaceHolder, {},
+ FormatPlaceHolderType::Named, 0, std::move(name),
+ std::move(option));
+ }
+
+ FormatToken(FormatTokenType type, String data,
+ FormatPlaceHolderType place_holder_type,
+ int place_holder_position, String place_holder_name,
+ String place_holder_option)
+ : type(type),
+ data(std::move(data)),
+ place_holder_type(place_holder_type),
+ place_holder_position(place_holder_position),
+ place_holder_name(std::move(place_holder_name)),
+ place_holder_option(std::move(place_holder_option)) {}
+
+ CRU_DEFAULT_COPY(FormatToken)
+ CRU_DEFAULT_MOVE(FormatToken)
+
+ CRU_DEFAULT_DESTRUCTOR(FormatToken)
+
+ FormatTokenType type;
+ String data;
+ FormatPlaceHolderType place_holder_type;
+ int place_holder_position;
+ String place_holder_name;
+ String place_holder_option;
+};
+
+std::vector<FormatToken> CRU_BASE_API ParseToFormatTokenList(StringView str);
+
+void CRU_BASE_API FormatAppendFromFormatTokenList(
+ String& current, const std::vector<FormatToken>& format_token_list,
+ Index index);
+
+template <typename TA, typename... T>
+void FormatAppendFromFormatTokenList(
+ String& current, const std::vector<FormatToken>& format_token_list,
+ Index index, TA&& args0, T&&... args) {
+ for (Index i = index; i < static_cast<Index>(format_token_list.size()); i++) {
+ const auto& token = format_token_list[i];
+ if (token.type == FormatTokenType::PlaceHolder) {
+ if (token.place_holder_type == FormatPlaceHolderType::None) {
+ current += ToString(std::forward<TA>(args0), token.place_holder_option);
+ FormatAppendFromFormatTokenList(current, format_token_list, i + 1,
+ std::forward<T>(args)...);
+
+ return;
+ } else {
+ throw Exception(
+ u"Currently do not support positional or named place holder.");
+ }
+ } else {
+ current += token.data;
+ }
+ }
+}
+} // namespace details
+
+template <typename... T>
+String Format(StringView format, T&&... args) {
+ String result;
+
+ details::FormatAppendFromFormatTokenList(
+ result, details::ParseToFormatTokenList(format), 0,
+ std::forward<T>(args)...);
+
+ return result;
+}
+
+template <typename... T>
+String String::Format(T&&... args) const {
+ return cru::Format(*this, std::forward<T>(args)...);
+}
+} // namespace cru
diff --git a/include/cru/base/Guard.h b/include/cru/base/Guard.h
new file mode 100644
index 00000000..5a9f9c5d
--- /dev/null
+++ b/include/cru/base/Guard.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include <functional>
+
+namespace cru {
+struct Guard {
+ using ExitFunc = std::function<void()>;
+
+ Guard() = default;
+ explicit Guard(const ExitFunc& f) : on_exit(f) {}
+ explicit Guard(ExitFunc&& f) : on_exit(std::move(f)) {}
+ Guard(const Guard&) = delete;
+ Guard(Guard&&) = default;
+ Guard& operator=(const Guard&) = delete;
+ Guard& operator=(Guard&&) = default;
+ ~Guard() {
+ if (on_exit) {
+ on_exit();
+ }
+ }
+
+ void Drop() { on_exit = {}; }
+
+ ExitFunc on_exit;
+};
+} // namespace cru
diff --git a/include/cru/base/HandlerRegistry.h b/include/cru/base/HandlerRegistry.h
new file mode 100644
index 00000000..e405d1fd
--- /dev/null
+++ b/include/cru/base/HandlerRegistry.h
@@ -0,0 +1,87 @@
+#pragma once
+#include "Base.h"
+
+#include <algorithm>
+#include <functional>
+#include <utility>
+#include <vector>
+
+namespace cru {
+
+template <typename T>
+class HandlerRegistryIterator {
+ public:
+ using RawIterator =
+ typename std::vector<std::pair<int, std::function<T>>>::const_iterator;
+
+ explicit HandlerRegistryIterator(RawIterator raw) : raw_(std::move(raw)) {}
+
+ CRU_DELETE_COPY(HandlerRegistryIterator)
+ CRU_DELETE_MOVE(HandlerRegistryIterator)
+
+ ~HandlerRegistryIterator() = default;
+
+ const std::function<T>& operator*() const { return raw_->second; }
+ const std::function<T>* operator->() const { return &raw_->second; }
+
+ HandlerRegistryIterator& operator++() {
+ ++raw_;
+ return *this;
+ }
+
+ HandlerRegistryIterator operator++(int) {
+ auto c = *this;
+ this->operator++();
+ return c;
+ }
+
+ bool operator==(const HandlerRegistryIterator<T>& other) const {
+ return this->raw_ == other.raw_;
+ }
+
+ bool operator!=(const HandlerRegistryIterator<T>& other) const {
+ return !this->operator==(other);
+ }
+
+ private:
+ RawIterator raw_;
+};
+
+template <typename T>
+class HandlerRegistry final {
+ public:
+ HandlerRegistry() = default;
+ CRU_DEFAULT_COPY(HandlerRegistry)
+ CRU_DEFAULT_MOVE(HandlerRegistry)
+ ~HandlerRegistry() = default;
+
+ public:
+ int AddHandler(std::function<T> handler) {
+ auto id = current_id_++;
+ handler_list_.push_back({id, std::move(handler)});
+ return id;
+ }
+
+ void RemoveHandler(int id) {
+ auto result = std::find_if(handler_list_.cbegin(), handler_list_.cend(),
+ [id](const std::pair<int, std::function<T>>& d) {
+ return d.first == id;
+ });
+ if (result != handler_list_.cend()) {
+ handler_list_.erase(result);
+ }
+ }
+
+ HandlerRegistryIterator<T> begin() const {
+ return HandlerRegistryIterator<T>(handler_list_.begin());
+ }
+
+ HandlerRegistryIterator<T> end() const {
+ return HandlerRegistryIterator<T>(handler_list_.end());
+ }
+
+ private:
+ int current_id_ = 1;
+ std::vector<std::pair<int, std::function<T>>> handler_list_;
+};
+} // namespace cru
diff --git a/include/cru/base/PreConfig.h b/include/cru/base/PreConfig.h
new file mode 100644
index 00000000..3f26c589
--- /dev/null
+++ b/include/cru/base/PreConfig.h
@@ -0,0 +1,18 @@
+// IWYU pragma: always_keep
+
+#pragma once
+
+#ifdef _MSC_VER
+// disable the unnecessary warning about multi-inheritance
+#pragma warning(disable : 4250)
+// disable dll export template issue warning
+#pragma warning(disable : 4251)
+#endif
+
+#ifdef CRU_PLATFORM_WINDOWS
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#if defined(CRU_PLATFORM_OSX) || defined(CRU_PLATFORM_LINUX)
+#define CRU_PLATFORM_UNIX
+#endif
diff --git a/include/cru/base/PropertyTree.h b/include/cru/base/PropertyTree.h
new file mode 100644
index 00000000..54e185b9
--- /dev/null
+++ b/include/cru/base/PropertyTree.h
@@ -0,0 +1,63 @@
+#pragma once
+
+#include "Base.h"
+#include "String.h"
+
+#include <unordered_map>
+
+namespace cru {
+class PropertyTree;
+
+class CRU_BASE_API PropertySubTreeRef {
+ public:
+ static String CombineKey(StringView left, StringView right);
+
+ explicit PropertySubTreeRef(PropertyTree* tree, String path = {});
+
+ CRU_DEFAULT_COPY(PropertySubTreeRef);
+ CRU_DEFAULT_MOVE(PropertySubTreeRef);
+
+ CRU_DEFAULT_DESTRUCTOR(PropertySubTreeRef);
+
+ public:
+ PropertyTree* GetTree() const { return tree_; }
+
+ String GetPath() const { return path_; }
+ void SetPath(String path) { path_ = std::move(path); }
+
+ PropertySubTreeRef GetParent() const;
+ PropertySubTreeRef GetChild(const String& key) const;
+
+ String GetValue(const String& key) const;
+ void SetValue(const String& key, String value);
+ void DeleteValue(const String& key);
+
+ private:
+ PropertyTree* tree_;
+ String path_;
+};
+
+class CRU_BASE_API PropertyTree {
+ public:
+ static String CombineKey(StringView left, StringView right);
+
+ PropertyTree() = default;
+ explicit PropertyTree(std::unordered_map<String, String> values);
+
+ CRU_DELETE_COPY(PropertyTree);
+ CRU_DELETE_MOVE(PropertyTree);
+
+ CRU_DEFAULT_DESTRUCTOR(PropertyTree);
+
+ public:
+ String GetValue(const String& key) const;
+ void SetValue(const String& key, String value);
+ void DeleteValue(const String& key);
+
+ PropertySubTreeRef GetSubTreeRef(const String& path);
+
+ private:
+ std::unordered_map<String, String> values_;
+};
+
+} // namespace cru
diff --git a/include/cru/base/Range.h b/include/cru/base/Range.h
new file mode 100644
index 00000000..edc2ec55
--- /dev/null
+++ b/include/cru/base/Range.h
@@ -0,0 +1,42 @@
+#pragma once
+#include "Base.h"
+
+namespace cru {
+struct Range final {
+ constexpr static Range FromTwoSides(Index start, Index end) {
+ return Range(start, end - start);
+ }
+
+ constexpr static Range FromTwoSides(Index start, Index end, Index offset) {
+ return Range(start + offset, end - start);
+ }
+
+ constexpr Range() = default;
+ constexpr Range(const Index position, const Index count = 0)
+ : position(position), count(count) {}
+
+ Index GetStart() const { return position; }
+ Index GetEnd() const { return position + count; }
+
+ void ChangeEnd(Index new_end) { count = new_end - position; }
+
+ Range Normalize() const {
+ auto result = *this;
+ if (result.count < 0) {
+ result.position += result.count;
+ result.count = -result.count;
+ }
+ return result;
+ }
+
+ Range CoerceInto(Index min, Index max) const {
+ auto coerce = [min, max](Index index) {
+ return index > max ? max : (index < min ? min : index);
+ };
+ return Range::FromTwoSides(coerce(GetStart()), coerce(GetEnd()));
+ }
+
+ Index position = 0;
+ Index count = 0;
+};
+} // namespace cru
diff --git a/include/cru/base/SelfResolvable.h b/include/cru/base/SelfResolvable.h
new file mode 100644
index 00000000..84fa54f6
--- /dev/null
+++ b/include/cru/base/SelfResolvable.h
@@ -0,0 +1,153 @@
+#pragma once
+
+#include <cassert>
+#include <functional>
+#include <memory>
+#include <type_traits>
+
+namespace cru {
+template <typename T>
+class SelfResolvable;
+
+template <typename T>
+class ObjectResolver {
+ friend SelfResolvable<T>;
+ template <typename U>
+ friend class ObjectResolver;
+
+ private:
+ template <typename U>
+ using Accessor_ = std::function<U*(const std::shared_ptr<void*>&)>;
+ using ThisAccessor_ = Accessor_<T>;
+
+ explicit ObjectResolver(T* o)
+ : shared_object_ptr_(new void*(o)),
+ accessor_([](const std::shared_ptr<void*>& ptr) {
+ return static_cast<T*>(*ptr);
+ }) {}
+ explicit ObjectResolver(std::shared_ptr<void*> ptr, ThisAccessor_ accessor)
+ : shared_object_ptr_(std::move(ptr)), accessor_(std::move(accessor)) {}
+
+ template <typename U>
+ static ThisAccessor_ CreateAccessor(Accessor_<U> parent_accessor) {
+ return [parent_accessor =
+ std::move(parent_accessor)](const std::shared_ptr<void*>& ptr) {
+ return static_cast<T*>(parent_accessor(ptr));
+ };
+ }
+
+ public:
+ template <typename U,
+ typename = std::enable_if_t<std::is_convertible_v<U*, T*>>>
+ ObjectResolver(const ObjectResolver<U>& other)
+ : shared_object_ptr_(other.shared_object_ptr_),
+ accessor_(CreateAccessor(other.accessor_)) {}
+
+ template <typename U,
+ typename = std::enable_if_t<std::is_convertible_v<U*, T*>>>
+ ObjectResolver(ObjectResolver<U>&& other)
+ : shared_object_ptr_(std::move(other.shared_object_ptr_)),
+ accessor_(CreateAccessor(std::move(other.accessor_))) {}
+
+ ObjectResolver(const ObjectResolver&) = default;
+ ObjectResolver& operator=(const ObjectResolver&) = default;
+ ObjectResolver(ObjectResolver&&) = default;
+ ObjectResolver& operator=(ObjectResolver&&) = default;
+ ~ObjectResolver() = default;
+
+ template <typename U,
+ typename = std::enable_if_t<std::is_convertible_v<U*, T*>>>
+ ObjectResolver& operator=(const ObjectResolver<U>& other) {
+ if (this != &other) {
+ this->shared_object_ptr_ = other.shared_object_ptr_;
+ this->accessor_ = CreateAccessor(other.accessor_);
+ }
+ return *this;
+ }
+
+ template <typename U,
+ typename = std::enable_if_t<std::is_convertible_v<U*, T*>>>
+ ObjectResolver& operator=(ObjectResolver<U>&& other) {
+ if (this != &other) {
+ this->shared_object_ptr_ = std::move(other.shared_object_ptr_);
+ this->accessor_ = CreateAccessor(std::move(other.shared_object_ptr_));
+ }
+ return *this;
+ }
+
+ bool IsValid() const { return this->shared_object_ptr_ != nullptr; }
+
+ T* Resolve() const {
+ assert(IsValid());
+ return this->accessor_(this->shared_object_ptr_);
+ }
+
+ /**
+ * @remarks So this class can be used as a functor.
+ */
+ T* operator()() const { return Resolve(); }
+
+ template <typename U,
+ typename = std::enable_if_t<std::is_convertible_v<T*, U*>>>
+ operator ObjectResolver<U>() const {
+ return ObjectResolver<U>(*this);
+ }
+
+ private:
+ void SetResolvedObject(T* o) {
+ assert(IsValid());
+ *this->shared_object_ptr_ = o;
+ }
+
+ private:
+ std::shared_ptr<void*> shared_object_ptr_;
+ std::function<T*(const std::shared_ptr<void*>&)> accessor_;
+};
+
+/**
+ * @remarks
+ * This class is not copyable and movable since subclass is polymorphic and
+ * copying is then nonsense. However, you can even delete move capability in
+ * subclass because it may also be nonsense for subclass. The move capability is
+ * optional.
+ *
+ * Whether this class needs to be thread-safe still has to be considered.
+ */
+template <typename T>
+class SelfResolvable {
+ public:
+ SelfResolvable() : resolver_(CastToSubClass()) {}
+ SelfResolvable(const SelfResolvable&) = delete;
+ SelfResolvable& operator=(const SelfResolvable&) = delete;
+
+ // Resolvers to old object will resolve to new object.
+ SelfResolvable(SelfResolvable&& other)
+ : resolver_(std::move(other.resolver_)) {
+ this->resolver_.SetResolvedObject(CastToSubClass());
+ }
+
+ // Old resolvers for this object will resolve to nullptr.
+ // Other's resolvers will now resolve to this.
+ SelfResolvable& operator=(SelfResolvable&& other) {
+ if (this != &other) {
+ this->resolver_ = std::move(other.resolver_);
+ this->resolver_.SetResolvedObject(CastToSubClass());
+ }
+ return *this;
+ }
+
+ virtual ~SelfResolvable() {
+ if (this->resolver_.IsValid()) {
+ this->resolver_.SetResolvedObject(nullptr);
+ }
+ }
+
+ ObjectResolver<T> CreateResolver() { return resolver_; }
+
+ private:
+ T* CastToSubClass() { return static_cast<T*>(this); }
+
+ private:
+ ObjectResolver<T> resolver_;
+};
+} // namespace cru
diff --git a/include/cru/base/String.h b/include/cru/base/String.h
new file mode 100644
index 00000000..21a3db51
--- /dev/null
+++ b/include/cru/base/String.h
@@ -0,0 +1,492 @@
+#pragma once
+#include "Base.h"
+
+#include "Buffer.h"
+#include "Range.h"
+#include "StringToNumberConverter.h"
+#include "StringUtil.h"
+
+#include <filesystem>
+#include <initializer_list>
+#include <iterator>
+#include <string>
+#include <string_view>
+#include <type_traits>
+#include <vector>
+
+namespace cru {
+class StringView;
+
+class CRU_BASE_API String {
+ public:
+ using value_type = char16_t;
+ using size_type = Index;
+ using difference_type = Index;
+ using reference = value_type&;
+ using const_reference = const value_type&;
+ using pointer = value_type*;
+ using const_pointer = const value_type*;
+ using iterator = value_type*;
+ using const_iterator = const value_type*;
+ using reverse_iterator = std::reverse_iterator<iterator>;
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+ public:
+ static String FromUtf8(const char* str);
+ static String FromUtf8(const char* str, Index size);
+ static String FromUtf8(const std::byte* str, Index size);
+ static String FromUtf8(std::string_view str) {
+ return FromUtf8(str.data(), str.size());
+ }
+ static String FromUtf8(const Buffer& buffer);
+
+ static String FromUtf16(const char16_t* str) { return String(str); }
+ static String FromUtf16(const char16_t* str, Index size) {
+ return String(str, size);
+ }
+ static String FromUtf16(std::u16string_view str) {
+ return FromUtf16(str.data(), str.size());
+ }
+
+ static inline String From(StringView str);
+
+ // Never use this if you don't know what this mean!
+ static String FromBuffer(pointer buffer, Index size, Index capacity) {
+ return String{from_buffer_tag{}, buffer, size, capacity};
+ }
+
+ static String FromStdPath(const std::filesystem::path& path);
+
+#ifdef CRU_PLATFORM_WINDOWS
+ static String FromUtf16(wchar_t* str) { return String(str); }
+ static String FromUtf16(wchar_t* str, Index size) {
+ return String(str, size);
+ }
+#endif
+
+ public:
+ String() = default;
+
+ String(const_pointer str);
+ String(const_pointer str, size_type size);
+
+ template <Index size>
+ String(const char16_t (&str)[size])
+ : String(reinterpret_cast<const_pointer>(str), size - 1) {}
+
+ template <typename Iter>
+ String(Iter start, Iter end) {
+ for (; start != end; start++) {
+ append(*start);
+ }
+ }
+
+ String(size_type size, value_type ch = 0);
+
+ String(std::initializer_list<value_type> l);
+
+ explicit String(StringView str);
+
+#ifdef CRU_PLATFORM_WINDOWS
+ String(const wchar_t* str);
+ String(const wchar_t* str, Index size);
+ String(const std::wstring& str) : String(str.data(), str.size()) {}
+#endif
+
+ String(const String& other);
+ String(String&& other) noexcept;
+
+ String& operator=(const String& other);
+ String& operator=(String&& other) noexcept;
+
+ ~String();
+
+ private:
+ struct from_buffer_tag {};
+ String(from_buffer_tag, pointer buffer, Index size, Index capacity);
+
+ public:
+ bool empty() const { return this->size_ == 0; }
+ Index size() const { return this->size_; }
+ Index length() const { return this->size(); }
+ Index capacity() const { return this->capacity_; }
+ pointer data() { return this->buffer_; }
+ const_pointer data() const { return this->buffer_; }
+
+ void resize(Index new_size);
+ void reserve(Index new_capacity);
+ void shrink_to_fit();
+
+ reference front() { return this->operator[](0); }
+ const_reference front() const { return this->operator[](0); }
+
+ reference back() { return this->operator[](size_ - 1); }
+ const_reference back() const { return this->operator[](size_ - 1); }
+
+ const_pointer c_str() const { return buffer_; }
+
+ reference operator[](Index index) { return buffer_[index]; }
+ const_reference operator[](Index index) const { return buffer_[index]; }
+
+ public:
+ iterator begin() { return this->buffer_; }
+ const_iterator begin() const { return this->buffer_; }
+ const_iterator cbegin() const { return this->buffer_; }
+
+ iterator end() { return this->buffer_ + this->size_; }
+ const_iterator end() const { return this->buffer_ + this->size_; }
+ const_iterator cend() const { return this->buffer_ + this->size_; }
+
+ reverse_iterator rbegin() { return reverse_iterator{end()}; }
+ const_reverse_iterator rbegin() const {
+ return const_reverse_iterator{end()};
+ }
+ const_reverse_iterator crbegin() const {
+ return const_reverse_iterator{cend()};
+ }
+
+ reverse_iterator rend() { return reverse_iterator{begin()}; }
+ const_reverse_iterator rend() const {
+ return const_reverse_iterator{begin()};
+ }
+ const_reverse_iterator crend() const {
+ return const_reverse_iterator{cbegin()};
+ }
+
+ public:
+ void clear();
+ iterator insert(const_iterator pos, value_type value) {
+ return this->insert(pos, &value, 1);
+ }
+ iterator insert(const_iterator pos, const_iterator str, Index size);
+ iterator insert(const_iterator pos, StringView str);
+ iterator erase(const_iterator pos) { return this->erase(pos, pos + 1); }
+ iterator erase(const_iterator start, const_iterator end);
+ void push_back(value_type value) { this->append(value); }
+ void pop_back() { this->erase(cend() - 1); }
+ void append(value_type value) { this->insert(cend(), value); }
+ void append(const_iterator str, Index size) {
+ this->insert(cend(), str, size);
+ }
+ inline void append(StringView str);
+
+ String substr(size_type start, size_type size = -1) const {
+ if (size == -1) {
+ size = this->size_ - start;
+ }
+ return String(this->buffer_ + start, size);
+ }
+
+ String& operator+=(value_type value) {
+ this->append(value);
+ return *this;
+ }
+ String& operator+=(StringView other);
+
+ public:
+ operator std::u16string_view() const {
+ return std::u16string_view(data(), size());
+ }
+
+ StringView View() const;
+
+ public:
+ Index Find(value_type value, Index start = 0) const;
+ std::vector<String> Split(value_type separator,
+ bool remove_space_line = false) const;
+ std::vector<String> SplitToLines(bool remove_space_line = false) const;
+
+ bool StartWith(StringView str) const;
+ bool EndWith(StringView str) const;
+
+ String& TrimStart();
+ String& TrimEnd();
+ String& Trim();
+
+ public:
+ void AppendCodePoint(CodePoint code_point);
+
+ Utf16CodePointIterator CodePointIterator() const {
+ return Utf16CodePointIterator(buffer_, size_);
+ }
+
+ Index IndexFromCodeUnitToCodePoint(Index code_unit_index) const;
+ Index IndexFromCodePointToCodeUnit(Index code_point_index) const;
+ Range RangeFromCodeUnitToCodePoint(Range code_unit_range) const;
+ Range RangeFromCodePointToCodeUnit(Range code_point_range) const;
+
+ template <typename TInteger>
+ std::enable_if_t<std::is_signed_v<TInteger>, TInteger> ParseToInteger(
+ Index* processed_characters_count, unsigned flags, int base) const;
+
+ int ParseToInt(Index* processed_characters_count = nullptr,
+ StringToNumberFlag flags = {}, int base = 0) const;
+ long long ParseToLongLong(Index* processed_characters_count = nullptr,
+ StringToNumberFlag flags = {}, int base = 0) const;
+
+ float ParseToFloat(Index* processed_characters_count = nullptr,
+ StringToNumberFlag flags = {}) const;
+ double ParseToDouble(Index* processed_characters_count = nullptr,
+ StringToNumberFlag flags = {}) const;
+ std::vector<float> ParseToFloatList(value_type separator = u' ') const;
+ std::vector<double> ParseToDoubleList(value_type separator = u' ') const;
+
+#ifdef CRU_PLATFORM_WINDOWS
+ const wchar_t* WinCStr() const {
+ return reinterpret_cast<const wchar_t*>(c_str());
+ }
+#endif
+
+ template <typename... T>
+ String Format(T&&... args) const;
+
+ std::string ToUtf8() const;
+ Buffer ToUtf8Buffer(bool end_zero = true) const;
+
+ int Compare(const String& other) const;
+ int CaseInsensitiveCompare(const String& other) const;
+ bool CaseInsensitiveEqual(const String& other) const {
+ return CaseInsensitiveCompare(other) == 0;
+ }
+
+ private:
+ static char16_t kEmptyBuffer[1];
+
+ private:
+ char16_t* buffer_ = kEmptyBuffer;
+ Index size_ = 0; // not including trailing '\0'
+ Index capacity_ = 0; // always 1 smaller than real buffer size
+};
+
+std::ostream& CRU_BASE_API operator<<(std::ostream& os, const String& value);
+
+class CRU_BASE_API StringView {
+ public:
+ using value_type = char16_t;
+ using size_type = Index;
+ using difference_type = Index;
+ using reference = value_type&;
+ using const_reference = const value_type&;
+ using pointer = value_type*;
+ using const_pointer = const value_type*;
+ using iterator = const value_type*;
+ using const_iterator = const value_type*;
+ using reverse_iterator = std::reverse_iterator<iterator>;
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+ StringView() = default;
+
+ constexpr StringView(const_pointer ptr, Index size)
+ : ptr_(ptr), size_(size) {}
+
+ template <Index size>
+ constexpr StringView(const value_type (&array)[size])
+ : StringView(array, size - 1) {}
+
+ StringView(const String& str) : StringView(str.data(), str.size()) {}
+
+ CRU_DEFAULT_COPY(StringView)
+ CRU_DEFAULT_MOVE(StringView)
+
+ ~StringView() = default;
+
+ bool empty() const { return size_ == 0; }
+ Index size() const { return size_; }
+ const value_type* data() const { return ptr_; }
+
+ public:
+ iterator begin() { return this->ptr_; }
+ const_iterator begin() const { return this->ptr_; }
+ const_iterator cbegin() const { return this->ptr_; }
+
+ iterator end() { return this->ptr_ + this->size_; }
+ const_iterator end() const { return this->ptr_ + this->size_; }
+ const_iterator cend() const { return this->ptr_ + this->size_; }
+
+ reverse_iterator rbegin() { return reverse_iterator{end()}; }
+ const_reverse_iterator rbegin() const {
+ return const_reverse_iterator{end()};
+ }
+ const_reverse_iterator crbegin() const {
+ return const_reverse_iterator{cend()};
+ }
+
+ reverse_iterator rend() { return reverse_iterator{begin()}; }
+ const_reverse_iterator rend() const {
+ return const_reverse_iterator{begin()};
+ }
+ const_reverse_iterator crend() const {
+ return const_reverse_iterator{cbegin()};
+ }
+
+ StringView substr(Index pos);
+ StringView substr(Index pos, Index size);
+
+ value_type operator[](Index index) const { return ptr_[index]; }
+
+ operator std::u16string_view() const {
+ return std::u16string_view(data(), size());
+ }
+
+ public:
+ int Compare(const StringView& other) const;
+ int CaseInsensitiveCompare(const StringView& other) const;
+ bool CaseInsensitiveEqual(const StringView& other) const {
+ return CaseInsensitiveCompare(other) == 0;
+ }
+
+ String ToString() const { return String(ptr_, size_); }
+
+ Utf16CodePointIterator CodePointIterator() const {
+ return Utf16CodePointIterator(ptr_, size_);
+ }
+
+ Index Find(value_type value, Index start = 0) const;
+ std::vector<String> Split(value_type separator,
+ bool remove_space_line = false) const;
+ std::vector<String> SplitToLines(bool remove_space_line = false) const;
+
+ bool StartWith(StringView str) const;
+ bool EndWith(StringView str) const;
+
+ Index IndexFromCodeUnitToCodePoint(Index code_unit_index) const;
+ Index IndexFromCodePointToCodeUnit(Index code_point_index) const;
+ Range RangeFromCodeUnitToCodePoint(Range code_unit_range) const;
+ Range RangeFromCodePointToCodeUnit(Range code_point_range) const;
+
+ template <typename TInteger>
+ std::enable_if_t<std::is_signed_v<TInteger>, TInteger> ParseToInteger(
+ Index* processed_characters_count, StringToNumberFlag flags,
+ int base) const {
+ auto utf8_string = ToUtf8();
+ auto result = StringToIntegerConverter(flags, base)
+ .Parse(utf8_string.data(), utf8_string.size(),
+ processed_characters_count);
+ return result.negate ? -result.value : result.value;
+ }
+
+ int ParseToInt(Index* processed_characters_count = nullptr,
+ StringToNumberFlag flags = {}, int base = 0) const;
+ long long ParseToLongLong(Index* processed_characters_count = nullptr,
+ StringToNumberFlag flags = {}, int base = 0) const;
+
+ float ParseToFloat(Index* processed_characters_count = nullptr,
+ StringToNumberFlag flags = {}) const;
+ double ParseToDouble(Index* processed_characters_count = nullptr,
+ StringToNumberFlag flags = {}) const;
+ std::vector<float> ParseToFloatList(value_type separator = u' ') const;
+ std::vector<double> ParseToDoubleList(value_type separator = u' ') const;
+
+ std::string ToUtf8() const;
+ Buffer ToUtf8Buffer(bool end_zero = true) const;
+
+ private:
+ const char16_t* ptr_;
+ Index size_;
+};
+
+CRU_DEFINE_COMPARE_OPERATORS(String)
+
+inline String operator+(const String& left, const String& right) {
+ String result;
+ result += left;
+ result += right;
+ return result;
+}
+
+inline String operator+(String::value_type left, const String& right) {
+ String result;
+ result += left;
+ result += right;
+ return result;
+}
+
+inline String operator+(const String& left, String::value_type right) {
+ String result;
+ result += left;
+ result += right;
+ return result;
+}
+
+CRU_DEFINE_COMPARE_OPERATORS(StringView)
+
+inline String::iterator String::insert(const_iterator pos, StringView str) {
+ return insert(pos, str.data(), str.size());
+}
+
+inline void String::append(StringView str) {
+ this->append(str.data(), str.size());
+}
+
+inline String String::From(StringView str) { return str.ToString(); }
+
+inline String::String(StringView str) : String(str.data(), str.size()) {}
+
+inline String ToString(StringView value) { return value.ToString(); }
+
+inline CodePoint Utf16PreviousCodePoint(StringView str, Index current,
+ Index* previous_position) {
+ return Utf16PreviousCodePoint(str.data(), str.size(), current,
+ previous_position);
+}
+
+inline CodePoint Utf16NextCodePoint(StringView str, Index current,
+ Index* next_position) {
+ return Utf16NextCodePoint(str.data(), str.size(), current, next_position);
+}
+
+inline bool Utf16IsValidInsertPosition(StringView str, Index position) {
+ return Utf16IsValidInsertPosition(str.data(), str.size(), position);
+}
+
+// Return position after the character making predicate returns true or 0 if no
+// character doing so.
+inline Index CRU_BASE_API
+Utf16BackwardUntil(StringView str, Index position,
+ const std::function<bool(CodePoint)>& predicate) {
+ return Utf16BackwardUntil(str.data(), str.size(), position, predicate);
+}
+// Return position before the character making predicate returns true or
+// str.size() if no character doing so.
+inline Index CRU_BASE_API
+Utf16ForwardUntil(StringView str, Index position,
+ const std::function<bool(CodePoint)>& predicate) {
+ return Utf16ForwardUntil(str.data(), str.size(), position, predicate);
+}
+
+inline Index Utf16PreviousWord(StringView str, Index position,
+ bool* is_space = nullptr) {
+ return Utf16PreviousWord(str.data(), str.size(), position, is_space);
+}
+
+inline Index Utf16NextWord(StringView str, Index position,
+ bool* is_space = nullptr) {
+ return Utf16NextWord(str.data(), str.size(), position, is_space);
+}
+
+String CRU_BASE_API ToLower(StringView s);
+String CRU_BASE_API ToUpper(StringView s);
+
+template <typename TInteger>
+std::enable_if_t<std::is_signed_v<TInteger>, TInteger> String::ParseToInteger(
+ Index* processed_characters_count, unsigned flags, int base) const {
+ View().ParseToInteger<TInteger>(processed_characters_count, flags, base);
+}
+
+} // namespace cru
+
+template <>
+struct std::hash<cru::String> {
+ std::size_t operator()(const cru::String& value) const {
+ return std::hash<std::u16string_view>{}(std::u16string_view(
+ reinterpret_cast<const char16_t*>(value.data()), value.size()));
+ }
+};
+
+template <>
+struct std::hash<cru::StringView> {
+ std::size_t operator()(const cru::StringView& value) const {
+ return std::hash<std::u16string_view>{}(std::u16string_view(
+ reinterpret_cast<const char16_t*>(value.data()), value.size()));
+ }
+};
diff --git a/include/cru/base/StringToNumberConverter.h b/include/cru/base/StringToNumberConverter.h
new file mode 100644
index 00000000..758b26c8
--- /dev/null
+++ b/include/cru/base/StringToNumberConverter.h
@@ -0,0 +1,107 @@
+#pragma once
+#include "Base.h"
+#include "Bitmask.h"
+
+#include <cstddef>
+#include <ostream>
+
+namespace cru {
+namespace details {
+struct StringToNumberFlagTag {};
+} // namespace details
+
+using StringToNumberFlag = Bitmask<details::StringToNumberFlagTag>;
+
+struct StringToNumberFlags {
+ constexpr static StringToNumberFlag kAllowLeadingSpaces =
+ StringToNumberFlag::FromOffset(0);
+ constexpr static StringToNumberFlag kAllowTrailingSpaces =
+ StringToNumberFlag::FromOffset(1);
+ constexpr static StringToNumberFlag kAllowTrailingJunk =
+ StringToNumberFlag::FromOffset(2);
+ constexpr static StringToNumberFlag kAllowLeadingZeroForInteger =
+ StringToNumberFlag::FromOffset(3);
+ constexpr static StringToNumberFlag kThrowOnError =
+ StringToNumberFlag::FromOffset(4);
+};
+
+template <typename TResult>
+struct IStringToNumberConverter : virtual Interface {
+ virtual TResult Parse(const char* str, Index size,
+ Index* processed_characters_count) const = 0;
+
+ template <std::size_t Size>
+ TResult Parse(const char (&str)[Size],
+ Index* processed_characters_count) const {
+ return Parse(str, Size - 1, processed_characters_count);
+ }
+};
+
+struct CRU_BASE_API StringToIntegerResult {
+ StringToIntegerResult() = default;
+ StringToIntegerResult(bool negate, unsigned long long value)
+ : negate(negate), value(value) {}
+
+ bool negate;
+ unsigned long long value;
+};
+
+inline bool CRU_BASE_API operator==(const StringToIntegerResult& left,
+ const StringToIntegerResult& right) {
+ return left.negate == right.negate && left.value == right.value;
+}
+
+inline bool CRU_BASE_API operator!=(const StringToIntegerResult& left,
+ const StringToIntegerResult& right) {
+ return !(left == right);
+}
+
+inline std::ostream& CRU_BASE_API
+operator<<(std::ostream& stream, const StringToIntegerResult& result) {
+ return stream << "StringToIntegerConverterImplResult("
+ << (result.negate ? "-" : "") << result.value << ")";
+}
+
+/**
+ * \brief A converter that convert number into long long.
+ */
+struct CRU_BASE_API StringToIntegerConverter
+ : IStringToNumberConverter<StringToIntegerResult> {
+ public:
+ explicit StringToIntegerConverter(StringToNumberFlag flags, int base = 0)
+ : flags(flags), base(base) {}
+
+ bool CheckParams() const;
+
+ /**
+ * \brief Convert string to long long.
+ * \param str The string to convert.
+ * \param size The size of the string.
+ * \param processed_characters_count The number of characters that were
+ * processed. Or nullptr to not retrieve.
+ */
+ StringToIntegerResult Parse(const char* str, Index size,
+ Index* processed_characters_count) const override;
+ using IStringToNumberConverter<StringToIntegerResult>::Parse;
+
+ StringToNumberFlag flags;
+ /**
+ * \brief The base of the number used for parse or 0 for auto detect.
+ * \remarks Base can only be of range [2, 36] or 0. If base is 0, decimal is
+ * assumed by default ,or if str is started with "0x" or "0X" hexadecimal is
+ * assumed, or if str is started with a single "0" octoral is assumed, or if
+ * str is started with "0b" or "0B" binary is assumed. Otherwise it is an
+ * error.
+ */
+ int base;
+};
+
+struct CRU_BASE_API StringToFloatConverter {
+ StringToFloatConverter(StringToNumberFlag flags) : flags(flags) {}
+
+ double Parse(const char* str, Index size,
+ Index* processed_characters_count) const;
+
+ StringToNumberFlag flags;
+};
+} // namespace cru
diff --git a/include/cru/base/StringUtil.h b/include/cru/base/StringUtil.h
new file mode 100644
index 00000000..8f085283
--- /dev/null
+++ b/include/cru/base/StringUtil.h
@@ -0,0 +1,226 @@
+#pragma once
+#include "Base.h"
+
+#include <functional>
+#include <type_traits>
+
+namespace cru {
+using CodePoint = std::int32_t;
+constexpr CodePoint k_invalid_code_point = -1;
+
+inline bool IsUtf16SurrogatePairCodeUnit(char16_t c) {
+ return c >= 0xD800 && c <= 0xDFFF;
+}
+
+inline bool IsUtf16SurrogatePairLeading(char16_t c) {
+ return c >= 0xD800 && c <= 0xDBFF;
+}
+
+inline bool IsUtf16SurrogatePairTrailing(char16_t c) {
+ return c >= 0xDC00 && c <= 0xDFFF;
+}
+
+CodePoint CRU_BASE_API Utf8NextCodePoint(const char* ptr, Index size,
+ Index current, Index* next_position);
+
+CodePoint CRU_BASE_API Utf16NextCodePoint(const char16_t* ptr, Index size,
+ Index current, Index* next_position);
+CodePoint CRU_BASE_API Utf16PreviousCodePoint(const char16_t* ptr, Index size,
+ Index current,
+ Index* previous_position);
+
+template <typename CharType>
+using NextCodePointFunctionType = CodePoint (*)(const CharType*, Index, Index,
+ Index*);
+
+template <typename CharType,
+ NextCodePointFunctionType<CharType> NextCodePointFunction>
+class CodePointIterator {
+ public:
+ using difference_type = Index;
+ using value_type = CodePoint;
+ using pointer = void;
+ using reference = value_type;
+ using iterator_category = std::forward_iterator_tag;
+
+ public:
+ struct past_end_tag_t {};
+
+ explicit CodePointIterator(const CharType* ptr, Index size, Index current = 0)
+ : ptr_(ptr), size_(size), position_(current) {}
+ explicit CodePointIterator(const CharType* ptr, Index size, past_end_tag_t)
+ : ptr_(ptr), size_(size), position_(size) {}
+
+ CRU_DEFAULT_COPY(CodePointIterator)
+ CRU_DEFAULT_MOVE(CodePointIterator)
+
+ ~CodePointIterator() = default;
+
+ public:
+ const CharType* GetPtr() const { return ptr_; }
+ Index GetSize() const { return size_; }
+ Index GetPosition() const { return position_; }
+
+ bool IsPastEnd() const { return position_ == static_cast<Index>(size_); }
+
+ public:
+ CodePointIterator begin() const { return *this; }
+ CodePointIterator end() const {
+ return CodePointIterator{ptr_, size_, past_end_tag_t{}};
+ }
+
+ public:
+ bool operator==(const CodePointIterator& other) const {
+ // You should compare iterator that iterate on the same string.
+ Expects(this->ptr_ == other.ptr_ && this->size_ == other.size_);
+ return this->position_ == other.position_;
+ }
+ bool operator!=(const CodePointIterator& other) const {
+ return !this->operator==(other);
+ }
+
+ CodePointIterator& operator++() {
+ Expects(!IsPastEnd());
+ Forward();
+ return *this;
+ }
+
+ CodePointIterator operator++(int) {
+ Expects(!IsPastEnd());
+ CodePointIterator old = *this;
+ Forward();
+ return old;
+ }
+
+ CodePoint operator*() const {
+ return NextCodePointFunction(ptr_, size_, position_, &next_position_cache_);
+ }
+
+ private:
+ void Forward() {
+ if (next_position_cache_ > position_) {
+ position_ = next_position_cache_;
+ } else {
+ NextCodePointFunction(ptr_, size_, position_, &position_);
+ }
+ }
+
+ private:
+ const CharType* ptr_;
+ Index size_;
+ Index position_;
+ mutable Index next_position_cache_ = 0;
+};
+
+using Utf8CodePointIterator = CodePointIterator<char, &Utf8NextCodePoint>;
+
+using Utf16CodePointIterator = CodePointIterator<char16_t, &Utf16NextCodePoint>;
+
+namespace details {
+template <typename UInt, int number_of_bit, typename ReturnType>
+inline std::enable_if_t<std::is_unsigned_v<UInt>, ReturnType> ExtractBits(
+ UInt n) {
+ return static_cast<ReturnType>(n & ((1u << number_of_bit) - 1));
+}
+} // namespace details
+
+template <typename CharWriter>
+std::enable_if_t<std::is_invocable_v<CharWriter, char>, bool>
+Utf8EncodeCodePointAppend(CodePoint code_point, CharWriter&& writer) {
+ auto write_continue_byte = [&writer](std::uint8_t byte6) {
+ writer((1u << 7) + (((1u << 6) - 1) & byte6));
+ };
+
+ if (code_point >= 0 && code_point <= 0x007F) {
+ writer(static_cast<char>(code_point));
+ return true;
+ } else if (code_point >= 0x0080 && code_point <= 0x07FF) {
+ std::uint32_t unsigned_code_point = code_point;
+ writer(
+ static_cast<char>(details::ExtractBits<std::uint32_t, 5, std::uint8_t>(
+ (unsigned_code_point >> 6)) +
+ 0b11000000));
+ write_continue_byte(details::ExtractBits<std::uint32_t, 6, std::uint8_t>(
+ unsigned_code_point));
+ return true;
+ } else if (code_point >= 0x0800 && code_point <= 0xFFFF) {
+ std::uint32_t unsigned_code_point = code_point;
+ writer(
+ static_cast<char>(details::ExtractBits<std::uint32_t, 4, std::uint8_t>(
+ (unsigned_code_point >> (6 * 2))) +
+ 0b11100000));
+ write_continue_byte(details::ExtractBits<std::uint32_t, 6, std::uint8_t>(
+ unsigned_code_point >> 6));
+ write_continue_byte(details::ExtractBits<std::uint32_t, 6, std::uint8_t>(
+ unsigned_code_point));
+ return true;
+ } else if (code_point >= 0x10000 && code_point <= 0x10FFFF) {
+ std::uint32_t unsigned_code_point = code_point;
+ writer(
+ static_cast<char>(details::ExtractBits<std::uint32_t, 3, std::uint8_t>(
+ (unsigned_code_point >> (6 * 3))) +
+ 0b11110000));
+ write_continue_byte(details::ExtractBits<std::uint32_t, 6, std::uint8_t>(
+ unsigned_code_point >> (6 * 2)));
+ write_continue_byte(details::ExtractBits<std::uint32_t, 6, std::uint8_t>(
+ unsigned_code_point >> 6));
+ write_continue_byte(details::ExtractBits<std::uint32_t, 6, std::uint8_t>(
+ unsigned_code_point));
+ return true;
+ } else {
+ return false;
+ }
+}
+
+template <typename CharWriter>
+std::enable_if_t<std::is_invocable_v<CharWriter, char16_t>, bool>
+Utf16EncodeCodePointAppend(CodePoint code_point, CharWriter&& writer) {
+ if ((code_point >= 0 && code_point <= 0xD7FF) ||
+ (code_point >= 0xE000 && code_point <= 0xFFFF)) {
+ writer(static_cast<char16_t>(code_point));
+ return true;
+ } else if (code_point >= 0x10000 && code_point <= 0x10FFFF) {
+ std::uint32_t u = code_point - 0x10000;
+ writer(static_cast<char16_t>(
+ details::ExtractBits<std::uint32_t, 10, std::uint32_t>(u >> 10) +
+ 0xD800u));
+ writer(static_cast<char16_t>(
+ details::ExtractBits<std::uint32_t, 10, std::uint32_t>(u) + 0xDC00u));
+ return true;
+ } else {
+ return false;
+ }
+}
+
+// If given s is not a valid utf16 string, return value is UD.
+bool CRU_BASE_API Utf16IsValidInsertPosition(const char16_t* ptr, Index size,
+ Index position);
+
+// Return position after the character making predicate returns true or 0 if no
+// character doing so.
+Index CRU_BASE_API
+Utf16BackwardUntil(const char16_t* ptr, Index size, Index position,
+ const std::function<bool(CodePoint)>& predicate);
+// Return position before the character making predicate returns true or
+// str.size() if no character doing so.
+Index CRU_BASE_API
+Utf16ForwardUntil(const char16_t* ptr, Index size, Index position,
+ const std::function<bool(CodePoint)>& predicate);
+
+Index CRU_BASE_API Utf16PreviousWord(const char16_t* ptr, Index size,
+ Index position, bool* is_space = nullptr);
+Index CRU_BASE_API Utf16NextWord(const char16_t* ptr, Index size,
+ Index position, bool* is_space = nullptr);
+
+char16_t CRU_BASE_API ToLower(char16_t c);
+char16_t CRU_BASE_API ToUpper(char16_t c);
+
+bool CRU_BASE_API IsWhitespace(char16_t c);
+bool CRU_BASE_API IsDigit(char16_t c);
+
+Utf8CodePointIterator CRU_BASE_API CreateUtf8Iterator(const std::byte* buffer,
+ Index size);
+Utf8CodePointIterator CRU_BASE_API
+CreateUtf8Iterator(const std::vector<std::byte>& buffer);
+
+} // namespace cru
diff --git a/include/cru/base/SubProcess.h b/include/cru/base/SubProcess.h
new file mode 100644
index 00000000..fbe8ad2b
--- /dev/null
+++ b/include/cru/base/SubProcess.h
@@ -0,0 +1,258 @@
+#pragma once
+#include "Base.h"
+#include "Exception.h"
+#include "String.h"
+#include "io/Stream.h"
+
+#include <chrono>
+#include <condition_variable>
+#include <mutex>
+#include <optional>
+#include <unordered_map>
+#include <vector>
+
+namespace cru {
+enum class SubProcessStatus {
+ /**
+ * @brief The process has not been created and started.
+ */
+ Prepare,
+ /**
+ * @brief The process is failed to start.
+ */
+ FailedToStart,
+ /**
+ * @brief The process is running now.
+ */
+ Running,
+ /**
+ * @brief The process has been exited.
+ */
+ Exited,
+};
+
+class CRU_BASE_API SubProcessException : public Exception {
+ public:
+ using Exception::Exception;
+};
+
+class CRU_BASE_API SubProcessFailedToStartException
+ : public SubProcessException {
+ public:
+ using SubProcessException::SubProcessException;
+};
+
+class CRU_BASE_API SubProcessInternalException : public SubProcessException {
+ public:
+ using SubProcessException::SubProcessException;
+};
+
+struct SubProcessStartInfo {
+ String program;
+ std::vector<String> arguments;
+ std::unordered_map<String, String> environments;
+};
+
+enum class SubProcessExitType {
+ Unknown,
+ Normal,
+ Signal,
+};
+
+struct SubProcessExitResult {
+ SubProcessExitType exit_type;
+ int exit_code;
+ int exit_signal;
+ bool has_core_dump;
+
+ bool IsSuccess() const {
+ return exit_type == SubProcessExitType::Normal && exit_code == 0;
+ }
+
+ static SubProcessExitResult Unknown() {
+ return {SubProcessExitType::Unknown, 0, 0, false};
+ }
+
+ static SubProcessExitResult Normal(int exit_code) {
+ return {SubProcessExitType::Normal, exit_code, 0, false};
+ }
+
+ static SubProcessExitResult Signal(int exit_signal, bool has_core_dump) {
+ return {SubProcessExitType::Normal, 0, exit_signal, has_core_dump};
+ }
+};
+
+struct IPlatformSubProcessImpl : virtual Interface {
+ /**
+ * @brief Create and start a real process.
+ *
+ * If the program can't be created or start, an exception should be
+ * thrown.
+ *
+ * Implementation should fill internal data of the new process and start
+ * it.
+ *
+ * This method will be called only once in first call of `Start` on this
+ * thread with lock holdDefaultConstructible.
+ */
+ virtual void PlatformCreateProcess(const SubProcessStartInfo& start_info) = 0;
+
+ /**
+ * @brief Wait for the created process forever and return the exit result
+ * when process stops.
+ *
+ * Implementation should wait for the real process forever, after that,
+ * fill internal data and returns exit result.
+ *
+ * This method will be called only once on another thread after
+ * `PlatformCreateProcess` returns successfully
+ */
+ virtual SubProcessExitResult PlatformWaitForProcess() = 0;
+
+ /**
+ * @brief Kill the process immediately.
+ *
+ * This method will be called only once on this thread given
+ * `PlatformCreateProcess` returns successfully. There will be a window
+ * that the window already exits but the status has not been updated and
+ * this is called. So handle this gracefully and write to internal data
+ * carefully.
+ */
+ virtual void PlatformKillProcess() = 0;
+
+ virtual io::Stream* GetStdinStream() = 0;
+ virtual io::Stream* GetStdoutStream() = 0;
+ virtual io::Stream* GetStderrStream() = 0;
+};
+
+/**
+ * @brief A wrapper platform process. It is one-time, which means it
+ * starts and exits and can't start again.
+ *
+ * TODO: Current implementation has a problem. If the process does not exit for
+ * a long time, the resource related to it will not be released. It may cause a
+ * leak.
+ */
+class PlatformSubProcess : public Object {
+ CRU_DEFINE_CLASS_LOG_TAG(u"PlatformSubProcess")
+
+ private:
+ struct State {
+ explicit State(SubProcessStartInfo start_info,
+ std::shared_ptr<IPlatformSubProcessImpl> impl)
+ : start_info(std::move(start_info)), impl(std::move(impl)) {}
+
+ std::mutex mutex;
+ std::condition_variable condition_variable;
+ SubProcessStartInfo start_info;
+ SubProcessExitResult exit_result;
+ SubProcessStatus status = SubProcessStatus::Prepare;
+ bool killed = false;
+ std::shared_ptr<IPlatformSubProcessImpl> impl;
+ };
+
+ public:
+ PlatformSubProcess(SubProcessStartInfo start_info,
+ std::shared_ptr<IPlatformSubProcessImpl> impl);
+
+ ~PlatformSubProcess() override;
+
+ /**
+ * @brief Create and start a real process. If the process can't be created or
+ * start, `SubProcessFailedToStartException` will be thrown. If this function
+ * is already called once, `SubProcessException` will be thrown. Ensure only
+ * call this method once.
+ */
+ void Start();
+
+ /**
+ * @brief Wait for the process to exit optionally for at most `wait_time`. If
+ * the process already exits, it will return immediately. If the process has
+ * not started or failed to start, `SubProcessException` will be thrown.
+ * Ensure `Start` is called and does not throw before calling this.
+ *
+ * @remarks You may wish this returns bool to indicate whether it is timeout
+ * or the process exits. But no, even if it is timeout, the process may also
+ * have exited due to task schedule.
+ */
+ void Wait(std::optional<std::chrono::milliseconds> wait_time);
+
+ /**
+ * @brief kill the process if it is running. If the process already exits,
+ * nothing will happen. If the process has not started or failed to start,
+ * `SubProcessException` will be throw. Ensure `Start` is called and does not
+ * throw before calling this.
+ */
+ void Kill();
+
+ /**
+ * @brief Get the status of the process.
+ * 1. If the process has tried to start, aka `Start` is called, then this
+ * method will return one of `Running`, `FailedToStart`, `Exited`.
+ * 2. If it returns `Prepare`, `Start` is not called.
+ * 3. It does NOT guarantee that this return `Running` and the process is
+ * actually running. Because there might be a window that the process exits
+ * already but status is not updated.
+ */
+ SubProcessStatus GetStatus();
+
+ /**
+ * @brief Get the exit result. If the process is not started, failed to start
+ * or running, `SubProcessException` will be thrown.
+ */
+ SubProcessExitResult GetExitResult();
+
+ io::Stream* GetStdinStream();
+ io::Stream* GetStdoutStream();
+ io::Stream* GetStderrStream();
+
+ private:
+ std::shared_ptr<State> state_;
+ std::unique_lock<std::mutex> lock_;
+};
+
+class CRU_BASE_API SubProcess : public Object {
+ CRU_DEFINE_CLASS_LOG_TAG(u"SubProcess")
+
+ public:
+ static SubProcess Create(
+ String program, std::vector<String> arguments = {},
+ std::unordered_map<String, String> environments = {});
+
+ static SubProcessExitResult Call(
+ String program, std::vector<String> arguments = {},
+ std::unordered_map<String, String> environments = {});
+
+ public:
+ SubProcess(SubProcessStartInfo start_info);
+
+ CRU_DELETE_COPY(SubProcess)
+
+ SubProcess(SubProcess&& other) = default;
+ SubProcess& operator=(SubProcess&& other) = default;
+
+ ~SubProcess() override;
+
+ public:
+ void Wait(std::optional<std::chrono::milliseconds> wait_time = std::nullopt);
+ void Kill();
+
+ SubProcessStatus GetStatus();
+ SubProcessExitResult GetExitResult();
+
+ io::Stream* GetStdinStream();
+ io::Stream* GetStdoutStream();
+ io::Stream* GetStderrStream();
+
+ void Detach();
+
+ bool IsValid() const { return platform_process_ != nullptr; }
+ explicit operator bool() const { return IsValid(); }
+
+ private:
+ void CheckValid() const;
+
+ private:
+ std::unique_ptr<PlatformSubProcess> platform_process_;
+};
+} // namespace cru
diff --git a/include/cru/base/concurrent/ConcurrentQueue.h b/include/cru/base/concurrent/ConcurrentQueue.h
new file mode 100644
index 00000000..e311d5f9
--- /dev/null
+++ b/include/cru/base/concurrent/ConcurrentQueue.h
@@ -0,0 +1,88 @@
+#pragma once
+#include <condition_variable>
+#include <mutex>
+#include <optional>
+#include <utility>
+
+namespace cru::concurrent {
+namespace details {
+template <typename T>
+struct ConcurrentQueueNode {
+ ConcurrentQueueNode(T&& value, ConcurrentQueueNode* next = nullptr)
+ : value(std::move(value)), next(next) {}
+
+ T value;
+ ConcurrentQueueNode* next;
+};
+} // namespace details
+
+template <typename T>
+class ConcurrentQueue {
+ public:
+ ConcurrentQueue() {}
+
+ ConcurrentQueue(const ConcurrentQueue&) = delete;
+ ConcurrentQueue& operator=(const ConcurrentQueue&) = delete;
+
+ ~ConcurrentQueue() {
+ if (head_) {
+ auto node = head_;
+ while (node) {
+ auto next = node->next;
+ delete node;
+ node = next;
+ }
+ }
+ }
+
+ public:
+ void Push(T&& value) {
+ std::unique_lock<std::mutex> lock(mutex_);
+ if (head_ == nullptr) {
+ head_ = tail_ = new details::ConcurrentQueueNode<T>(std::move(value));
+ condition_variable_.notify_one();
+ } else {
+ tail_->next = new details::ConcurrentQueueNode<T>(std::move(value));
+ tail_ = tail_->next;
+ }
+ }
+
+ T Pull() {
+ std::unique_lock<std::mutex> lock(mutex_);
+ if (head_ == nullptr) {
+ condition_variable_.wait(lock);
+ }
+ assert(head_ != nullptr);
+ auto value = std::move(head_->value);
+ auto next = head_->next;
+ delete head_;
+ head_ = next;
+ if (next == nullptr) {
+ tail_ = nullptr;
+ }
+ return value;
+ }
+
+ std::optional<T> Poll() {
+ std::unique_lock<std::mutex> lock(mutex_);
+ if (head_ == nullptr) {
+ return std::nullopt;
+ }
+ auto value = std::move(head_->value);
+ auto next = head_->next;
+ delete head_;
+ head_ = next;
+ if (next == nullptr) {
+ tail_ = nullptr;
+ }
+ return value;
+ }
+
+ private:
+ details::ConcurrentQueueNode<T>* head_ = nullptr;
+ details::ConcurrentQueueNode<T>* tail_ = nullptr;
+
+ std::mutex mutex_;
+ std::condition_variable condition_variable_;
+};
+} // namespace cru::concurrent
diff --git a/include/cru/base/io/AutoReadStream.h b/include/cru/base/io/AutoReadStream.h
new file mode 100644
index 00000000..759d5026
--- /dev/null
+++ b/include/cru/base/io/AutoReadStream.h
@@ -0,0 +1,72 @@
+#pragma once
+
+#include "BufferStream.h"
+#include "Stream.h"
+
+#include <mutex>
+#include <thread>
+
+namespace cru::io {
+struct AutoReadStreamOptions {
+ /**
+ * @brief Will be passed to BufferStreamOptions::block_size.
+ */
+ Index block_size = 0;
+
+ /**
+ * @brief Will be passed to BufferStreamOptions::total_size_limit.
+ */
+ Index total_size_limit = 0;
+
+ BufferStreamOptions GetBufferStreamOptions() const {
+ BufferStreamOptions options;
+ options.block_size = block_size;
+ options.total_size_limit = total_size_limit;
+ return options;
+ }
+};
+
+/**
+ * @brief A stream that wraps another stream and auto read it into a buffer in a
+ * background thread.
+ */
+class CRU_BASE_API AutoReadStream : public Stream {
+ public:
+ /**
+ * @brief Wrap a stream and auto read it in background.
+ * @param stream The stream to auto read.
+ * @param auto_delete Whether to delete the stream object in destructor.
+ * @param options Options to modify the behavior.
+ */
+ AutoReadStream(
+ Stream* stream, bool auto_delete,
+ const AutoReadStreamOptions& options = AutoReadStreamOptions());
+
+ ~AutoReadStream() override;
+
+ public:
+ CRU_STREAM_IMPLEMENT_CLOSE_BY_DO_CLOSE
+
+ void BeginToDrop(bool auto_delete = true);
+
+ protected:
+ Index DoRead(std::byte* buffer, Index offset, Index size) override;
+ Index DoWrite(const std::byte* buffer, Index offset, Index size) override;
+ void DoFlush() override;
+
+ private:
+ void DoClose();
+
+ void BackgroundThreadRun();
+
+ private:
+ Stream* stream_;
+ bool auto_delete_;
+
+ Index size_per_read_;
+ std::unique_ptr<BufferStream> buffer_stream_;
+ std::mutex buffer_stream_mutex_;
+
+ std::thread background_thread_;
+};
+} // namespace cru::io
diff --git a/include/cru/base/io/BufferStream.h b/include/cru/base/io/BufferStream.h
new file mode 100644
index 00000000..5ebff546
--- /dev/null
+++ b/include/cru/base/io/BufferStream.h
@@ -0,0 +1,85 @@
+#pragma once
+
+#include "../Buffer.h"
+#include "../Exception.h"
+#include "Stream.h"
+
+#include <condition_variable>
+#include <list>
+#include <mutex>
+
+namespace cru::io {
+class WriteAfterEofException : public Exception {
+ public:
+ using Exception::Exception;
+ ~WriteAfterEofException() override = default;
+};
+
+struct BufferStreamOptions {
+ /**
+ * Actually I have no ideas about the best value for this. May change it later
+ * when I get some ideas.
+ */
+ constexpr static Index kDefaultBlockSize = 1024;
+
+ /**
+ * @brief The size of a single buffer allocated each time new space is needed.
+ * Use default value if <= 0.
+ *
+ * When current buffer is full and there is no space for following data, a new
+ * buffer will be allocated and appended to the buffer list. Note if sum size
+ * of all buffers reaches the total_buffer_limit, no more buffer will be
+ * allocated but wait.
+ */
+ Index block_size = 0;
+
+ /**
+ * @brief Total size limit of saved data in buffer. No limit if <= 0.
+ *
+ * The size will be floor(total_size_limit / block_size). When the buffer is
+ * filled, it will block and wait for user to read to get free space of buffer
+ * to continue read.
+ */
+ Index total_size_limit = 0;
+
+ Index GetBlockSizeOrDefault() const {
+ return block_size <= 0 ? kDefaultBlockSize : block_size;
+ }
+
+ Index GetMaxBlockCount() const {
+ return total_size_limit / GetBlockSizeOrDefault();
+ }
+};
+
+/**
+ * @brief SPSC (Single Producer Single Consumer) buffer stream.
+ *
+ * If used by multiple producer or multiple consumer, the behavior is undefined.
+ */
+class BufferStream : public Stream {
+ public:
+ BufferStream(const BufferStreamOptions& options);
+ ~BufferStream() override;
+
+ CRU_STREAM_IMPLEMENT_CLOSE_BY_DO_CLOSE
+
+ void SetEof();
+
+ protected:
+ Index DoRead(std::byte* buffer, Index offset, Index size) override;
+ Index DoWrite(const std::byte* buffer, Index offset, Index size) override;
+
+ private:
+ void DoClose();
+
+ private:
+ Index block_size_;
+ Index max_block_count_;
+
+ std::list<Buffer> buffer_list_;
+ bool eof_;
+
+ std::mutex mutex_;
+ std::condition_variable condition_variable_;
+};
+} // namespace cru::io
diff --git a/include/cru/base/io/CFileStream.h b/include/cru/base/io/CFileStream.h
new file mode 100644
index 00000000..0b58bdc9
--- /dev/null
+++ b/include/cru/base/io/CFileStream.h
@@ -0,0 +1,39 @@
+#pragma once
+
+#include "Stream.h"
+
+#include <cstdio>
+
+namespace cru::io {
+class CRU_BASE_API CFileStream : public Stream {
+ public:
+ CFileStream(const char* path, const char* mode);
+ explicit CFileStream(std::FILE* file, bool readable = true,
+ bool writable = true, bool auto_close = true);
+
+ CRU_DELETE_COPY(CFileStream)
+ CRU_DELETE_MOVE(CFileStream)
+
+ ~CFileStream() override;
+
+ public:
+ CRU_STREAM_IMPLEMENT_CLOSE_BY_DO_CLOSE
+
+ std::FILE* GetHandle() const;
+
+ protected:
+ Index DoSeek(Index offset, SeekOrigin origin) override;
+ Index DoTell() override;
+ void DoRewind() override;
+ Index DoRead(std::byte* buffer, Index offset, Index size) override;
+ Index DoWrite(const std::byte* buffer, Index offset, Index size) override;
+ void DoFlush() override;
+
+ private:
+ void DoClose();
+
+ private:
+ std::FILE* file_;
+ bool auto_close_;
+};
+} // namespace cru::io
diff --git a/include/cru/base/io/MemoryStream.h b/include/cru/base/io/MemoryStream.h
new file mode 100644
index 00000000..a1f90c3b
--- /dev/null
+++ b/include/cru/base/io/MemoryStream.h
@@ -0,0 +1,36 @@
+#pragma once
+
+#include "Stream.h"
+
+#include <functional>
+
+namespace cru::io {
+class CRU_BASE_API MemoryStream : public Stream {
+ public:
+ MemoryStream(
+ std::byte* buffer, Index size, bool read_only = false,
+ std::function<void(std::byte* buffer, Index size)> release_func = {});
+
+ ~MemoryStream() override;
+
+ public:
+ void Close() override;
+
+ std::byte* GetBuffer() const { return buffer_; }
+
+ protected:
+ Index DoSeek(Index offset, SeekOrigin origin) override;
+ Index DoGetSize() override { return size_; }
+ Index DoRead(std::byte* buffer, Index offset, Index size) override;
+ Index DoWrite(const std::byte* buffer, Index offset, Index size) override;
+
+ private:
+ void DoClose();
+
+ private:
+ std::byte* buffer_;
+ Index size_;
+ Index position_;
+ std::function<void(std::byte* buffer, Index size)> release_func_;
+};
+} // namespace cru::io
diff --git a/include/cru/base/io/OpenFileFlag.h b/include/cru/base/io/OpenFileFlag.h
new file mode 100644
index 00000000..4a5789fb
--- /dev/null
+++ b/include/cru/base/io/OpenFileFlag.h
@@ -0,0 +1,56 @@
+#pragma once
+
+#include "../Bitmask.h"
+
+namespace cru::io {
+namespace details {
+struct OpenFileFlagTag {};
+} // namespace details
+using OpenFileFlag = Bitmask<details::OpenFileFlagTag>;
+
+struct OpenFileFlags {
+ /**
+ * \brief for reading
+ * If the file does not exist, FileNotExistException should be thrown.
+ */
+ static constexpr OpenFileFlag Read{0x1};
+
+ /**
+ * \brief for writing
+ * If the file does not exist and Create is not specified,
+ * FileNotExistException should be thrown.
+ */
+ static constexpr OpenFileFlag Write{0x2};
+
+ /**
+ * \brief when writing, seek to end first
+ * Only effective for writing.
+ */
+ static constexpr OpenFileFlag Append{0x4};
+
+ /**
+ * \brief when writing, truncate the file to empty
+ * Only effective for writing.
+ * So the content is erased! Be careful!
+ */
+ static constexpr OpenFileFlag Truncate{0x8};
+
+ /**
+ * \brief when writing, if the file does not exist, create one
+ * Only effective for writing. When file does not exist, FileNotExistException
+ * will NOT be thrown and a new file will be created.
+ */
+ static constexpr OpenFileFlag Create{0x10};
+
+ /**
+ * TODO: ???
+ */
+ static constexpr OpenFileFlag Exclusive{0x20};
+};
+
+/**
+ * Append, Truncate, Create must be used with Write.
+ * Append and Truncate must not be used together.
+ */
+bool CheckOpenFileFlag(OpenFileFlag flags);
+} // namespace cru::io
diff --git a/include/cru/base/io/ProxyStream.h b/include/cru/base/io/ProxyStream.h
new file mode 100644
index 00000000..42ec9dfd
--- /dev/null
+++ b/include/cru/base/io/ProxyStream.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include "Stream.h"
+
+#include <functional>
+
+namespace cru::io {
+struct ProxyStreamHandlers {
+ std::function<Index(Index offset, Stream::SeekOrigin origin)> seek;
+ std::function<Index(std::byte* buffer, Index offset, Index size)> read;
+ std::function<Index(const std::byte* buffer, Index offset, Index size)> write;
+ std::function<void()> flush;
+
+ /**
+ * @brief This method will be only called once when `Close` is called or the
+ * stream is destructed.
+ */
+ std::function<void()> close;
+};
+
+class ProxyStream : public Stream {
+ public:
+ explicit ProxyStream(ProxyStreamHandlers handlers);
+
+ ~ProxyStream() override;
+
+ public:
+ CRU_STREAM_IMPLEMENT_CLOSE_BY_DO_CLOSE
+
+ protected:
+ Index DoSeek(Index offset, SeekOrigin origin) override;
+ Index DoRead(std::byte* buffer, Index offset, Index size) override;
+ Index DoWrite(const std::byte* buffer, Index offset, Index size) override;
+ void DoFlush() override;
+
+ private:
+ void DoClose();
+
+ private:
+ ProxyStreamHandlers handlers_;
+};
+} // namespace cru::io
diff --git a/include/cru/base/io/Resource.h b/include/cru/base/io/Resource.h
new file mode 100644
index 00000000..1d5313a6
--- /dev/null
+++ b/include/cru/base/io/Resource.h
@@ -0,0 +1,8 @@
+#pragma once
+#include "../Base.h"
+
+#include <filesystem>
+
+namespace cru::io {
+std::filesystem::path CRU_BASE_API GetResourceDir();
+}
diff --git a/include/cru/base/io/Stream.h b/include/cru/base/io/Stream.h
new file mode 100644
index 00000000..e0b61627
--- /dev/null
+++ b/include/cru/base/io/Stream.h
@@ -0,0 +1,126 @@
+#pragma once
+
+#include "../Base.h"
+
+#include "../Exception.h"
+#include "../String.h"
+
+#include <cstddef>
+
+namespace cru::io {
+class CRU_BASE_API StreamOperationNotSupportedException : public Exception {
+ public:
+ explicit StreamOperationNotSupportedException(String operation);
+
+ CRU_DEFAULT_DESTRUCTOR(StreamOperationNotSupportedException)
+
+ public:
+ String GetOperation() const { return operation_; }
+
+ public:
+ static void CheckSeek(bool seekable);
+ static void CheckRead(bool readable);
+ static void CheckWrite(bool writable);
+
+ private:
+ String operation_;
+};
+
+class CRU_BASE_API StreamAlreadyClosedException : public Exception {
+ public:
+ StreamAlreadyClosedException();
+
+ CRU_DEFAULT_DESTRUCTOR(StreamAlreadyClosedException)
+
+ static void Check(bool closed);
+};
+
+#define CRU_STREAM_IMPLEMENT_CLOSE_BY_DO_CLOSE \
+ void Close() override { DoClose(); }
+
+#define CRU_STREAM_BEGIN_CLOSE \
+ if (GetClosed()) return; \
+ CloseGuard close_guard(this);
+
+/**
+ * All stream is thread-unsafe by default unless being documented.
+ */
+class CRU_BASE_API Stream : public Object {
+ protected:
+ struct SupportedOperations {
+ std::optional<bool> can_seek;
+ std::optional<bool> can_read;
+ std::optional<bool> can_write;
+ };
+
+ struct CloseGuard {
+ explicit CloseGuard(Stream* stream) : stream(stream) {}
+ ~CloseGuard() { stream->SetClosed(true); }
+ Stream* stream;
+ };
+
+ protected:
+ explicit Stream(SupportedOperations supported_operations = {});
+ Stream(std::optional<bool> can_seek, std::optional<bool> can_read,
+ std::optional<bool> can_write);
+
+ public:
+ enum class SeekOrigin { Current, Begin, End };
+
+ CRU_DELETE_COPY(Stream)
+ CRU_DELETE_MOVE(Stream)
+
+ ~Stream() override = default;
+
+ public:
+ bool CanSeek();
+ Index Seek(Index offset, SeekOrigin origin = SeekOrigin::Current);
+ Index Tell();
+ void Rewind();
+ Index GetSize();
+
+ bool CanRead();
+ Index Read(std::byte* buffer, Index offset, Index size);
+ Index Read(std::byte* buffer, Index size);
+ Index Read(char* buffer, Index offset, Index size);
+ Index Read(char* buffer, Index size);
+
+ bool CanWrite();
+ Index Write(const std::byte* buffer, Index offset, Index size);
+ Index Write(const std::byte* buffer, Index size);
+ Index Write(const char* buffer, Index offset, Index size);
+ Index Write(const char* buffer, Index size);
+
+ void Flush();
+ virtual void Close() = 0;
+
+ virtual Buffer ReadToEnd(Index grow_size = 256);
+
+ // Utf8 encoding.
+ String ReadToEndAsUtf8String();
+
+ protected:
+ virtual bool DoCanSeek();
+ virtual bool DoCanRead();
+ virtual bool DoCanWrite();
+ virtual Index DoSeek(Index offset, SeekOrigin origin);
+ virtual Index DoTell();
+ virtual void DoRewind();
+ virtual Index DoGetSize();
+ virtual Index DoRead(std::byte* buffer, Index offset, Index size);
+ virtual Index DoWrite(const std::byte* buffer, Index offset, Index size);
+ virtual void DoFlush();
+
+ void SetSupportedOperations(SupportedOperations supported_operations) {
+ supported_operations_ = std::move(supported_operations);
+ }
+
+ bool GetClosed() { return closed_; }
+ void SetClosed(bool closed) { closed_ = closed; }
+ void CheckClosed() { StreamAlreadyClosedException::Check(closed_); }
+
+ private:
+ std::optional<SupportedOperations> supported_operations_;
+ bool closed_;
+};
+} // namespace cru::io
diff --git a/include/cru/base/log/Logger.h b/include/cru/base/log/Logger.h
new file mode 100644
index 00000000..25735e14
--- /dev/null
+++ b/include/cru/base/log/Logger.h
@@ -0,0 +1,88 @@
+#pragma once
+#include "../Base.h"
+
+#include "../Format.h"
+#include "../String.h"
+#include "../concurrent/ConcurrentQueue.h"
+
+#include <memory>
+#include <mutex>
+#include <thread>
+#include <vector>
+
+namespace cru::log {
+enum class LogLevel { Debug, Info, Warn, Error };
+
+struct CRU_BASE_API LogInfo {
+ LogInfo(LogLevel level, String tag, String message)
+ : level(level), tag(std::move(tag)), message(std::move(message)) {}
+
+ CRU_DEFAULT_COPY(LogInfo)
+ CRU_DEFAULT_MOVE(LogInfo)
+
+ ~LogInfo() = default;
+
+ LogLevel level;
+ String tag;
+ String message;
+};
+
+struct CRU_BASE_API ILogTarget : virtual Interface {
+ // Write the string s. LogLevel is just a helper. It has no effect on the
+ // content to write.
+ virtual void Write(LogLevel level, StringView s) = 0;
+};
+
+class CRU_BASE_API Logger : public Object {
+ public:
+ static Logger* GetInstance();
+
+ public:
+ Logger();
+
+ CRU_DELETE_COPY(Logger)
+ CRU_DELETE_MOVE(Logger)
+
+ ~Logger() override;
+
+ public:
+ void AddLogTarget(std::unique_ptr<ILogTarget> source);
+ void RemoveLogTarget(ILogTarget* source);
+
+ public:
+ void Log(LogLevel level, String tag, String message) {
+ Log(LogInfo(level, std::move(tag), std::move(message)));
+ }
+ void Log(LogInfo log_info);
+
+ template <typename... Args>
+ void FormatLog(LogLevel level, String tag, StringView format,
+ Args&&... args) {
+ Log(level, std::move(tag), Format(format, std::forward<Args>(args)...));
+ }
+
+ private:
+ concurrent::ConcurrentQueue<LogInfo> log_queue_;
+
+ std::mutex target_list_mutex_;
+ std::vector<std::unique_ptr<ILogTarget>> target_list_;
+
+ std::thread log_thread_;
+};
+} // namespace cru::log
+
+#define CRU_LOG_DEBUG(...) \
+ cru::log::Logger::GetInstance()->FormatLog(cru::log::LogLevel::Debug, \
+ kLogTag, __VA_ARGS__)
+
+#define CRU_LOG_INFO(...) \
+ cru::log::Logger::GetInstance()->FormatLog(cru::log::LogLevel::Info, \
+ kLogTag, __VA_ARGS__)
+
+#define CRU_LOG_WARN(...) \
+ cru::log::Logger::GetInstance()->FormatLog(cru::log::LogLevel::Warn, \
+ kLogTag, __VA_ARGS__)
+
+#define CRU_LOG_ERROR(...) \
+ cru::log::Logger::GetInstance()->FormatLog(cru::log::LogLevel::Error, \
+ kLogTag, __VA_ARGS__)
diff --git a/include/cru/base/log/StdioLogTarget.h b/include/cru/base/log/StdioLogTarget.h
new file mode 100644
index 00000000..4123766b
--- /dev/null
+++ b/include/cru/base/log/StdioLogTarget.h
@@ -0,0 +1,17 @@
+#pragma once
+#include "Logger.h"
+
+namespace cru::log {
+class StdioLogTarget : public Object, public virtual log::ILogTarget {
+ public:
+ explicit StdioLogTarget();
+
+ CRU_DELETE_COPY(StdioLogTarget)
+ CRU_DELETE_MOVE(StdioLogTarget)
+
+ ~StdioLogTarget() override;
+
+ public:
+ void Write(log::LogLevel level, StringView s) override;
+};
+} // namespace cru::log
diff --git a/include/cru/base/platform/Exception.h b/include/cru/base/platform/Exception.h
new file mode 100644
index 00000000..74dd6ad4
--- /dev/null
+++ b/include/cru/base/platform/Exception.h
@@ -0,0 +1,12 @@
+#pragma once
+#include "../Base.h"
+#include "../Exception.h"
+
+namespace cru::platform {
+class CRU_BASE_API PlatformException : public Exception {
+ public:
+ using Exception::Exception; // inherit constructors
+
+ CRU_DEFAULT_DESTRUCTOR(PlatformException)
+};
+} // namespace cru::platform
diff --git a/include/cru/base/platform/osx/Convert.h b/include/cru/base/platform/osx/Convert.h
new file mode 100644
index 00000000..395cbbae
--- /dev/null
+++ b/include/cru/base/platform/osx/Convert.h
@@ -0,0 +1,18 @@
+#pragma once
+#include "../../PreConfig.h"
+
+#ifndef CRU_PLATFORM_OSX
+#error "This file can only be included on osx."
+#endif
+
+#include "../../String.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+
+namespace cru::platform::osx {
+CFStringRef Convert(const String& string);
+String Convert(CFStringRef string);
+
+CFRange Convert(const Range& range);
+Range Convert(const CFRange& range);
+} // namespace cru::platform::osx
diff --git a/include/cru/base/platform/osx/Exception.h b/include/cru/base/platform/osx/Exception.h
new file mode 100644
index 00000000..5ab14ebd
--- /dev/null
+++ b/include/cru/base/platform/osx/Exception.h
@@ -0,0 +1,15 @@
+#pragma once
+#include "../../PreConfig.h"
+
+#ifndef CRU_PLATFORM_OSX
+#error "This file can only be included on osx."
+#endif
+
+#include "../Exception.h"
+
+namespace cru::platform::osx {
+class OsxException : public PlatformException {
+ public:
+ using PlatformException::PlatformException;
+};
+} // namespace cru::platform::osx
diff --git a/include/cru/base/platform/unix/PosixSpawnSubProcess.h b/include/cru/base/platform/unix/PosixSpawnSubProcess.h
new file mode 100644
index 00000000..ee4e912a
--- /dev/null
+++ b/include/cru/base/platform/unix/PosixSpawnSubProcess.h
@@ -0,0 +1,50 @@
+#pragma once
+
+#include "../../PreConfig.h"
+
+#ifndef CRU_PLATFORM_UNIX
+#error "This file can only be included on unix."
+#endif
+
+#include "../../Base.h"
+#include "../../SubProcess.h"
+#include "../../io/AutoReadStream.h"
+
+#include "UnixFileStream.h"
+#include "UnixPipe.h"
+
+#include <spawn.h>
+
+namespace cru::platform::unix {
+class PosixSpawnSubProcessImpl : public Object,
+ public virtual IPlatformSubProcessImpl {
+ CRU_DEFINE_CLASS_LOG_TAG(u"PosixSpawnSubProcess")
+
+ public:
+ explicit PosixSpawnSubProcessImpl();
+ ~PosixSpawnSubProcessImpl();
+
+ void PlatformCreateProcess(const SubProcessStartInfo& start_info) override;
+ SubProcessExitResult PlatformWaitForProcess() override;
+ void PlatformKillProcess() override;
+
+ io::Stream* GetStdinStream() override;
+ io::Stream* GetStdoutStream() override;
+ io::Stream* GetStderrStream() override;
+
+ private:
+ pid_t pid_;
+ int exit_code_;
+
+ UnixPipe stdin_pipe_;
+ UnixPipe stdout_pipe_;
+ UnixPipe stderr_pipe_;
+
+ std::unique_ptr<UnixFileStream> stdin_stream_;
+ std::unique_ptr<UnixFileStream> stdout_stream_;
+ std::unique_ptr<UnixFileStream> stderr_stream_;
+
+ std::unique_ptr<io::AutoReadStream> stdout_buffer_stream_;
+ std::unique_ptr<io::AutoReadStream> stderr_buffer_stream_;
+};
+} // namespace cru::platform::unix
diff --git a/include/cru/base/platform/unix/UnixFileStream.h b/include/cru/base/platform/unix/UnixFileStream.h
new file mode 100644
index 00000000..8021f21a
--- /dev/null
+++ b/include/cru/base/platform/unix/UnixFileStream.h
@@ -0,0 +1,39 @@
+#pragma once
+
+#include "../../PreConfig.h"
+
+#ifndef CRU_PLATFORM_UNIX
+#error "This file can only be included on unix."
+#endif
+
+#include "../../io/Stream.h"
+
+namespace cru::platform::unix {
+class UnixFileStream : public io::Stream {
+ private:
+ static constexpr auto kLogTag = u"cru::platform::unix::UnixFileStream";
+
+ public:
+ UnixFileStream(const char* path, int oflag, mode_t mode = 0660);
+ UnixFileStream(int fd, bool can_seek, bool can_read, bool can_write,
+ bool auto_close);
+ ~UnixFileStream() override;
+
+ public:
+ CRU_STREAM_IMPLEMENT_CLOSE_BY_DO_CLOSE
+
+ int GetFileDescriptor() const { return file_descriptor_; }
+
+ protected:
+ Index DoSeek(Index offset, SeekOrigin origin = SeekOrigin::Current) override;
+ Index DoRead(std::byte* buffer, Index offset, Index size) override;
+ Index DoWrite(const std::byte* buffer, Index offset, Index size) override;
+
+ private:
+ void DoClose();
+
+ private:
+ int file_descriptor_; // -1 for no file descriptor
+ bool auto_close_;
+};
+} // namespace cru::platform::unix
diff --git a/include/cru/base/platform/unix/UnixPipe.h b/include/cru/base/platform/unix/UnixPipe.h
new file mode 100644
index 00000000..cf35fb11
--- /dev/null
+++ b/include/cru/base/platform/unix/UnixPipe.h
@@ -0,0 +1,70 @@
+#pragma once
+
+#include "../../PreConfig.h"
+
+#ifndef CRU_PLATFORM_UNIX
+#error "This file can only be included on unix."
+#endif
+
+#include "../../Base.h"
+#include "../../Bitmask.h"
+
+namespace cru::platform::unix {
+namespace details {
+struct UnixPipeFlagTag;
+}
+using UnixPipeFlag = Bitmask<details::UnixPipeFlagTag>;
+struct UnixPipeFlags {
+ constexpr static auto NonBlock = UnixPipeFlag::FromOffset(1);
+};
+
+/**
+ * @brief an unix pipe, commonly for communication of parent process and child
+ * process.
+ *
+ * There are two types of pipes sorted by its usage. For stdin, parent process
+ * SEND data to child process. For stdout and stderr, parent process RECEIVE
+ * data from child process. Each pipe has two ends, one for read and the other
+ * for write. But for send and receive, they are reversed. It is a little
+ * confused to consider which end should be used by parent and which end should
+ * be used by child. So this class help you make it clear. You specify SEND or
+ * RECEIVE, and this class give you a parent used end and a child used end.
+ *
+ * This class will only close the end used by parent when it is destructed. It
+ * is the user's duty to close the one used by child.
+ */
+class UnixPipe : public Object {
+ private:
+ constexpr static auto kLogTag = u"cru::platform::unix::UnixPipe";
+
+ public:
+ enum class Usage {
+ Send,
+ Receive,
+ };
+
+ explicit UnixPipe(Usage usage, bool auto_close, UnixPipeFlag flags = {});
+
+ CRU_DELETE_COPY(UnixPipe)
+ CRU_DELETE_MOVE(UnixPipe)
+
+ ~UnixPipe();
+
+ /**
+ * @brief aka, the one used by parent process.
+ */
+ int GetSelfFileDescriptor();
+
+ /**
+ * @brief aka, the one used by child process.
+ */
+ int GetOtherFileDescriptor();
+
+ private:
+ Usage usage_;
+ bool auto_close_;
+ UnixPipeFlag flags_;
+ int read_fd_;
+ int write_fd_;
+};
+} // namespace cru::platform::unix
diff --git a/include/cru/base/platform/web/WebException.h b/include/cru/base/platform/web/WebException.h
new file mode 100644
index 00000000..d98b8943
--- /dev/null
+++ b/include/cru/base/platform/web/WebException.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include "../../PreConfig.h"
+
+#ifdef CRU_PLATFORM_EMSCRIPTEN
+
+#include "../Exception.h"
+
+namespace cru::platform::web {
+class WebException : public PlatformException {
+ public:
+ using PlatformException::PlatformException;
+};
+} // namespace cru::platform::web
+
+#endif
diff --git a/include/cru/base/platform/win/ComAutoInit.h b/include/cru/base/platform/win/ComAutoInit.h
new file mode 100644
index 00000000..569085c8
--- /dev/null
+++ b/include/cru/base/platform/win/ComAutoInit.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "../../PreConfig.h"
+#ifdef CRU_PLATFORM_WINDOWS
+
+#include "WinPreConfig.h"
+#include "cru/base/Base.h"
+
+namespace cru::platform::win {
+class CRU_BASE_API ComAutoInit {
+ public:
+ ComAutoInit();
+
+ CRU_DELETE_COPY(ComAutoInit)
+ CRU_DELETE_MOVE(ComAutoInit)
+
+ ~ComAutoInit();
+};
+} // namespace cru::platform::win
+
+#endif
diff --git a/include/cru/base/platform/win/DebugLogTarget.h b/include/cru/base/platform/win/DebugLogTarget.h
new file mode 100644
index 00000000..8257f637
--- /dev/null
+++ b/include/cru/base/platform/win/DebugLogTarget.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include "../../PreConfig.h"
+#ifdef CRU_PLATFORM_WINDOWS
+
+#include "WinPreConfig.h"
+
+#include "../../log/Logger.h"
+
+namespace cru::platform::win {
+
+class CRU_BASE_API WinDebugLogTarget : public ::cru::log::ILogTarget {
+ public:
+ WinDebugLogTarget() = default;
+
+ CRU_DELETE_COPY(WinDebugLogTarget)
+ CRU_DELETE_MOVE(WinDebugLogTarget)
+
+ ~WinDebugLogTarget() = default;
+
+ void Write(::cru::log::LogLevel level, StringView s) override;
+};
+} // namespace cru::platform::win
+
+#endif
diff --git a/include/cru/base/platform/win/Exception.h b/include/cru/base/platform/win/Exception.h
new file mode 100644
index 00000000..3e63b191
--- /dev/null
+++ b/include/cru/base/platform/win/Exception.h
@@ -0,0 +1,56 @@
+#pragma once
+#include "../../PreConfig.h"
+#ifdef CRU_PLATFORM_WINDOWS
+
+#include "WinPreConfig.h"
+
+#include "../Exception.h"
+
+#include <stdexcept>
+#include <string_view>
+
+namespace cru::platform::win {
+class CRU_BASE_API HResultError : public platform::PlatformException {
+ public:
+ explicit HResultError(HRESULT h_result);
+ explicit HResultError(HRESULT h_result, std::string_view message);
+
+ CRU_DEFAULT_COPY(HResultError)
+ CRU_DEFAULT_MOVE(HResultError)
+
+ ~HResultError() override = default;
+
+ HRESULT GetHResult() const { return h_result_; }
+
+ private:
+ HRESULT h_result_;
+};
+
+inline void ThrowIfFailed(const HRESULT h_result) {
+ if (FAILED(h_result)) throw HResultError(h_result);
+}
+
+inline void ThrowIfFailed(const HRESULT h_result, std::string_view message) {
+ if (FAILED(h_result)) throw HResultError(h_result, message);
+}
+
+class CRU_BASE_API Win32Error : public platform::PlatformException {
+ public:
+ // ::GetLastError is automatically called to get the error code.
+ // The same as Win32Error(::GetLastError(), message)
+ explicit Win32Error(String message);
+ Win32Error(DWORD error_code, String message);
+
+ CRU_DEFAULT_COPY(Win32Error)
+ CRU_DEFAULT_MOVE(Win32Error)
+
+ ~Win32Error() override = default;
+
+ DWORD GetErrorCode() const { return error_code_; }
+
+ private:
+ DWORD error_code_;
+};
+} // namespace cru::platform::win
+
+#endif
diff --git a/include/cru/base/platform/win/StreamConvert.h b/include/cru/base/platform/win/StreamConvert.h
new file mode 100644
index 00000000..3499604a
--- /dev/null
+++ b/include/cru/base/platform/win/StreamConvert.h
@@ -0,0 +1,14 @@
+#pragma once
+#include "../../PreConfig.h"
+
+#ifdef CRU_PLATFORM_WINDOWS
+
+#include "../../io/Stream.h"
+
+#include <objidlbase.h>
+
+namespace cru::platform::win {
+CRU_BASE_API IStream* ConvertStreamToComStream(io::Stream* stream);
+}
+
+#endif
diff --git a/include/cru/base/platform/win/Win32FileStream.h b/include/cru/base/platform/win/Win32FileStream.h
new file mode 100644
index 00000000..06656466
--- /dev/null
+++ b/include/cru/base/platform/win/Win32FileStream.h
@@ -0,0 +1,56 @@
+#pragma once
+
+#include "../../PreConfig.h"
+
+#ifdef CRU_PLATFORM_WINDOWS
+
+#include "../../String.h"
+#include "../../io/OpenFileFlag.h"
+#include "../../io/Stream.h"
+
+namespace cru::platform::win {
+namespace details {
+class Win32FileStreamPrivate;
+}
+
+class CRU_BASE_API Win32FileStream : public io::Stream {
+ public:
+ Win32FileStream(String path, io::OpenFileFlag flags);
+
+ CRU_DELETE_COPY(Win32FileStream)
+ CRU_DELETE_MOVE(Win32FileStream)
+
+ ~Win32FileStream() override;
+
+ public:
+ bool CanSeek() override;
+ Index Seek(Index offset, SeekOrigin origin = SeekOrigin::Current) override;
+
+ bool CanRead() override;
+ Index Read(std::byte* buffer, Index offset, Index size) override;
+ using Stream::Read;
+
+ bool CanWrite() override;
+ Index Write(const std::byte* buffer, Index offset, Index size) override;
+ using Stream::Write;
+
+ void Close() override;
+
+ String GetPath() const { return path_; }
+ io::OpenFileFlag GetOpenFileFlags() const { return flags_; }
+
+ details::Win32FileStreamPrivate* GetPrivate_() { return p_; }
+
+ private:
+ void CheckClosed();
+
+ private:
+ String path_;
+ io::OpenFileFlag flags_;
+ bool closed_ = false;
+
+ details::Win32FileStreamPrivate* p_;
+};
+} // namespace cru::platform::win
+
+#endif
diff --git a/include/cru/base/platform/win/WinPreConfig.h b/include/cru/base/platform/win/WinPreConfig.h
new file mode 100644
index 00000000..c2284df3
--- /dev/null
+++ b/include/cru/base/platform/win/WinPreConfig.h
@@ -0,0 +1,13 @@
+#pragma once
+#include "../../PreConfig.h"
+#ifdef CRU_PLATFORM_WINDOWS
+
+#define NOMINMAX
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+#undef CreateWindow
+#undef DrawText
+#undef CreateFont
+#undef CreateEvent
+
+#endif