Tuesday, November 18, 2014

How to create a parsing/printing system with 1 line of code per field

Recently had a friend claim that you cant parse and print a blob of data without cutting and pasting stuff every where or using the new c++11 compilers and techniques.

I have in the past(2012) shown techniques to do this kind of parsing and generation in c11-tuples-and-schema-generation.html. So with a few short hours of hacking and here is the proof of concept.. The solution is meta programming black magic but hey u can define the parser and printer (and whatever else u want to add) for "SomeMessage" in basically 1 line of code per field.

// compile with:
// g++ -I"c:\tools\boost_1_49_0" -L"c:\tools\boost_1_49_0\stage\lib" -static self_print_struct.cpp -o self_print_struct.exe

#include <iostream>
#include <boost/mpl/for_each.hpp>
#include <boost/mpl/list.hpp>
#include <boost/mpl/string.hpp>
#include <boost/mpl/range_c.hpp>

template <typename T,
          typename N>
struct Field
{
    typedef T Type;
    typedef N Name;

    static unsigned char* print(std::ostream& os, unsigned char* ptr)
    {
        os << boost::mpl::c_str<N>::value
           << " : " << *(static_cast<T*>(static_cast<void*>(ptr)));
        return ptr + sizeof(Type);
    }
};

template <typename Base>
struct PrintMixin
{
    struct DoPrint
    {
        unsigned char* cursor_;

        DoPrint(unsigned char* cursor) :
           cursor_(cursor)
        {}

        template< typename U > void operator()(U x)
        {
            std::cout << " + ";
            U::print(std::cout, cursor_);
            std::cout << '\n';
            cursor_ += sizeof(typename U::Type);
        }
    };

    static void print(std::ostream& os, unsigned char* ptr)
    {
        boost::mpl::for_each< typename Base::Type >( DoPrint(ptr) );
    }
};

struct SomeMessage : PrintMixin<SomeMessage>
{
    typedef boost::mpl::list<Field<int,  boost::mpl::string<'fiel','d 1'> >,
                             Field<char, boost::mpl::string<'fiel','d 2'> > > Type;
};

struct AnotherMessage : PrintMixin<AnotherMessage>
{
    typedef boost::mpl::list<Field<int,   boost::mpl::string<'this'> >,
                             Field<char,  boost::mpl::string<'that'> >,
                             Field<float, boost::mpl::string<'the ','othe','r'> >
                             > Type;
};

int main()
{
    unsigned char message1[] =
    {
        0x01,0x02,0x03,0x04,
        'a'
    };

    std::cout << "message 1\n";
    SomeMessage::print(std::cout, message1);

    unsigned char message2[] =
    {
        0x15, 0xCD, 0x5B, 0x07,
        'b',
        0x19, 0x04, 0x9e, 0x3f
    };

    std::cout << "message 2\n";
    AnotherMessage::print(std::cout, message2);
}


And the output looks like:
$ self_print_struct.exe
message 1
 + field 1 : 67305985
 + field 2 : a
message 2
 + this : 123456789
 + that : b
 + the other : 1.2345