From 98c81cc1ed3e42c7c8d80711517d101553b6f5c5 Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Mon, 3 Sep 2018 19:49:31 +0100 Subject: Greatly improved CTF printf support --- libadhocutil/compileTimeFormatter.h | 2 - libadhocutil/ctf-impl/printf-compat.h | 182 +++++++++++++++++++++ libadhocutil/detail/compileTimeFormatters.h | 165 ------------------- .../unittests/testCompileTimeFormatter.cpp | 26 +++ 4 files changed, 208 insertions(+), 167 deletions(-) create mode 100644 libadhocutil/ctf-impl/printf-compat.h delete mode 100644 libadhocutil/detail/compileTimeFormatters.h diff --git a/libadhocutil/compileTimeFormatter.h b/libadhocutil/compileTimeFormatter.h index bb80edf..77fa753 100644 --- a/libadhocutil/compileTimeFormatter.h +++ b/libadhocutil/compileTimeFormatter.h @@ -176,8 +176,6 @@ namespace AdHoc { }; } -#include "detail/compileTimeFormatters.h" - #define AdHocFormatterTypedef(name, str, id) \ inline constexpr auto id = str; \ typedef ::AdHoc::Formatter name diff --git a/libadhocutil/ctf-impl/printf-compat.h b/libadhocutil/ctf-impl/printf-compat.h new file mode 100644 index 0000000..3367b4a --- /dev/null +++ b/libadhocutil/ctf-impl/printf-compat.h @@ -0,0 +1,182 @@ +#ifndef ADHOCUTIL_COMPILE_TIME_FORMATTER_PRINTF_H +#define ADHOCUTIL_COMPILE_TIME_FORMATTER_PRINTF_H + +#include "../compileTimeFormatter.h" +#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(NULL)); \ + 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(NULL)); + 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(NULL)); + 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(NULL)); + 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(NULL)); + 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 + diff --git a/libadhocutil/detail/compileTimeFormatters.h b/libadhocutil/detail/compileTimeFormatters.h deleted file mode 100644 index 00ee852..0000000 --- a/libadhocutil/detail/compileTimeFormatters.h +++ /dev/null @@ -1,165 +0,0 @@ -#ifndef ADHOCUTIL_COMPILE_TIME_FORMATTER_PRINTF_H -#define ADHOCUTIL_COMPILE_TIME_FORMATTER_PRINTF_H - -#include "../compileTimeFormatter.h" -#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(NULL)); \ - 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; - 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); - 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); - StreamWriter::next(s, pn...); - } - }; - - //// - // Width/precision embedded in format string - // Limitted to 3 digits at the moment - template - struct StreamWriter::type, '%', n0, nn, sn...> { - template - static inline void write(stream & s, const Pn & ... pn) - { - constexpr auto p = (n0 - '0'); - s << std::setw(p) << std::setprecision(p); - StreamWriter::write(s, pn...); - } - }; - template - struct StreamWriter::type, '%', n0, n1, nn, sn...> { - template - static inline void write(stream & s, const Pn & ... pn) - { - constexpr auto p = ((n0 - '0') * 10) + (n1 - '0'); - s << std::setw(p) << std::setprecision(p); - StreamWriter::write(s, pn...); - } - }; - template - struct StreamWriter::type, '%', n0, n1, n2, nn, sn...> { - template - static inline void write(stream & s, const Pn & ... pn) - { - constexpr auto p = ((n0 - '0') * 100) + ((n1 - '0') * 10) + (n2 - '0'); - s << std::setw(p) << std::setprecision(p); - StreamWriter::write(s, pn...); - } - }; - //// - - StreamWriterT('.', '*') { - template - static inline void write(stream & s, int l, const Pn & ... pn) - { - s << std::setw(l) << std::setprecision(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); - 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::setfill(' '), ' '); -#undef FLAGCONV -} - -#endif - diff --git a/libadhocutil/unittests/testCompileTimeFormatter.cpp b/libadhocutil/unittests/testCompileTimeFormatter.cpp index 138d5dc..b966078 100644 --- a/libadhocutil/unittests/testCompileTimeFormatter.cpp +++ b/libadhocutil/unittests/testCompileTimeFormatter.cpp @@ -308,6 +308,25 @@ BOOST_AUTO_TEST_CASE( filestar ) free(buf); } +#include "ctf-impl/printf-compat.h" + +static_assert(isdigit('0')); +static_assert(isdigit('9')); +static_assert(!isdigit('.')); +static_assert(!isdigit('a')); + +static_assert(!ispositivedigit('0')); +static_assert(ispositivedigit('1')); +static_assert(ispositivedigit('9')); +static_assert(!ispositivedigit('.')); +static_assert(!ispositivedigit('a')); + +static_assert(0 == decdigits<'0'>()); +static_assert(1 == decdigits<'1'>()); +static_assert(19 == decdigits<'1', '9'>()); +static_assert(419 == decdigits<'4', '1', '9'>()); +static_assert(419 == decdigits<'0', '4', '1', '9'>()); + // The following tests represent CTF's [partial] emulation of many // POSIX formatting features #define GLIBC_FMT_TEST(NAME, FMT, ...) \ @@ -332,6 +351,10 @@ GLIBC_FMT_TEST(s5, "in %.*s.", 7, "other"); GLIBC_FMT_TEST(s35, "in %3s.", "other"); GLIBC_FMT_TEST(s55, "in %5s.", "other"); GLIBC_FMT_TEST(s115, "in %11s.", "other"); +//std::setw does not truncate strings +//GLIBC_FMT_TEST(sd35, "in %.3s.", "other"); +GLIBC_FMT_TEST(sd55, "in %.5s.", "other"); +GLIBC_FMT_TEST(sd115, "in %.11s.", "other"); GLIBC_FMT_TEST(c1, "in %c.", 'b'); GLIBC_FMT_TEST(c2, "in %c.", 'B'); @@ -398,6 +421,9 @@ GLIBC_FMT_TEST(g6, "in %G.", -123.456789); GLIBC_FMT_TEST(g7, "in %g.", 123456789.123); GLIBC_FMT_TEST(g8, "in %g.", -123456789.123); +GLIBC_FMT_TEST(fmtlibt_fmt, "%0.10f:%04d:%+g:%s:%p:%c:%%\n", + 1.234, 42, 3.13, "str", (void*)1000, (int)'X'); + AdHocFormatter(chars_written_fmt, "%n %s %n %d %n"); BOOST_AUTO_TEST_CASE(chars_written) { -- cgit v1.2.3