// ------------------------------------------------------------------------------
//  format_matrix.cpp : tool to check for regressions between boost format
//                      releases and compare format specification handling
// ------------------------------------------------------------------------------

//  Copyright 2017 James E. King, III. Use, modification, and distribution 
//  are subject to the Boost Software License, Version 1.0. (See accompanying
//  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

//  See http://www.boost.org/libs/format for library home page

// ------------------------------------------------------------------------------
// reference (ISO C99)   : http://en.cppreference.com/w/cpp/io/c/fprintf
// reference (Java)      : http://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html
// reference (Microsoft) : https://docs.microsoft.com/en-us/cpp/c-runtime-library/format-specification-syntax-printf-and-wprintf-functions
// reference (POSIX 2008): http://pubs.opengroup.org/onlinepubs/9699919799/functions/printf.html

#include <boost/array.hpp>
#include <boost/config.hpp>
#include <boost/cstdint.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/format.hpp>
#include <boost/io/ios_state.hpp>
#include <boost/predef.h>
#include <boost/program_options.hpp>
#include <cerrno>
#include <climits>
#include <clocale>
#if BOOST_COMP_MSVC && BOOST_VERSION_NUMBER_MAJOR(BOOST_COMP_MSVC) >= 19
#include <corecrt.h>    // wint_t
#endif
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cwchar>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <math.h>

#define SNPRINTF snprintf
#if BOOST_COMP_MSVC && BOOST_VERSION_NUMBER_MAJOR(BOOST_COMP_MSVC) < 19
#undef SNPRINTF
#define SNPRINTF _snprintf
#endif

namespace fs = boost::filesystem;
namespace po = boost::program_options;
using namespace std;

namespace matrix
{

enum interop_datatype
{
    // special types:
    ID_UNDEF,           // undefined behavior according to the spec, so combination is not tested
    ID_NOTSUP,          // behavior is not supported and therefore not tested currently

    // boolean type values:
    ID_BOOLF,           // false
    ID_BOOLT,           // true

    // string type values:
    ID_CHAR,            // single signed character
    ID_UCHAR,           // single unsigned character
    ID_WCHAR,           // single wide character
    ID_STR,             // C style string
    ID_WSTR,            // C style wide string

    // integer type values:
    ID_ZERO,            // zero value
    ID_BYTE,            // int8_t
    ID_UBYTE,           // uint8_t
    ID_SHORT,           // signed short
    ID_USHORT,          // unsigned short
    ID_INT,             // signed integer
    ID_UINT,            // unsigned integer
    ID_LONG,            // signed long
    ID_ULONG,           // unsigned long
    ID_LLONG,           // signed long long
    ID_ULLONG,          // unsigned long long
    ID_INTMAX,          // intmax_t
    ID_SSIZET,          // ssize_t
    ID_SIZET,           // size_t
    ID_SPTRDF,          // signed ptrdiff_t
    ID_PTRDIF,          // ptrdiff_t

    // floating type values:
    ID_INF,             // infinity
    ID_NAN,             // not a number
    ID_DOUBLE,          // double
    ID_NEGDBL,          // negative double
    ID_LNGDBL,          // long double
    ID_NEGLNG           // negative long double
};

BOOST_CONSTEXPR const bool            g_bf  = false;
BOOST_CONSTEXPR const bool            g_bt  = true;
BOOST_CONSTEXPR const uint64_t        g_z   = 0;
BOOST_CONSTEXPR const char            g_by  = 0x60;
BOOST_CONSTEXPR const unsigned char   g_uby = 0xA0;
BOOST_CONSTEXPR const char            g_c   = 0x58;
BOOST_CONSTEXPR const wint_t          g_wc  = L'X';          // 'X', but wide
BOOST_CONSTEXPR const char *          g_s   = " string"; 
BOOST_CONSTEXPR const wchar_t *       g_ws  = L"widestr";
BOOST_CONSTEXPR const short           g_h   = numeric_limits<short>::min() + 12345;
BOOST_CONSTEXPR const unsigned short  g_uh  = numeric_limits<unsigned short>::max() - 12345;
BOOST_CONSTEXPR const int             g_i   = numeric_limits<int>::max() - 12345;
BOOST_CONSTEXPR const unsigned int    g_ui  = numeric_limits<unsigned int>::max() - 12345;
BOOST_CONSTEXPR const long            g_l   = numeric_limits<long>::max() - 12345;
BOOST_CONSTEXPR const unsigned long   g_ul  = numeric_limits<unsigned long>::max() - 12345;
BOOST_CONSTEXPR const int64_t         g_ll  = numeric_limits<int64_t>::max() - 12345;
BOOST_CONSTEXPR const uint64_t        g_ull = numeric_limits<uint64_t>::max() - 12345;
BOOST_CONSTEXPR const intmax_t        g_max = numeric_limits<intmax_t>::max() - 12345;
BOOST_CONSTEXPR const size_t          g_sst = numeric_limits<size_t>::min() - 12345;
BOOST_CONSTEXPR const size_t          g_st  = numeric_limits<size_t>::max() - 12345;
BOOST_CONSTEXPR const ptrdiff_t       g_pt  = numeric_limits<ptrdiff_t>::max() - 12345;
BOOST_CONSTEXPR const double          g_db  = 1234567.891234f;
BOOST_CONSTEXPR const double          g_ndb = -1234567.891234f;
BOOST_CONSTEXPR const long double     g_ldb = 6543211234567.891234l;
BOOST_CONSTEXPR const long double     g_nld = -6543211234567.891234l;
#if defined(INFINITY)
BOOST_CONSTEXPR const double          g_inf = INFINITY;
#endif
#if defined(NAN)
BOOST_CONSTEXPR const double          g_nan = NAN;
#endif

boost::array<const char *, 12> length_modifier = { { "hh", "h", "", "l", "ll", "j", "z", "L", "w", "I", "I32", "I64" } };
boost::array<const char *, 6>  format_flags    = { { "", "-", "+", " ", "#", "0" } };
boost::array<const char *, 6>  minimum_width   = { { "", "1", "2", "5", "10", "20" } };           // TODO: , "*" } };
boost::array<const char *, 7>  precision       = { { "", ".", ".0", ".2", ".5", ".10", ".20" } }; // TODO: , ".*" } };

struct interop_row
{
    char conversion_specifier;
    interop_datatype datatype[12];
};

// Each row represents a conversion specifier which is indicated in the first column
// The subsequent columns are argument type specifiers for that conversion specifier
// The data in the cell is the value to pass into snprintf and format to see what comes out

interop_row interop_matrix[] = {
    //         |----------------------------------- ISO C99 ---------------------------------------|   |-------------- Microsoft --------------|
    //  spc,   hh       , h        , (none)   , l        , ll       , j        , z        , L        , w        , I        , I32      , I64 
      { 'c', { ID_UNDEF , ID_UNDEF , ID_CHAR  , ID_WCHAR , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_WCHAR , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 's', { ID_UNDEF , ID_UNDEF , ID_STR   , ID_WSTR  , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_WSTR  , ID_UNDEF , ID_UNDEF , ID_UNDEF } },

      { 'd', { ID_BYTE  , ID_SHORT , ID_INT   , ID_LONG  , ID_LLONG , ID_INTMAX, ID_SSIZET, ID_UNDEF , ID_UNDEF , ID_SPTRDF, ID_INT   , ID_LLONG } },
      { 'd', { ID_UBYTE , ID_USHORT, ID_UINT  , ID_ULONG , ID_ULLONG, ID_INTMAX, ID_SIZET , ID_UNDEF , ID_UNDEF , ID_PTRDIF, ID_UINT  , ID_ULLONG} },
      { 'd', { ID_ZERO  , ID_ZERO  , ID_ZERO  , ID_ZERO  , ID_ZERO  , ID_ZERO  , ID_ZERO  , ID_UNDEF , ID_UNDEF , ID_ZERO  , ID_ZERO  , ID_ZERO  } },
      { 'i', { ID_BYTE  , ID_SHORT , ID_INT   , ID_LONG  , ID_LLONG , ID_INTMAX, ID_SSIZET, ID_UNDEF , ID_UNDEF , ID_SPTRDF, ID_INT   , ID_LLONG } },
      { 'i', { ID_UBYTE , ID_USHORT, ID_UINT  , ID_ULONG , ID_ULLONG, ID_INTMAX, ID_SIZET , ID_UNDEF , ID_UNDEF , ID_PTRDIF, ID_UINT  , ID_ULLONG} },
      { 'i', { ID_ZERO  , ID_ZERO  , ID_ZERO  , ID_ZERO  , ID_ZERO  , ID_ZERO  , ID_ZERO  , ID_UNDEF , ID_UNDEF , ID_ZERO  , ID_ZERO  , ID_ZERO  } },

      { 'o', { ID_UBYTE , ID_USHORT, ID_UINT  , ID_ULONG , ID_ULLONG, ID_INTMAX, ID_SIZET , ID_UNDEF , ID_UNDEF , ID_PTRDIF, ID_UINT  , ID_ULLONG} },
      { 'o', { ID_ZERO  , ID_ZERO  , ID_ZERO  , ID_ZERO  , ID_ZERO  , ID_ZERO  , ID_ZERO  , ID_UNDEF , ID_UNDEF , ID_ZERO  , ID_ZERO  , ID_ZERO  } },
      { 'x', { ID_UBYTE , ID_USHORT, ID_UINT  , ID_ULONG , ID_ULLONG, ID_INTMAX, ID_SIZET , ID_UNDEF , ID_UNDEF , ID_PTRDIF, ID_UINT  , ID_ULLONG} },
      { 'x', { ID_ZERO  , ID_ZERO  , ID_ZERO  , ID_ZERO  , ID_ZERO  , ID_ZERO  , ID_ZERO  , ID_UNDEF , ID_UNDEF , ID_ZERO  , ID_ZERO  , ID_ZERO  } },
      { 'X', { ID_UBYTE , ID_USHORT, ID_UINT  , ID_ULONG , ID_ULLONG, ID_INTMAX, ID_SIZET , ID_UNDEF , ID_UNDEF , ID_PTRDIF, ID_UINT  , ID_ULLONG} },
      { 'X', { ID_ZERO  , ID_ZERO  , ID_ZERO  , ID_ZERO  , ID_ZERO  , ID_ZERO  , ID_ZERO  , ID_UNDEF , ID_UNDEF , ID_ZERO  , ID_ZERO  , ID_ZERO  } },
      { 'u', { ID_UBYTE , ID_USHORT, ID_UINT  , ID_ULONG , ID_ULLONG, ID_INTMAX, ID_SIZET , ID_UNDEF , ID_UNDEF , ID_PTRDIF, ID_UINT  , ID_ULLONG} },
      { 'u', { ID_ZERO  , ID_ZERO  , ID_ZERO  , ID_ZERO  , ID_ZERO  , ID_ZERO  , ID_ZERO  , ID_UNDEF , ID_UNDEF , ID_ZERO  , ID_ZERO  , ID_ZERO  } },

      { 'f', { ID_UNDEF , ID_UNDEF , ID_DOUBLE, ID_DOUBLE, ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_LNGDBL, ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'f', { ID_UNDEF , ID_UNDEF , ID_NEGDBL, ID_NEGDBL, ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_NEGLNG, ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'f', { ID_UNDEF , ID_UNDEF , ID_INF   , ID_INF   , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_INF   , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'f', { ID_UNDEF , ID_UNDEF , ID_NAN   , ID_NAN   , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_NAN   , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'f', { ID_UNDEF , ID_UNDEF , ID_ZERO  , ID_ZERO  , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_ZERO  , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'F', { ID_UNDEF , ID_UNDEF , ID_DOUBLE, ID_DOUBLE, ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_LNGDBL, ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'F', { ID_UNDEF , ID_UNDEF , ID_NEGDBL, ID_NEGDBL, ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_NEGLNG, ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'F', { ID_UNDEF , ID_UNDEF , ID_INF   , ID_INF   , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_INF   , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'F', { ID_UNDEF , ID_UNDEF , ID_NAN   , ID_NAN   , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_NAN   , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'F', { ID_UNDEF , ID_UNDEF , ID_ZERO  , ID_ZERO  , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_ZERO  , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'e', { ID_UNDEF , ID_UNDEF , ID_DOUBLE, ID_DOUBLE, ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_LNGDBL, ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'e', { ID_UNDEF , ID_UNDEF , ID_NEGDBL, ID_NEGDBL, ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_NEGLNG, ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'e', { ID_UNDEF , ID_UNDEF , ID_INF   , ID_INF   , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_INF   , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'e', { ID_UNDEF , ID_UNDEF , ID_NAN   , ID_NAN   , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_NAN   , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'e', { ID_UNDEF , ID_UNDEF , ID_ZERO  , ID_ZERO  , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_ZERO  , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'E', { ID_UNDEF , ID_UNDEF , ID_DOUBLE, ID_DOUBLE, ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_LNGDBL, ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'E', { ID_UNDEF , ID_UNDEF , ID_NEGDBL, ID_NEGDBL, ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_NEGLNG, ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'E', { ID_UNDEF , ID_UNDEF , ID_INF   , ID_INF   , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_INF   , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'E', { ID_UNDEF , ID_UNDEF , ID_NAN   , ID_NAN   , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_NAN   , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'E', { ID_UNDEF , ID_UNDEF , ID_ZERO  , ID_ZERO  , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_ZERO  , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'a', { ID_UNDEF , ID_UNDEF , ID_DOUBLE, ID_DOUBLE, ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_LNGDBL, ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'a', { ID_UNDEF , ID_UNDEF , ID_NEGDBL, ID_NEGDBL, ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_NEGLNG, ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'a', { ID_UNDEF , ID_UNDEF , ID_INF   , ID_INF   , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_INF   , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'a', { ID_UNDEF , ID_UNDEF , ID_NAN   , ID_NAN   , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_NAN   , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'a', { ID_UNDEF , ID_UNDEF , ID_ZERO  , ID_ZERO  , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_ZERO  , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'A', { ID_UNDEF , ID_UNDEF , ID_DOUBLE, ID_DOUBLE, ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_LNGDBL, ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'A', { ID_UNDEF , ID_UNDEF , ID_NEGDBL, ID_NEGDBL, ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_NEGLNG, ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'A', { ID_UNDEF , ID_UNDEF , ID_INF   , ID_INF   , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_INF   , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'A', { ID_UNDEF , ID_UNDEF , ID_NAN   , ID_NAN   , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_NAN   , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'A', { ID_UNDEF , ID_UNDEF , ID_ZERO  , ID_ZERO  , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_ZERO  , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'g', { ID_UNDEF , ID_UNDEF , ID_DOUBLE, ID_DOUBLE, ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_LNGDBL, ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'g', { ID_UNDEF , ID_UNDEF , ID_NEGDBL, ID_NEGDBL, ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_NEGLNG, ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'g', { ID_UNDEF , ID_UNDEF , ID_INF   , ID_INF   , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_INF   , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'g', { ID_UNDEF , ID_UNDEF , ID_NAN   , ID_NAN   , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_NAN   , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'g', { ID_UNDEF , ID_UNDEF , ID_ZERO  , ID_ZERO  , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_ZERO  , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'G', { ID_UNDEF , ID_UNDEF , ID_DOUBLE, ID_DOUBLE, ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_LNGDBL, ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'G', { ID_UNDEF , ID_UNDEF , ID_NEGDBL, ID_NEGDBL, ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_NEGLNG, ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'G', { ID_UNDEF , ID_UNDEF , ID_INF   , ID_INF   , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_INF   , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'G', { ID_UNDEF , ID_UNDEF , ID_NAN   , ID_NAN   , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_NAN   , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'G', { ID_UNDEF , ID_UNDEF , ID_ZERO  , ID_ZERO  , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_ZERO  , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },

    // boolalpha - not supported in snprintf per ISO C99 but is by boost::format so...
      { 'b', { ID_UNDEF , ID_UNDEF , ID_BOOLF , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
      { 'b', { ID_UNDEF , ID_UNDEF , ID_BOOLT , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },

    // this is the terminator for conversion specifier loops:
      {  0 , { ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF , ID_UNDEF } },
};

std::string call_snprintf(const std::string& fmtStr, interop_datatype type)
{
    // enough space to hold any value in this test
    char buf[BUFSIZ];
    int len = 0;

    switch (type)
    {
        case ID_ZERO:   len = SNPRINTF(buf, BUFSIZ, fmtStr.c_str(), g_z  ); break;
        case ID_BOOLF:  len = SNPRINTF(buf, BUFSIZ, fmtStr.c_str(), g_bf ); break;
        case ID_BOOLT:  len = SNPRINTF(buf, BUFSIZ, fmtStr.c_str(), g_bt ); break;
        case ID_BYTE:   len = SNPRINTF(buf, BUFSIZ, fmtStr.c_str(), g_by ); break;
        case ID_UBYTE:  len = SNPRINTF(buf, BUFSIZ, fmtStr.c_str(), g_uby); break;
        case ID_CHAR:   len = SNPRINTF(buf, BUFSIZ, fmtStr.c_str(), g_c  ); break;
        case ID_WCHAR:  len = SNPRINTF(buf, BUFSIZ, fmtStr.c_str(), g_wc ); break;
        case ID_STR:    len = SNPRINTF(buf, BUFSIZ, fmtStr.c_str(), g_s  ); break;
        case ID_WSTR:   len = SNPRINTF(buf, BUFSIZ, fmtStr.c_str(), g_ws ); break;
        case ID_SHORT:  len = SNPRINTF(buf, BUFSIZ, fmtStr.c_str(), g_h  ); break;
        case ID_USHORT: len = SNPRINTF(buf, BUFSIZ, fmtStr.c_str(), g_uh ); break;
        case ID_INT:    len = SNPRINTF(buf, BUFSIZ, fmtStr.c_str(), g_i  ); break;
        case ID_UINT:   len = SNPRINTF(buf, BUFSIZ, fmtStr.c_str(), g_ui ); break;
        case ID_LONG:   len = SNPRINTF(buf, BUFSIZ, fmtStr.c_str(), g_l  ); break;
        case ID_ULONG:  len = SNPRINTF(buf, BUFSIZ, fmtStr.c_str(), g_ul ); break;
        case ID_LLONG:  len = SNPRINTF(buf, BUFSIZ, fmtStr.c_str(), g_ll ); break;
        case ID_ULLONG: len = SNPRINTF(buf, BUFSIZ, fmtStr.c_str(), g_ull); break;
        case ID_INTMAX: len = SNPRINTF(buf, BUFSIZ, fmtStr.c_str(), g_max); break;
        case ID_SSIZET: len = SNPRINTF(buf, BUFSIZ, fmtStr.c_str(), g_sst); break;
        case ID_SIZET:  len = SNPRINTF(buf, BUFSIZ, fmtStr.c_str(), g_st ); break;
        case ID_SPTRDF: len = SNPRINTF(buf, BUFSIZ, fmtStr.c_str(), g_pt ); break;
        case ID_PTRDIF: len = SNPRINTF(buf, BUFSIZ, fmtStr.c_str(), g_pt ); break;
#if defined(INFINITY)
        case ID_INF:    len = SNPRINTF(buf, BUFSIZ, fmtStr.c_str(), g_inf); break;
#endif
#if defined(NAN)
        case ID_NAN:    len = SNPRINTF(buf, BUFSIZ, fmtStr.c_str(), g_nan); break;
#endif
        case ID_DOUBLE: len = SNPRINTF(buf, BUFSIZ, fmtStr.c_str(), g_db ); break;
        case ID_NEGDBL: len = SNPRINTF(buf, BUFSIZ, fmtStr.c_str(), g_ndb); break;
        case ID_LNGDBL: len = SNPRINTF(buf, BUFSIZ, fmtStr.c_str(), g_ldb); break;
        case ID_NEGLNG: len = SNPRINTF(buf, BUFSIZ, fmtStr.c_str(), g_nld); break;
        default: throw logic_error("unhandled type in call_snprintf");
    }

    if (len < 0)
    {
        throw logic_error("snprintf length 0");
    }

    return std::string(buf, len);
}

std::string call_format(const std::string& fmtStr, interop_datatype type)
{
    switch (type)
    {
        case ID_ZERO:   return ::boost::str(::boost::format(fmtStr) % g_z  );
        case ID_BOOLF:  return ::boost::str(::boost::format(fmtStr) % g_bf );
        case ID_BOOLT:  return ::boost::str(::boost::format(fmtStr) % g_bt );
        case ID_BYTE:   return ::boost::str(::boost::format(fmtStr) % g_by );
        case ID_UBYTE:  return ::boost::str(::boost::format(fmtStr) % g_uby);
        case ID_CHAR:   return ::boost::str(::boost::format(fmtStr) % g_c  );
        case ID_WCHAR:  return ::boost::str(::boost::format(fmtStr) % g_wc );
        case ID_STR:    return ::boost::str(::boost::format(fmtStr) % g_s  );
        case ID_WSTR:   return ::boost::str(::boost::format(fmtStr) % g_ws );
        case ID_SHORT:  return ::boost::str(::boost::format(fmtStr) % g_h  );
        case ID_USHORT: return ::boost::str(::boost::format(fmtStr) % g_uh );
        case ID_INT:    return ::boost::str(::boost::format(fmtStr) % g_i  );
        case ID_UINT:   return ::boost::str(::boost::format(fmtStr) % g_ui );
        case ID_LONG:   return ::boost::str(::boost::format(fmtStr) % g_l  );
        case ID_ULONG:  return ::boost::str(::boost::format(fmtStr) % g_ul );
        case ID_LLONG:  return ::boost::str(::boost::format(fmtStr) % g_ll );
        case ID_ULLONG: return ::boost::str(::boost::format(fmtStr) % g_ull);
        case ID_INTMAX: return ::boost::str(::boost::format(fmtStr) % g_max);
        case ID_SSIZET: return ::boost::str(::boost::format(fmtStr) % g_sst);
        case ID_SIZET:  return ::boost::str(::boost::format(fmtStr) % g_st );
        case ID_SPTRDF: return ::boost::str(::boost::format(fmtStr) % g_pt );
        case ID_PTRDIF: return ::boost::str(::boost::format(fmtStr) % g_pt );
#if defined(INFINITY)
        case ID_INF:    return ::boost::str(::boost::format(fmtStr) % g_inf);
#endif
#if defined(NAN)
        case ID_NAN:    return ::boost::str(::boost::format(fmtStr) % g_nan);
#endif
        case ID_DOUBLE: return ::boost::str(::boost::format(fmtStr) % g_db );
        case ID_NEGDBL: return ::boost::str(::boost::format(fmtStr) % g_ndb);
        case ID_LNGDBL: return ::boost::str(::boost::format(fmtStr) % g_ldb);
        case ID_NEGLNG: return ::boost::str(::boost::format(fmtStr) % g_nld);
        default: throw logic_error("unhandled type in call_format");
    }
}

po::variables_map g_args;
ofstream g_os;

void
write_header()
{
    if (g_args.count("snprintf"))
    {
#if BOOST_LIB_C_GNU
        g_os << "# glibc.version = " << BOOST_VERSION_NUMBER_MAJOR(BOOST_LIB_C_GNU) << "."
                                     << BOOST_VERSION_NUMBER_MINOR(BOOST_LIB_C_GNU) << "."
                                     << BOOST_VERSION_NUMBER_PATCH(BOOST_LIB_C_GNU)
                                     << endl;
#elif BOOST_COMP_MSVC
        g_os << "# msvc.version = "  << BOOST_VERSION_NUMBER_MAJOR(BOOST_COMP_MSVC) << "."
                                     << BOOST_VERSION_NUMBER_MINOR(BOOST_COMP_MSVC) << "."
                                     << BOOST_VERSION_NUMBER_PATCH(BOOST_COMP_MSVC)
                                     << endl;
#else
        g_os << "# libc.version = unknown" << endl;
#endif
    }
    else
    {
        g_os << "# boost.version = " << BOOST_VERSION / 100000 << "."      // major version
                                     << BOOST_VERSION / 100 % 1000 << "."  // minor version
                                     << BOOST_VERSION % 100                // patch level
                                     << endl;
    }
}

void
log(const std::string& spec, bool ok, const std::string& result)
{
    boost::io::ios_all_saver saver(g_os);
    g_os << setw(20) << right << spec << "\t"
         << (ok ? "OK " : "ERR") << "\t" << "\"" << result << "\"" << endl;
}

void cell(std::size_t nrow, std::size_t ncol)
{
    const interop_row& row(interop_matrix[nrow]);

    const interop_datatype& dataType(row.datatype[ncol]);
    if (dataType == ID_UNDEF || dataType == ID_NOTSUP)
    {
        return;
    }

#if !defined(INFINITY)
    if (dataType == ID_INF)
    {
        return;
    }
#endif

#if !defined(NAN)
    if (dataType == ID_NAN)
    {
        return;
    }
#endif

    // TODO: every combination of format flags - right now we do only one
    for (std::size_t ffi = 0; ffi < format_flags.size(); ++ffi)
    {
        for (std::size_t mwi = 0; mwi < minimum_width.size(); ++mwi)
        {
            for (std::size_t pri = 0; pri < precision.size(); ++pri)
            {
                // Make the format string
                std::stringstream fss;
                fss << '%';
                fss << format_flags[ffi];
                fss << minimum_width[mwi];
                fss << precision[pri];
                fss << length_modifier[ncol];
                fss << row.conversion_specifier;
                std::string fmtStr = fss.str();

                try
                {
                    std::string result = g_args.count("snprintf") ?
                                            call_snprintf(fmtStr, dataType) : 
                                            call_format  (fmtStr, dataType) ;
                    log(fmtStr, true, result);
                }
                catch (const std::exception& ex)
                {
                    log(fmtStr, false, ex.what());
                }
            }
        }
    }
}

void
matrix()
{
    for (std::size_t row = 0; interop_matrix[row].conversion_specifier != 0x00; ++row)
    {
        for (std::size_t col = 0; col < length_modifier.size(); ++col)
        {
            cell(row, col);
        }
    }
}

void
write_pctpct()
{
    if (g_args.count("snprintf"))
    {
        char buf[BUFSIZ];
        int len = SNPRINTF(buf, BUFSIZ, "%%");
        log("%%", len == 1, len == 1 ? buf : "snprintf length != 1");
    }
    else
    {
        try
        {
            log("%%", true, ::boost::format("%%").str());
        }
        catch (std::exception& ex)
        {
            log("%%", false, ex.what());
        }
    }
}

void
generate()
{
    string genpath = g_args["generate"].as<string>();
    g_os.open(genpath.c_str(), ios::out | ios::trunc);
    write_header();
    write_pctpct();
    matrix();
    g_os.close();
}

} // matrix

///////////////////////////////////////////////////////////////////////////////
//  main entry point
int
main(int argc, char *argv[])
{
    using matrix::g_args;

    po::options_description desc("Allowed options");
    desc.add_options()
        ("generate,g", po::value<string>()->required(), "generate output filename")
        ("help,h", "produce help message")
        ("snprintf,s", "use snprintf instead of boost::format")
        ;

    po::store(po::command_line_parser(argc, argv).options(desc).run(), g_args);
    po::notify(g_args);

    if (g_args.count("help")) {
        cout << "Usage: format_matrix [options]\n";
        cout << desc;
        return 0;
    }

    matrix::generate();

    return 0;
}
