#include "libimp/fmt.h" #include // std::snprintf #include // std::put_time #include // std::ostringstream #include #include // std::memcpy #include // std::min #include #include "libimp/codecvt.h" LIBIMP_NAMESPACE_BEG_ /** * @brief Format conversions helpers. * @see http://personal.ee.surrey.ac.uk/Personal/R.Bowden/C/printf.html * https://en.cppreference.com/w/cpp/io/c/fprintf */ namespace { constexpr std::size_t roundup(std::size_t sz) noexcept { return (sz & ~(fmt_context_aligned_size - 1)) + fmt_context_aligned_size; } span normalize(span const &a) { if (a.empty()) return {}; return a.first(a.size() - (a.back() == '\0' ? 1 : 0)); } span smem_cpy(span const &sbuf, span a) noexcept { if (sbuf.empty()) return {}; a = normalize(a); auto sz = (std::min)(sbuf.size() - 1, a.size()); if (sz != 0) std::memcpy(sbuf.data(), a.data(), sz); return sbuf.first(sz); } span sbuf_cpy(span sbuf, span const &a) noexcept { sbuf = smem_cpy(sbuf, a); *sbuf.end() = '\0'; return sbuf; } span sbuf_cat(span const &sbuf, std::initializer_list> args) noexcept { std::size_t remain = sbuf.size(); for (auto s : args) { remain -= smem_cpy(sbuf.last(remain), s).size(); } auto sz = sbuf.size() - remain; sbuf[sz] = '\0'; return sbuf.first(sz); } span local_fmt_str() noexcept { thread_local std::array sbuf; return sbuf; } char const *as_cstr(span const &a) { if (a.empty()) return ""; if (a.back() == '\0') return a.data(); return sbuf_cpy(local_fmt_str(), a).data(); } span fmt_of(span const &fstr, span const &s) { return sbuf_cat(local_fmt_str(), {"%", fstr, s}); } span fmt_of_unsigned(span fstr, span const &l) { if (fstr.empty()) { return fmt_of(l, "u"); } fstr = normalize(fstr); switch (fstr.back()) { case 'o': case 'x': case 'X': case 'u': return sbuf_cat(local_fmt_str(), {"%", fstr.first(fstr.size() - 1), l, fstr.last(1)}); default : return sbuf_cat(local_fmt_str(), {"%", fstr, l, "u"}); } } span fmt_of_signed(span fstr, span const &l) { if (fstr.empty()) { return fmt_of(l, "d"); } fstr = normalize(fstr); switch (fstr.back()) { case 'o': case 'x': case 'X': case 'u': return fmt_of_unsigned(fstr, l); default : return sbuf_cat(local_fmt_str(), {"%", fstr, l, "d"}); } } span fmt_of_float(span fstr, span const &l) { if (fstr.empty()) { return fmt_of(l, "f"); } fstr = normalize(fstr); switch (fstr.back()) { case 'e': case 'E': case 'g': case 'G': return sbuf_cat(local_fmt_str(), {"%", fstr.first(fstr.size() - 1), l, fstr.last(1)}); default : return sbuf_cat(local_fmt_str(), {"%", fstr, l, "f"}); } } template int sprintf(fmt_context &ctx, span const &sfmt, A a) { for (int sz = -1;;) { auto sbuf = ctx.buffer(sz + 1); sz = std::snprintf(sbuf.data(), sbuf.size(), sfmt.data(), a); if (sz <= 0) { return sz; } if (sz < sbuf.size()) { ctx.expend(sz); return sz; } } } template bool sprintf(fmt_context &ctx, F fop, span const &fstr, span const &s, A a) noexcept { LIBIMP_TRY { return ::LIBIMP::sprintf(ctx, fop(fstr, s), a) >= 0; } LIBIMP_CATCH(...) { return false; } } } // namespace /// @brief The context of fmt. fmt_context::fmt_context(std::string &j) noexcept : joined_(j) , offset_(0) {} std::size_t fmt_context::capacity() noexcept { return (offset_ < sbuf_.size()) ? sbuf_.size() : joined_.size(); } void fmt_context::reset() noexcept { offset_ = 0; } bool fmt_context::finish() noexcept { LIBIMP_TRY { if (offset_ < sbuf_.size()) { joined_.assign(sbuf_.data(), offset_); } else { joined_.resize(offset_); } return true; } LIBIMP_CATCH(...) { return false; } } span fmt_context::buffer(std::size_t sz) noexcept { LIBIMP_TRY { if (offset_ < sbuf_.size()) { if ((offset_ + sz) < sbuf_.size()) { return make_span(sbuf_).subspan(offset_); } else { /// @remark switch the cache to std::string joined_.assign(sbuf_.data(), offset_); joined_.resize(roundup(offset_ + sz)); } } else if ((offset_ + sz) >= joined_.size()) { joined_.resize(roundup(offset_ + sz)); } return {&joined_[offset_], joined_.size() - offset_}; } LIBIMP_CATCH(...) { return {}; } } void fmt_context::expend(std::size_t sz) noexcept { offset_ += sz; } bool fmt_context::append(std::string const &str) noexcept { auto sbuf = buffer(str.size()); if (sbuf.size() < str.size()) { return false; } std::memcpy(sbuf.data(), str.data(), str.size()); offset_ += str.size(); return true; } /// @brief To string conversion. bool to_string(fmt_context &ctx, char const *a) noexcept { return to_string(ctx, a, {}); } bool to_string(fmt_context &ctx, std::string const &a) noexcept { return ctx.append(a); } bool to_string(fmt_context &ctx, char const *a, span fstr) noexcept { if (a == nullptr) return false; return ::LIBIMP::sprintf(ctx, fmt_of, fstr, "s", a); } bool to_string(fmt_context &ctx, char a) noexcept { return ::LIBIMP::sprintf(ctx, fmt_of, {}, "c", a); } bool to_string(fmt_context &ctx, wchar_t a) noexcept { LIBIMP_TRY { std::string des; cvt_sstr(std::wstring{a}, des); return ctx.append(des); } LIBIMP_CATCH(...) { return false; } } bool to_string(fmt_context &ctx, char16_t a) noexcept { LIBIMP_TRY { std::string des; cvt_sstr(std::u16string{a}, des); return ctx.append(des); } LIBIMP_CATCH(...) { return false; } } bool to_string(fmt_context &ctx, char32_t a) noexcept { LIBIMP_TRY { std::string des; cvt_sstr(std::u32string{a}, des); return ctx.append(des); } LIBIMP_CATCH(...) { return false; } } bool to_string(fmt_context &ctx, signed short a, span fstr) noexcept { return ::LIBIMP::sprintf(ctx, fmt_of_signed, fstr, "h", a); } bool to_string(fmt_context &ctx, unsigned short a, span fstr) noexcept { return ::LIBIMP::sprintf(ctx, fmt_of_unsigned, fstr, "h", a); } bool to_string(fmt_context &ctx, signed int a, span fstr) noexcept { return ::LIBIMP::sprintf(ctx, fmt_of_signed, fstr, "", a); } bool to_string(fmt_context &ctx, unsigned int a, span fstr) noexcept { return ::LIBIMP::sprintf(ctx, fmt_of_unsigned, fstr, "", a); } bool to_string(fmt_context &ctx, signed long a, span fstr) noexcept { return ::LIBIMP::sprintf(ctx, fmt_of_signed, fstr, "l", a); } bool to_string(fmt_context &ctx, unsigned long a, span fstr) noexcept { return ::LIBIMP::sprintf(ctx, fmt_of_unsigned, fstr, "l", a); } bool to_string(fmt_context &ctx, signed long long a, span fstr) noexcept { return ::LIBIMP::sprintf(ctx, fmt_of_signed, fstr, "ll", a); } bool to_string(fmt_context &ctx, unsigned long long a, span fstr) noexcept { return ::LIBIMP::sprintf(ctx, fmt_of_unsigned, fstr, "ll", a); } bool to_string(fmt_context &ctx, double a, span fstr) noexcept { return ::LIBIMP::sprintf(ctx, fmt_of_float, fstr, "", a); } bool to_string(fmt_context &ctx, long double a, span fstr) noexcept { return ::LIBIMP::sprintf(ctx, fmt_of_float, fstr, "L", a); } bool to_string(fmt_context &ctx, std::nullptr_t) noexcept { return ctx.append("null"); } template <> bool to_string(fmt_context &ctx, void const volatile *a) noexcept { if (a == nullptr) { return to_string(ctx, nullptr); } return ::LIBIMP::sprintf(ctx, fmt_of, "", "p", a); } bool to_string(fmt_context &ctx, std::tm const &a, span fstr) noexcept { if (fstr.empty()) { fstr = "%Y-%m-%d %H:%M:%S"; } LIBIMP_TRY { std::ostringstream ss; ss << std::put_time(&a, as_cstr(fstr)); return ctx.append(ss.str()); } LIBIMP_CATCH(...) { return {}; } } LIBIMP_NAMESPACE_END_