aboutsummaryrefslogtreecommitdiff
path: root/src/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 /src/base
parentf51eb955e188858272230a990565931e7403f23b (diff)
downloadcru-dfe62dcf8bcefc523b466e127c3edc4dc2756629.tar.gz
cru-dfe62dcf8bcefc523b466e127c3edc4dc2756629.tar.bz2
cru-dfe62dcf8bcefc523b466e127c3edc4dc2756629.zip
Rename common to base.
Diffstat (limited to 'src/base')
-rw-r--r--src/base/Base.cpp7
-rw-r--r--src/base/Buffer.cpp277
-rw-r--r--src/base/CMakeLists.txt80
-rw-r--r--src/base/Exception.cpp36
-rw-r--r--src/base/Format.cpp111
-rw-r--r--src/base/PropertyTree.cpp71
-rw-r--r--src/base/String.cpp672
-rw-r--r--src/base/StringToNumberConverter.cpp170
-rw-r--r--src/base/StringUtil.cpp243
-rw-r--r--src/base/SubProcess.cpp209
-rw-r--r--src/base/io/AutoReadStream.cpp56
-rw-r--r--src/base/io/BufferStream.cpp109
-rw-r--r--src/base/io/CFileStream.cpp96
-rw-r--r--src/base/io/MemoryStream.cpp74
-rw-r--r--src/base/io/OpenFileFlag.cpp19
-rw-r--r--src/base/io/ProxyStream.cpp37
-rw-r--r--src/base/io/Resource.cpp48
-rw-r--r--src/base/io/Stream.cpp199
-rw-r--r--src/base/log/Logger.cpp88
-rw-r--r--src/base/log/StdioLogTarget.cpp27
-rw-r--r--src/base/platform/Exception.cpp1
-rw-r--r--src/base/platform/osx/Convert.cpp29
-rw-r--r--src/base/platform/osx/Exception.cpp1
-rw-r--r--src/base/platform/unix/PosixSpawnSubProcess.cpp204
-rw-r--r--src/base/platform/unix/UnixFileStream.cpp106
-rw-r--r--src/base/platform/unix/UnixPipe.cpp51
-rw-r--r--src/base/platform/web/WebException.cpp1
-rw-r--r--src/base/platform/win/BridgeComStream.cpp106
-rw-r--r--src/base/platform/win/BrigdeComStream.h43
-rw-r--r--src/base/platform/win/ComAutoInit.cpp15
-rw-r--r--src/base/platform/win/DebugLogTarget.cpp10
-rw-r--r--src/base/platform/win/Exception.cpp38
-rw-r--r--src/base/platform/win/StreamConvert.cpp28
-rw-r--r--src/base/platform/win/Win32FileStream.cpp122
-rw-r--r--src/base/platform/win/Win32FileStreamPrivate.h13
35 files changed, 3397 insertions, 0 deletions
diff --git a/src/base/Base.cpp b/src/base/Base.cpp
new file mode 100644
index 00000000..1704f8a9
--- /dev/null
+++ b/src/base/Base.cpp
@@ -0,0 +1,7 @@
+#include "cru/base/Base.h"
+
+#include <exception>
+
+namespace cru {
+void UnreachableCode() { std::terminate(); }
+} // namespace cru
diff --git a/src/base/Buffer.cpp b/src/base/Buffer.cpp
new file mode 100644
index 00000000..1213364a
--- /dev/null
+++ b/src/base/Buffer.cpp
@@ -0,0 +1,277 @@
+#include "cru/base/Buffer.h"
+#include "cru/base/Exception.h"
+
+#include <cstring>
+
+namespace cru {
+namespace {
+void CheckSize(Index size) {
+ if (size < 0) {
+ throw Exception(u"Size of buffer can't be smaller than 0.");
+ }
+}
+} // namespace
+
+Buffer::Buffer() {
+ ptr_ = nullptr;
+ size_ = used_begin_ = used_end_ = 0;
+}
+
+Buffer::Buffer(Index size) {
+ CheckSize(size);
+ if (size == 0) {
+ ptr_ = nullptr;
+ size_ = used_begin_ = used_end_ = 0;
+ } else {
+ ptr_ = new std::byte[size];
+ size_ = size;
+ used_begin_ = used_end_ = 0;
+ }
+ AssertValid();
+}
+
+Buffer::Buffer(const Buffer& other) { Copy_(other); }
+
+Buffer::Buffer(Buffer&& other) noexcept { Move_(std::move(other)); }
+
+Buffer& Buffer::operator=(const Buffer& other) {
+ if (this != &other) {
+ Delete_();
+ Copy_(other);
+ }
+ return *this;
+}
+
+Buffer& Buffer::operator=(Buffer&& other) noexcept {
+ if (this != &other) {
+ Delete_();
+ Move_(std::move(other));
+ }
+ return *this;
+}
+
+Buffer::~Buffer() { Delete_(); }
+
+void Buffer::AssignBytes(Index dst_offset, std::byte* src, Index src_offset,
+ Index src_size, bool use_memmove) {
+ CheckSize(src_size);
+
+ AssertValid();
+
+ (use_memmove ? std::memmove : std::memcpy)(ptr_ + dst_offset,
+ src + src_offset, src_size);
+ AssertValid();
+}
+
+void Buffer::ResizeBuffer(Index new_size, bool preserve_used) {
+ CheckSize(new_size);
+
+ AssertValid();
+
+ if (new_size == 0) {
+ Delete_();
+ ptr_ = nullptr;
+ size_ = used_begin_ = used_end_ = 0;
+ return;
+ }
+
+ auto old_ptr = ptr_;
+
+ ptr_ = new std::byte[new_size];
+ size_ = new_size;
+ used_begin_ = std::min(new_size, used_begin_);
+ used_end_ = std::min(new_size, used_end_);
+
+ if (old_ptr) {
+ if (preserve_used && used_begin_ < used_end_) {
+ std::memcpy(ptr_ + used_begin_, old_ptr + used_begin_,
+ used_end_ - used_begin_);
+ }
+ delete[] old_ptr;
+ }
+
+ AssertValid();
+}
+
+Index Buffer::PushFront(const std::byte* other, Index other_size,
+ bool use_memmove) {
+ CheckSize(other_size);
+
+ AssertValid();
+
+ auto copy_size = std::min(used_begin_, other_size);
+
+ if (copy_size) {
+ used_begin_ -= copy_size;
+ (use_memmove ? std::memmove : std::memcpy)(ptr_ + used_begin_, other,
+ copy_size);
+ }
+
+ AssertValid();
+
+ return copy_size;
+}
+
+bool Buffer::PushBack(std::byte b) {
+ AssertValid();
+ if (IsUsedReachEnd()) {
+ return false;
+ }
+ ptr_[used_end_] = b;
+ used_end_++;
+ AssertValid();
+ return true;
+}
+
+Index Buffer::PushBack(const std::byte* other, Index other_size,
+ bool use_memmove) {
+ CheckSize(other_size);
+
+ AssertValid();
+
+ auto copy_size = std::min(size_ - used_end_, other_size);
+
+ if (copy_size) {
+ (use_memmove ? std::memmove : std::memcpy)(ptr_ + used_end_, other,
+ copy_size);
+ used_end_ += copy_size;
+ }
+
+ AssertValid();
+
+ return copy_size;
+}
+
+void Buffer::PushBackCount(Index count) {
+ if (count < 0 || count > GetBackFree()) {
+ throw Exception(u"Count out of range in PushBackCount.");
+ }
+ used_end_ += count;
+}
+
+Index Buffer::PopFront(Index size) {
+ CheckSize(size);
+
+ AssertValid();
+
+ auto move = std::min(used_begin_, size);
+ used_begin_ -= move;
+
+ AssertValid();
+
+ return move;
+}
+
+Index Buffer::PopFront(std::byte* buffer, Index size, bool use_memmove) {
+ CheckSize(size);
+
+ AssertValid();
+
+ auto pop_size = std::min(GetUsedSize(), size);
+
+ if (pop_size) {
+ used_begin_ += pop_size;
+ (use_memmove ? std::memmove : std::memcpy)(
+ buffer, GetUsedBeginPtr() - pop_size, pop_size);
+ }
+
+ AssertValid();
+
+ return pop_size;
+}
+
+Index Buffer::PopEnd(Index size) {
+ CheckSize(size);
+
+ AssertValid();
+
+ auto move = std::min(size_ - used_end_, size);
+ used_end_ += move;
+
+ AssertValid();
+
+ return move;
+}
+
+Index Buffer::PopEnd(std::byte* buffer, Index size, bool use_memmove) {
+ CheckSize(size);
+
+ AssertValid();
+
+ auto pop_size = std::min(GetUsedSize(), size);
+
+ if (pop_size) {
+ used_end_ -= pop_size;
+ (use_memmove ? std::memmove : std::memcpy)(buffer, GetUsedEndPtr(),
+ pop_size);
+ }
+
+ AssertValid();
+
+ return pop_size;
+}
+
+std::byte* Buffer::Detach(Index* size) {
+ AssertValid();
+
+ auto ptr = this->ptr_;
+ if (size) {
+ *size = this->size_;
+ }
+ this->ptr_ = nullptr;
+ this->size_ = this->used_begin_ = this->used_end_ = 0;
+
+ AssertValid();
+
+ return ptr;
+}
+
+void Buffer::Copy_(const Buffer& other) {
+ if (other.ptr_ == nullptr) {
+ ptr_ = nullptr;
+ size_ = used_begin_ = used_end_ = 0;
+ } else {
+ ptr_ = new std::byte[other.size_];
+ size_ = other.size_;
+ used_begin_ = other.used_begin_;
+ used_end_ = other.used_end_;
+ std::memcpy(ptr_ + used_begin_, other.ptr_ + used_begin_,
+ used_end_ - used_begin_);
+ }
+ AssertValid();
+}
+
+void Buffer::Move_(Buffer&& other) noexcept {
+ ptr_ = other.ptr_;
+ size_ = other.size_;
+ used_begin_ = other.used_begin_;
+ used_end_ = other.used_end_;
+ other.ptr_ = nullptr;
+ other.size_ = other.used_begin_ = other.used_end_ = 0;
+ AssertValid();
+}
+
+void Buffer::Delete_() noexcept {
+ if (ptr_) {
+ delete[] ptr_;
+ }
+}
+
+void Buffer::AssertValid() {
+ assert(size_ >= 0);
+ assert(used_begin_ >= 0);
+ assert(used_begin_ <= size_);
+ assert(used_end_ >= 0);
+ assert(used_end_ <= size_);
+ assert(used_end_ >= used_begin_);
+ assert((ptr_ == nullptr && size_ == 0) || (ptr_ != nullptr && size_ > 0));
+}
+
+void swap(Buffer& left, Buffer& right) noexcept {
+ using std::swap;
+ swap(left.ptr_, right.ptr_);
+ swap(left.size_, right.size_);
+ swap(left.used_begin_, right.used_begin_);
+ swap(left.used_end_, right.used_end_);
+}
+} // namespace cru
diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt
new file mode 100644
index 00000000..19feddba
--- /dev/null
+++ b/src/base/CMakeLists.txt
@@ -0,0 +1,80 @@
+add_library(CruBase
+ Base.cpp
+ Buffer.cpp
+ Exception.cpp
+ Format.cpp
+ PropertyTree.cpp
+ String.cpp
+ StringToNumberConverter.cpp
+ StringUtil.cpp
+ SubProcess.cpp
+ io/AutoReadStream.cpp
+ io/BufferStream.cpp
+ io/CFileStream.cpp
+ io/Stream.cpp
+ io/ProxyStream.cpp
+ io/Resource.cpp
+ io/MemoryStream.cpp
+ log/Logger.cpp
+ log/StdioLogTarget.cpp
+ platform/Exception.cpp
+)
+target_compile_definitions(CruBase PRIVATE CRU_BASE_EXPORT_API)
+target_include_directories(CruBase PUBLIC ${CRU_INCLUDE_DIR})
+target_compile_definitions(CruBase PUBLIC $<$<CONFIG:Debug>:CRU_DEBUG>)
+
+if (UNIX AND NOT EMSCRIPTEN)
+ target_sources(CruBase PRIVATE
+ platform/unix/PosixSpawnSubProcess.cpp
+ platform/unix/UnixFileStream.cpp
+ platform/unix/UnixPipe.cpp
+ )
+
+ if (NOT APPLE)
+ target_link_libraries(CruBase PUBLIC pthread)
+ endif()
+endif()
+
+if (APPLE)
+ find_library(CORE_FOUNDATION CoreFoundation REQUIRED)
+ target_link_libraries(CruBase PUBLIC ${CORE_FOUNDATION})
+
+ target_sources(CruBase PRIVATE
+ platform/osx/Convert.cpp
+ platform/osx/Exception.cpp
+ )
+endif()
+
+if (EMSCRIPTEN)
+ target_compile_options(CruBase PUBLIC "-fwasm-exceptions")
+ target_link_options(CruBase PUBLIC "-fwasm-exceptions")
+
+ target_sources(CruBase PRIVATE
+ platform/web/WebException.cpp
+ )
+endif()
+
+if (WIN32)
+ target_sources(CruBase PRIVATE
+ platform/win/BridgeComStream.cpp
+ platform/win/ComAutoInit.cpp
+ platform/win/DebugLogTarget.cpp
+ platform/win/Exception.cpp
+ platform/win/StreamConvert.cpp
+ platform/win/Win32FileStream.cpp
+ )
+
+ target_link_libraries(CruBase PUBLIC Shlwapi.lib)
+endif()
+
+if (WIN32)
+ target_compile_definitions(CruBase PUBLIC CRU_PLATFORM_WINDOWS)
+elseif(APPLE)
+ target_compile_definitions(CruBase PUBLIC CRU_PLATFORM_OSX)
+elseif(EMSCRIPTEN)
+ target_compile_definitions(CruBase PUBLIC CRU_PLATFORM_EMSCRIPTEN)
+else()
+ target_compile_definitions(CruBase PUBLIC CRU_PLATFORM_LINUX)
+endif()
+
+target_link_libraries(CruBase PUBLIC double-conversion)
diff --git a/src/base/Exception.cpp b/src/base/Exception.cpp
new file mode 100644
index 00000000..19938970
--- /dev/null
+++ b/src/base/Exception.cpp
@@ -0,0 +1,36 @@
+#include "cru/base/Exception.h"
+
+#include "cru/base/Format.h"
+
+#include <cerrno>
+
+namespace cru {
+Exception::Exception(String message, std::unique_ptr<std::exception> inner)
+ : message_(std::move(message)), inner_(std::move(inner)) {}
+
+Exception::~Exception() {}
+
+const char* Exception::what() const noexcept {
+ if (!message_.empty() && utf8_message_.empty()) {
+ utf8_message_ = message_.ToUtf8();
+ }
+
+ return utf8_message_.c_str();
+}
+
+void Exception::AppendMessage(StringView additional_message) {
+ message_ += u" ";
+ message_ += additional_message;
+}
+
+void Exception::AppendMessage(std::optional<StringView> additional_message) {
+ if (additional_message) AppendMessage(*additional_message);
+}
+
+ErrnoException::ErrnoException(String message)
+ : ErrnoException(message, errno) {}
+
+ErrnoException::ErrnoException(String message, int errno_code)
+ : Exception(Format(u"{}. Errno is {}.", message, errno_code)),
+ errno_code_(errno_code) {}
+} // namespace cru
diff --git a/src/base/Format.cpp b/src/base/Format.cpp
new file mode 100644
index 00000000..cba4137f
--- /dev/null
+++ b/src/base/Format.cpp
@@ -0,0 +1,111 @@
+#include "cru/base/Format.h"
+
+namespace cru {
+namespace details {
+FormatToken ParsePlaceHolder(String place_holder_string) {
+ if (place_holder_string.empty()) {
+ return FormatToken::NonePlaceHolder({});
+ }
+
+ if (place_holder_string.StartWith(u":")) {
+ if (place_holder_string.Find(u':', 1) != -1) {
+ throw Exception(u"Two ':' inside placeholder.");
+ }
+
+ return FormatToken::NonePlaceHolder(place_holder_string.substr(1));
+ }
+ if (IsDigit(place_holder_string[0])) {
+ int position = 0;
+ int index = 0;
+ while (index < place_holder_string.size() &&
+ IsDigit(place_holder_string[index])) {
+ position = position * 10 + place_holder_string[index] - '0';
+ index++;
+ }
+
+ String option;
+
+ if (index != place_holder_string.size()) {
+ if (place_holder_string[index] != ':') {
+ throw Exception(u"Invalid placeholder in format.");
+ }
+
+ option = place_holder_string.substr(index + 1);
+ }
+
+ return FormatToken::PositionedPlaceHolder(position, std::move(option));
+ }
+
+ auto separator_index = place_holder_string.Find(':');
+ if (separator_index == -1) {
+ return FormatToken::NamedPlaceHolder(place_holder_string, {});
+ } else {
+ return FormatToken::NamedPlaceHolder(
+ place_holder_string.substr(0, separator_index),
+ place_holder_string.substr(separator_index + 1));
+ }
+}
+
+std::vector<FormatToken> ParseToFormatTokenList(StringView str) {
+ std::vector<FormatToken> result;
+
+ auto push_char = [&result](char16_t c) {
+ if (result.empty() || result.back().type == FormatTokenType::PlaceHolder) {
+ result.push_back(FormatToken::Text());
+ }
+ result.back().data.append(c);
+ };
+
+ bool try_to_escape = false;
+ bool is_in_place_holder = false;
+ String place_holder_string;
+
+ for (auto c : str) {
+ if (c == u'{') {
+ if (try_to_escape) {
+ push_char(u'{');
+ try_to_escape = false;
+ is_in_place_holder = false;
+ } else {
+ if (is_in_place_holder) {
+ throw Exception(u"Invalid format string: '{' inside placeholder.");
+ }
+
+ try_to_escape = true;
+ is_in_place_holder = true;
+ }
+ } else if (c == u'}') {
+ if (is_in_place_holder) {
+ is_in_place_holder = false;
+ result.push_back(ParsePlaceHolder(std::move(place_holder_string)));
+ place_holder_string.clear();
+ } else {
+ push_char(u'}');
+ }
+ try_to_escape = false;
+ } else {
+ if (is_in_place_holder) {
+ place_holder_string.push_back(c);
+ } else {
+ push_char(c);
+ }
+ try_to_escape = false;
+ }
+ }
+ return result;
+}
+
+void FormatAppendFromFormatTokenList(
+ String& current, const std::vector<FormatToken>& format_token_list,
+ Index index) {
+ 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) {
+ throw Exception(u"More placeholder than args.");
+ } else {
+ current += token.data;
+ }
+ }
+}
+} // namespace details
+} // namespace cru
diff --git a/src/base/PropertyTree.cpp b/src/base/PropertyTree.cpp
new file mode 100644
index 00000000..8303a706
--- /dev/null
+++ b/src/base/PropertyTree.cpp
@@ -0,0 +1,71 @@
+#include "cru/base/PropertyTree.h"
+#include <unordered_map>
+#include "cru/base/Exception.h"
+
+namespace cru {
+String PropertySubTreeRef::CombineKey(StringView left, StringView right) {
+ return PropertyTree::CombineKey(left, right);
+}
+
+PropertySubTreeRef::PropertySubTreeRef(PropertyTree* tree, String path)
+ : tree_(tree), path_(std::move(path)) {
+ Expects(tree);
+}
+
+PropertySubTreeRef PropertySubTreeRef::GetParent() const {
+ for (Index i = path_.size() - 1; i >= 0; i--) {
+ if (path_[i] == '.') {
+ return PropertySubTreeRef(tree_, path_.substr(0, i));
+ }
+ }
+
+ return PropertySubTreeRef(tree_, {});
+}
+
+PropertySubTreeRef PropertySubTreeRef::GetChild(const String& key) const {
+ return PropertySubTreeRef(tree_, CombineKey(path_, key));
+}
+
+String PropertySubTreeRef::GetValue(const String& key) const {
+ return tree_->GetValue(CombineKey(path_, key));
+}
+
+void PropertySubTreeRef::SetValue(const String& key, String value) {
+ tree_->SetValue(CombineKey(path_, key), std::move(value));
+}
+
+void PropertySubTreeRef::DeleteValue(const String& key) {
+ tree_->DeleteValue(CombineKey(path_, key));
+}
+
+String PropertyTree::CombineKey(StringView left, StringView right) {
+ return String(left) + String(left.empty() ? u"" : u".") + String(right);
+}
+
+PropertyTree::PropertyTree(std::unordered_map<String, String> values)
+ : values_(std::move(values)) {}
+
+String PropertyTree::GetValue(const String& key) const {
+ auto it = values_.find(key);
+ if (it == values_.end()) {
+ throw Exception(u"Property tree has no value.");
+ }
+ return it->second;
+}
+
+void PropertyTree::SetValue(const String& key, String value) {
+ values_[key] = std::move(value);
+}
+
+void PropertyTree::DeleteValue(const String& key) {
+ auto it = values_.find(key);
+ if (it != values_.end()) {
+ values_.erase(it);
+ }
+}
+
+PropertySubTreeRef PropertyTree::GetSubTreeRef(const String& path) {
+ return PropertySubTreeRef(this, path);
+}
+
+} // namespace cru
diff --git a/src/base/String.cpp b/src/base/String.cpp
new file mode 100644
index 00000000..47b64373
--- /dev/null
+++ b/src/base/String.cpp
@@ -0,0 +1,672 @@
+#include "cru/base/String.h"
+
+#include "cru/base/Buffer.h"
+#include "cru/base/Exception.h"
+#include "cru/base/StringToNumberConverter.h"
+#include "cru/base/StringUtil.h"
+
+#include <double-conversion/double-conversion.h>
+#include <double-conversion/string-to-double.h>
+
+#include <algorithm>
+#include <cmath>
+#include <cstring>
+#include <functional>
+#include <string_view>
+
+namespace cru {
+template <typename C>
+Index GetStrSize(const C* str) {
+ Index i = 0;
+ while (str[i]) {
+ i++;
+ }
+ return i;
+}
+
+String String::FromUtf8(const char* str) {
+ return FromUtf8(str, GetStrSize(str));
+}
+
+String String::FromUtf8(const char* str, Index size) {
+ String result;
+ Utf8CodePointIterator iter(str, size);
+ for (auto cp : iter) {
+ Utf16EncodeCodePointAppend(
+ cp,
+ std::bind(&String::push_back, std::ref(result), std::placeholders::_1));
+ }
+ return result;
+}
+
+String String::FromUtf8(const std::byte* str, Index size) {
+ return String::FromUtf8(reinterpret_cast<const char*>(str), size);
+}
+
+String String::FromUtf8(const Buffer& buffer) {
+ return String::FromUtf8(buffer.GetUsedBeginPtr(), buffer.GetUsedSize());
+}
+
+String String::FromStdPath(const std::filesystem::path& path) {
+ return String::FromUtf8(path.string());
+}
+
+char16_t String::kEmptyBuffer[1] = {0};
+
+String::String(const_pointer str) : String(str, GetStrSize(str)) {}
+
+String::String(const_pointer str, Index size) {
+ this->buffer_ = new value_type[size + 1];
+ std::memcpy(this->buffer_, str, size * sizeof(char16_t));
+ this->buffer_[size] = 0;
+ this->size_ = size;
+ this->capacity_ = size;
+}
+
+String::String(size_type size, value_type ch) : String() {
+ reserve(size);
+ for (Index i = 0; i < size; i++) {
+ append(ch);
+ }
+}
+
+String::String(std::initializer_list<char16_t> l)
+ : String(l.begin(), l.size()) {}
+
+#ifdef CRU_PLATFORM_WINDOWS
+String::String(const wchar_t* str) : String(str, GetStrSize(str)) {}
+String::String(const wchar_t* str, Index size)
+ : String(reinterpret_cast<const char16_t*>(str), size) {}
+#endif
+
+String::String(const String& other) {
+ if (other.size_ == 0) return;
+ this->buffer_ = new value_type[other.size_ + 1];
+ std::memcpy(this->buffer_, other.buffer_, other.size_ * sizeof(value_type));
+ this->buffer_[other.size_] = 0;
+ this->size_ = other.size_;
+ this->capacity_ = other.size_;
+}
+
+String::String(String&& other) noexcept {
+ this->buffer_ = other.buffer_;
+ this->size_ = other.size_;
+ this->capacity_ = other.capacity_;
+ other.buffer_ = kEmptyBuffer;
+ other.size_ = 0;
+ other.capacity_ = 0;
+}
+
+String& String::operator=(const String& other) {
+ if (this != &other) {
+ if (this->buffer_ != kEmptyBuffer) {
+ delete[] this->buffer_;
+ }
+
+ if (other.buffer_ == kEmptyBuffer) {
+ this->buffer_ = kEmptyBuffer;
+ this->size_ = 0;
+ this->capacity_ = 0;
+ } else {
+ this->buffer_ = new value_type[other.size_ + 1];
+ std::memcpy(this->buffer_, other.buffer_,
+ other.size_ * sizeof(value_type));
+ this->buffer_[other.size_] = 0;
+ this->size_ = other.size_;
+ this->capacity_ = other.size_;
+ }
+ }
+ return *this;
+}
+
+String& String::operator=(String&& other) noexcept {
+ if (this != &other) {
+ if (this->buffer_ != kEmptyBuffer) {
+ delete[] this->buffer_;
+ }
+
+ this->buffer_ = other.buffer_;
+ this->size_ = other.size_;
+ this->capacity_ = other.capacity_;
+ other.buffer_ = kEmptyBuffer;
+ other.size_ = 0;
+ other.capacity_ = 0;
+ }
+ return *this;
+}
+
+String::~String() {
+ if (this->buffer_ != kEmptyBuffer) {
+ delete[] this->buffer_;
+ }
+}
+
+String::String(from_buffer_tag, pointer buffer, Index size, Index capacity)
+ : buffer_(buffer), size_(size), capacity_(capacity) {}
+
+void String::clear() { resize(0); }
+
+void String::resize(Index new_size) {
+ Expects(new_size >= 0);
+
+ if (new_size == size_) return;
+
+ if (new_size < size_) {
+ size_ = new_size;
+ buffer_[size_] = 0;
+ } else {
+ reserve(new_size);
+ std::memset(buffer_ + size_, 0, sizeof(value_type) * (new_size - size_));
+ buffer_[new_size] = 0;
+ size_ = new_size;
+ }
+}
+
+void String::shrink_to_fit() {
+ if (capacity_ == size_) return;
+ if (size_ == 0) {
+ delete[] buffer_;
+ buffer_ = kEmptyBuffer;
+ size_ = 0;
+ capacity_ = 0;
+ } else {
+ auto new_buffer = new value_type[size_ + 1];
+ std::memcpy(new_buffer, buffer_, sizeof(value_type) * size_);
+ delete[] buffer_;
+ buffer_ = new_buffer;
+ capacity_ = size_;
+ }
+}
+
+void String::reserve(Index new_capacity) {
+ Expects(new_capacity >= 0);
+ if (new_capacity <= this->capacity_) return;
+ if (new_capacity > 0) {
+ pointer new_buffer = new value_type[new_capacity + 1];
+ if (this->buffer_ != kEmptyBuffer) {
+ memcpy(new_buffer, this->buffer_, this->size_ * sizeof(value_type));
+ delete[] this->buffer_;
+ }
+ new_buffer[this->size_] = 0;
+ this->buffer_ = new_buffer;
+ this->capacity_ = new_capacity;
+ }
+}
+
+String::iterator String::insert(const_iterator pos, const_iterator str,
+ Index size) {
+ Expects(pos >= cbegin() && pos <= cend());
+
+ std::vector<value_type> backup_buffer;
+ if (str >= buffer_ && str < buffer_ + size_) {
+ backup_buffer.resize(size);
+ std::copy(str, str + size, backup_buffer.begin());
+ str = backup_buffer.data();
+ }
+
+ Index index = pos - cbegin();
+
+ Index new_size = size_ + size;
+ if (new_size > capacity_) {
+ auto new_capacity = capacity_;
+ if (new_capacity == 0) {
+ new_capacity = new_size;
+ } else {
+ while (new_capacity < new_size) {
+ new_capacity *= 2;
+ }
+ }
+
+ this->reserve(new_capacity);
+ }
+
+ std::memmove(begin() + index + size, begin() + index,
+ (size_ - index) * sizeof(value_type));
+ std::memcpy(begin() + index, str, size * sizeof(value_type));
+
+ buffer_[new_size] = 0;
+ size_ = new_size;
+
+ return begin() + new_size;
+}
+
+String::iterator String::erase(const_iterator start, const_iterator end) {
+ Expects(buffer_ <= start && start <= end && end <= buffer_ + size_);
+
+ Index new_size = size_ - (end - start);
+
+ auto s = const_cast<iterator>(start);
+ auto e = const_cast<iterator>(end);
+
+ std::memmove(s, e, (cend() - end) * sizeof(value_type));
+ this->size_ = new_size;
+ this->buffer_[new_size] = 0;
+
+ return s;
+}
+
+String& String::operator+=(StringView other) {
+ append(other);
+ return *this;
+}
+
+StringView String::View() const { return *this; }
+
+Index String::Find(value_type value, Index start) const {
+ return View().Find(value, start);
+}
+
+std::vector<String> String::Split(value_type separator,
+ bool remove_space_line) const {
+ return View().Split(separator, remove_space_line);
+}
+
+std::vector<String> String::SplitToLines(bool remove_space_line) const {
+ return View().SplitToLines(remove_space_line);
+}
+
+bool String::StartWith(StringView str) const { return View().StartWith(str); }
+
+bool String::EndWith(StringView str) const { return View().EndWith(str); }
+
+std::string String::ToUtf8() const { return View().ToUtf8(); }
+
+Buffer String::ToUtf8Buffer(bool end_zero) const {
+ return View().ToUtf8Buffer();
+}
+
+String& String::TrimStart() {
+ if (size_ == 0) return *this;
+
+ auto start = begin();
+ while (start != end() && IsWhitespace(*start)) {
+ ++start;
+ }
+
+ if (start == end()) {
+ clear();
+ } else {
+ erase(begin(), start);
+ }
+
+ return *this;
+}
+
+String& String::TrimEnd() {
+ if (size_ == 0) return *this;
+ while (size_ > 0 && IsWhitespace(buffer_[size_ - 1])) {
+ size_--;
+ }
+
+ return *this;
+}
+
+String& String::Trim() {
+ TrimStart();
+ TrimEnd();
+ return *this;
+}
+
+void String::AppendCodePoint(CodePoint code_point) {
+ if (!Utf16EncodeCodePointAppend(
+ code_point,
+ std::bind(&String::push_back, this, std::placeholders::_1))) {
+ throw TextEncodeException(u"Code point out of range.");
+ }
+}
+
+Index String::IndexFromCodeUnitToCodePoint(Index code_unit_index) const {
+ return View().IndexFromCodeUnitToCodePoint(code_unit_index);
+}
+
+Index String::IndexFromCodePointToCodeUnit(Index code_point_index) const {
+ return View().IndexFromCodePointToCodeUnit(code_point_index);
+}
+
+Range String::RangeFromCodeUnitToCodePoint(Range code_unit_range) const {
+ return View().RangeFromCodeUnitToCodePoint(code_unit_range);
+}
+
+Range String::RangeFromCodePointToCodeUnit(Range code_point_range) const {
+ return View().RangeFromCodePointToCodeUnit(code_point_range);
+}
+
+int String::ParseToInt(Index* processed_characters_count,
+ StringToNumberFlag flags, int base) const {
+ return View().ParseToInt(processed_characters_count, flags, base);
+}
+
+long long String::ParseToLongLong(Index* processed_characters_count,
+ StringToNumberFlag flags, int base) const {
+ return View().ParseToLongLong(processed_characters_count, flags, base);
+}
+
+float String::ParseToFloat(Index* processed_characters_count,
+ StringToNumberFlag flags) const {
+ return View().ParseToFloat(processed_characters_count, flags);
+}
+
+double String::ParseToDouble(Index* processed_characters_count,
+ StringToNumberFlag flags) const {
+ return View().ParseToDouble(processed_characters_count, flags);
+}
+
+std::vector<float> String::ParseToFloatList(value_type separator) const {
+ return View().ParseToFloatList(separator);
+}
+
+std::vector<double> String::ParseToDoubleList(value_type separator) const {
+ return View().ParseToDoubleList(separator);
+}
+
+std::ostream& operator<<(std::ostream& os, const String& value) {
+ os << value.ToUtf8();
+ return os;
+}
+
+namespace {
+inline int Compare(char16_t left, char16_t right) {
+ if (left < right) return -1;
+ if (left > right) return 1;
+ return 0;
+}
+
+inline int CaseInsensitiveCompare(char16_t left, char16_t right) {
+ return Compare(ToLower(left), ToLower(right));
+}
+} // namespace
+
+int String::Compare(const String& other) const { return View().Compare(other); }
+int String::CaseInsensitiveCompare(const String& other) const {
+ return View().CaseInsensitiveCompare(other);
+}
+
+int StringView::Compare(const StringView& other) const {
+ const_iterator i1 = cbegin();
+ const_iterator i2 = other.cbegin();
+
+ const_iterator end1 = cend();
+ const_iterator end2 = other.cend();
+
+ while (i1 != end1 && i2 != end2) {
+ int r = cru::Compare(*i1, *i2);
+ if (r != 0) return r;
+ i1++;
+ i2++;
+ }
+
+ if (i1 == end1) {
+ if (i2 == end2) {
+ return 0;
+ } else {
+ return -1;
+ }
+ } else {
+ return 1;
+ }
+}
+
+int StringView::CaseInsensitiveCompare(const StringView& other) const {
+ const_iterator i1 = cbegin();
+ const_iterator i2 = other.cbegin();
+
+ const_iterator end1 = cend();
+ const_iterator end2 = other.cend();
+
+ while (i1 != end1 && i2 != end2) {
+ int r = cru::CaseInsensitiveCompare(*i1, *i2);
+ if (r != 0) return r;
+ i1++;
+ i2++;
+ }
+
+ if (i1 == end1) {
+ if (i2 == end2) {
+ return 0;
+ } else {
+ return -1;
+ }
+ } else {
+ return 1;
+ }
+}
+
+StringView StringView::substr(Index pos) {
+ Expects(pos >= 0 && pos < size_);
+ return StringView(ptr_ + pos, size_ - pos);
+}
+
+StringView StringView::substr(Index pos, Index size) {
+ Expects(pos >= 0 && pos < size_);
+
+ return StringView(ptr_ + pos, std::min(size, size_ - pos));
+}
+
+Index StringView::Find(value_type value, Index start) const {
+ Expects(start >= 0 && start <= size_);
+
+ for (Index i = start; i < size_; ++i) {
+ if (ptr_[i] == value) return i;
+ }
+ return -1;
+}
+
+std::vector<String> StringView::Split(value_type separator,
+ bool remove_space_line) const {
+ std::vector<String> result;
+
+ if (size_ == 0) return result;
+
+ Index line_start = 0;
+ Index line_end = 0;
+ while (line_end < size_) {
+ if (ptr_[line_end] == separator) {
+ if (remove_space_line) {
+ bool add = false;
+ for (Index i = line_start; i < line_end; i++) {
+ if (!IsWhitespace(ptr_[i])) {
+ add = true;
+ break;
+ }
+ }
+ if (add) result.emplace_back(begin() + line_start, begin() + line_end);
+ } else {
+ result.emplace_back(begin() + line_start, begin() + line_end);
+ }
+ line_start = line_end + 1;
+ line_end = line_start;
+ } else {
+ line_end++;
+ }
+ }
+
+ if (remove_space_line) {
+ bool add = false;
+ for (Index i = line_start; i < size_; i++) {
+ if (!IsWhitespace(ptr_[i])) {
+ add = true;
+ break;
+ }
+ }
+ if (add) result.emplace_back(begin() + line_start, begin() + size_);
+ } else {
+ result.emplace_back(begin() + line_start, begin() + size_);
+ }
+
+ return result;
+}
+
+std::vector<String> StringView::SplitToLines(bool remove_space_line) const {
+ return Split(u'\n', remove_space_line);
+}
+
+bool StringView::StartWith(StringView str) const {
+ if (str.size() > size_) return false;
+ return std::memcmp(str.data(), ptr_, str.size()) == 0;
+}
+
+bool StringView::EndWith(StringView str) const {
+ if (str.size() > size_) return false;
+ return std::memcmp(str.data(), ptr_ + size_ - str.size(), str.size()) == 0;
+}
+
+Index StringView::IndexFromCodeUnitToCodePoint(Index code_unit_index) const {
+ auto iter = CodePointIterator();
+ Index result = 0;
+ while (iter.GetPosition() < code_unit_index && !iter.IsPastEnd()) {
+ ++iter;
+ ++result;
+ }
+ return result;
+}
+
+Index StringView::IndexFromCodePointToCodeUnit(Index code_point_index) const {
+ auto iter = CodePointIterator();
+ Index cpi = 0;
+ while (cpi < code_point_index && !iter.IsPastEnd()) {
+ ++iter;
+ ++cpi;
+ }
+ return iter.GetPosition();
+}
+
+Range StringView::RangeFromCodeUnitToCodePoint(Range code_unit_range) const {
+ return Range::FromTwoSides(
+ IndexFromCodeUnitToCodePoint(code_unit_range.GetStart()),
+ IndexFromCodeUnitToCodePoint(code_unit_range.GetEnd()));
+}
+
+Range StringView::RangeFromCodePointToCodeUnit(Range code_point_range) const {
+ return Range::FromTwoSides(
+ IndexFromCodePointToCodeUnit(code_point_range.GetStart()),
+ IndexFromCodePointToCodeUnit(code_point_range.GetEnd()));
+}
+
+std::string StringView::ToUtf8() const {
+ std::string result;
+ for (auto cp : CodePointIterator()) {
+ Utf8EncodeCodePointAppend(
+ cp, std::bind(&std::string::push_back, std::ref(result),
+ std::placeholders::_1));
+ }
+ return result;
+}
+
+Buffer StringView::ToUtf8Buffer(bool end_zero) const {
+ const Index grow_step = 10;
+ Buffer buffer(grow_step); // Maybe another init value is more reasonable.
+ auto push_back = [&buffer](char c) {
+ if (buffer.IsUsedReachEnd()) {
+ buffer.ResizeBuffer(buffer.GetBufferSize() + grow_step, true);
+ }
+ buffer.PushBack(static_cast<std::byte>(c));
+ };
+ for (auto cp : CodePointIterator()) {
+ Utf8EncodeCodePointAppend(cp, push_back);
+ }
+ if (end_zero) {
+ push_back(0);
+ }
+ return buffer;
+}
+
+int StringView::ParseToInt(Index* processed_characters_count,
+ StringToNumberFlag flags, int base) const {
+ return ParseToInteger<int>(processed_characters_count, flags, base);
+}
+
+long long StringView::ParseToLongLong(Index* processed_characters_count,
+ StringToNumberFlag flags,
+ int base) const {
+ return ParseToInteger<long long>(processed_characters_count, flags, base);
+}
+
+static int MapStringToDoubleFlags(StringToNumberFlag flags) {
+ int f = double_conversion::StringToDoubleConverter::ALLOW_CASE_INSENSIBILITY;
+ if (flags & StringToNumberFlags::kAllowLeadingSpaces) {
+ f |= double_conversion::StringToDoubleConverter::ALLOW_LEADING_SPACES;
+ }
+ if (flags & StringToNumberFlags::kAllowTrailingSpaces) {
+ f |= double_conversion::StringToDoubleConverter::ALLOW_TRAILING_SPACES;
+ }
+ if (flags & StringToNumberFlags::kAllowTrailingJunk) {
+ f |= double_conversion::StringToDoubleConverter::ALLOW_TRAILING_JUNK;
+ }
+ return f;
+}
+
+static double_conversion::StringToDoubleConverter CreateStringToDoubleConverter(
+ StringToNumberFlag flags) {
+ return {MapStringToDoubleFlags(flags), 0.0, NAN, "inf", "nan"};
+}
+
+float StringView::ParseToFloat(Index* processed_characters_count,
+ StringToNumberFlag flags) const {
+ int pcc;
+ auto result = CreateStringToDoubleConverter(flags).StringToFloat(
+ reinterpret_cast<const uc16*>(ptr_), static_cast<int>(size_), &pcc);
+ if (processed_characters_count != nullptr) {
+ *processed_characters_count = pcc;
+ }
+
+ if (flags & StringToNumberFlags::kThrowOnError && std::isnan(result)) {
+ throw Exception(u"Result of string to float conversion is NaN");
+ }
+
+ return result;
+}
+
+double StringView::ParseToDouble(Index* processed_characters_count,
+ StringToNumberFlag flags) const {
+ int pcc;
+ auto result = CreateStringToDoubleConverter(flags).StringToDouble(
+ reinterpret_cast<const uc16*>(ptr_), static_cast<int>(size_), &pcc);
+ if (processed_characters_count != nullptr) {
+ *processed_characters_count = pcc;
+ }
+
+ if (flags & StringToNumberFlags::kThrowOnError && std::isnan(result)) {
+ throw Exception(u"Result of string to double conversion is NaN");
+ }
+
+ return result;
+}
+
+std::vector<float> StringView::ParseToFloatList(value_type separator) const {
+ std::vector<float> result;
+ auto list = Split(separator, true);
+ for (auto& item : list) {
+ auto value = item.ParseToFloat();
+ if (std::isnan(value)) {
+ throw Exception(u"Invalid double value.");
+ }
+ result.push_back(value);
+ }
+ return result;
+}
+
+std::vector<double> StringView::ParseToDoubleList(value_type separator) const {
+ std::vector<double> result;
+ auto list = Split(separator, true);
+ for (auto& item : list) {
+ auto value = item.ParseToDouble();
+ if (std::isnan(value)) {
+ throw Exception(u"Invalid double value.");
+ }
+ result.push_back(value);
+ }
+ return result;
+}
+
+String ToLower(StringView s) {
+ String result;
+ for (auto c : s) result.push_back(ToLower(c));
+ return result;
+}
+
+String ToUpper(StringView s) {
+ String result;
+ for (auto c : s) result.push_back(ToUpper(c));
+ return result;
+}
+} // namespace cru
diff --git a/src/base/StringToNumberConverter.cpp b/src/base/StringToNumberConverter.cpp
new file mode 100644
index 00000000..65aec48e
--- /dev/null
+++ b/src/base/StringToNumberConverter.cpp
@@ -0,0 +1,170 @@
+#include "cru/base/StringToNumberConverter.h"
+#include "cru/base/Exception.h"
+
+namespace cru {
+bool StringToIntegerConverter::CheckParams() const {
+ return base == 0 || base >= 2 & base <= 36;
+}
+
+static bool IsSpace(char c) {
+ return c == ' ' || c == '\t' || c == '\n' || c == '\r';
+}
+
+StringToIntegerResult StringToIntegerConverter::Parse(
+ const char* const str, const Index size,
+ Index* processed_characters_count) const {
+ if (str == nullptr) throw std::invalid_argument("Invalid str.");
+ if (size < 0) throw std::invalid_argument("Invalid size.");
+ if (!CheckParams()) throw std::invalid_argument("Invalid parsing flags.");
+
+ const bool throw_on_error = flags.Has(StringToNumberFlags::kThrowOnError);
+
+ auto const end = str + size;
+
+ auto current = str;
+
+ if (flags & StringToNumberFlags::kAllowLeadingSpaces) {
+ while (current != end && IsSpace(*current)) {
+ current++;
+ }
+ }
+
+ if (current == end) {
+ if (processed_characters_count) {
+ *processed_characters_count = 0;
+ }
+ if (throw_on_error) {
+ throw Exception(u"Empty string (after reading leading spaces).");
+ } else {
+ return {false, 0};
+ }
+ }
+
+ bool negate = false;
+
+ if (*current == '-') {
+ ++current;
+ negate = true;
+ } else if (*current == '+') {
+ ++current;
+ }
+
+ if (current == end) {
+ if (processed_characters_count) {
+ *processed_characters_count = 0;
+ }
+ if (throw_on_error) {
+ throw Exception(u"Empty string (after reading sign).");
+ } else {
+ return {false, 0};
+ }
+ }
+
+ int real_base = base;
+
+ if (real_base == 0) {
+ if (*current == '0') {
+ ++current;
+ if (current == end) {
+ if (processed_characters_count) {
+ *processed_characters_count = current - str;
+ }
+ return {negate, 0};
+ } else if (*current == 'x' || *current == 'X') {
+ ++current;
+ real_base = 16;
+ } else if (*current == 'b' || *current == 'B') {
+ ++current;
+ real_base = 2;
+ } else {
+ real_base = 8;
+ }
+ } else {
+ real_base = 10;
+ }
+ }
+
+ if (current == end) {
+ if (processed_characters_count) {
+ *processed_characters_count = 0;
+ }
+ if (throw_on_error) {
+ throw Exception(u"Empty string (after reading head base indicator).");
+ } else {
+ return {false, 0};
+ }
+ }
+
+ const bool allow_leading_zero =
+ flags.Has(StringToNumberFlags::kAllowLeadingZeroForInteger);
+
+ while (current != end && *current == '0') {
+ current++;
+ }
+
+ if (current == end) {
+ if (processed_characters_count) {
+ *processed_characters_count = current - str;
+ }
+ return {negate, 0};
+ }
+
+ const bool allow_trailing_junk =
+ flags.Has(StringToNumberFlags::kAllowTrailingJunk);
+ const bool allow_trailing_spaces =
+ flags.Has(StringToNumberFlags::kAllowTrailingSpaces);
+
+ unsigned long long result = 0;
+
+ while (current != end) {
+ const char c = *current;
+ if (c >= '0' && c <= (real_base > 10 ? '9' : real_base + '0' - 1)) {
+ result = result * real_base + c - '0';
+ current++;
+ } else if (real_base > 10 && c >= 'a' && c <= (real_base + 'a' - 10 - 1)) {
+ result = result * real_base + c - 'a' + 10;
+ current++;
+ } else if (real_base > 10 && c >= 'A' && c <= (real_base + 'A' - 10 - 1)) {
+ result = result * real_base + c - 'A' + 10;
+ current++;
+ } else if (allow_trailing_junk) {
+ break;
+ } else if (allow_trailing_spaces && IsSpace(c)) {
+ break;
+ } else {
+ if (processed_characters_count) {
+ *processed_characters_count = 0;
+ }
+ if (throw_on_error) {
+ throw Exception(String(u"Read invalid character '") + c + u"'.");
+ } else {
+ return {false, 0};
+ }
+ }
+ }
+
+ if (allow_trailing_spaces) {
+ while (current != end && IsSpace(*current)) {
+ current++;
+ }
+
+ if (current != end) {
+ if (processed_characters_count) {
+ *processed_characters_count = 0;
+ }
+ if (throw_on_error) {
+ throw Exception(u"There is trailing junk.");
+ } else {
+ return {false, 0};
+ }
+ }
+ }
+
+ if (processed_characters_count) {
+ *processed_characters_count = current - str;
+ }
+
+ return {negate, result};
+}
+
+} // namespace cru
diff --git a/src/base/StringUtil.cpp b/src/base/StringUtil.cpp
new file mode 100644
index 00000000..9053f384
--- /dev/null
+++ b/src/base/StringUtil.cpp
@@ -0,0 +1,243 @@
+#include "cru/base/StringUtil.h"
+#include "cru/base/Base.h"
+#include "cru/base/Exception.h"
+
+namespace cru {
+using details::ExtractBits;
+
+CodePoint Utf8NextCodePoint(const char* ptr, Index size, Index current,
+ Index* next_position) {
+ CodePoint result;
+
+ if (current >= size) {
+ result = k_invalid_code_point;
+ } else {
+ const auto cu0 = static_cast<std::uint8_t>(ptr[current++]);
+
+ auto read_next_folowing_code = [ptr, size, &current]() -> CodePoint {
+ if (current == size)
+ throw TextEncodeException(
+ u"Unexpected end when read continuing byte of multi-byte code "
+ "point.");
+
+ const auto u = static_cast<std::uint8_t>(ptr[current]);
+ if (!(u & (1u << 7)) || (u & (1u << 6))) {
+ throw TextEncodeException(
+ u"Unexpected bad-format (not 0b10xxxxxx) continuing byte of "
+ "multi-byte code point.");
+ }
+
+ return ExtractBits<std::uint8_t, 6, CodePoint>(ptr[current++]);
+ };
+
+ if ((1u << 7) & cu0) {
+ if ((1u << 6) & cu0) { // 2~4-length code point
+ if ((1u << 5) & cu0) { // 3~4-length code point
+ if ((1u << 4) & cu0) { // 4-length code point
+ if (cu0 & (1u << 3)) {
+ throw TextEncodeException(
+ u"Unexpected bad-format begin byte (not 0b11110xxx) of 4-byte"
+ "code point.");
+ }
+
+ const CodePoint s0 = ExtractBits<std::uint8_t, 3, CodePoint>(cu0)
+ << (6 * 3);
+ const CodePoint s1 = read_next_folowing_code() << (6 * 2);
+ const CodePoint s2 = read_next_folowing_code() << 6;
+ const CodePoint s3 = read_next_folowing_code();
+ result = s0 + s1 + s2 + s3;
+ } else { // 3-length code point
+ const CodePoint s0 = ExtractBits<std::uint8_t, 4, CodePoint>(cu0)
+ << (6 * 2);
+ const CodePoint s1 = read_next_folowing_code() << 6;
+ const CodePoint s2 = read_next_folowing_code();
+ result = s0 + s1 + s2;
+ }
+ } else { // 2-length code point
+ const CodePoint s0 = ExtractBits<std::uint8_t, 5, CodePoint>(cu0)
+ << 6;
+ const CodePoint s1 = read_next_folowing_code();
+ result = s0 + s1;
+ }
+ } else {
+ throw TextEncodeException(
+ u"Unexpected bad-format (0b10xxxxxx) begin byte of a code point.");
+ }
+ } else {
+ result = static_cast<CodePoint>(cu0);
+ }
+ }
+
+ if (next_position != nullptr) *next_position = current;
+ return result;
+}
+
+CodePoint Utf16NextCodePoint(const char16_t* ptr, Index size, Index current,
+ Index* next_position) {
+ CodePoint result;
+
+ if (current >= size) {
+ result = k_invalid_code_point;
+ } else {
+ const auto cu0 = ptr[current++];
+
+ if (!IsUtf16SurrogatePairCodeUnit(cu0)) { // 1-length code point
+ result = static_cast<CodePoint>(cu0);
+ } else if (IsUtf16SurrogatePairLeading(cu0)) { // 2-length code point
+ if (current >= size) {
+ throw TextEncodeException(
+ u"Unexpected end when reading second code unit of surrogate pair.");
+ }
+ const auto cu1 = ptr[current++];
+
+ if (!IsUtf16SurrogatePairTrailing(cu1)) {
+ throw TextEncodeException(
+ u"Unexpected bad-range second code unit of surrogate pair.");
+ }
+
+ const auto s0 = ExtractBits<std::uint16_t, 10, CodePoint>(cu0) << 10;
+ const auto s1 = ExtractBits<std::uint16_t, 10, CodePoint>(cu1);
+
+ result = s0 + s1 + 0x10000;
+
+ } else {
+ throw TextEncodeException(
+ u"Unexpected bad-range first code unit of surrogate pair.");
+ }
+ }
+
+ if (next_position != nullptr) *next_position = current;
+ return result;
+}
+
+CodePoint Utf16PreviousCodePoint(const char16_t* ptr, Index size, Index current,
+ Index* previous_position) {
+ CRU_UNUSED(size)
+
+ CodePoint result;
+ if (current <= 0) {
+ result = k_invalid_code_point;
+ } else {
+ const auto cu0 = ptr[--current];
+
+ if (!IsUtf16SurrogatePairCodeUnit(cu0)) { // 1-length code point
+ result = static_cast<CodePoint>(cu0);
+ } else if (IsUtf16SurrogatePairTrailing(cu0)) { // 2-length code point
+ if (current <= 0) {
+ throw TextEncodeException(
+ u"Unexpected end when reading first code unit of surrogate pair.");
+ }
+ const auto cu1 = ptr[--current];
+
+ if (!IsUtf16SurrogatePairLeading(cu1)) {
+ throw TextEncodeException(
+ u"Unexpected bad-range first code unit of surrogate pair.");
+ }
+
+ const auto s0 = ExtractBits<std::uint16_t, 10, CodePoint>(cu1) << 10;
+ const auto s1 = ExtractBits<std::uint16_t, 10, CodePoint>(cu0);
+
+ result = s0 + s1 + 0x10000;
+
+ } else {
+ throw TextEncodeException(
+ u"Unexpected bad-range second code unit of surrogate pair.");
+ }
+ }
+
+ if (previous_position != nullptr) *previous_position = current;
+ return result;
+}
+
+bool Utf16IsValidInsertPosition(const char16_t* ptr, Index size,
+ Index position) {
+ if (position < 0) return false;
+ if (position > size) return false;
+ if (position == 0) return true;
+ if (position == size) return true;
+ return !IsUtf16SurrogatePairTrailing(ptr[position]);
+}
+
+Index Utf16BackwardUntil(const char16_t* ptr, Index size, Index position,
+ const std::function<bool(CodePoint)>& predicate) {
+ if (position <= 0) return position;
+ while (true) {
+ Index p = position;
+ auto c = Utf16PreviousCodePoint(ptr, size, p, &position);
+ if (predicate(c)) return p;
+ if (c == k_invalid_code_point) return p;
+ }
+ UnreachableCode();
+}
+
+Index Utf16ForwardUntil(const char16_t* ptr, Index size, Index position,
+ const std::function<bool(CodePoint)>& predicate) {
+ if (position >= size) return position;
+ while (true) {
+ Index p = position;
+ auto c = Utf16NextCodePoint(ptr, size, p, &position);
+ if (predicate(c)) return p;
+ if (c == k_invalid_code_point) return p;
+ }
+ UnreachableCode();
+}
+
+inline bool IsSpace(CodePoint c) { return c == 0x20 || c == 0xA; }
+
+Index Utf16PreviousWord(const char16_t* ptr, Index size, Index position,
+ bool* is_space) {
+ if (position <= 0) return position;
+ auto c = Utf16PreviousCodePoint(ptr, size, position, nullptr);
+ if (IsSpace(c)) { // TODO: Currently only test against 0x20(space).
+ if (is_space) *is_space = true;
+ return Utf16BackwardUntil(ptr, size, position,
+ [](CodePoint c) { return !IsSpace(c); });
+ } else {
+ if (is_space) *is_space = false;
+ return Utf16BackwardUntil(ptr, size, position, IsSpace);
+ }
+}
+
+Index Utf16NextWord(const char16_t* ptr, Index size, Index position,
+ bool* is_space) {
+ if (position >= size) return position;
+ auto c = Utf16NextCodePoint(ptr, size, position, nullptr);
+ if (IsSpace(c)) { // TODO: Currently only test against 0x20(space).
+ if (is_space) *is_space = true;
+ return Utf16ForwardUntil(ptr, size, position,
+ [](CodePoint c) { return !IsSpace(c); });
+ } else {
+ if (is_space) *is_space = false;
+ return Utf16ForwardUntil(ptr, size, position, IsSpace);
+ }
+}
+
+char16_t ToLower(char16_t c) {
+ if (c >= u'A' && c <= u'Z') {
+ return c - u'A' + u'a';
+ }
+ return c;
+}
+
+char16_t ToUpper(char16_t c) {
+ if (c >= u'a' && c <= u'z') {
+ return c - u'a' + u'A';
+ }
+ return c;
+}
+
+bool IsWhitespace(char16_t c) {
+ return c == u' ' || c == u'\t' || c == u'\n' || c == u'\r';
+}
+
+bool IsDigit(char16_t c) { return c >= u'0' && c <= u'9'; }
+
+Utf8CodePointIterator CreateUtf8Iterator(const std::byte* buffer, Index size) {
+ return Utf8CodePointIterator(reinterpret_cast<const char*>(buffer), size);
+}
+
+Utf8CodePointIterator CreateUtf8Iterator(const std::vector<std::byte>& buffer) {
+ return CreateUtf8Iterator(buffer.data(), buffer.size());
+}
+
+} // namespace cru
diff --git a/src/base/SubProcess.cpp b/src/base/SubProcess.cpp
new file mode 100644
index 00000000..1133b848
--- /dev/null
+++ b/src/base/SubProcess.cpp
@@ -0,0 +1,209 @@
+#include "cru/base/SubProcess.h"
+
+#include <thread>
+
+#ifdef CRU_PLATFORM_UNIX
+#include "cru/base/platform/unix/PosixSpawnSubProcess.h"
+#endif
+
+namespace cru {
+
+#ifdef CRU_PLATFORM_UNIX
+using ThisPlatformSubProcessImpl = platform::unix::PosixSpawnSubProcessImpl;
+#endif
+
+PlatformSubProcess::PlatformSubProcess(
+ SubProcessStartInfo start_info,
+ std::shared_ptr<IPlatformSubProcessImpl> impl)
+ : state_(new State(std::move(start_info), std::move(impl))),
+ lock_(state_->mutex, std::defer_lock) {}
+
+PlatformSubProcess::~PlatformSubProcess() {}
+
+void PlatformSubProcess::Start() {
+ std::lock_guard lock_guard(this->lock_);
+
+ if (this->state_->status != SubProcessStatus::Prepare) {
+ throw SubProcessException(u"The process has already tried to start.");
+ }
+
+ try {
+ this->state_->impl->PlatformCreateProcess(this->state_->start_info);
+ this->state_->status = SubProcessStatus::Running;
+
+ auto thread = std::thread([state = state_] {
+ std::unique_lock lock(state->mutex);
+ state->exit_result = state->impl->PlatformWaitForProcess();
+ state->status = SubProcessStatus::Exited;
+ state->condition_variable.notify_all();
+ });
+
+ thread.detach();
+ } catch (const std::exception& e) {
+ this->state_->status = SubProcessStatus::FailedToStart;
+ throw SubProcessFailedToStartException(u"Sub-process failed to start. " +
+ String::FromUtf8(e.what()));
+ }
+}
+
+void PlatformSubProcess::Wait(
+ std::optional<std::chrono::milliseconds> wait_time) {
+ std::lock_guard lock_guard(this->lock_);
+
+ if (this->state_->status == SubProcessStatus::Prepare) {
+ throw SubProcessException(
+ u"The process does not start. Can't wait for it.");
+ }
+
+ if (this->state_->status == SubProcessStatus::FailedToStart) {
+ throw SubProcessException(
+ u"The process failed to start. Can't wait for it.");
+ }
+
+ if (this->state_->status == SubProcessStatus::Exited) {
+ return;
+ }
+
+ auto predicate = [this] {
+ return this->state_->status == SubProcessStatus::Exited;
+ };
+
+ if (wait_time) {
+ this->state_->condition_variable.wait_for(this->lock_, *wait_time,
+ predicate);
+ } else {
+ this->state_->condition_variable.wait(this->lock_, predicate);
+ }
+}
+
+void PlatformSubProcess::Kill() {
+ std::lock_guard lock_guard(this->lock_);
+
+ if (this->state_->status == SubProcessStatus::Prepare) {
+ throw SubProcessException(u"The process does not start. Can't kill it.");
+ }
+
+ if (this->state_->status == SubProcessStatus::FailedToStart) {
+ throw SubProcessException(u"The process failed to start. Can't kill it.");
+ }
+
+ if (this->state_->status == SubProcessStatus::Exited) {
+ return;
+ }
+
+ if (this->state_->killed) {
+ return;
+ }
+
+ this->state_->impl->PlatformKillProcess();
+ this->state_->killed = true;
+}
+
+SubProcessStatus PlatformSubProcess::GetStatus() {
+ std::lock_guard lock_guard(this->lock_);
+ return this->state_->status;
+}
+
+SubProcessExitResult PlatformSubProcess::GetExitResult() {
+ std::lock_guard lock_guard(this->lock_);
+
+ if (this->state_->status == SubProcessStatus::Prepare) {
+ throw SubProcessException(
+ u"The process does not start. Can't get exit result.");
+ }
+
+ if (this->state_->status == SubProcessStatus::FailedToStart) {
+ throw SubProcessException(
+ u"The process failed to start. Can't get exit result.");
+ }
+
+ if (this->state_->status == SubProcessStatus::Running) {
+ throw SubProcessException(
+ u"The process is running. Can't get exit result.");
+ }
+
+ return this->state_->exit_result;
+}
+
+io::Stream* PlatformSubProcess::GetStdinStream() {
+ return this->state_->impl->GetStdinStream();
+}
+
+io::Stream* PlatformSubProcess::GetStdoutStream() {
+ return this->state_->impl->GetStdoutStream();
+}
+
+io::Stream* PlatformSubProcess::GetStderrStream() {
+ return this->state_->impl->GetStderrStream();
+}
+
+SubProcess SubProcess::Create(String program, std::vector<String> arguments,
+ std::unordered_map<String, String> environments) {
+ SubProcessStartInfo start_info;
+ start_info.program = std::move(program);
+ start_info.arguments = std::move(arguments);
+ start_info.environments = std::move(environments);
+ return SubProcess(std::move(start_info));
+}
+
+SubProcessExitResult SubProcess::Call(
+ String program, std::vector<String> arguments,
+ std::unordered_map<String, String> environments) {
+ auto process =
+ Create(std::move(program), std::move(arguments), std::move(environments));
+ process.Wait();
+ return process.GetExitResult();
+}
+
+SubProcess::SubProcess(SubProcessStartInfo start_info) {
+ platform_process_.reset(new PlatformSubProcess(
+ std::move(start_info), std::make_shared<ThisPlatformSubProcessImpl>()));
+ platform_process_->Start();
+}
+
+SubProcess::~SubProcess() {}
+
+void SubProcess::Wait(std::optional<std::chrono::milliseconds> wait_time) {
+ CheckValid();
+ platform_process_->Wait(wait_time);
+}
+
+void SubProcess::Kill() {
+ CheckValid();
+ platform_process_->Kill();
+}
+
+SubProcessStatus SubProcess::GetStatus() {
+ CheckValid();
+ return platform_process_->GetStatus();
+}
+
+SubProcessExitResult SubProcess::GetExitResult() {
+ CheckValid();
+ return platform_process_->GetExitResult();
+}
+
+io::Stream* SubProcess::GetStdinStream() {
+ CheckValid();
+ return platform_process_->GetStdinStream();
+}
+
+io::Stream* SubProcess::GetStdoutStream() {
+ CheckValid();
+ return platform_process_->GetStdoutStream();
+}
+
+io::Stream* SubProcess::GetStderrStream() {
+ CheckValid();
+ return platform_process_->GetStderrStream();
+}
+
+void SubProcess::Detach() { auto p = platform_process_.release(); }
+
+void SubProcess::CheckValid() const {
+ if (!IsValid()) {
+ throw SubProcessException(u"SubProcess instance is invalid.");
+ }
+}
+
+} // namespace cru
diff --git a/src/base/io/AutoReadStream.cpp b/src/base/io/AutoReadStream.cpp
new file mode 100644
index 00000000..c24f61d1
--- /dev/null
+++ b/src/base/io/AutoReadStream.cpp
@@ -0,0 +1,56 @@
+#include "cru/base/io/AutoReadStream.h"
+#include "cru/base/io/Stream.h"
+
+#include <thread>
+
+namespace cru::io {
+
+AutoReadStream::AutoReadStream(Stream* stream, bool auto_delete,
+ const AutoReadStreamOptions& options)
+ : Stream(false, true, stream->CanSeek()) {
+ auto buffer_stream_options = options.GetBufferStreamOptions();
+ stream_ = stream;
+ size_per_read_ = buffer_stream_options.GetBlockSizeOrDefault();
+ buffer_stream_ = std::make_unique<BufferStream>(buffer_stream_options);
+ background_thread_ = std::thread(&AutoReadStream::BackgroundThreadRun, this);
+}
+
+AutoReadStream::~AutoReadStream() {
+ if (auto_delete_) {
+ delete stream_;
+ }
+}
+
+Index AutoReadStream::DoRead(std::byte* buffer, Index offset, Index size) {
+ std::unique_lock lock(buffer_stream_mutex_);
+ return buffer_stream_->Read(buffer, offset, size);
+}
+
+Index AutoReadStream::DoWrite(const std::byte* buffer, Index offset,
+ Index size) {
+ return stream_->Write(buffer, offset, size);
+}
+
+void AutoReadStream::DoFlush() { stream_->Flush(); }
+
+void AutoReadStream::DoClose() {}
+
+void AutoReadStream::BackgroundThreadRun() {
+ std::vector<std::byte> buffer(size_per_read_);
+ while (true) {
+ try {
+ auto read = stream_->Read(buffer.data(), buffer.size());
+ if (read == 0) {
+ buffer_stream_->SetEof();
+ break;
+ } else {
+ buffer_stream_->Write(buffer.data(), read);
+ }
+ } catch (const StreamAlreadyClosedException& exception) {
+ buffer_stream_->SetEof();
+ break;
+ }
+ }
+}
+
+} // namespace cru::io
diff --git a/src/base/io/BufferStream.cpp b/src/base/io/BufferStream.cpp
new file mode 100644
index 00000000..e81731e8
--- /dev/null
+++ b/src/base/io/BufferStream.cpp
@@ -0,0 +1,109 @@
+#include "cru/base/io/BufferStream.h"
+#include "cru/base/io/Stream.h"
+
+namespace cru::io {
+BufferStream::BufferStream(const BufferStreamOptions& options)
+ : Stream(false, true, true) {
+ block_size_ = options.GetBlockSizeOrDefault();
+ max_block_count_ = options.GetMaxBlockCount();
+
+ eof_ = false;
+}
+
+BufferStream::~BufferStream() { DoClose(); }
+
+Index BufferStream::DoRead(std::byte* buffer, Index offset, Index size) {
+ std::unique_lock lock(mutex_);
+
+ condition_variable_.wait(lock,
+ [this] { return !buffer_list_.empty() || eof_; });
+
+ if (buffer_list_.empty() && eof_) {
+ return 0;
+ }
+
+ auto full = max_block_count_ > 0 && buffer_list_.size() == max_block_count_;
+
+ Index read = 0;
+
+ while (!buffer_list_.empty()) {
+ auto& stream_buffer = buffer_list_.front();
+ auto this_read =
+ stream_buffer.PopFront(buffer + offset + read, size - read);
+ if (stream_buffer.GetUsedSize() == 0) {
+ buffer_list_.pop_front();
+ }
+ read += this_read;
+ if (read == size) {
+ break;
+ }
+ }
+
+ if (full && buffer_list_.size() < max_block_count_) {
+ // By convention, there should be at most one producer waiting. So
+ // notify_one and notify_all should be the same.
+ condition_variable_.notify_one();
+ }
+
+ return read;
+}
+
+Index BufferStream::DoWrite(const std::byte* buffer, Index offset, Index size) {
+ std::unique_lock lock(mutex_);
+
+ if (eof_) {
+ throw WriteAfterEofException(
+ u"Stream has been set eof. Can't write to it any more.");
+ }
+
+ condition_variable_.wait(lock, [this] {
+ return max_block_count_ <= 0 || buffer_list_.size() < max_block_count_ ||
+ buffer_list_.back().GetBackFree() > 0;
+ });
+
+ auto empty = buffer_list_.empty();
+
+ Index written = 0;
+
+ if (empty) {
+ buffer_list_.push_back(Buffer(block_size_));
+ }
+
+ while (true) {
+ if (buffer_list_.back().GetBackFree() == 0) {
+ if (max_block_count_ > 0 && buffer_list_.size() == max_block_count_) {
+ break;
+ }
+ buffer_list_.push_back(Buffer(block_size_));
+ }
+ auto& stream_buffer = buffer_list_.back();
+ auto this_written =
+ stream_buffer.PushBack(buffer + offset + written, size - written);
+ written += this_written;
+ if (written == size) {
+ break;
+ }
+ }
+
+ if (empty) {
+ // By convention, there should be at most one consumer waiting. So
+ // notify_one and notify_all should be the same.
+ condition_variable_.notify_one();
+ }
+
+ return written;
+}
+
+void BufferStream::SetEof() {
+ std::unique_lock lock(mutex_);
+
+ eof_ = true;
+ if (buffer_list_.empty()) {
+ // By convention, there should be at most one consumer waiting. So
+ // notify_one and notify_all should be the same.
+ condition_variable_.notify_one();
+ }
+}
+
+void BufferStream::DoClose() { CRU_STREAM_BEGIN_CLOSE }
+} // namespace cru::io
diff --git a/src/base/io/CFileStream.cpp b/src/base/io/CFileStream.cpp
new file mode 100644
index 00000000..d5acc707
--- /dev/null
+++ b/src/base/io/CFileStream.cpp
@@ -0,0 +1,96 @@
+#include "cru/base/io/CFileStream.h"
+#include "cru/base/Exception.h"
+#include "cru/base/io/Stream.h"
+
+#include <cstdio>
+
+namespace cru::io {
+static bool ModeCanRead(const char* mode) {
+ for (const char* p = mode; *p != '\0'; p++) {
+ if (*p == 'r' || *p == '+') {
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool ModeCanWrite(const char* mode) {
+ for (const char* p = mode; *p != '\0'; p++) {
+ if (*p == 'w' || *p == 'a' || *p == '+') {
+ return true;
+ }
+ }
+ return false;
+}
+
+CFileStream::CFileStream(const char* path, const char* mode)
+ : Stream(true, ModeCanRead(mode), ModeCanWrite(mode)),
+ file_(std::fopen(path, mode)),
+ auto_close_(true) {
+ if (file_ == nullptr) {
+ throw ErrnoException(u"Cannot open file.");
+ }
+}
+
+CFileStream::CFileStream(std::FILE* file, bool readable, bool writable,
+ bool auto_close)
+ : Stream(true, readable, writable), file_(file), auto_close_(auto_close) {
+ if (file_ == nullptr) {
+ throw Exception(u"File is NULL.");
+ }
+}
+
+CFileStream::~CFileStream() {
+ if (auto_close_ && file_ != nullptr) {
+ std::fclose(file_);
+ }
+}
+
+static int ConvertOriginFlag(Stream::SeekOrigin origin) {
+ switch (origin) {
+ case Stream::SeekOrigin::Begin:
+ return SEEK_SET;
+ case Stream::SeekOrigin::Current:
+ return SEEK_CUR;
+ case Stream::SeekOrigin::End:
+ return SEEK_END;
+ default:
+ throw Exception(u"Unknown seek origin.");
+ }
+}
+
+Index CFileStream::DoSeek(Index offset, SeekOrigin origin) {
+ if (std::fseek(file_, offset, ConvertOriginFlag(origin))) {
+ throw ErrnoException(u"Seek failed.");
+ }
+ return DoTell();
+}
+
+Index CFileStream::DoTell() {
+ long position = std::ftell(file_);
+ if (position == -1) {
+ throw ErrnoException(u"Tell failed.");
+ }
+ return position;
+}
+
+void CFileStream::DoRewind() { std::rewind(file_); }
+
+Index CFileStream::DoRead(std::byte* buffer, Index offset, Index size) {
+ auto count = std::fread(buffer + offset, 1, size, file_);
+ return count;
+}
+
+Index CFileStream::DoWrite(const std::byte* buffer, Index offset, Index size) {
+ auto count = std::fwrite(buffer + offset, 1, size, file_);
+ return count;
+}
+
+void CFileStream::DoFlush() { std::fflush(file_); }
+
+void CFileStream::DoClose() {
+ CRU_STREAM_BEGIN_CLOSE
+ std::fclose(file_);
+ file_ = nullptr;
+}
+} // namespace cru::io
diff --git a/src/base/io/MemoryStream.cpp b/src/base/io/MemoryStream.cpp
new file mode 100644
index 00000000..4c650f3e
--- /dev/null
+++ b/src/base/io/MemoryStream.cpp
@@ -0,0 +1,74 @@
+#include "cru/base/io/MemoryStream.h"
+
+#include <cstring>
+#include "cru/base/Exception.h"
+#include "cru/base/io/Stream.h"
+
+namespace cru::io {
+MemoryStream::MemoryStream(
+ std::byte* buffer, Index size, bool read_only,
+ std::function<void(std::byte* buffer, Index size)> release_func)
+ : Stream(true, true, !read_only),
+ buffer_(buffer),
+ size_(size),
+ position_(0),
+ release_func_(std::move(release_func)) {
+ if (!buffer) {
+ throw Exception(u"Buffer is nullptr");
+ }
+ if (size <= 0) {
+ throw Exception(u"Size is 0 or negative.");
+ }
+}
+
+MemoryStream::~MemoryStream() {}
+
+void MemoryStream::Close() { DoClose(); }
+
+Index MemoryStream::DoSeek(Index offset, SeekOrigin origin) {
+ switch (origin) {
+ case SeekOrigin::Current:
+ position_ += offset;
+ break;
+ case SeekOrigin::Begin:
+ position_ = offset;
+ break;
+ case SeekOrigin::End:
+ position_ = size_ + offset;
+ break;
+ }
+ return position_;
+}
+
+Index MemoryStream::DoRead(std::byte* buffer, Index offset, Index size) {
+ if (position_ + size > size_) {
+ size = size_ - position_;
+ }
+ if (size <= 0) {
+ return 0;
+ }
+ std::memmove(buffer + offset, buffer_ + position_, size);
+ position_ += size;
+ return size;
+}
+
+Index MemoryStream::DoWrite(const std::byte* buffer, Index offset, Index size) {
+ if (position_ + size > size_) {
+ size = size_ - position_;
+ }
+ if (size <= 0) {
+ return 0;
+ }
+ std::memmove(buffer_ + position_, buffer + offset, size);
+ position_ += size;
+ return size;
+}
+
+void MemoryStream::DoClose() {
+ CRU_STREAM_BEGIN_CLOSE
+ release_func_(buffer_, size_);
+ buffer_ = nullptr;
+ release_func_ = {};
+}
+
+} // namespace cru::io
diff --git a/src/base/io/OpenFileFlag.cpp b/src/base/io/OpenFileFlag.cpp
new file mode 100644
index 00000000..47069b29
--- /dev/null
+++ b/src/base/io/OpenFileFlag.cpp
@@ -0,0 +1,19 @@
+#include "cru/base/io/OpenFileFlag.h"
+
+namespace cru::io {
+bool CheckOpenFileFlag(OpenFileFlag flags) {
+ auto has = [flags](OpenFileFlag flag) { return flags & flag; };
+
+ if ((has(OpenFileFlags::Append) || has(OpenFileFlags::Truncate) ||
+ has(OpenFileFlags::Create)) &&
+ !has(OpenFileFlags::Write)) {
+ return false;
+ }
+
+ if (has(OpenFileFlags::Truncate) && has(OpenFileFlags::Append)) {
+ return false;
+ }
+
+ return true;
+}
+} // namespace cru::io
diff --git a/src/base/io/ProxyStream.cpp b/src/base/io/ProxyStream.cpp
new file mode 100644
index 00000000..de66169e
--- /dev/null
+++ b/src/base/io/ProxyStream.cpp
@@ -0,0 +1,37 @@
+#include "cru/base/io/ProxyStream.h"
+#include "cru/base/io/Stream.h"
+
+namespace cru::io {
+ProxyStream::ProxyStream(ProxyStreamHandlers handlers)
+ : Stream(static_cast<bool>(handlers.seek), static_cast<bool>(handlers.read),
+ static_cast<bool>(handlers.write)),
+ handlers_(std::move(handlers)) {}
+
+ProxyStream::~ProxyStream() { DoClose(); }
+
+Index ProxyStream::DoSeek(Index offset, SeekOrigin origin) {
+ return handlers_.seek(offset, origin);
+}
+
+Index ProxyStream::DoRead(std::byte* buffer, Index offset, Index size) {
+ return handlers_.read(buffer, offset, size);
+}
+
+Index ProxyStream::DoWrite(const std::byte* buffer, Index offset, Index size) {
+ return handlers_.write(buffer, offset, size);
+}
+
+void ProxyStream::DoFlush() {
+ if (handlers_.flush) {
+ handlers_.flush();
+ }
+}
+
+void ProxyStream::DoClose() {
+ CRU_STREAM_BEGIN_CLOSE
+ if (handlers_.close) {
+ handlers_.close();
+ }
+ handlers_ = {};
+}
+} // namespace cru::io
diff --git a/src/base/io/Resource.cpp b/src/base/io/Resource.cpp
new file mode 100644
index 00000000..d369b5f5
--- /dev/null
+++ b/src/base/io/Resource.cpp
@@ -0,0 +1,48 @@
+#include "cru/base/io/Resource.h"
+#include "cru/base/Exception.h"
+#include "cru/base/log/Logger.h"
+
+#if defined(CRU_PLATFORM_OSX)
+#include <CoreFoundation/CoreFoundation.h>
+#elif defined(CRU_PLATFORM_WINDOWS)
+#include <Windows.h>
+#endif
+
+#include <filesystem>
+
+namespace cru::io {
+std::filesystem::path GetResourceDir() {
+ constexpr auto kLogTag = u"GetResourceDir";
+
+#if defined(CRU_PLATFORM_OSX)
+ CFBundleRef main_bundle = CFBundleGetMainBundle();
+ CFURLRef bundle_url = CFBundleCopyBundleURL(main_bundle);
+ CFStringRef cf_string_ref =
+ CFURLCopyFileSystemPath(bundle_url, kCFURLPOSIXPathStyle);
+ std::filesystem::path bundle_path(
+ CFStringGetCStringPtr(cf_string_ref, kCFStringEncodingUTF8));
+
+ CFRelease(bundle_url);
+ CFRelease(cf_string_ref);
+
+ return bundle_path / "Contents/Resources";
+#elif defined(CRU_PLATFORM_WINDOWS)
+ wchar_t buffer[MAX_PATH];
+ DWORD size = ::GetModuleFileNameW(nullptr, buffer, MAX_PATH);
+ std::filesystem::path module_path(buffer, buffer + size);
+ auto p = module_path;
+ while (p.has_parent_path()) {
+ p = p.parent_path();
+ auto resource_dir_path = p / "assets";
+ if (std::filesystem::exists(resource_dir_path) &&
+ std::filesystem::is_directory(resource_dir_path)) {
+ return resource_dir_path;
+ }
+ }
+
+ throw Exception(u"Failed to find resource directory.");
+#else
+ throw Exception(u"Not implemented.");
+#endif
+}
+} // namespace cru::io
diff --git a/src/base/io/Stream.cpp b/src/base/io/Stream.cpp
new file mode 100644
index 00000000..d65bac46
--- /dev/null
+++ b/src/base/io/Stream.cpp
@@ -0,0 +1,199 @@
+#include "cru/base/io/Stream.h"
+#include "cru/base/Exception.h"
+#include "cru/base/Format.h"
+
+#include <utility>
+
+namespace cru::io {
+StreamOperationNotSupportedException::StreamOperationNotSupportedException(
+ String operation)
+ : operation_(std::move(operation)) {
+ SetMessage(Format(u"Stream operation {} not supported.", operation_));
+}
+
+void StreamOperationNotSupportedException::CheckSeek(bool seekable) {
+ if (!seekable) throw StreamOperationNotSupportedException(u"seek");
+}
+
+void StreamOperationNotSupportedException::CheckRead(bool readable) {
+ if (!readable) throw StreamOperationNotSupportedException(u"read");
+}
+
+void StreamOperationNotSupportedException::CheckWrite(bool writable) {
+ if (!writable) throw StreamOperationNotSupportedException(u"write");
+}
+
+StreamAlreadyClosedException::StreamAlreadyClosedException()
+ : Exception(u"Stream is already closed.") {}
+
+void StreamAlreadyClosedException::Check(bool closed) {
+ if (closed) throw StreamAlreadyClosedException();
+}
+
+Stream::Stream(SupportedOperations supported_operations)
+ : supported_operations_(std::move(supported_operations)), closed_(false) {}
+
+Stream::Stream(std::optional<bool> can_seek, std::optional<bool> can_read,
+ std::optional<bool> can_write)
+ : Stream(SupportedOperations{can_seek, can_read, can_write}) {}
+
+bool Stream::CanSeek() {
+ CheckClosed();
+ return DoCanSeek();
+}
+
+Index Stream::Seek(Index offset, SeekOrigin origin) {
+ CheckClosed();
+ StreamOperationNotSupportedException::CheckSeek(DoCanSeek());
+ return DoSeek(offset, origin);
+}
+
+Index Stream::Tell() {
+ CheckClosed();
+ return DoTell();
+}
+
+void Stream::Rewind() {
+ CheckClosed();
+ return DoRewind();
+}
+
+Index Stream::GetSize() {
+ CheckClosed();
+ return DoGetSize();
+}
+
+bool Stream::CanRead() {
+ CheckClosed();
+ return DoCanRead();
+}
+
+Index Stream::Read(std::byte* buffer, Index offset, Index size) {
+ CheckClosed();
+ StreamOperationNotSupportedException::CheckRead(DoCanRead());
+ return DoRead(buffer, offset, size);
+}
+
+Index Stream::Read(std::byte* buffer, Index size) {
+ return Read(buffer, 0, size);
+}
+
+Index Stream::Read(char* buffer, Index offset, Index size) {
+ return Read(reinterpret_cast<std::byte*>(buffer), offset, size);
+}
+
+Index Stream::Read(char* buffer, Index size) {
+ return Read(reinterpret_cast<std::byte*>(buffer), 0, size);
+}
+
+bool Stream::CanWrite() {
+ CheckClosed();
+ return DoCanWrite();
+}
+
+Index Stream::Write(const std::byte* buffer, Index offset, Index size) {
+ CheckClosed();
+ StreamOperationNotSupportedException::CheckWrite(DoCanWrite());
+ return DoWrite(buffer, offset, size);
+}
+
+Index Stream::Write(const std::byte* buffer, Index size) {
+ return Write(buffer, 0, size);
+}
+
+Index Stream::Write(const char* buffer, Index offset, Index size) {
+ return Write(reinterpret_cast<const std::byte*>(buffer), offset, size);
+}
+
+Index Stream::Write(const char* buffer, Index size) {
+ return Write(reinterpret_cast<const std::byte*>(buffer), size);
+}
+
+void Stream::Flush() {
+ CheckClosed();
+ DoFlush();
+}
+
+bool Stream::DoCanSeek() {
+ if (supported_operations_->can_seek) {
+ return *supported_operations_->can_seek;
+ } else {
+ throw Exception(
+ u"Can seek is neither set in supported_operations nor implemeted in "
+ u"virtual function.");
+ }
+}
+
+bool Stream::DoCanRead() {
+ if (supported_operations_->can_read) {
+ return *supported_operations_->can_read;
+ } else {
+ throw Exception(
+ u"Can read is neither set in supported_operations nor implemeted in "
+ u"virtual function.");
+ }
+}
+
+bool Stream::DoCanWrite() {
+ if (supported_operations_->can_write) {
+ return *supported_operations_->can_write;
+ } else {
+ throw Exception(
+ u"Can write is neither set in supported_operations nor implemeted in "
+ u"virtual function.");
+ }
+}
+
+Index Stream::DoSeek(Index offset, SeekOrigin origin) {
+ throw Exception(u"Stream is seekable but DoSeek is not implemented.");
+}
+
+Index Stream::DoTell() {
+ StreamOperationNotSupportedException::CheckSeek(DoCanSeek());
+ return DoSeek(0, SeekOrigin::Current);
+}
+
+void Stream::DoRewind() {
+ StreamOperationNotSupportedException::CheckSeek(DoCanSeek());
+ DoSeek(0, SeekOrigin::Begin);
+}
+
+Index Stream::DoGetSize() {
+ StreamOperationNotSupportedException::CheckSeek(DoCanSeek());
+ Index current_position = DoTell();
+ Seek(0, SeekOrigin::End);
+ Index size = DoTell();
+ Seek(current_position, SeekOrigin::Begin);
+ return size;
+}
+
+Index Stream::DoRead(std::byte* buffer, Index offset, Index size) {
+ throw Exception(u"Stream is readable but DoSeek is not implemented.");
+}
+
+Index Stream::DoWrite(const std::byte* buffer, Index offset, Index size) {
+ throw Exception(u"Stream is writable but DoSeek is not implemented.");
+}
+
+void Stream::DoFlush() {}
+
+Buffer Stream::ReadToEnd(Index grow_size) {
+ Buffer buffer(grow_size);
+ while (true) {
+ auto read = Read(buffer.GetUsedEndPtr(), buffer.GetBackFree());
+ buffer.PushBackCount(read);
+ if (read == 0) {
+ break;
+ }
+ if (buffer.IsUsedReachEnd()) {
+ buffer.ResizeBuffer(buffer.GetBufferSize() + grow_size, true);
+ }
+ }
+ return buffer;
+}
+
+String Stream::ReadToEndAsUtf8String() {
+ auto buffer = ReadToEnd();
+ return String::FromUtf8(buffer);
+}
+} // namespace cru::io
diff --git a/src/base/log/Logger.cpp b/src/base/log/Logger.cpp
new file mode 100644
index 00000000..86c65dbc
--- /dev/null
+++ b/src/base/log/Logger.cpp
@@ -0,0 +1,88 @@
+#include "cru/base/log/Logger.h"
+#include "cru/base/log/StdioLogTarget.h"
+
+#include <ctime>
+#include <algorithm>
+
+#ifdef CRU_PLATFORM_WINDOWS
+#include "cru/base/platform/win/DebugLogTarget.h"
+#endif
+
+namespace cru::log {
+Logger *Logger::GetInstance() {
+ static Logger logger;
+
+ logger.AddLogTarget(std::make_unique<StdioLogTarget>());
+
+#ifdef CRU_PLATFORM_WINDOWS
+ logger.AddLogTarget(std::make_unique<platform::win::WinDebugLogTarget>());
+#endif
+
+ return &logger;
+}
+
+void Logger::AddLogTarget(std::unique_ptr<ILogTarget> target) {
+ std::lock_guard<std::mutex> lock(target_list_mutex_);
+ target_list_.push_back(std::move(target));
+}
+
+void Logger::RemoveLogTarget(ILogTarget *target) {
+ std::lock_guard<std::mutex> lock(target_list_mutex_);
+ target_list_.erase(
+ std::remove_if(target_list_.begin(), target_list_.end(),
+ [target](const auto &t) { return t.get() == target; }),
+ target_list_.end());
+}
+
+namespace {
+String LogLevelToString(LogLevel level) {
+ switch (level) {
+ case LogLevel::Debug:
+ return u"DEBUG";
+ case LogLevel::Info:
+ return u"INFO";
+ case LogLevel::Warn:
+ return u"WARN";
+ case LogLevel::Error:
+ return u"ERROR";
+ default:
+ std::terminate();
+ }
+}
+
+String GetLogTime() {
+ auto time = std::time(nullptr);
+ auto calendar = std::localtime(&time);
+ return Format(u"{}:{}:{}", calendar->tm_hour, calendar->tm_min,
+ calendar->tm_sec);
+}
+
+String MakeLogFinalMessage(const LogInfo &log_info) {
+ return Format(u"[{}] {} {}: {}\n", GetLogTime(),
+ LogLevelToString(log_info.level), log_info.tag,
+ log_info.message);
+}
+} // namespace
+
+Logger::Logger()
+ : log_thread_([this] {
+ while (true) {
+ auto log_info = log_queue_.Pull();
+ std::lock_guard<std::mutex> lock_guard{target_list_mutex_};
+ for (const auto &target : target_list_) {
+ target->Write(log_info.level, MakeLogFinalMessage(log_info));
+ }
+ }
+ }) {}
+
+Logger::~Logger() { log_thread_.detach(); }
+
+void Logger::Log(LogInfo log_info) {
+#ifndef CRU_DEBUG
+ if (log_info.level == LogLevel::Debug) {
+ return;
+ }
+#endif
+ log_queue_.Push(std::move(log_info));
+}
+} // namespace cru::log
diff --git a/src/base/log/StdioLogTarget.cpp b/src/base/log/StdioLogTarget.cpp
new file mode 100644
index 00000000..64ddcacc
--- /dev/null
+++ b/src/base/log/StdioLogTarget.cpp
@@ -0,0 +1,27 @@
+#include "cru/base/log/StdioLogTarget.h"
+
+#include <iostream>
+
+namespace cru::log {
+StdioLogTarget::StdioLogTarget() {}
+
+StdioLogTarget::~StdioLogTarget() {}
+
+void StdioLogTarget::Write(log::LogLevel level, StringView s) {
+#ifdef CRU_PLATFORM_WINDOWS
+ if (level == log::LogLevel::Error) {
+ std::wcerr.write(reinterpret_cast<const wchar_t*>(s.data()), s.size());
+ } else {
+ std::wcout.write(reinterpret_cast<const wchar_t*>(s.data()), s.size());
+ }
+#else
+ std::string m = s.ToUtf8();
+
+ if (level == log::LogLevel::Error) {
+ std::cerr << m;
+ } else {
+ std::cout << m;
+ }
+#endif
+}
+} // namespace cru::log
diff --git a/src/base/platform/Exception.cpp b/src/base/platform/Exception.cpp
new file mode 100644
index 00000000..b5e8c5b9
--- /dev/null
+++ b/src/base/platform/Exception.cpp
@@ -0,0 +1 @@
+#include "cru/base/platform/Exception.h"
diff --git a/src/base/platform/osx/Convert.cpp b/src/base/platform/osx/Convert.cpp
new file mode 100644
index 00000000..1a6deb8f
--- /dev/null
+++ b/src/base/platform/osx/Convert.cpp
@@ -0,0 +1,29 @@
+#include "cru/base/platform/osx/Convert.h"
+
+namespace cru::platform::osx {
+CFStringRef Convert(const String& string) {
+ return CFStringCreateWithBytes(
+ nullptr, reinterpret_cast<const UInt8*>(string.data()),
+ string.size() * sizeof(std::uint16_t), kCFStringEncodingUTF16, false);
+}
+
+String Convert(CFStringRef string) {
+ auto length = CFStringGetLength(string);
+
+ String result;
+
+ for (int i = 0; i < length; i++) {
+ result.AppendCodePoint(CFStringGetCharacterAtIndex(string, i));
+ }
+
+ return result;
+}
+
+CFRange Convert(const Range& range) {
+ return CFRangeMake(range.position, range.count);
+}
+
+Range Convert(const CFRange& range) {
+ return Range(range.location, range.length);
+}
+} // namespace cru::platform::osx
diff --git a/src/base/platform/osx/Exception.cpp b/src/base/platform/osx/Exception.cpp
new file mode 100644
index 00000000..a60448dd
--- /dev/null
+++ b/src/base/platform/osx/Exception.cpp
@@ -0,0 +1 @@
+#include "cru/base/platform//osx/Exception.h"
diff --git a/src/base/platform/unix/PosixSpawnSubProcess.cpp b/src/base/platform/unix/PosixSpawnSubProcess.cpp
new file mode 100644
index 00000000..75d48cc2
--- /dev/null
+++ b/src/base/platform/unix/PosixSpawnSubProcess.cpp
@@ -0,0 +1,204 @@
+#include "cru/base/platform/unix/PosixSpawnSubProcess.h"
+#include "cru/base/Exception.h"
+#include "cru/base/Format.h"
+#include "cru/base/Guard.h"
+#include "cru/base/String.h"
+#include "cru/base/SubProcess.h"
+#include "cru/base/log/Logger.h"
+
+#include <signal.h>
+#include <spawn.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <memory>
+#include <unordered_map>
+
+namespace cru::platform::unix {
+PosixSpawnSubProcessImpl::PosixSpawnSubProcessImpl()
+ : pid_(0),
+ exit_code_(0),
+ stdin_pipe_(UnixPipe::Usage::Send, false),
+ stdout_pipe_(UnixPipe::Usage::Receive, false),
+ stderr_pipe_(UnixPipe::Usage::Receive, false) {
+ stdin_stream_ = std::make_unique<UnixFileStream>(
+ stdin_pipe_.GetSelfFileDescriptor(), false, false, true, true);
+ stdout_stream_ = std::make_unique<UnixFileStream>(
+ stdout_pipe_.GetSelfFileDescriptor(), false, true, false, true);
+ stderr_stream_ = std::make_unique<UnixFileStream>(
+ stderr_pipe_.GetSelfFileDescriptor(), false, true, false, true);
+
+ stdout_buffer_stream_ =
+ std::make_unique<io::AutoReadStream>(stdout_stream_.get(), false);
+ stderr_buffer_stream_ =
+ std::make_unique<io::AutoReadStream>(stderr_stream_.get(), false);
+}
+
+PosixSpawnSubProcessImpl::~PosixSpawnSubProcessImpl() {}
+
+namespace {
+char** CreateCstrArray(const std::vector<String>& argv) {
+ std::vector<Buffer> utf8_argv;
+ for (const auto& arg : argv) {
+ utf8_argv.push_back(arg.ToUtf8Buffer());
+ }
+ char** result = new char*[utf8_argv.size() + 1];
+ for (int i = 0; i < utf8_argv.size(); i++) {
+ result[i] = reinterpret_cast<char*>(utf8_argv[i].Detach());
+ }
+ result[utf8_argv.size()] = nullptr;
+ return result;
+}
+
+char** CreateCstrArray(const std::unordered_map<String, String>& envp) {
+ std::vector<String> str_array;
+ for (auto& [key, value] : envp) {
+ str_array.push_back(cru::Format(u"{}={}", key, value));
+ }
+ return CreateCstrArray(str_array);
+}
+
+void DestroyCstrArray(char** argv) {
+ int index = 0;
+ char* arg = argv[index];
+ while (arg) {
+ delete[] arg;
+ index++;
+ arg = argv[index];
+ }
+ delete[] argv;
+}
+} // namespace
+
+void PosixSpawnSubProcessImpl::PlatformCreateProcess(
+ const SubProcessStartInfo& start_info) {
+ int error;
+ auto check_error = [&error](String message) {
+ if (error == 0) return;
+ std::unique_ptr<ErrnoException> inner(new ErrnoException({}, error));
+ throw SubProcessFailedToStartException(std::move(message),
+ std::move(inner));
+ };
+
+ posix_spawn_file_actions_t file_actions;
+ error = posix_spawn_file_actions_init(&file_actions);
+ check_error(u"Failed to call posix_spawn_file_actions_init.");
+ Guard file_actions_guard(
+ [&file_actions] { posix_spawn_file_actions_destroy(&file_actions); });
+
+ // dup2 stdin/stdout/stderr
+ error = posix_spawn_file_actions_adddup2(
+ &file_actions, stdin_pipe_.GetOtherFileDescriptor(), STDIN_FILENO);
+ check_error(u"Failed to call posix_spawn_file_actions_adddup2 on stdin.");
+ error = posix_spawn_file_actions_adddup2(
+ &file_actions, stdout_pipe_.GetOtherFileDescriptor(), STDOUT_FILENO);
+ check_error(u"Failed to call posix_spawn_file_actions_adddup2 on stdout.");
+ error = posix_spawn_file_actions_adddup2(
+ &file_actions, stderr_pipe_.GetOtherFileDescriptor(), STDERR_FILENO);
+ check_error(u"Failed to call posix_spawn_file_actions_adddup2 on stderr.");
+
+ error = posix_spawn_file_actions_addclose(
+ &file_actions, stdin_pipe_.GetOtherFileDescriptor());
+ check_error(
+ u"Failed to call posix_spawn_file_actions_addclose on self fd of stdin.");
+ error = posix_spawn_file_actions_addclose(
+ &file_actions, stdout_pipe_.GetOtherFileDescriptor());
+ check_error(
+ u"Failed to call posix_spawn_file_actions_addclose on self fd stdout.");
+ error = posix_spawn_file_actions_addclose(
+ &file_actions, stderr_pipe_.GetOtherFileDescriptor());
+ check_error(
+ u"Failed to call posix_spawn_file_actions_addclose on self fd stderr.");
+
+ error = posix_spawn_file_actions_addclose(
+ &file_actions, stdin_pipe_.GetSelfFileDescriptor());
+ check_error(
+ u"Failed to call posix_spawn_file_actions_addclose on parent fd of "
+ u"stdin.");
+ error = posix_spawn_file_actions_addclose(
+ &file_actions, stdout_pipe_.GetSelfFileDescriptor());
+ check_error(
+ u"Failed to call posix_spawn_file_actions_addclose on parent fd of "
+ u"stdout.");
+ error = posix_spawn_file_actions_addclose(
+ &file_actions, stderr_pipe_.GetSelfFileDescriptor());
+ check_error(
+ u"Failed to call posix_spawn_file_actions_addclose on parent fd of "
+ u"stderr.");
+
+ posix_spawnattr_t attr;
+ error = posix_spawnattr_init(&attr);
+ check_error(u"Failed to call posix_spawnattr_init.");
+ Guard attr_guard([&attr] { posix_spawnattr_destroy(&attr); });
+
+#ifdef CRU_PLATFORM_OSX
+ error = posix_spawnattr_setflags(&attr, POSIX_SPAWN_CLOEXEC_DEFAULT);
+ check_error(u"Failed to set flag POSIX_SPAWN_CLOEXEC_DEFAULT (osx).");
+#endif
+
+ auto exe = start_info.program.ToUtf8();
+ std::vector<String> arguments{start_info.program};
+ arguments.insert(arguments.cend(), start_info.arguments.cbegin(),
+ start_info.arguments.cend());
+
+ auto argv = CreateCstrArray(arguments);
+ Guard argv_guard([argv] { DestroyCstrArray(argv); });
+
+ auto envp = CreateCstrArray(start_info.environments);
+ Guard envp_guard([envp] { DestroyCstrArray(envp); });
+
+ error = posix_spawnp(&pid_, exe.c_str(), &file_actions, &attr, argv, envp);
+ check_error(u"Failed to call posix_spawnp.");
+
+ error = ::close(stdin_pipe_.GetOtherFileDescriptor());
+ check_error(u"Failed to close child stdin.");
+ error = ::close(stdout_pipe_.GetOtherFileDescriptor());
+ check_error(u"Failed to close child stdout.");
+ error = ::close(stderr_pipe_.GetOtherFileDescriptor());
+ check_error(u"Failed to close child stderr.");
+}
+
+SubProcessExitResult PosixSpawnSubProcessImpl::PlatformWaitForProcess() {
+ int wstatus;
+
+ while (waitpid(pid_, &wstatus, 0) == -1) {
+ if (errno == EINTR) {
+ CRU_LOG_INFO(u"Waitpid is interrupted by a signal. Call it again.");
+ continue;
+ }
+
+ std::unique_ptr<ErrnoException> inner(new ErrnoException({}, errno));
+
+ throw SubProcessInternalException(
+ u"Failed to call waitpid on a subprocess.", std::move(inner));
+ }
+
+ if (WIFEXITED(wstatus)) {
+ return SubProcessExitResult::Normal(WEXITSTATUS(wstatus));
+ } else if (WIFEXITED(wstatus)) {
+ return SubProcessExitResult::Signal(WTERMSIG(wstatus), WCOREDUMP(wstatus));
+ } else {
+ return SubProcessExitResult::Unknown();
+ }
+}
+
+void PosixSpawnSubProcessImpl::PlatformKillProcess() {
+ int error = kill(pid_, SIGKILL);
+ if (error != 0) {
+ std::unique_ptr<ErrnoException> inner(new ErrnoException({}, errno));
+ throw SubProcessInternalException(u"Failed to call kill on a subprocess.",
+ std::move(inner));
+ }
+}
+
+io::Stream* PosixSpawnSubProcessImpl::GetStdinStream() {
+ return stdin_stream_.get();
+}
+
+io::Stream* PosixSpawnSubProcessImpl::GetStdoutStream() {
+ return stdout_buffer_stream_.get();
+}
+
+io::Stream* PosixSpawnSubProcessImpl::GetStderrStream() {
+ return stderr_buffer_stream_.get();
+}
+} // namespace cru::platform::unix
diff --git a/src/base/platform/unix/UnixFileStream.cpp b/src/base/platform/unix/UnixFileStream.cpp
new file mode 100644
index 00000000..c53bbbaa
--- /dev/null
+++ b/src/base/platform/unix/UnixFileStream.cpp
@@ -0,0 +1,106 @@
+#include "cru/base/platform/unix/UnixFileStream.h"
+#include "cru/base/Exception.h"
+#include "cru/base/Format.h"
+#include "cru/base/io/Stream.h"
+#include "cru/base/log/Logger.h"
+
+#include <fcntl.h>
+#include <sys/fcntl.h>
+#include <unistd.h>
+#include <cerrno>
+
+namespace cru::platform::unix {
+using namespace cru::io;
+
+namespace {
+bool OflagCanSeek([[maybe_unused]] int oflag) {
+ // Treat every file seekable.
+ return true;
+}
+
+bool OflagCanRead(int oflag) {
+ // There is a problem: O_RDONLY is 0. So we must test it specially.
+ // If it is not write-only. Then it can read.
+ return !(oflag & O_WRONLY);
+}
+
+bool OflagCanWrite(int oflag) { return oflag & O_WRONLY || oflag & O_RDWR; }
+
+int MapSeekOrigin(Stream::SeekOrigin origin) {
+ switch (origin) {
+ case Stream::SeekOrigin::Begin:
+ return SEEK_SET;
+ case Stream::SeekOrigin::Current:
+ return SEEK_CUR;
+ case Stream::SeekOrigin::End:
+ return SEEK_END;
+ default:
+ throw Exception(u"Invalid seek origin.");
+ }
+}
+} // namespace
+
+UnixFileStream::UnixFileStream(const char *path, int oflag, mode_t mode) {
+ file_descriptor_ = ::open(path, oflag, mode);
+ if (file_descriptor_ == -1) {
+ throw ErrnoException(
+ Format(u"Failed to open file {} with oflag {}, mode {}.",
+ String::FromUtf8(path), oflag, mode));
+ }
+
+ SetSupportedOperations(
+ {OflagCanSeek(oflag), OflagCanRead(oflag), OflagCanWrite(oflag)});
+
+ auto_close_ = true;
+}
+
+UnixFileStream::UnixFileStream(int fd, bool can_seek, bool can_read,
+ bool can_write, bool auto_close)
+ : Stream(can_seek, can_read, can_write) {
+ file_descriptor_ = fd;
+ auto_close_ = auto_close;
+}
+
+UnixFileStream::~UnixFileStream() {
+ if (auto_close_ && file_descriptor_ >= 0) {
+ if (::close(file_descriptor_) == -1) {
+ // We are in destructor, so we can not throw.
+ CRU_LOG_WARN(u"Failed to close file descriptor {}, errno {}.",
+ file_descriptor_, errno);
+ }
+ }
+}
+
+Index UnixFileStream::DoSeek(Index offset, SeekOrigin origin) {
+ off_t result = ::lseek(file_descriptor_, offset, MapSeekOrigin(origin));
+ if (result == -1) {
+ throw ErrnoException(u"Failed to seek file.");
+ }
+ return result;
+}
+
+Index UnixFileStream::DoRead(std::byte *buffer, Index offset, Index size) {
+ auto result = ::read(file_descriptor_, buffer + offset, size);
+ if (result == -1) {
+ throw ErrnoException(u"Failed to read file.");
+ }
+ return result;
+}
+
+Index UnixFileStream::DoWrite(const std::byte *buffer, Index offset,
+ Index size) {
+ auto result = ::write(file_descriptor_, buffer + offset, size);
+ if (result == -1) {
+ throw ErrnoException(u"Failed to write file.");
+ }
+ return result;
+}
+
+void UnixFileStream::DoClose() {
+ CRU_STREAM_BEGIN_CLOSE
+ if (auto_close_ && ::close(file_descriptor_) == -1) {
+ throw ErrnoException(u"Failed to close file.");
+ }
+ file_descriptor_ = -1;
+}
+} // namespace cru::platform::unix
diff --git a/src/base/platform/unix/UnixPipe.cpp b/src/base/platform/unix/UnixPipe.cpp
new file mode 100644
index 00000000..747efe94
--- /dev/null
+++ b/src/base/platform/unix/UnixPipe.cpp
@@ -0,0 +1,51 @@
+#include "cru/base/platform/unix/UnixPipe.h"
+#include "cru/base/Exception.h"
+#include "cru/base/log/Logger.h"
+
+#include <fcntl.h>
+#include <sys/fcntl.h>
+#include <unistd.h>
+
+namespace cru::platform::unix {
+UnixPipe::UnixPipe(Usage usage, bool auto_close, UnixPipeFlag flags)
+ : usage_(usage), auto_close_(auto_close), flags_(flags) {
+ int fds[2];
+ if (pipe(fds) != 0) {
+ throw ErrnoException(u"Failed to create unix pipe.");
+ }
+
+ if (flags & UnixPipeFlags::NonBlock) {
+ fcntl(fds[0], F_SETFL, O_NONBLOCK);
+ fcntl(fds[1], F_SETFL, O_NONBLOCK);
+ }
+
+ read_fd_ = fds[0];
+ write_fd_ = fds[1];
+}
+
+int UnixPipe::GetSelfFileDescriptor() {
+ if (usage_ == Usage::Send) {
+ return write_fd_;
+ } else {
+ return read_fd_;
+ }
+}
+
+int UnixPipe::GetOtherFileDescriptor() {
+ if (usage_ == Usage::Send) {
+ return read_fd_;
+ } else {
+ return write_fd_;
+ }
+}
+
+UnixPipe::~UnixPipe() {
+ if (auto_close_) {
+ auto self_fd = GetSelfFileDescriptor();
+ if (::close(self_fd) != 0) {
+ CRU_LOG_ERROR(u"Failed to close unix pipe file descriptor {}, errno {}.",
+ self_fd, errno);
+ }
+ }
+}
+} // namespace cru::platform::unix
diff --git a/src/base/platform/web/WebException.cpp b/src/base/platform/web/WebException.cpp
new file mode 100644
index 00000000..f473bb5b
--- /dev/null
+++ b/src/base/platform/web/WebException.cpp
@@ -0,0 +1 @@
+#include "cru/base/platform/web/WebException.h"
diff --git a/src/base/platform/win/BridgeComStream.cpp b/src/base/platform/win/BridgeComStream.cpp
new file mode 100644
index 00000000..c6987ab2
--- /dev/null
+++ b/src/base/platform/win/BridgeComStream.cpp
@@ -0,0 +1,106 @@
+#include "BrigdeComStream.h"
+#include "cru/base/io/Stream.h"
+
+namespace cru::platform::win {
+BridgeComStream::BridgeComStream(io::Stream *stream)
+ : stream_(stream), ref_count_(1) {}
+
+BridgeComStream::~BridgeComStream() {}
+
+ULONG BridgeComStream::AddRef() { return ++ref_count_; }
+
+ULONG BridgeComStream::Release() {
+ --ref_count_;
+
+ if (ref_count_ == 0) {
+ delete this;
+ return 0;
+ }
+
+ return ref_count_;
+}
+
+HRESULT BridgeComStream::QueryInterface(const IID &riid, void **ppvObject) {
+ if (riid == IID_IStream) {
+ *ppvObject = static_cast<IStream *>(this);
+ AddRef();
+ return S_OK;
+ } else if (riid == IID_ISequentialStream) {
+ *ppvObject = static_cast<ISequentialStream *>(this);
+ AddRef();
+ return S_OK;
+ } else if (riid == IID_IUnknown) {
+ *ppvObject = static_cast<IUnknown *>(this);
+ AddRef();
+ return S_OK;
+ } else {
+ return E_NOINTERFACE;
+ }
+}
+
+HRESULT BridgeComStream::Read(void *pv, ULONG cb, ULONG *pcbRead) {
+ *pcbRead = stream_->Read(static_cast<std::byte *>(pv), cb);
+ return S_OK;
+}
+
+HRESULT BridgeComStream::Write(const void *pv, ULONG cb, ULONG *pcbWritten) {
+ *pcbWritten = stream_->Write(static_cast<const std::byte *>(pv), cb);
+ return S_OK;
+}
+
+HRESULT BridgeComStream::Seek(LARGE_INTEGER dlibMove, DWORD dwOrigin,
+ ULARGE_INTEGER *plibNewPosition) {
+ io::Stream::SeekOrigin so;
+
+ switch (dwOrigin) {
+ case STREAM_SEEK_SET:
+ so = io::Stream::SeekOrigin::Begin;
+ break;
+ case STREAM_SEEK_CUR:
+ so = io::Stream::SeekOrigin::Current;
+ break;
+ case STREAM_SEEK_END:
+ so = io::Stream::SeekOrigin::End;
+ break;
+ default:
+ return STG_E_INVALIDFUNCTION;
+ };
+
+ plibNewPosition->QuadPart = stream_->Seek(dlibMove.QuadPart, so);
+ return S_OK;
+}
+
+HRESULT BridgeComStream::SetSize(ULARGE_INTEGER libNewSize) {
+ return E_NOTIMPL;
+}
+
+HRESULT BridgeComStream::CopyTo(IStream *pstm, ULARGE_INTEGER cb,
+ ULARGE_INTEGER *pcbRead,
+ ULARGE_INTEGER *pcbWritten) {
+ return E_NOTIMPL;
+}
+
+HRESULT BridgeComStream::Commit(DWORD grfCommitFlags) { return S_OK; }
+
+HRESULT BridgeComStream::Revert() { return S_OK; }
+
+HRESULT BridgeComStream::LockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb,
+ DWORD dwLockType) {
+ return S_OK;
+}
+
+HRESULT BridgeComStream::UnlockRegion(ULARGE_INTEGER libOffset,
+ ULARGE_INTEGER cb, DWORD dwLockType) {
+ return S_OK;
+}
+
+HRESULT BridgeComStream::Stat(STATSTG *pstatstg, DWORD grfStatFlag) {
+ return E_NOTIMPL;
+}
+
+HRESULT BridgeComStream::Clone(IStream **ppstm) {
+ *ppstm = new BridgeComStream(stream_);
+ return S_OK;
+}
+
+} // namespace cru::platform::win
diff --git a/src/base/platform/win/BrigdeComStream.h b/src/base/platform/win/BrigdeComStream.h
new file mode 100644
index 00000000..1621b567
--- /dev/null
+++ b/src/base/platform/win/BrigdeComStream.h
@@ -0,0 +1,43 @@
+#pragma once
+#include "cru/base/platform/win/WinPreConfig.h"
+
+#include "cru/base/io/Stream.h"
+
+#include <objidlbase.h>
+
+namespace cru::platform::win {
+class BridgeComStream : public IStream {
+ public:
+ explicit BridgeComStream(io::Stream* stream);
+
+ CRU_DELETE_COPY(BridgeComStream)
+ CRU_DELETE_MOVE(BridgeComStream)
+
+ ~BridgeComStream();
+
+ public:
+ ULONG AddRef() override;
+ ULONG Release() override;
+ HRESULT QueryInterface(REFIID riid, void** ppvObject) override;
+
+ HRESULT Read(void* pv, ULONG cb, ULONG* pcbRead) override;
+ HRESULT Write(const void* pv, ULONG cb, ULONG* pcbWritten) override;
+ HRESULT Seek(LARGE_INTEGER dlibMove, DWORD dwOrigin,
+ ULARGE_INTEGER* plibNewPosition) override;
+ HRESULT SetSize(ULARGE_INTEGER libNewSize) override;
+ HRESULT CopyTo(IStream* pstm, ULARGE_INTEGER cb, ULARGE_INTEGER* pcbRead,
+ ULARGE_INTEGER* pcbWritten) override;
+ HRESULT Commit(DWORD grfCommitFlags) override;
+ HRESULT Revert() override;
+ HRESULT LockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb,
+ DWORD dwLockType) override;
+ HRESULT UnlockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb,
+ DWORD dwLockType) override;
+ HRESULT Stat(STATSTG* pstatstg, DWORD grfStatFlag) override;
+ HRESULT Clone(IStream** ppstm) override;
+
+ private:
+ io::Stream* stream_;
+ ULONG ref_count_;
+};
+} // namespace cru::platform::win
diff --git a/src/base/platform/win/ComAutoInit.cpp b/src/base/platform/win/ComAutoInit.cpp
new file mode 100644
index 00000000..548a7bea
--- /dev/null
+++ b/src/base/platform/win/ComAutoInit.cpp
@@ -0,0 +1,15 @@
+#include "cru/base/platform/win/ComAutoInit.h"
+#include "cru/base/platform/win/Exception.h"
+
+#include <combaseapi.h>
+
+namespace cru::platform::win {
+ComAutoInit::ComAutoInit() {
+ const auto hresult = ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
+ if (FAILED(hresult)) {
+ throw HResultError(hresult, "Failed to call CoInitializeEx.");
+ }
+}
+
+ComAutoInit::~ComAutoInit() { ::CoUninitialize(); }
+} // namespace cru::platform::win
diff --git a/src/base/platform/win/DebugLogTarget.cpp b/src/base/platform/win/DebugLogTarget.cpp
new file mode 100644
index 00000000..89bd3d19
--- /dev/null
+++ b/src/base/platform/win/DebugLogTarget.cpp
@@ -0,0 +1,10 @@
+#include "cru/base/platform/win/DebugLogTarget.h"
+
+namespace cru::platform::win {
+void WinDebugLogTarget::Write(::cru::log::LogLevel level, StringView s) {
+ CRU_UNUSED(level)
+
+ String m = s.ToString();
+ ::OutputDebugStringW(reinterpret_cast<const wchar_t*>(m.c_str()));
+}
+} // namespace cru::platform::win
diff --git a/src/base/platform/win/Exception.cpp b/src/base/platform/win/Exception.cpp
new file mode 100644
index 00000000..5ff6146b
--- /dev/null
+++ b/src/base/platform/win/Exception.cpp
@@ -0,0 +1,38 @@
+#include "cru/base/platform/win/Exception.h"
+#include "cru/base/Format.h"
+
+#include <optional>
+
+namespace cru::platform::win {
+
+inline String HResultMakeMessage(HRESULT h_result,
+ std::optional<String> message) {
+ if (message.has_value())
+ return Format(u"HRESULT: {}. Message: {}", h_result, message->WinCStr());
+ else
+ return Format(u"HRESULT: {}.", h_result);
+}
+
+HResultError::HResultError(HRESULT h_result)
+ : PlatformException(HResultMakeMessage(h_result, std::nullopt)),
+ h_result_(h_result) {}
+
+HResultError::HResultError(HRESULT h_result,
+ std::string_view additional_message)
+ : PlatformException(HResultMakeMessage(
+ h_result, String::FromUtf8(additional_message.data(),
+ additional_message.size()))),
+ h_result_(h_result) {}
+
+inline String Win32MakeMessage(DWORD error_code, String message) {
+ return Format(u"Last error code: {}.\nMessage: {}\n", error_code,
+ message.WinCStr());
+}
+
+Win32Error::Win32Error(String message)
+ : Win32Error(::GetLastError(), message) {}
+
+Win32Error::Win32Error(DWORD error_code, String message)
+ : PlatformException(Win32MakeMessage(error_code, message)),
+ error_code_(error_code) {}
+} // namespace cru::platform::win
diff --git a/src/base/platform/win/StreamConvert.cpp b/src/base/platform/win/StreamConvert.cpp
new file mode 100644
index 00000000..f7a0537c
--- /dev/null
+++ b/src/base/platform/win/StreamConvert.cpp
@@ -0,0 +1,28 @@
+#include "cru/base/platform/win/StreamConvert.h"
+#include "BrigdeComStream.h"
+#include "Win32FileStreamPrivate.h"
+#include "cru/base/Exception.h"
+#include "cru/base/io/MemoryStream.h"
+#include "cru/base/io/OpenFileFlag.h"
+#include "cru/base/platform/win/ComAutoInit.h"
+#include "cru/base/platform/win/Exception.h"
+#include "cru/base/platform/win/Win32FileStream.h"
+
+#include <shlwapi.h>
+#include <winnt.h>
+
+namespace cru::platform::win {
+IStream* ConvertStreamToComStream(io::Stream* stream) {
+ static ComAutoInit com_auto_init;
+
+ if (auto memory_stream = dynamic_cast<io::MemoryStream*>(stream)) {
+ return SHCreateMemStream(
+ reinterpret_cast<const BYTE*>(memory_stream->GetBuffer()),
+ memory_stream->GetSize());
+ } else if (auto file_stream = dynamic_cast<Win32FileStream*>(stream)) {
+ return file_stream->GetPrivate_()->stream_;
+ } else {
+ return new BridgeComStream(stream);
+ }
+}
+} // namespace cru::platform::win
diff --git a/src/base/platform/win/Win32FileStream.cpp b/src/base/platform/win/Win32FileStream.cpp
new file mode 100644
index 00000000..54e6ae45
--- /dev/null
+++ b/src/base/platform/win/Win32FileStream.cpp
@@ -0,0 +1,122 @@
+#include "cru/base/platform/win/Win32FileStream.h"
+
+#include "Win32FileStreamPrivate.h"
+#include "cru/base/io/OpenFileFlag.h"
+#include "cru/base/platform/win/Exception.h"
+
+#include <Windows.h>
+#include <coml2api.h>
+#include <shlwapi.h>
+#include <winnt.h>
+#include <filesystem>
+
+namespace cru::platform::win {
+using namespace cru::io;
+
+Win32FileStream::Win32FileStream(String path, OpenFileFlag flags)
+ : path_(std::move(path)),
+ flags_(flags),
+ p_(new details::Win32FileStreamPrivate()) {
+ DWORD grfMode = STGM_SHARE_DENY_NONE;
+ if (flags & io::OpenFileFlags::Read) {
+ if (flags & io::OpenFileFlags::Write) {
+ grfMode |= STGM_READWRITE;
+ } else {
+ grfMode |= STGM_READ;
+ }
+ } else {
+ if (flags & io::OpenFileFlags::Write) {
+ grfMode |= STGM_WRITE;
+ } else {
+ throw Exception(u"Stream must be readable or writable.");
+ }
+ }
+
+ if (flags & io::OpenFileFlags::Truncate) {
+ grfMode |= STGM_CREATE;
+ }
+
+ IStream* stream;
+
+ ThrowIfFailed(SHCreateStreamOnFileEx(
+ path_.WinCStr(), grfMode, FILE_ATTRIBUTE_NORMAL,
+ flags & io::OpenFileFlags::Create ? TRUE : FALSE, NULL, &stream));
+
+ p_->stream_ = stream;
+}
+
+Win32FileStream::~Win32FileStream() {
+ Close();
+ delete p_;
+}
+
+bool Win32FileStream::CanSeek() { return true; }
+
+Index Win32FileStream::Seek(Index offset, SeekOrigin origin) {
+ CheckClosed();
+
+ DWORD dwOrigin = 0;
+
+ if (origin == SeekOrigin::Current) {
+ dwOrigin = STREAM_SEEK_CUR;
+ } else if (origin == SeekOrigin::End) {
+ dwOrigin = STREAM_SEEK_END;
+ } else {
+ dwOrigin = STREAM_SEEK_SET;
+ }
+
+ LARGE_INTEGER n_offset;
+ n_offset.QuadPart = offset;
+ ULARGE_INTEGER n_new_offset;
+
+ ThrowIfFailed(p_->stream_->Seek(n_offset, dwOrigin, &n_new_offset));
+
+ return n_new_offset.QuadPart;
+}
+
+bool Win32FileStream::CanRead() { return true; }
+
+Index Win32FileStream::Read(std::byte* buffer, Index offset, Index size) {
+ if (size < 0) {
+ throw Exception(u"Size must be greater than 0.");
+ }
+
+ CheckClosed();
+
+ ULONG n_read;
+ ThrowIfFailed(p_->stream_->Read(buffer + offset, size, &n_read));
+ return n_read;
+}
+
+bool Win32FileStream::CanWrite() { return true; }
+
+Index Win32FileStream::Write(const std::byte* buffer, Index offset,
+ Index size) {
+ if (size < 0) {
+ throw Exception(u"Size must be greater than 0.");
+ }
+
+ CheckClosed();
+
+ ULONG n_written;
+ ThrowIfFailed(p_->stream_->Write(buffer + offset, size, &n_written));
+
+ return n_written;
+}
+
+void Win32FileStream::Close() {
+ if (closed_) return;
+
+ if (p_->stream_) {
+ p_->stream_->Release();
+ p_->stream_ = nullptr;
+ }
+
+ closed_ = true;
+}
+
+void Win32FileStream::CheckClosed() {
+ if (closed_) throw Exception(u"Stream is closed.");
+}
+
+} // namespace cru::platform::win
diff --git a/src/base/platform/win/Win32FileStreamPrivate.h b/src/base/platform/win/Win32FileStreamPrivate.h
new file mode 100644
index 00000000..718f8d9a
--- /dev/null
+++ b/src/base/platform/win/Win32FileStreamPrivate.h
@@ -0,0 +1,13 @@
+#include "cru/base/platform/win/WinPreConfig.h"
+
+#include <objidl.h>
+
+namespace cru::platform::win {
+
+namespace details {
+struct Win32FileStreamPrivate {
+ IStream* stream_ = nullptr;
+};
+} // namespace details
+
+} // namespace cru::platform::win