#pragma once #if !defined(MIJIN_UTIL_STRING_HPP_INCLUDED) #define MIJIN_UTIL_STRING_HPP_INCLUDED 1 #include #include #include #include #include #include #include #include #include #include #include #include #include #include "./iterators.hpp" #include "../internal/common.hpp" #include "../util/traits.hpp" namespace mijin { // // public defines // // // public constants // namespace detail { template static constexpr std::array DEFAULT_TRIM_CHARS_DATA = {TChar(' '), TChar('\t'), TChar('\r'), TChar('\n')}; } template static const std::basic_string_view> DEFAULT_TRIM_CHARS = {detail::DEFAULT_TRIM_CHARS_DATA.begin(), detail::DEFAULT_TRIM_CHARS_DATA.end()}; // // public traits // template inline constexpr bool is_string_v = is_template_instance_v>; template concept std_string_type = is_string_v; template inline constexpr bool is_string_view_v = is_template_instance_v>; template concept std_string_view_type = is_string_view_v; template inline constexpr bool is_char_v = is_any_type_v, char, wchar_t, char8_t, char16_t, char32_t>; template concept char_type = is_char_v; template inline constexpr bool is_cstring_v = std::is_pointer_v && is_char_v>; template concept cstring_type = is_cstring_v; template struct str_char_type { using type = void; }; template struct str_char_type { using type = std::remove_cvref_t; }; template struct str_char_type { using type = typename std::remove_cvref_t::value_type; }; template struct str_char_type { using type = typename std::remove_cvref_t::value_type; }; template using str_char_type_t = str_char_type::type; // // public types // struct SplitOptions { std::size_t limitParts = std::numeric_limits::max(); bool ignoreEmpty = true; }; struct [[nodiscard]] ConvertCharTypeResult { unsigned numRead = 0; unsigned numWritten = 0; constexpr operator bool() const MIJIN_NOEXCEPT { return numRead != 0 || numWritten != 0; } constexpr bool operator !() const MIJIN_NOEXCEPT { return !static_cast(*this); } }; struct SplitViewOptions { bool ignoreEmpty = true; bool trim = false; }; template> struct SplitStringTraitsCT { using char_t = TChar; using string_view_t = std::basic_string_view; static constexpr char_t getSplitAt() MIJIN_NOEXCEPT { return splitAt; } static constexpr bool getIgnoreEmpty() MIJIN_NOEXCEPT { return options.ignoreEmpty; } static constexpr bool getTrim() MIJIN_NOEXCEPT { return options.trim; } static constexpr auto getTrimChars() MIJIN_NOEXCEPT { return DEFAULT_TRIM_CHARS; } }; template> struct SplitStringTraitsRT { using char_t = TChar; using string_view_t = std::basic_string_view; char_t splitAt; bool ignoreEmpty; string_view_t trimChars = {}; constexpr char_t getSplitAt() const MIJIN_NOEXCEPT { return splitAt; } constexpr bool getIgnoreEmpty() const MIJIN_NOEXCEPT { return ignoreEmpty; } constexpr bool getTrim() const MIJIN_NOEXCEPT { return !trimChars.empty(); } constexpr string_view_t getTrimChars() const MIJIN_NOEXCEPT { return trimChars; } }; template concept SplitStringTraitsType = std::is_copy_constructible_v && requires(const T& object) { typename T::char_t; typename T::string_view_t; { object.getSplitAt() } -> std::convertible_to; { object.getIgnoreEmpty() } -> std::convertible_to; { object.getTrim() } -> std::convertible_to; { object.getTrimChars() } -> std::convertible_to; }; static_assert(SplitStringTraitsType, char>); static_assert(SplitStringTraitsType, char>); template> struct SplitLineTraitsCT : SplitStringTraitsCT { using base_t = SplitStringTraitsCT; using char_t = TChar; using line_t = TLine; line_t line = 1; using base_t::getSplitAt; using base_t::getIgnoreEmpty; using base_t::getTrim; using base_t::getTrimChars; constexpr void onNext() MIJIN_NOEXCEPT { ++line; } constexpr line_t getLine() const MIJIN_NOEXCEPT { return line; } }; template> struct SplitLineTraitsRT { using char_t = TChar; using line_t = TLine; using string_view_t = std::basic_string_view; line_t line = 1; bool ignoreEmpty; string_view_t trimChars = {}; constexpr char_t getSplitAt() const MIJIN_NOEXCEPT { return '\n'; } constexpr bool getIgnoreEmpty() const MIJIN_NOEXCEPT { return ignoreEmpty; } constexpr bool getTrim() const MIJIN_NOEXCEPT { return !trimChars.empty(); } constexpr string_view_t getTrimChars() const MIJIN_NOEXCEPT { return trimChars; } constexpr line_t getLine() const MIJIN_NOEXCEPT { return line; } constexpr void onNext() MIJIN_NOEXCEPT { ++line; } }; template concept SplitLineTraitsType = SplitStringTraitsType && requires (const T& object) { { object.getLine() } -> std::convertible_to; }; static_assert(SplitLineTraitsType, char, unsigned>); static_assert(SplitLineTraitsType, char, unsigned>); template [[nodiscard]] auto trim(TString&& string, TChars&& chars); template TTraits> class SplitStringIterator { public: using char_t = TChar; using traits_t = TTraits; using string_view_t = traits_t::string_view_t; using base_t = string_view_t::iterator; using value_type = string_view_t; private: [[no_unique_address]] traits_t traits_; string_view_t full_; string_view_t::iterator pos_; string_view_t::iterator next_; public: constexpr SplitStringIterator(string_view_t full, base_t pos, traits_t traits = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v) : full_(full), pos_(pos), traits_(std::move(traits)) { findNext(); } constexpr explicit SplitStringIterator(traits_t traits = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v) : traits_(std::move(traits)) {} constexpr SplitStringIterator(const SplitStringIterator&) noexcept(std::is_nothrow_copy_constructible_v) = default; constexpr SplitStringIterator(SplitStringIterator&&) noexcept(std::is_nothrow_move_constructible_v) = default; constexpr SplitStringIterator& operator=(const SplitStringIterator&) noexcept(std::is_nothrow_copy_assignable_v) = default; constexpr SplitStringIterator& operator=(SplitStringIterator&&) noexcept(std::is_nothrow_move_assignable_v) = default; constexpr bool operator==(const SplitStringIterator& other) const MIJIN_NOEXCEPT { MIJIN_ASSERT(full_ == other.full_, "Comparing unrelated iterators."); return pos_ == other.pos_; } constexpr bool operator!=(const SplitStringIterator& other) const MIJIN_NOEXCEPT { MIJIN_ASSERT(full_ == other.full_, "Comparing unrelated iterators."); return pos_ != other.pos_; } constexpr bool operator<(const SplitStringIterator& other) const MIJIN_NOEXCEPT { MIJIN_ASSERT(full_ == other.full_, "Comparing unrelated iterators."); return pos_ < other.pos_; } constexpr bool operator<=(const SplitStringIterator& other) const MIJIN_NOEXCEPT { MIJIN_ASSERT(full_ == other.full_, "Comparing unrelated iterators."); return pos_ <= other.pos_; } constexpr bool operator>(const SplitStringIterator& other) const MIJIN_NOEXCEPT { MIJIN_ASSERT(full_ == other.full_, "Comparing unrelated iterators."); return pos_ > other.pos_; } constexpr bool operator>=(const SplitStringIterator& other) const MIJIN_NOEXCEPT { MIJIN_ASSERT(full_ == other.full_, "Comparing unrelated iterators."); return pos_ >= other.pos_; } [[nodiscard]] traits_t& getTraits() MIJIN_NOEXCEPT { return traits_; } [[nodiscard]] const traits_t& getTraits() const MIJIN_NOEXCEPT { return traits_; } constexpr value_type operator*() const MIJIN_NOEXCEPT { MIJIN_ASSERT(pos_ != full_.end(), "Dereferencing an invalid iterator."); string_view_t result{pos_, next_}; if (traits_.getTrim()) { result = trim(result, traits_.getTrimChars()); } return result; } constexpr SplitStringIterator& operator++() MIJIN_NOEXCEPT { MIJIN_ASSERT(pos_ != full_.end(), "Iterating past end."); if (next_ == full_.end()) { pos_ = full_.end(); } else { pos_ = std::next(next_); findNext(); } return *this; } constexpr SplitStringIterator operator++(int) const MIJIN_NOEXCEPT { SplitStringIterator copy(*this); ++copy; return copy; } // TODO // SplitStringIterator& operator--() MIJIN_NOEXCEPT // { // MIJIN_ASSERT(pos_ != full_.begin(), "Iterating past begin."); // next_ = std::prev(pos_); // pos_ = std::find(std::reverse_iterator(next_), std::reverse_iterator(full_.begin()), separator).base(); // } private: constexpr void findNext() { while (true) { if constexpr (requires{{ traits_.onNext() };}) { traits_.onNext(); } next_ = std::find(pos_, full_.end(), traits_.getSplitAt()); if (!traits_.getIgnoreEmpty() || pos_ == full_.end()) { break; } if (traits_.getTrim()) { const string_view_t trimChars = traits_.getTrimChars(); typename string_view_t::iterator trimmedPos = std::find_if(pos_, next_, [&](char_t chr) { return !trimChars.contains(chr); }); if (trimmedPos == next_) { pos_ = next_; // skip this part } } if (pos_ != next_) { break; } pos_ = std::next(pos_); } } }; template TTraits> class SplitStringRange { public: using char_t = TChar; using traits_t = TTraits; using string_view_t = traits_t::string_view_t; using iterator = SplitStringIterator; private: [[no_unique_address]] traits_t traits_; string_view_t stringView_; public: constexpr explicit SplitStringRange(string_view_t stringView, traits_t traits = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v) : stringView_(stringView), traits_(std::move(traits)) {} constexpr SplitStringRange(const SplitStringRange&) noexcept(std::is_nothrow_copy_constructible_v) = default; constexpr SplitStringRange(SplitStringRange&&) noexcept(std::is_nothrow_move_constructible_v) = default; constexpr SplitStringRange& operator=(const SplitStringRange&) noexcept(std::is_nothrow_copy_assignable_v) = default; constexpr SplitStringRange& operator=(SplitStringRange&&) noexcept(std::is_nothrow_move_assignable_v) = default; constexpr auto operator<=>(const SplitStringRange&) const noexcept = default; constexpr iterator begin() const MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v) { return iterator(stringView_, stringView_.begin(), traits_); } constexpr iterator end() const MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v) { return iterator(stringView_, stringView_.end(), traits_); } }; template TTraits = SplitLineTraitsCT> class LineIterator { public: using char_t = TChar; using line_t = TLine; using traits_t = TTraits; using base_t = SplitStringIterator; using string_view_t = base_t::string_view_t; using value_type = std::pair; private: base_t base_ = {}; public: constexpr LineIterator(string_view_t full, string_view_t::iterator pos, traits_t traits = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v) : base_(full, pos, std::move(traits)) {} constexpr explicit LineIterator(traits_t traits = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v) : base_(std::move(traits)) {} LineIterator(const LineIterator&) noexcept = default; LineIterator(LineIterator&&) noexcept = default; LineIterator& operator=(const LineIterator&) noexcept = default; LineIterator& operator=(LineIterator&&) noexcept = default; bool operator==(const LineIterator& other) const MIJIN_NOEXCEPT { return base_ == other.base_; } bool operator!=(const LineIterator& other) const MIJIN_NOEXCEPT { return base_ != other.base_; } bool operator<(const LineIterator& other) const MIJIN_NOEXCEPT { return base_ < other.base_; } bool operator>(const LineIterator& other) const MIJIN_NOEXCEPT { return base_ > other.base_; } bool operator<=(const LineIterator& other) const MIJIN_NOEXCEPT { return base_ <= other.base_; } bool operator>=(const LineIterator& other) const MIJIN_NOEXCEPT { return base_ >= other.base_; } constexpr value_type operator*() const MIJIN_NOEXCEPT { string_view_t stringView = *base_; if (!base_.getTraits().getTrim()) { // always split \r, even if not trimming other whitespace if (stringView.ends_with('\r')) { stringView = stringView.substr(0, stringView.size() - 1); } } return {stringView, base_.getTraits().line}; } constexpr LineIterator& operator++() MIJIN_NOEXCEPT { ++base_; return *this; } constexpr LineIterator operator++(int) const MIJIN_NOEXCEPT { SplitStringIterator copy(*this); ++copy; return copy; } }; template TTraits = SplitLineTraitsCT> class LineRange { public: using char_t = TChar; using line_t = TLine; using traits_t = TTraits; using iterator = LineIterator; using string_view_t = iterator::string_view_t; private: [[no_unique_address]] traits_t traits_; string_view_t stringView_; public: constexpr explicit LineRange(string_view_t stringView, traits_t traits = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v) : traits_(std::move(traits)), stringView_(stringView) {} constexpr LineRange(const LineRange&) noexcept(std::is_nothrow_copy_constructible_v) = default; constexpr LineRange(LineRange&&) noexcept(std::is_nothrow_move_constructible_v) = default; constexpr LineRange& operator=(const LineRange&) noexcept(std::is_nothrow_copy_assignable_v) = default; constexpr LineRange& operator=(LineRange&&) noexcept(std::is_nothrow_move_assignable_v) = default; constexpr auto operator<=>(const LineRange&) const noexcept = default; constexpr iterator begin() const MIJIN_NOEXCEPT { return iterator(stringView_, stringView_.begin(), traits_); } constexpr iterator end() const MIJIN_NOEXCEPT { return iterator(stringView_, stringView_.end(), traits_); } }; // // public functions // template [[nodiscard]] std::string join(const TRange& elements, const char* const delimiter) { std::ostringstream oss; auto first = std::begin(elements); auto last = std::end(elements); if (first != last) { std::copy(first, std::prev(last), std::ostream_iterator(oss, delimiter)); first = std::prev(last); } if (first != last) { oss << *first; } return oss.str(); } namespace detail { template void splitImpl(std::basic_string_view stringView, std::basic_string_view separator, const SplitOptions& options, TOutIterator outIterator, std::size_t& numEmitted) { using sv_t = std::basic_string_view; MIJIN_ASSERT(options.limitParts > 0, "Cannot split to zero parts."); MIJIN_ASSERT(!separator.empty(), "Separator cannot be empty."); if (separator.empty()) { return; } auto emit = [&](std::string_view result) { *outIterator = result; ++outIterator; ++numEmitted; }; if (options.limitParts <= 1) { emit(stringView); return; } auto start = stringView.begin(); auto pos = start; const auto end = stringView.end(); auto seperatorFound = [&]() { return pos <= (end - separator.size()) && sv_t(pos, pos + separator.size()) == separator; }; while (pos != end) { if (seperatorFound()) { if (!options.ignoreEmpty || pos != start) { emit(std::string_view(start, pos)); } start = pos = (pos + separator.size()); if (numEmitted == options.limitParts - 1) { if (options.ignoreEmpty) { while (seperatorFound()) { pos += separator.size(); } } emit(std::string_view(pos, end)); return; } if (!options.ignoreEmpty && pos == end) { // skipped a separator at the very end, add an empty entry emit(""); return; } } else { ++pos; } } if (start != end) { emit(std::string_view(start, end)); } } template std::vector> splitImpl(std::basic_string_view stringView, std::basic_string_view separator, const SplitOptions& options) { std::vector> result; std::size_t numEmitted = 0; splitImpl(stringView, separator, options, std::back_inserter(result), numEmitted); return result; } template std::array, count> splitFixedImpl(std::basic_string_view stringView, std::basic_string_view separator, SplitOptions options, std::size_t* outNumResults) { options.limitParts = count; std::array, count> result; std::size_t numEmitted = 0; splitImpl(stringView, separator, options, result.begin(), numEmitted); if (outNumResults != nullptr) { *outNumResults = numEmitted; } return result; } template bool equalsIgnoreCaseImpl(std::basic_string_view stringA, std::basic_string_view stringB) MIJIN_NOEXCEPT { if (stringA.size() != stringB.size()) { return false; } for (const auto [charA, charB] : zip(stringA, stringB)) { if (std::tolower(charA) != std::tolower(charB)) { return false; } } return true; } template std::basic_string_view trimPrefixImpl(std::basic_string_view stringView, std::basic_string_view charsToTrim) { stringView.remove_prefix(std::min(stringView.find_first_not_of(charsToTrim), stringView.size())); return stringView; } template std::basic_string_view trimSuffixImpl(std::basic_string_view stringView, std::basic_string_view charsToTrim) { stringView.remove_suffix(stringView.size() - std::min(stringView.find_last_not_of(charsToTrim) + 1, stringView.size())); return stringView; } template std::basic_string_view trimImpl(std::basic_string_view stringView, std::basic_string_view charsToTrim) { return trimPrefixImpl(trimSuffixImpl(stringView, charsToTrim), charsToTrim); } } template [[nodiscard]] auto split(TLeft&& stringView, TRight&& separator, const SplitOptions& options = {}) { return detail::splitImpl(std::basic_string_view(std::forward(stringView)), std::basic_string_view(std::forward(separator)), options); } template [[nodiscard]] auto splitFixed(TLeft&& stringView, TRight&& separator, SplitOptions options = {}, std::size_t* outNumResults = nullptr) { return detail::splitFixedImpl(std::basic_string_view(std::forward(stringView)), std::basic_string_view(std::forward(separator)), options, outNumResults); } template requires (SplitStringTraitsType) [[nodiscard]] SplitStringRange splitView(typename TTraits::string_view_t stringView, TTraits traits = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v) { return SplitStringRange(stringView, std::move(traits)); } template> [[nodiscard]] SplitStringRange> splitView(TStringView&& stringView) MIJIN_NOEXCEPT { return splitView>(std::basic_string_view(std::forward(stringView))); } template, typename TTrimChars = std::basic_string_view> [[nodiscard]] auto splitView(TStringView&& stringView, TChar splitAt, bool ignoreEmpty = true, TTrimChars trimChars = {}) MIJIN_NOEXCEPT { return splitView(std::basic_string_view(std::forward(stringView)), SplitStringTraitsRT{ .splitAt = splitAt, .ignoreEmpty = ignoreEmpty, .trimChars = std::basic_string_view(std::forward(trimChars)) }); } template requires(SplitLineTraitsType) [[nodiscard]] LineRange splitLines(typename TTraits::string_view_t stringView, TTraits traits = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v) { return LineRange(stringView, std::move(traits)); } template())), typename TChar = typename TStringView::value_type, typename TCharTraits = typename TStringView::traits_type, typename TTraits = SplitLineTraitsCT> [[nodiscard]] auto splitLines(TParam&& stringView) MIJIN_NOEXCEPT -> LineRange { return LineRange(std::basic_string_view(std::forward(stringView))); } template())), typename TChar = typename TStringView::value_type, typename TCharTraits = typename TStringView::traits_type, typename TTraits = SplitLineTraitsRT, typename TTrimChars = TStringView> [[nodiscard]] auto splitLines(TParam&& stringView, bool ignoreEmpty, TTrimChars&& trimChars = {}) MIJIN_NOEXCEPT -> LineRange { return LineRange(std::basic_string_view(std::forward(stringView)), TTraits{ .ignoreEmpty = ignoreEmpty, .trimChars = std::basic_string_view(std::forward(trimChars)) }); } template [[nodiscard]] auto trimPrefix(TString&& string, TChars&& chars) { return detail::trimPrefixImpl(std::string_view(std::forward(string)), std::string_view(std::forward(chars))); } template [[nodiscard]] auto trimPrefix(TString&& string) { return trimPrefix(string, DEFAULT_TRIM_CHARS>); } template [[nodiscard]] auto trimSuffix(TString&& string, TChars&& chars) { return detail::trimSuffixImpl(std::string_view(std::forward(string)), std::string_view(std::forward(chars))); } template [[nodiscard]] auto trimSuffix(TString&& string) { return trimSuffix(string, DEFAULT_TRIM_CHARS>); } template [[nodiscard]] auto trim(TString&& string, TChars&& chars) { return detail::trimImpl(std::string_view(std::forward(string)), std::string_view(std::forward(chars))); } template [[nodiscard]] auto trim(TString&& string) { return trim(string, DEFAULT_TRIM_CHARS>); } template [[nodiscard]] bool equalsIgnoreCase(TLeft&& left, TRight&& right) MIJIN_NOEXCEPT { return detail::equalsIgnoreCaseImpl(std::string_view(left), std::string_view(right)); } template constexpr void makeLower(std::basic_string& string) { std::transform(string.begin(), string.end(), string.begin(), [locale = std::locale()](TChar chr) { return std::tolower(chr, locale); }); } template constexpr void makeUpper(std::basic_string& string) { std::transform(string.begin(), string.end(), string.begin(), [locale = std::locale()](TChar chr) { return std::toupper(chr, locale); }); } template [[nodiscard]] constexpr auto toLower(TArgs&&... args) { std::basic_string string(std::forward(args)...); makeLower(string); return string; } template [[nodiscard]] constexpr auto toUpper(TArgs&&... args) { std::basic_string string(std::forward(args)...); makeUpper(string); return string; } template [[nodiscard]] constexpr bool toNumber(std::string_view stringView, TNumber& outNumber, int base = 10) MIJIN_NOEXCEPT { const char* start = &*stringView.begin(); const char* end = start + stringView.size(); const std::from_chars_result res = std::from_chars(start, end, outNumber, base); return res.ec == std::errc{} && res.ptr == end; } template [[nodiscard]] constexpr bool toNumber(std::basic_string_view stringView, TNumber& outNumber, int base = 10) MIJIN_NOEXCEPT requires (!std::is_same_v) { std::string asString; asString.resize(stringView.size()); // should only contain number symbols, so just cast down to char std::transform(stringView.begin(), stringView.end(), asString.begin(), [](TChar chr) { return static_cast(chr); }); return toNumber(asString, outNumber, base); } template [[nodiscard]] constexpr bool toNumber(std::string_view stringView, TNumber& outNumber, std::chars_format fmt = std::chars_format::general) MIJIN_NOEXCEPT { const char* start = &*stringView.begin(); const char* end = start + stringView.size(); const std::from_chars_result res = std::from_chars(start, end, outNumber, fmt); return res.ec == std::errc{} && res.ptr == end; } template [[nodiscard]] constexpr bool toNumber(std::basic_string_view stringView, TNumber& outNumber, std::chars_format fmt = std::chars_format::general) MIJIN_NOEXCEPT requires (!std::is_same_v) { std::string asString; asString.resize(stringView.size()); // should only contain number symbols, so just cast down to char std::transform(stringView.begin(), stringView.end(), asString.begin(), [](TChar chr) { return static_cast(chr); }); return toNumber(asString, outNumber, fmt); } template [[nodiscard]] constexpr bool isDecimalChar(TChar chr) noexcept { return (chr >= TChar('0') && chr <= TChar('9')); } template [[nodiscard]] constexpr bool isHexadecimalChar(TChar chr) noexcept { return isDecimalChar(chr) || (chr >= TChar('A') && chr <= TChar('F')) || (chr >= TChar('a') && chr <= TChar('f')); } template bool compareIgnoreCase(TChar left, TChar right) MIJIN_NOEXCEPT { return std::tolower(left) == std::tolower(right); } [[nodiscard]] inline auto findIgnoreCase(std::string_view haystack, std::string_view needle) { return std::ranges::search(haystack, needle, &compareIgnoreCase); } [[nodiscard]] inline bool startsWithIgnoreCase(std::string_view string, std::string_view part) { if (part.size() > string.size()) { return false; } return std::ranges::equal(string.substr(0, part.size()), part, &compareIgnoreCase); } [[nodiscard]] inline bool endsWithIgnoreCase(std::string_view string, std::string_view part) { if (part.size() > string.size()) { return false; } return std::ranges::equal(string.substr(string.size() - part.size()), part, &compareIgnoreCase); } namespace pipe { struct Join { const char* delimiter; explicit Join(const char* delimiter_) MIJIN_NOEXCEPT: delimiter(delimiter_) {} }; template auto operator|(TIterable&& iterable, const Join& joiner) { return join(std::forward(iterable), joiner.delimiter); } } // namespace pipe template ConvertCharTypeResult convertCharType(const TFrom* chrFrom, std::size_t numFrom, TTo* outTo, std::size_t numTo, std::mbstate_t& mbstate) MIJIN_NOEXCEPT { if constexpr (std::is_same_v) { if constexpr (std::is_same_v) { const std::size_t result = std::mbrtowc(outTo, chrFrom, numFrom, &mbstate); if (result == static_cast(-1)) { return {}; } return { .numRead = static_cast(result), .numWritten = 1 }; } if constexpr (std::is_same_v) { } } if constexpr (std::is_same_v) { if constexpr (std::is_same_v) { if (numTo < MB_CUR_MAX) { char tmpBuf[MB_LEN_MAX]; const ConvertCharTypeResult result = convertCharType(chrFrom, numFrom, tmpBuf, MB_LEN_MAX, mbstate); if (result && result.numWritten <= numTo) { std::memcpy(outTo, tmpBuf, result.numWritten); } return result; } const std::size_t result = std::wcrtomb(outTo, *chrFrom, &mbstate); if (result == static_cast(-1)) { return {}; } return { .numRead = 1, .numWritten = static_cast(result) }; } if constexpr (std::is_same_v) { } } if constexpr (std::is_same_v) { } } template ConvertCharTypeResult convertCharType(const TFrom* chrFrom, std::size_t numFrom, TTo* outTo, std::size_t numTo) MIJIN_NOEXCEPT { std::mbstate_t mbstate; return convertCharType(chrFrom, numFrom, outTo, numTo, mbstate); } template struct [[nodiscard]] ConvertStringTypeResult { TIterator iterator; bool success; }; template TIterator> ConvertStringTypeResult convertStringType(std::basic_string_view strFrom, TIterator outIterator) { TTo outBuffer[MB_LEN_MAX]; std::mbstate_t mbstate = {}; for (auto it = strFrom.begin(); it != strFrom.end();) { const std::size_t remaining = std::distance(it, strFrom.end()); const ConvertCharTypeResult result = convertCharType(&*it, remaining, outBuffer, MB_LEN_MAX, mbstate); if (!result) { return { .iterator = outIterator, .success = false }; } for (unsigned pos = 0; pos < result.numWritten; ++pos) { *outIterator = outBuffer[pos]; ++outIterator; } it = std::next(it, result.numRead); } return { .iterator = outIterator, .success = true }; } template bool convertStringType(std::basic_string_view strFrom, std::basic_string& outString) { if constexpr (std::is_same_v) { outString += strFrom; return true; } else { return convertStringType(strFrom, std::back_inserter(outString)).success; } } template bool convertStringType(const TFrom* strFrom, std::basic_string& outString) { return convertStringType(std::basic_string_view(strFrom), outString); } template> std::basic_string quoted(std::basic_string_view input) { std::basic_string result; result.reserve(input.size() + 2); result.push_back(TChar('"')); for (const TChar chr : input) { if (chr == TChar('"') || chr == TChar('\\')) { result.push_back(TChar('\\')); } result.push_back(chr); } result.push_back(TChar('"')); return result; } template std::basic_string quoted(const std::basic_string& input) { return quoted(std::basic_string_view(input)); } } // namespace mijin #endif // !defined(MIJIN_UTIL_STRING_HPP_INCLUDED)