c++boostboost-spiritboost-variantboost-spirit-karma

C++ boost::spirit::karma rule for variants


I have a boost::variant in my program that takes types of double, uint16_t, std::string, etc. I'm storing these and I'd like to use boost::karma to generate/print them out. I'm new to boost::spirit, but I understand it works well with variants. What options do I have to go about this? What would a simple grammar/rule look like to generate one of these? Any help would be great!


Solution

  • 1. Simplicity Rules

    The absolute simplest example I can think of, showcases how karma can even synthesize an auto_ rule for your particular variant on the fly[1] :

    #include <boost/spirit/include/karma.hpp>    
    namespace karma = boost::spirit::karma;
    
    int main() {
        typedef boost::variant<double, unsigned int, std::string> V;
    
        for(auto v : { V{42u}, V{3.1416}, V{"Life Of Pi"} })
            std::cout << karma::format(karma::auto_, v) << "\n";
    }
    

    Prints:

    42
    3.142
    Life Of Pi
    

    Easy as pie!

    The equivalent in a separate grammar: Live On Coliru

    2. A more involved sample

    A more involved grammar (also Live On Coliru), which shows you how Spirit's Attribute compatibility rules DoTheRightThing™ magically:

    #include <boost/spirit/include/karma.hpp>
    
    namespace karma = boost::spirit::karma;
    
    typedef boost::variant<double, unsigned int, std::string> V;
    
    struct gen : karma::grammar<boost::spirit::ostream_iterator, V()> {
        gen() : gen::base_type(start) 
        {
            using namespace karma;
    
            start = my_real | my_uint | my_text;
    
            my_uint = "The value is unsigned integral value (" << uint_ << ")";
            my_real = "The value is double precision floating point value (" << double_ << ")";
            my_text = "The value is an epos: '" << *quoted_char << "'";
    
            quoted_char = '\\' << char_("'") | graph | "\\x" << uint_generator<uint8_t, 16>();
        }
      private:
        karma::rule<boost::spirit::ostream_iterator, V()>           start;
        karma::rule<boost::spirit::ostream_iterator, double()>      my_real;
        karma::rule<boost::spirit::ostream_iterator, unsigned       int()>   my_uint;
        karma::rule<boost::spirit::ostream_iterator, std::string()> my_text;
        karma::rule<boost::spirit::ostream_iterator, uint8_t()>     quoted_char;
    };
    
    int main()
    {
        for(auto v : { V{42u}, V{3.1416}, V{"It's a beautiful day!"} })
            std::cout << karma::format(gen(), v) << "\n";
    }
    

    This prints:

    The value is unsigned integral value (42)
    The value is double precision floating point value (3.142)
    The value is an epos: 'It\'s\x20a\x20beautiful\x20day!'
    

    main could also be written as

    int main() {
        std::cout << karma::format(gen() % "\n", std::vector<V>{42u,3.1416,"It's a beautiful day!"}) << "\n";
    }
    

    Which should give you a glimpse of just how versatile the Spirit Parser/Generator framework can be.


    [1] As long as the auto parser generator trait is present; Spirit provides them for a host of types, including uint, std::string, double and variant (but also optionals, vectors, maps, anything that can be adapted as a Fusion sequence etc. etc.)