From fcdca58617caf6a8c034a91588d6abb399be6b57 Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Tue, 18 May 2021 00:06:37 +0100 Subject: Initial commit, still lots to do! --- lib/compileTimeFormatter.h | 495 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 495 insertions(+) create mode 100644 lib/compileTimeFormatter.h (limited to 'lib/compileTimeFormatter.h') diff --git a/lib/compileTimeFormatter.h b/lib/compileTimeFormatter.h new file mode 100644 index 0000000..6e8c813 --- /dev/null +++ b/lib/compileTimeFormatter.h @@ -0,0 +1,495 @@ +#ifndef ADHOCUTIL_COMPILE_TIME_FORMATTER_H +#define ADHOCUTIL_COMPILE_TIME_FORMATTER_H + +#include +#include +#include +#include +#include +#include // IWYU pragma: export + +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 std::array { + public: + // cppcheck-suppress noExplicitConstructor + constexpr basic_fixed_string(const CharT (&str)[N + 1]) + { + for (decltype(N) x = 0; x < N; x++) { + this->at(x) = str[x]; + } + } + constexpr basic_fixed_string(const CharT * str, decltype(N) len) + { + for (decltype(N) x = 0; x < len; x++) { + this->at(x) = str[x]; + } + } + }; + + 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 -- cgit v1.2.3