From 72f85f4d49862b67ebfdd404d8f9451d0a277abf Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Wed, 4 Jul 2018 21:13:41 +0100 Subject: CTF printf Adds support for a lot (but not all) of printf like formatters --- libadhocutil/compileTimeFormatter.h | 8 ++ libadhocutil/detail/compileTimeFormatters.h | 128 +++++++++++++++++++++ .../unittests/testCompileTimeFormatter.cpp | 105 +++++++++++++++++ 3 files changed, 241 insertions(+) create mode 100644 libadhocutil/detail/compileTimeFormatters.h diff --git a/libadhocutil/compileTimeFormatter.h b/libadhocutil/compileTimeFormatter.h index fecbd91..b05e9f4 100644 --- a/libadhocutil/compileTimeFormatter.h +++ b/libadhocutil/compileTimeFormatter.h @@ -95,6 +95,12 @@ namespace AdHoc { s.write(p, n); } + template + static inline auto streamLength(stream & s) + { + return s.tellp(); + } + /** * Compile time string formatter. * @param S the format string. @@ -161,6 +167,8 @@ namespace AdHoc { }; } +#include "detail/compileTimeFormatters.h" + #define AdHocFormatterTypedef(name, str, id) \ inline constexpr auto id = str; \ typedef ::AdHoc::Formatter name diff --git a/libadhocutil/detail/compileTimeFormatters.h b/libadhocutil/detail/compileTimeFormatters.h new file mode 100644 index 0000000..6fa77d0 --- /dev/null +++ b/libadhocutil/detail/compileTimeFormatters.h @@ -0,0 +1,128 @@ +#ifndef ADHOCUTIL_COMPILE_TIME_FORMATTER_PRINTF_H +#define ADHOCUTIL_COMPILE_TIME_FORMATTER_PRINTF_H + +#include "../compileTimeFormatter.h" +#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...); + } + }; + 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 & 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.setfill('0'), '0'); + FLAGCONV(s << std::left, '-'); + FLAGCONV(s.setfill(' '), ' '); +#undef FLAGCONV +} + +#endif + diff --git a/libadhocutil/unittests/testCompileTimeFormatter.cpp b/libadhocutil/unittests/testCompileTimeFormatter.cpp index a04b92e..6ad491d 100644 --- a/libadhocutil/unittests/testCompileTimeFormatter.cpp +++ b/libadhocutil/unittests/testCompileTimeFormatter.cpp @@ -308,3 +308,108 @@ BOOST_AUTO_TEST_CASE( filestar ) free(buf); } +// The following tests represent CTF's [partial] emulation of many +// POSIX formatting features +#define GLIBC_FMT_TEST(NAME, FMT, ...) \ + AdHocFormatter(NAME ## fmtr, FMT); \ + BOOST_AUTO_TEST_CASE(NAME ## t) { \ + BOOST_TEST_CONTEXT(FMT) { \ + auto str = NAME ## fmtr::get(__VA_ARGS__); \ + char * buf = NULL; \ + int len = asprintf(&buf, FMT, __VA_ARGS__); \ + BOOST_REQUIRE(buf); \ + BOOST_CHECK_EQUAL(str.length(), len); \ + BOOST_CHECK_EQUAL(str, buf); \ + free(buf); \ + } \ + } + +GLIBC_FMT_TEST(s1, "in %s.", "string"); +GLIBC_FMT_TEST(s2, "in %s %s.", "string", "other"); +GLIBC_FMT_TEST(s3, "in %.*s.", 3, "other"); +GLIBC_FMT_TEST(s4, "in %.*s.", 5, "other"); +GLIBC_FMT_TEST(s5, "in %.*s.", 7, "other"); + +GLIBC_FMT_TEST(c1, "in %c.", 'b'); +GLIBC_FMT_TEST(c2, "in %c.", 'B'); + +GLIBC_FMT_TEST(d1, "in %d.", 123); +GLIBC_FMT_TEST(d2, "in %d.", 123456); +GLIBC_FMT_TEST(d3, "in %hd.", -12345); +GLIBC_FMT_TEST(d4, "in %hhd.", -123); +GLIBC_FMT_TEST(d5, "in %ld.", -123456L); +GLIBC_FMT_TEST(d6, "in %lld.", -123456LL); +GLIBC_FMT_TEST(i1, "in %i.", 123); +GLIBC_FMT_TEST(i2, "in %i.", -123); + +GLIBC_FMT_TEST(x1, "in %x.", 123); +GLIBC_FMT_TEST(x2, "in %x %d.", 123, 256); +GLIBC_FMT_TEST(x3, "in %d %x.", 123, 1024); +GLIBC_FMT_TEST(x4, "in %X %x.", 123, 13); +GLIBC_FMT_TEST(x5, "in %X %s.", 123, "miXED case after UPPER X"); +GLIBC_FMT_TEST(x6, "in %#x.", 123); +GLIBC_FMT_TEST(x7, "in %#X.", 123); +GLIBC_FMT_TEST(x8, "in %#X %x.", 123, 150); + +GLIBC_FMT_TEST(o1, "in %o.", 123); +GLIBC_FMT_TEST(o2, "in %o %d.", 123, 256); +GLIBC_FMT_TEST(o3, "in %d %o.", 123, 1024); + +GLIBC_FMT_TEST(a1, "in %a.", 123.456789); +GLIBC_FMT_TEST(a2, "in %a.", -123.456789); +GLIBC_FMT_TEST(a3, "in %a.", .123456789); +GLIBC_FMT_TEST(a4, "in %a.", -.123456789); +GLIBC_FMT_TEST(a5, "in %A.", -.123456789); +GLIBC_FMT_TEST(a6, "in %A.", -123.456789); +GLIBC_FMT_TEST(a7, "in %a.", 123456789.123); +GLIBC_FMT_TEST(a8, "in %a.", -123456789.123); + +GLIBC_FMT_TEST(e1, "in %e.", 123.456789); +GLIBC_FMT_TEST(e2, "in %e.", -123.456789); +GLIBC_FMT_TEST(e3, "in %e.", .123456789); +GLIBC_FMT_TEST(e4, "in %e.", -.123456789); +GLIBC_FMT_TEST(e5, "in %E.", -.123456789); +GLIBC_FMT_TEST(e6, "in %E.", -123.456789); +GLIBC_FMT_TEST(e7, "in %e.", 123456789.123); +GLIBC_FMT_TEST(e8, "in %e.", -123456789.123); + +GLIBC_FMT_TEST(f1, "in %f.", 123.456789); +GLIBC_FMT_TEST(f2, "in %f.", -123.456789); +GLIBC_FMT_TEST(f3, "in %f.", .123456789); +GLIBC_FMT_TEST(f4, "in %f.", -.123456789); +GLIBC_FMT_TEST(f5, "in %F.", -.123456789); +GLIBC_FMT_TEST(f6, "in %F.", -123.456789); +GLIBC_FMT_TEST(f7, "in %f.", 123456789.123); +GLIBC_FMT_TEST(f8, "in %f.", -123456789.123); + +GLIBC_FMT_TEST(g1, "in %g.", 123.456789); +GLIBC_FMT_TEST(g2, "in %g.", -123.456789); +GLIBC_FMT_TEST(g3, "in %g.", .123456789); +GLIBC_FMT_TEST(g4, "in %g.", -.123456789); +GLIBC_FMT_TEST(g5, "in %G.", -.123456789); +GLIBC_FMT_TEST(g6, "in %G.", -123.456789); +GLIBC_FMT_TEST(g7, "in %g.", 123456789.123); +GLIBC_FMT_TEST(g8, "in %g.", -123456789.123); + +AdHocFormatter(chars_written_fmt, "%n %s %n %d %n"); +BOOST_AUTO_TEST_CASE(chars_written) +{ + int a = -1, b = -1, c = -1; + auto s = chars_written_fmt::get(&a, "some string", &b, 10, &c); + BOOST_CHECK_EQUAL(s, " some string 10 "); + BOOST_CHECK_EQUAL(a, 0); + BOOST_CHECK_EQUAL(b, s.length() - 4); + BOOST_CHECK_EQUAL(c, s.length()); +} + +GLIBC_FMT_TEST(p2, "in %p.", this); + +AdHocFormatter(smartptr_fmt, "Address is %p."); +BOOST_AUTO_TEST_CASE(smartptr) +{ + auto uni = std::make_unique(42); + smartptr_fmt::get(uni); + auto shrd = std::make_shared(42); + smartptr_fmt::get(shrd); +} + -- cgit v1.2.3