/* String utilities for OctaSTD. * * This file is part of OctaSTD. See COPYING.md for futher information. */ #ifndef OSTD_STRING_HH #define OSTD_STRING_HH #include #include #include #include #include #include #include #include "ostd/utility.hh" #include "ostd/range.hh" #include "ostd/vector.hh" #include "ostd/type_traits.hh" #include "ostd/algorithm.hh" namespace ostd { template struct CharRangeBase: InputRange< CharRangeBase, ContiguousRangeTag, T > { private: struct Nat {}; public: CharRangeBase(): p_beg(nullptr), p_end(nullptr) {}; template CharRangeBase(T *beg, U end, EnableIf< (IsPointer || IsNullPointer) && IsConvertible, Nat > = Nat()): p_beg(beg), p_end(end) {} CharRangeBase(T *beg, size_t n): p_beg(beg), p_end(beg + n) {} /* TODO: traits for utf-16/utf-32 string lengths, for now assume char */ template CharRangeBase( U beg, EnableIf && !IsArray, Nat> = Nat() ): p_beg(beg), p_end(static_cast(beg) + (beg ? strlen(beg) : 0)) {} CharRangeBase(std::nullptr_t): p_beg(nullptr), p_end(nullptr) {} template CharRangeBase(U (&beg)[N], EnableIf, Nat> = Nat()): p_beg(beg), p_end(beg + N - (beg[N - 1] == '\0')) {} template CharRangeBase(std::basic_string const &s, EnableIf< IsConvertible, Nat > = Nat()): p_beg(s.data()), p_end(s.data() + s.size()) {} template>> CharRangeBase(CharRangeBase const &v): p_beg(&v[0]), p_end(&v[v.size()]) {} CharRangeBase &operator=(CharRangeBase const &v) { p_beg = v.p_beg; p_end = v.p_end; return *this; } template CharRangeBase &operator=(std::basic_string const &s) { p_beg = s.data(); p_end = s.data() + s.size(); return *this; } /* TODO: traits for utf-16/utf-32 string lengths, for now assume char */ CharRangeBase &operator=(T *s) { p_beg = s; p_end = s + (s ? strlen(s) : 0); return *this; } bool empty() const { return p_beg == p_end; } bool pop_front() { if (p_beg == p_end) { return false; } ++p_beg; return true; } bool push_front() { --p_beg; return true; } size_t pop_front_n(size_t n) { size_t olen = p_end - p_beg; p_beg += n; if (p_beg > p_end) { p_beg = p_end; return olen; } return n; } size_t push_front_n(size_t n) { p_beg -= n; return true; } T &front() const { return *p_beg; } bool equals_front(CharRangeBase const &range) const { return p_beg == range.p_beg; } ptrdiff_t distance_front(CharRangeBase const &range) const { return range.p_beg - p_beg; } bool pop_back() { if (p_end == p_beg) { return false; } --p_end; return true; } bool push_back() { ++p_end; return true; } size_t pop_back_n(size_t n) { size_t olen = p_end - p_beg; p_end -= n; if (p_end < p_beg) { p_end = p_beg; return olen; } return n; } size_t push_back_n(size_t n) { p_end += n; return true; } T &back() const { return *(p_end - 1); } bool equals_back(CharRangeBase const &range) const { return p_end == range.p_end; } ptrdiff_t distance_back(CharRangeBase const &range) const { return range.p_end - p_end; } size_t size() const { return p_end - p_beg; } CharRangeBase slice(size_t start, size_t end) const { return CharRangeBase(p_beg + start, p_beg + end); } T &operator[](size_t i) const { return p_beg[i]; } bool put(T v) { if (empty()) { return false; } *(p_beg++) = v; return true; } size_t put_n(T const *p, size_t n) { size_t an = ostd::min(n, size()); memcpy(p_beg, p, an * sizeof(T)); p_beg += an; return an; } T *data() { return p_beg; } T const *data() const { return p_beg; } /* non-range */ int compare(CharRangeBase s) const { size_t s1 = size(), s2 = s.size(); int ret; if (!s1 || !s2) { goto diffsize; } if ((ret = memcmp(data(), s.data(), ostd::min(s1, s2)))) { return ret; } diffsize: return (s1 < s2) ? -1 : ((s1 > s2) ? 1 : 0); } int case_compare(CharRangeBase s) const { size_t s1 = size(), s2 = s.size(); for (size_t i = 0, ms = ostd::min(s1, s2); i < ms; ++i) { int d = toupper(p_beg[i]) - toupper(s[i]); if (d) { return d; } } return (s1 < s2) ? -1 : ((s1 > s2) ? 1 : 0); } template EnableIf, size_t> copy(R &&orange, size_t n = -1) { return orange.put_n(data(), ostd::min(n, size())); } size_t copy(RemoveCv *p, size_t n = -1) { size_t c = ostd::min(n, size()); memcpy(p, data(), c * sizeof(T)); return c; } /* that way we can assign, append etc to std::string */ operator std::basic_string_view>() const { return std::basic_string_view>{data(), size()}; } private: T *p_beg, *p_end; }; using CharRange = CharRangeBase; using ConstCharRange = CharRangeBase; inline bool operator==(ConstCharRange lhs, ConstCharRange rhs) { return !lhs.compare(rhs); } inline bool operator!=(ConstCharRange lhs, ConstCharRange rhs) { return lhs.compare(rhs); } inline bool operator<(ConstCharRange lhs, ConstCharRange rhs) { return lhs.compare(rhs) < 0; } inline bool operator>(ConstCharRange lhs, ConstCharRange rhs) { return lhs.compare(rhs) > 0; } inline bool operator<=(ConstCharRange lhs, ConstCharRange rhs) { return lhs.compare(rhs) <= 0; } inline bool operator>=(ConstCharRange lhs, ConstCharRange rhs) { return lhs.compare(rhs) >= 0; } inline bool starts_with(ConstCharRange a, ConstCharRange b) { if (a.size() < b.size()) { return false; } return a.slice(0, b.size()) == b; } template struct ranged_traits> { static CharRangeBase iter(std::basic_string &v) { return CharRangeBase{v.data(), v.size()}; } }; template struct ranged_traits const> { static CharRangeBase iter(std::basic_string const &v) { return CharRangeBase{v.data(), v.size()}; } }; template< typename T, typename TR = std::char_traits, typename A = std::allocator, typename R > inline std::basic_string make_string(R range, A const &alloc = A{}) { std::basic_string ret{alloc}; using C = RangeCategory; if constexpr(std::is_convertible_v) { /* finite random access or contiguous */ auto h = range.half(); ret.reserve(range.size()); ret.insert(ret.end(), h, h + range.size()); } else { /* infinite random access and below */ for (; !range.empty(); range.pop_front()) { ret.push_back(range.front()); } } return ret; } template< typename R, typename TR = std::char_traits>>, typename A = std::allocator>> > inline std::basic_string>, TR, A> make_string( R range, A const &alloc = A{} ) { return make_string>, TR, A>(std::move(range), alloc); } /* string literals */ inline namespace literals { inline namespace string_literals { inline ConstCharRange operator "" _sr(char const *str, size_t len) { return ConstCharRange(str, len); } } } namespace detail { template< typename T, bool = IsConvertible, bool = IsConvertible > struct ConcatPut; template struct ConcatPut { template static bool put(R &sink, ConstCharRange v) { return v.size() && (sink.put_n(&v[0], v.size()) == v.size()); } }; template struct ConcatPut { template static bool put(R &sink, char v) { return sink.put(v); } }; } template bool concat(R &&sink, T const &v, ConstCharRange sep, F func) { auto range = ostd::iter(v); if (range.empty()) { return true; } for (;;) { if (!detail::ConcatPut< decltype(func(range.front())) >::put(sink, func(range.front()))) { return false; } range.pop_front(); if (range.empty()) { break; } sink.put_n(&sep[0], sep.size()); } return true; } template bool concat(R &&sink, T const &v, ConstCharRange sep = " ") { auto range = ostd::iter(v); if (range.empty()) { return true; } for (;;) { ConstCharRange ret = range.front(); if (!ret.size() || (sink.put_n(&ret[0], ret.size()) != ret.size())) { return false; } range.pop_front(); if (range.empty()) { break; } sink.put_n(&sep[0], sep.size()); } return true; } template bool concat(R &&sink, std::initializer_list v, ConstCharRange sep, F func) { return concat(sink, ostd::iter(v), sep, func); } template bool concat(R &&sink, std::initializer_list v, ConstCharRange sep = " ") { return concat(sink, ostd::iter(v), sep); } namespace detail { template struct TostrRange: OutputRange, char> { TostrRange() = delete; TostrRange(R &out): p_out(out), p_written(0) {} bool put(char v) { bool ret = p_out.put(v); p_written += ret; return ret; } size_t put_n(char const *v, size_t n) { size_t ret = p_out.put_n(v, n); p_written += ret; return ret; } size_t put_string(ConstCharRange r) { return put_n(&r[0], r.size()); } size_t get_written() const { return p_written; } private: R &p_out; size_t p_written; }; template static auto test_stringify(int) -> BoolConstant().stringify()), std::string>>; template static True test_stringify(decltype(std::declval().to_string (std::declval())) *); template static False test_stringify(...); template constexpr bool StringifyTest = decltype(test_stringify(0))::value; template static True test_iterable(decltype(ostd::iter(std::declval())) *); template static False test_iterable(...); template constexpr bool IterableTest = decltype(test_iterable(0))::value; } template struct ToString; template struct ToString>> { using Argument = RemoveCv>; using Result = std::string; std::string operator()(T const &v) const { std::string ret("{"); auto x = appender(); if (concat(x, ostd::iter(v), ", ", ToString< RemoveConst >> >())) { ret += x.get(); } ret += "}"; return ret; } }; template struct ToString>> >> { using Argument = RemoveCv>; using Result = std::string; std::string operator()(T const &v) const { auto app = appender(); detail::TostrRange> sink(app); if (!v.to_string(sink)) { return std::string{}; } return std::move(app.get()); } }; template<> struct ToString { using Argument = bool; using Result = std::string; std::string operator()(bool b) { return b ? "true" : "false"; } }; template<> struct ToString { using Argument = char; using Result = std::string; std::string operator()(char c) { std::string ret; ret += c; return ret; } }; #define OSTD_TOSTR_NUM(T) \ template<> \ struct ToString { \ using Argument = T; \ using Result = std::string; \ std::string operator()(T v) { \ return std::to_string(v); \ } \ }; OSTD_TOSTR_NUM(sbyte) OSTD_TOSTR_NUM(short) OSTD_TOSTR_NUM(int) OSTD_TOSTR_NUM(long) OSTD_TOSTR_NUM(float) OSTD_TOSTR_NUM(double) OSTD_TOSTR_NUM(byte) OSTD_TOSTR_NUM(ushort) OSTD_TOSTR_NUM(uint) OSTD_TOSTR_NUM(ulong) OSTD_TOSTR_NUM(llong) OSTD_TOSTR_NUM(ullong) OSTD_TOSTR_NUM(ldouble) #undef OSTD_TOSTR_NUM template struct ToString { using Argument = T *; using Result = std::string; std::string operator()(Argument v) { char buf[16]; sprintf(buf, "%p", v); return buf; } }; template<> struct ToString { using Argument = char const *; using Result = std::string; std::string operator()(char const *s) { return s; } }; template<> struct ToString { using Argument = char *; using Result = std::string; std::string operator()(char *s) { return s; } }; template<> struct ToString { using Argument = std::string; using Result = std::string; std::string operator()(Argument const &s) { return s; } }; template<> struct ToString { using Argument = CharRange; using Result = std::string; std::string operator()(Argument const &s) { return std::string{s}; } }; template<> struct ToString { using Argument = ConstCharRange; using Result = std::string; std::string operator()(Argument const &s) { return std::string{s}; } }; template struct ToString> { using Argument = std::pair; using Result = std::string; std::string operator()(Argument const &v) { std::string ret{"{"}; ret += ToString>>()(v.first); ret += ", "; ret += ToString>>()(v.second); ret += "}"; return ret; } }; namespace detail { template struct TupleToString { template static void append(std::string &ret, T const &tup) { ret += ", "; ret += ToString(tup)) >>>()(std::get(tup)); TupleToString::append(ret, tup); } }; template struct TupleToString { template static void append(std::string &, T const &) {} }; template struct TupleToString<0, N> { template static void append(std::string &ret, T const &tup) { ret += ToString(tup)) >>>()(std::get<0>(tup)); TupleToString<1, N>::append(ret, tup); } }; } template struct ToString> { using Argument = std::tuple; using Result = std::string; std::string operator()(Argument const &v) { std::string ret("{"); detail::TupleToString<0, sizeof...(T)>::append(ret, v); ret += "}"; return ret; } }; template typename ToString::Result to_string(T const &v) { return ToString>>()(v); } template std::string to_string(std::initializer_list init) { return to_string(iter(init)); } template struct TempCString { private: RemoveCv> *p_buf; bool p_allocated; public: TempCString() = delete; TempCString(TempCString const &) = delete; TempCString(TempCString &&s): p_buf(s.p_buf), p_allocated(s.p_allocated) { s.p_buf = nullptr; s.p_allocated = false; } TempCString(R input, RemoveCv> *sbuf, size_t bufsize) : p_buf(nullptr), p_allocated(false) { if (input.empty()) { return; } if (input.size() >= bufsize) { p_buf = new RemoveCv>[input.size() + 1]; p_allocated = true; } else { p_buf = sbuf; } p_buf[input.copy(p_buf)] = '\0'; } ~TempCString() { if (p_allocated) { delete[] p_buf; } } TempCString &operator=(TempCString const &) = delete; TempCString &operator=(TempCString &&s) { swap(s); return *this; } operator RemoveCv> const *() const { return p_buf; } RemoveCv> const *get() const { return p_buf; } void swap(TempCString &s) { using std::swap; swap(p_buf, s.p_buf); swap(p_allocated, s.p_allocated); } }; template inline void swap(TempCString &a, TempCString &b) { a.swap(b); } template inline TempCString to_temp_cstr( R input, RemoveCv> *buf, size_t bufsize ) { return TempCString(input, buf, bufsize); } } /* namespace ostd */ namespace std { template<> struct hash { size_t operator()(ostd::CharRange const &v) const { return hash{}(v); } }; template<> struct hash { size_t operator()(ostd::ConstCharRange const &v) const { return hash{}(v); } }; } #endif