diff options
author | crupest <crupest@outlook.com> | 2024-10-06 13:57:39 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2024-10-06 13:57:39 +0800 |
commit | dfe62dcf8bcefc523b466e127c3edc4dc2756629 (patch) | |
tree | 1c751a14ba0da07ca2ff805633f97568060aa4c9 /src/base | |
parent | f51eb955e188858272230a990565931e7403f23b (diff) | |
download | cru-dfe62dcf8bcefc523b466e127c3edc4dc2756629.tar.gz cru-dfe62dcf8bcefc523b466e127c3edc4dc2756629.tar.bz2 cru-dfe62dcf8bcefc523b466e127c3edc4dc2756629.zip |
Rename common to base.
Diffstat (limited to 'src/base')
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, ¤t]() -> 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 |