#ifndef ADHOCUTIL_COMPILE_TIME_FORMATTER_H #define ADHOCUTIL_COMPILE_TIME_FORMATTER_H #include #include #include #include #include #include // IWYU pragma: export #include namespace AdHoc { // Template char utils template constexpr bool isdigit(const char_type & ch) { return (ch >= '0' && ch <= '9'); } template constexpr bool ispositivedigit(const char_type & ch) { return (ch >= '1' && ch <= '9'); } // Template string utils template static constexpr auto strlen() { auto off = 0U; while (S[off]) { ++off; } return off; } template static constexpr auto strlen(const char_type * S) { auto off = 0U; while (S[off]) { ++off; } return off; } template()> static constexpr std::optional strchr() { static_assert(start <= L); decltype(start) off = start; while (off < L && S[off] != n) { ++off; } if (off == L) { return {}; } return off; } template()> static constexpr decltype(L) strchrnul() { decltype(start) off = start; while (off < L && S[off] != n) { ++off; } return off; } template class FormatterDetail; /// Template used to apply parameters to a stream. template struct StreamWriter { /// Write parameters to stream. template static void write(stream &, const Pn &...) { static_assert(!L, "invalid format string/arguments"); } }; /// Helper to simplify implementations of StreamWriter. template struct StreamWriterBase { /// Continue processing parameters. template static inline void next(stream & s, const Pn &... pn) { FormatterDetail::template Parser::run(s, pn...); } }; #define StreamWriterT(C...) \ template \ struct StreamWriter : \ public StreamWriterBase #define StreamWriterTP(P, C...) \ template \ struct StreamWriter : \ public StreamWriterBase // Default stream writer formatter StreamWriterT('?') { template static inline void write(stream & s, const P & p, const Pn &... pn) { s << p; StreamWriter::next(s, pn...); } }; // Escaped % stream writer formatter StreamWriterT('%') { template static inline void write(stream & s, const Pn &... pn) { s << '%'; StreamWriter::next(s, pn...); } }; template static inline void appendStream(stream & s, const char_type * p, size_t n) { s.write(p, n); } template static inline auto streamLength(stream & s) { return s.tellp(); } /** * Compile time string formatter. * @param S the format string. */ template class FormatterDetail { private: using strlen_t = decltype(strlen()); template friend struct StreamWriterBase; public: /// The derived charater type of the format string. using char_type = typename std::decay::type; /** * Get a string containing the result of formatting. * @param pn the format arguments. * @return the formatted string. */ template static inline auto get(const Pn &... pn) { std::basic_stringstream s; return write(s, pn...).str(); } /** * Get a string containing the result of formatting. * @param pn the format arguments. * @return the formatted string. */ template inline auto operator()(const Pn &... pn) const { return get(pn...); } /** * Write the result of formatting to the given stream. * @param s the stream to write to. * @param pn the format arguments. * @return the stream. */ template static inline stream & write(stream & s, const Pn &... pn) { return Parser::run(s, pn...); } /** * Write the result of formatting to the given stream. * @param s the stream to write to. * @param pn the format arguments. * @return the stream. */ template inline typename std::enable_if, stream>, stream>::type & operator()(stream & s, const Pn &... pn) const { return write(s, pn...); } private: template struct Parser { static inline stream & run(stream & s, const Pn &... pn) { if (pos != L) { constexpr auto ph = strchrnul(); if constexpr (ph != pos) { appendStream(s, &S[pos], ph - pos); } if constexpr (ph != L) { packAndWrite(s, pn...); } } return s; } template static inline void packAndWrite(stream & s, const Pn &... pn) { if constexpr (ph + off == L || sizeof...(Pck) == 32) { StreamWriter::write(s, pn...); } else if constexpr (ph + off < L) { packAndWrite(s, pn...); } } }; }; // New C++20 implementation namespace support { template class basic_fixed_string { public: // cppcheck-suppress noExplicitConstructor constexpr basic_fixed_string(const CharT (&str)[N + 1]) { for (decltype(N) x = 0; x < N; x++) { arr.at(x) = str[x]; } arr.at(N) = '\0'; } constexpr basic_fixed_string(const CharT * str, decltype(N) len) { for (decltype(N) x = 0; x < len; x++) { arr.at(x) = str[x]; } arr.at(N) = '\0'; } constexpr const char * s() const { return arr.data(); } constexpr operator const char *() const { return s(); } constexpr std::string_view v() const { return {arr.data(), arr.size() - 1}; } constexpr auto & operator[](std::size_t n) const { return arr[n]; } constexpr auto size() const { return arr.size() - 1; } std::array arr; }; template basic_fixed_string(const CharT (&str)[N]) -> basic_fixed_string; } template class LiteralFormatter : public FormatterDetail { }; template class Formatter : public FormatterDetail::type, L>(S, L), L> { }; #define AdHocFormatter(name, str) using name = ::AdHoc::LiteralFormatter template inline auto scprintf(const Pn &... pn) { return FormatterDetail::get(pn...); } template inline auto & scprintf(stream & strm, const Pn &... pn) { return FormatterDetail::write(strm, pn...); } template inline auto & cprintf(const Pn &... pn) { return scprintf(std::cout, pn...); } } #include #include #include #include #include #include #include #include namespace AdHoc { #define BASICCONV(PARAMTYPE, OP, ...) \ StreamWriterT(__VA_ARGS__) { \ template \ static inline void \ write(stream & s, const PARAMTYPE & p, const Pn &... pn) \ { \ OP; \ s.copyfmt(std::ios(nullptr)); \ StreamWriter::next(s, pn...); \ } \ } // Integers (d, i, o, u, x, X) #define INTCONV(BASE, OP, CONV) \ BASICCONV(BASE, OP, CONV); \ BASICCONV(short BASE, OP, 'h', CONV); \ BASICCONV(long BASE, OP, 'l', CONV); \ BASICCONV(long long BASE, OP, 'l', 'l', CONV); INTCONV(int, s << std::dec << p, 'i'); INTCONV(int, s << std::dec << p, 'd'); INTCONV(unsigned int, s << std::oct << p, 'o'); INTCONV(unsigned int, s << std::dec << p, 'u'); INTCONV(unsigned int, s << std::nouppercase << std::hex << p, 'x'); INTCONV(unsigned int, s << std::uppercase << std::hex << p, 'X'); #undef INTCONV BASICCONV(intmax_t, s << std::dec << p, 'j', 'd'); BASICCONV(uintmax_t, s << std::dec << p, 'j', 'u'); BASICCONV(ssize_t, s << std::dec << p, 'z', 'd'); BASICCONV(size_t, s << std::dec << p, 'z', 'u'); BASICCONV(short int, s << std::dec << p, 'h', 'h', 'i'); // char BASICCONV(short int, s << std::dec << p, 'h', 'h', 'd'); // char BASICCONV(unsigned char, s << std::dec << p, 'h', 'h', 'u'); BASICCONV(unsigned char, s << std::oct << p, 'h', 'h', 'o'); BASICCONV(unsigned char, s << std::nouppercase << std::hex << p, 'h', 'h', 'x'); BASICCONV(unsigned char, s << std::uppercase << std::hex << p, 'h', 'h', 'X'); // Floating point (a, A, e, E, f, F, g, G) #define FPCONV(BASE, OP, CONV) \ BASICCONV(BASE, OP, CONV); \ BASICCONV(long BASE, OP, 'L', CONV); FPCONV(double, s << std::nouppercase << std::hexfloat << p, 'a'); FPCONV(double, s << std::uppercase << std::hexfloat << p, 'A'); FPCONV(double, s << std::nouppercase << std::scientific << p, 'e'); FPCONV(double, s << std::uppercase << std::scientific << p, 'E'); FPCONV(double, s << std::nouppercase << std::fixed << p, 'f'); FPCONV(double, s << std::uppercase << std::fixed << p, 'F'); FPCONV(double, s << std::nouppercase << std::defaultfloat << p, 'g'); FPCONV(double, s << std::uppercase << std::defaultfloat << p, 'G'); #undef FPCONV BASICCONV(std::string_view, s << p, 's'); BASICCONV(std::wstring_view, s << p, 'l', 's'); BASICCONV(char, s << p, 'c'); BASICCONV(wchar_t, s << p, 'l', 'c'); #undef BASICCONV StreamWriterT('p') { template static inline void write(stream & s, Obj * const ptr, const Pn &... pn) { s << std::showbase << std::hex << (long unsigned int)ptr; s.copyfmt(std::ios(nullptr)); StreamWriter::next(s, pn...); } template static inline void write(stream & s, const Ptr & ptr, const Pn &... pn) { write(s, ptr.get(), pn...); } }; StreamWriterT('m') { template static inline void write(stream & s, const Pn &... pn) { s << strerror(errno); s.copyfmt(std::ios(nullptr)); StreamWriter::next(s, pn...); } }; StreamWriterT('n') { template static inline void write(stream & s, int * n, const Pn &... pn) { BOOST_ASSERT_MSG(n, "%n conversion requires non-null parameter"); *n = streamLength(s); s.copyfmt(std::ios(nullptr)); StreamWriter::next(s, pn...); } }; //// // Width/precision embedded in format string template constexpr auto decdigits() { static_assert((isdigit(chs) && ... && true)); int n = 0; ( [&n](auto ch) { n = (n * 10) + (ch - '0'); }(chs), ...); return n; } #define AUTON(z, n, data) \ BOOST_PP_COMMA_IF(n) \ auto BOOST_PP_CAT(data, n) #define NS(z, n, data) \ BOOST_PP_COMMA_IF(n) \ BOOST_PP_CAT(data, n) #define ISDIGIT(z, n, data) &&isdigit(BOOST_PP_CAT(data, BOOST_PP_ADD(n, 1))) #define FMTWIDTH(unused, d, data) \ template \ struct StreamWriter::type, '%', \ BOOST_PP_REPEAT(BOOST_PP_ADD(d, 1), NS, n), nn, sn...> { \ template \ static inline void \ write(stream & s, const Pn &... pn) \ { \ constexpr auto p = decdigits(); \ s << std::setw(p); \ StreamWriter::write(s, pn...); \ } \ }; BOOST_PP_REPEAT(6, FMTWIDTH, void); #define FMTPRECISION(unused, d, data) \ template \ struct StreamWriter::type, '%', '.', \ BOOST_PP_REPEAT(BOOST_PP_ADD(d, 1), NS, n), nn, sn...> { \ template \ static inline void \ write(stream & s, const Pn &... pn) \ { \ constexpr auto p = decdigits(); \ s << std::setprecision(p); \ StreamWriter::write(s, pn...); \ } \ }; BOOST_PP_REPEAT(6, FMTPRECISION, void); #undef AUTON #undef NS #undef ISDIGIT #undef FMTWIDTH StreamWriterT('.', '*') { template static inline void write(stream & s, int l, const Pn &... pn) { s << std::setw(l); StreamWriter::write(s, pn...); } }; StreamWriterT('.', '*', 's') { template static inline void write(stream & s, int l, const std::string_view & p, const Pn &... pn) { s << p.substr(0, l); s.copyfmt(std::ios(nullptr)); StreamWriter::next(s, pn...); } }; // Flags #define FLAGCONV(OP, ...) \ StreamWriterT(__VA_ARGS__) { \ template \ static inline void \ write(stream & s, const Pn &... pn) \ { \ OP; \ StreamWriter::write(s, pn...); \ } \ }; FLAGCONV(s << std::showbase, '#'); FLAGCONV(s << std::setfill('0'), '0'); FLAGCONV(s << std::left, '-'); FLAGCONV(s << std::showpos, '+'); FLAGCONV(s << std::setfill(' '), ' '); #undef FLAGCONV } #endif