diff --git a/source/mijin/util/string.hpp b/source/mijin/util/string.hpp index 33cacad..42f1365 100644 --- a/source/mijin/util/string.hpp +++ b/source/mijin/util/string.hpp @@ -33,6 +33,16 @@ namespace mijin // 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 // @@ -98,6 +108,361 @@ struct SplitOptions bool ignoreEmpty = true; }; +struct [[nodiscard]] ConvertCharTypeResult +{ + unsigned numRead = 0; + unsigned numWritten = 0; + + constexpr operator bool() const MIJIN_NOEXCEPT + { + return numRead != 0 || numWritten != 0; + } + constexpr bool operator !() const MIJIN_NOEXCEPT + { + return !static_cast(*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 // @@ -273,13 +638,6 @@ std::basic_string_view trimImpl(std::basic_string_view -static const std::array DEFAULT_TRIM_CHARS_DATA = {TChar(' '), TChar('\t'), TChar('\r'), TChar('\n')}; - -template -static const std::basic_string_view> DEFAULT_TRIM_CHARS - = {DEFAULT_TRIM_CHARS_DATA.begin(), DEFAULT_TRIM_CHARS_DATA.end()}; } template @@ -296,6 +654,61 @@ template 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) @@ -307,7 +720,7 @@ template [[nodiscard]] auto trimPrefix(TString&& string) { - return trimPrefix(string, detail::DEFAULT_TRIM_CHARS>); + return trimPrefix(string, DEFAULT_TRIM_CHARS>); } template @@ -321,7 +734,7 @@ template [[nodiscard]] auto trimSuffix(TString&& string) { - return trimSuffix(string, detail::DEFAULT_TRIM_CHARS>); + return trimSuffix(string, DEFAULT_TRIM_CHARS>); } template @@ -335,7 +748,7 @@ template [[nodiscard]] auto trim(TString&& string) { - return trim(string, detail::DEFAULT_TRIM_CHARS>); + return trim(string, DEFAULT_TRIM_CHARS>); } template @@ -487,21 +900,6 @@ auto operator|(TIterable&& iterable, const Join& joiner) } } // namespace pipe -struct [[nodiscard]] ConvertCharTypeResult -{ - unsigned numRead = 0; - unsigned numWritten = 0; - - constexpr operator bool() const MIJIN_NOEXCEPT - { - return numRead != 0 || numWritten != 0; - } - constexpr bool operator !() const MIJIN_NOEXCEPT - { - return !static_cast(*this); - } -}; - template ConvertCharTypeResult convertCharType(const TFrom* chrFrom, std::size_t numFrom, TTo* outTo, std::size_t numTo, std::mbstate_t& mbstate) MIJIN_NOEXCEPT {