0
\$\begingroup\$

Original Problem

The original problem is parsing/generation of the transport header of the RTSP protocol, see RFC 2326 12.39 Transport, Page 60. The transport header is defined of multiple transport specs, at least one, and either comma separated or by repetition of the transport header. The transport specs are again a complex text based language, which also could be structs. Since the solution is relevant for all one list of items based grammars in conjunction with the used library, an example was extracted, with foo instead of RTSP, foo_struct instead of Transport, and integers instead of transport specs.

Generalised Problem

For the sake of an example assume we got a text based grammar/language for data exchange, like HTTP. But right now, this protocol called "foo" is consists of a comma separated list of numbers like "5,3,2" in decimal notation.

The internal representation of the data of this langauge in C++ is defined as foo_struct.

A. We want to parse individual ints from the list while also adding them as items to the std::vector<int>, inside of the struct foo_struct::bar_vector.

B. We want to generate a valid text string out from a foo_struct, so at the moment again just a list of comma separated numbers.

Approach

The C++ library boost has a very powerfull parsing and generating section, called spirit, which has it subparts qi for parsing and karma for generating.

Specific Problem

Qi and karma has some ways to make it easy for the user, to stuff the atomic items from the sub parsers/generators, like the number and list ones, into custom structs. This works well for structs which more than one member to put data into. However both libraries have some ugly caveats in the corner case of structs consisting simply out of one member, because then, they assume it follows the concepts of a container like std::list or std::vector

How to solve this for A. (Qi) was already discussed in https://stackoverflow.com/q/19823413/3537677, but now this code below solved this in a not so satisfying way for B. (Karma).


So all the code intents to do is to define the foo_struct with just one member, define the Qi grammar for demonstration purposes, like in the solution for A. and also a Karma Generator, which is the troublemaker.

The main routine is then just for demonstrating i.e. testing this example.

The ugly solution for B.

I have a one member struct that I am using with boost::spirit::karma, but, as far as I could get it to work, I have to use a boost::spirit::karma::attr_cast with a coustom boost::spirit::traits::transform_attribute<T, std::vector<u>>{static std::vector<U> pre (const &T) function,

which is very ugly and error prone. If you know how to write it as simple as in the parsing case, that would be much more readable i.e. maintainable.

#include <iostream>
#include <iterator>
#include <string>
#include <vector>
#include <boost/spirit/include/karma.hpp>
#include <boost/spirit/include/qi.hpp>

struct foo_struct {
    std::vector<int> bar_vector;

    foo_struct() = default;

    explicit foo_struct(std::vector<int> v) : bar_vector(std::move(v)) {}
};

template<typename Iterator>
struct foo_parser_grammar
        : ::boost::spirit::qi::grammar<Iterator, foo_struct()> {
    foo_parser_grammar() : foo_parser_grammar::base_type(start) {

        start %= boost::spirit::qi::as<std::vector<int>>()[
                boost::spirit::qi::int_ % ","];
    }

    boost::spirit::qi::rule<Iterator, foo_struct()> start;

};

namespace boost {
namespace spirit {
namespace traits {
template<>
struct transform_attribute<foo_struct const, std::vector<int>, karma::domain> {
    typedef int type;

    static std::vector<int> pre(foo_struct const &d) { return d.bar_vector; }
};
}
}
}

template<typename OutputIterator>
struct foo_generator_grammar : boost::spirit::karma::grammar<OutputIterator, foo_struct()> {
    foo_generator_grammar() : foo_generator_grammar::base_type(start) {

        start = boost::spirit::karma::attr_cast<std::vector<int>>(
                boost::spirit::karma::int_ % ",");

    }

    boost::spirit::karma::rule<OutputIterator, foo_struct()> start;
};


int main(int argc, char *argv[]) {
    foo_struct foo{};
    std::string input{"5,3,2"};
    foo_parser_grammar<std::string::const_iterator> parse_grammar{};

    boost::spirit::qi::phrase_parse(input.cbegin(), input.cend(), parse_grammar,
                                    boost::spirit::ascii::space, foo);

    std::cout << "Input\"";
    std::for_each(foo.bar_vector.cbegin(), foo.bar_vector.cend(), [](const auto &i) { std::cout << i << ","; });
    std::cout << "\"\n";

    std::string output;
    foo_generator_grammar<std::back_insert_iterator<std::string>> gen_grammar{};

    boost::spirit::karma::generate(std::back_inserter(output),
                                   gen_grammar, foo);

    std::cout << "Output\"" << output << "\"\n";

    return 0;
}
\$\endgroup\$
2
\$\begingroup\$

Here's what I'd do:

Live On Coliru

#include <string>
#include <vector>

struct foo_struct {
    std::vector<int> bar_vector;

    foo_struct(std::vector<int> v = {}) : bar_vector(std::move(v)) {}
};

#include <boost/fusion/adapted/struct.hpp>
BOOST_FUSION_ADAPT_STRUCT(foo_struct, bar_vector)

#include <boost/spirit/include/qi.hpp>
namespace qi = boost::spirit::qi;
template <typename Iterator>
struct foo_parser : qi::grammar<Iterator, foo_struct()> {
    foo_parser() : foo_parser::base_type(start) {
        using namespace qi;
        start = int_ % "," >> eps;
    }

  private:
    qi::rule<Iterator, foo_struct()> start;
};

#include <boost/spirit/include/karma.hpp>
namespace karma = boost::spirit::karma;

template <typename OutputIterator>
struct foo_generator : karma::grammar<OutputIterator, foo_struct()> {
    foo_generator() : foo_generator::base_type(start) {
        using namespace karma;
        start = int_ % "," << eps;
    }

  private:
    karma::rule<OutputIterator, foo_struct()> start;
};

#include <iostream>
#include <iterator>
#include <iomanip>
int main() {
    using It = std::string::const_iterator;
    using Out = boost::spirit::ostream_iterator;
    std::string const input{ "5,3,2" };

    foo_struct foo;
    parse(input.begin(), input.end(), foo_parser<It>{}, foo);

    std::copy(begin(foo.bar_vector), end(foo.bar_vector), std::ostream_iterator<int>(std::cout << "Input: ", ","));

    foo_generator<Out> gen;
    std::cout << "\nOutput: " << format(gen, foo) << "\n";
}

Prints

Input: 5,3,2,
Output: 5,3,2

Notes:

\$\endgroup\$
  • \$\begingroup\$ Added my comments in a nut-shell. If you have any questions, let me know \$\endgroup\$ – sehe Oct 25 '18 at 17:44
  • \$\begingroup\$ Thought of a rather important point about the lack of error handling and added that. \$\endgroup\$ – sehe Oct 25 '18 at 21:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.