Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check log arguments for correctness #2

Open
larytet opened this issue Dec 7, 2018 · 1 comment
Open

Check log arguments for correctness #2

larytet opened this issue Dec 7, 2018 · 1 comment

Comments

@larytet
Copy link
Owner Author

larytet commented Dec 7, 2018

#include <stdexcept>
#include <boost/format.hpp>
#include <boost/utility/string_ref.hpp>

template<std::size_t N>
constexpr bool checkValidFormats(const char (&fmt)[N], size_t n, char c)
{
    return n >= N ?
            false
        : fmt[n] == c ?
            true
        : checkValidFormats(fmt, n + 1, c);
}

template<class>
struct FormatSupportedType;

#define SUPPORTED_TYPE(T, Fmts) \
template<> \
struct FormatSupportedType<T> \
{ \
    constexpr static bool supports(char c) \
    { return checkValidFormats(Fmts, 0, c) \
            ? true : throw std::logic_error("invalid fmt for type"); } \
}

SUPPORTED_TYPE(char,              "c");
SUPPORTED_TYPE(int,               "d*");
SUPPORTED_TYPE(unsigned,          "u*");
SUPPORTED_TYPE(char*,             "s");
SUPPORTED_TYPE(const char*,       "s");
SUPPORTED_TYPE(std::string,       "s");
SUPPORTED_TYPE(boost::string_ref, "s");
SUPPORTED_TYPE(double,            "f");
SUPPORTED_TYPE(float,             "f");

/////////////////

constexpr bool isDigit(char c)
{
    return c >= '0' && c <= '9';
}

constexpr bool isModifier(char c)
{
    return  c == 'l' ||
            c == 'h' ||
            c == 'j' ||
            c == 'z' ||
            c == 't' ||
            c == 'L' ||
            c == '#' ||
            c == '+' ||
            c == '-' ||
            c == ' ' ||
            c == '\'' ||
            c == 'I' ||
            c == '.' ||
            c == '=' ||
            isDigit(c);
}

template<std::size_t N>
constexpr size_t nextNonModifier(const char (&fmt)[N], std::size_t n)
{
    return
        n >= N ?
            throw std::logic_error("invalid format string")
        : isModifier(fmt[n]) ?
                nextNonModifier(fmt, n + 1)
        : n;
}

////////////////////

template<std::size_t N>
constexpr bool checkFormatHelper(const char (&fmt)[N], std::size_t n);
template<std::size_t N, class T, class... Ts>
constexpr bool checkFormatHelper(const char (&fmt)[N], std::size_t n, const T& arg, const Ts&... args);

////////////////////

template<std::size_t N, typename T1, typename T2, typename T3, typename... Ts>
constexpr auto checkWidthAndPrecision(const char (&fmt)[N], std::size_t n, const T1& /*width*/, const T2& /*precision*/, const T3& /* arg */, const Ts&... args)
    -> typename std::enable_if<
            std::is_integral<T1>::value &&
            std::is_integral<T2>::value,
        bool>::type
{
    return FormatSupportedType< typename std::decay<T3>::type>::supports(fmt[n]) &&
            checkFormatHelper(fmt, n + 1, args...);
}

template<std::size_t N, typename... Ts>
constexpr bool checkWidthAndPrecision(const char (&)[N], std::size_t, const Ts&...)
{
    return false;
}

////////////////////

template<std::size_t N, typename T1, typename T2, typename... Ts>
constexpr auto checkWidthOrPrecision(const char (&fmt)[N], std::size_t n, const T1& /*precision*/, const T2& /* arg */, const Ts&... args)
    -> typename std::enable_if<
            std::is_integral<T1>::value,
        bool>::type
{
    return FormatSupportedType< typename std::decay<T2>::type>::supports(fmt[n]) &&
            checkFormatHelper(fmt, n + 1, args...);
}

template<std::size_t N, typename... Ts>
constexpr bool checkWidthOrPrecision(const char (&)[N], std::size_t, const Ts&...)
{
    return false;
}

////////////////////

template<std::size_t N>
constexpr bool checkFormatHelper(const char (&fmt)[N], std::size_t n)
{
    return
        n>= N ?
            true
        : fmt[n] != '%' ?
            checkFormatHelper(fmt, n + 1)
        : fmt[n + 1] == '%' ?
            checkFormatHelper(fmt, n + 2)
        : false;
}

template<std::size_t N, class T, class... Ts>
constexpr bool checkFormatHelper(const char (&fmt)[N], std::size_t n, const T& arg, const Ts&... args)
{
    return
        n >= N ?
            throw std::logic_error("too many arguments for provided format string")

        : fmt[n] != '%' ?
            checkFormatHelper(fmt, n + 1, arg, args...)

        // literal percent character
        : (fmt[n + 1] == '%') ?
            checkFormatHelper(fmt, n + 2, arg, args...)

        // long-long modifier
        : (fmt[n + 1] == 'l' && fmt[n + 2] == 'l') ?
            FormatSupportedType< typename std::decay<T>::type >::supports(fmt[n + 3]) &&
            checkFormatHelper(fmt, n + 4, args...)

        // width & precision modifier
        : (fmt[n + 1] == '*' && fmt[n + 2] == '.' && fmt[n + 3] == '*') ?
            checkWidthAndPrecision(fmt, n + 4, arg, args...)

        // width or precision modifier
        : ((fmt[n + 1] == '.' && fmt[n + 2] == '*') || (fmt[n + 1] == '*')) ?
            checkWidthOrPrecision(fmt, (fmt[n + 1] == '.' ? n + 3 : n + 2), arg, args...)

        // other modifier
        : (isModifier(fmt[n + 1])) ?
            FormatSupportedType< typename std::decay<T>::type>::supports(fmt[nextNonModifier(fmt, n + 2)]) &&
            checkFormatHelper(fmt, nextNonModifier(fmt, n + 2) + 1, args...)

        // no modifier
        : FormatSupportedType< typename std::decay<T>::type>::supports(fmt[n + 1]) &&
            checkFormatHelper(fmt, n + 2, args...);
}

template<std::size_t N, class... Ts>
constexpr bool checkFormat(const char (&fmt)[N], const Ts&... args)
{
    return checkFormatHelper(fmt, 0, args...);
}

// printing...

void add(boost::format&)
{ }

template<typename T, typename... Ts>
void add(boost::format& f, const T& arg, const Ts&... ts)
{
    f % arg;
    add(f, ts...);
}

#define LOG(fmt, ...) \
    { \
        static_assert(checkFormat(fmt, ##__VA_ARGS__), "Format is incorrect"); \
        boost::format f(fmt); \
        add(f, ##__VA_ARGS__); \
        std::cout << f.str() << std::endl; \
    }

int main()
{
    // char
    LOG("%c", 'x');

    // integral
    LOG("%d", -123);
    LOG("%ld", -123);
    LOG("%u", 123u);
    LOG("%lu", 123u);

    // strings
    LOG("%s", "hello world");
    { const char* s = "hello world"; LOG("%s", s); }
    { std::string s = "hello world"; LOG("%s", s); }
    { std::string s = "hello world"; boost::string_ref r(s); LOG("%s", r); }

    // floating point
    LOG("%f", 1.23);
    LOG("%f", 1.23f);

    // width / precision
    LOG("%02d", 1);
    LOG("%.2d", 123);
    LOG("% 3s", "hello");
    LOG("% 3s", "yo");
    LOG("%.3s", "hello");
    LOG("%.3s", "yo");
    
    // incorrect format string
    // LOG("%f", 1);
    // LOG("%d", 1.23);
    
    // not supported by boost::format
    // LOG("%*s", 3, "yo");
    // LOG("%*d", 3, 12);
    // LOG("%.*s", 3, "hello");
    // LOG("%.*d", 3, 12345);
    // LOG("%*.*s", 3, 3, "hello");
    // LOG("%*.*d", 3, 3, 12345);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant