Search code examples
c++boostboost-spiritboost-spirit-karma

How is it possible to pass attributes to child rules in boost spirit karma


I am parsing text into an AST via qi and generates text again via karma. This is working as expected, but wants some method to pass on an attribute from one rule to another.

Ported from the comments:

Current Code On Coliru

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/karma.hpp>

#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/adapt_adt.hpp>
#include <fstream>

  struct c_struct
  {
    int value1;
  };

  struct b_struct
  {
    std::string value1;
    std::vector< c_struct > value2;
  };

  struct a_struct
  { 
    std::string value1;
    std::vector< b_struct > value2;
  };

BOOST_FUSION_ADAPT_STRUCT( c_struct,    
    (int, value1)
)

BOOST_FUSION_ADAPT_STRUCT( b_struct,    
    (std::string, value1)
    (std::vector< c_struct >, value2)
)

BOOST_FUSION_ADAPT_STRUCT( a_struct,    
    (std::string, value1)
    (std::vector< b_struct >, value2)
)

using namespace boost::spirit;
using namespace boost::spirit::qi;
using namespace boost::spirit::karma;
using namespace boost::spirit::ascii;

template <typename Iterator>
struct grammarB : karma::grammar<Iterator, a_struct()>
{
  grammarB() : grammarB::base_type(ruleA)
  {
    ruleA %= karma::string << karma::lit(' ') << +ruleB << eps;
    ruleB %= karma::string << +ruleC << karma::lit(' ') << eps;    
    ruleC %= lit("  ->  ") << karma::int_;
  }

  karma::rule<Iterator, a_struct()> ruleA;
  karma::rule<Iterator, b_struct()> ruleB;
  karma::rule<Iterator, c_struct()> ruleC;
};

template <typename Iterator>
struct grammarA : qi::grammar<Iterator, a_struct(), boost::spirit::ascii::space_type>
{
  grammarA() : grammarA::base_type(ruleA)
  {
    ruleA %= ruleString >> omit[+qi::char_('.')] >> +ruleB;
    ruleB %= ruleString >> omit[qi::char_(',')] >> (ruleC % qi::char_(',')) >> omit[qi::char_(';')];
    ruleC %= qi::int_;

    ruleString %= +qi::char_("a-z");
  }           
  qi::rule<Iterator, a_struct(), boost::spirit::ascii::space_type> ruleA;   
  qi::rule<Iterator, b_struct(), boost::spirit::ascii::space_type> ruleB;  
  qi::rule<Iterator, c_struct(), boost::spirit::ascii::space_type> ruleC;

  qi::rule<Iterator, std::string(), boost::spirit::ascii::space_type> ruleString;
};    

int main(int argc, char **argv)
{   
  std::string storage("parent ... whee,4,5,6;ahhhh,5,6;"); // We will read the contents here.

  typedef grammarA<std::string::const_iterator> grammarA_t;
  grammarA_t grammar;
  a_struct ast;

  std::string::const_iterator iter = storage.begin();
  std::string::const_iterator end = storage.end();

  bool r = phrase_parse(iter, end, grammar, boost::spirit::ascii::space, ast);

  if (r && iter == end)
  {    
    std::cout << "Parsing succeeded" << std::endl;

    typedef std::back_insert_iterator<std::string> output_iterator_type;

    std::string generated;    
    output_iterator_type sink(generated);

    typedef grammarB<output_iterator_type> grammarB_t; 
    grammarB_t generator;


    if ( generate(sink, generator, ast) )
        std::cout << generated << std::endl;
    else
        std::cout << "fail" << std::endl;
  }

  return 0;
}

It prints

Parsing succeeded
parent whee  ->  4  ->  5  ->  6 ahhhh  ->  5  ->  6 

But I'd prefer it to print

Parsing succeeded
parent parent whee -> parent 4 -> parent 5 -> parent 6 ahhhh -> parent 5 -> parent 6

Is this possible, and how?


Solution

  • Okay. The sample of output "parent parent whee -> parent 4 -> parent 5 -> parent 6 ahhhh -> parent 5 -> parent 6" actually made things /almost/ clear.

    I think there's an inconsistency, since either you would expect

    parent parent whee -> parent 4 -> parent 5 -> parent 6 parent ahhhh -> parent 5 -> parent 6
    

    or you would expect

    parent whee -> parent 4 -> parent 5 -> parent 6 ahhhh -> parent 5 -> parent 6 
    

    I'll show you both.

    ruleA %= string [ _pass_along = _1 ] << +ruleB(_pass_along);
    ruleB  = string << +ruleC(_inherited);
    //ruleB  = lit(_inherited) << string << +ruleC(_inherited); // alternative interpretation
    ruleC  = "->" << lit(_inherited) << karma::int_;
    

    This uses both karma::locals<> and inherited attributes.

    Notes:

    • I've simplified both the Qi and Karma rules
    • favour delimiters in Karma rules
    • use lexeme where appropriate in the Qi rules (Boost spirit skipper issues)
    • use lit("x") instead of string("x") if you don't want to synthesize an attribute
    • drop lit() unless non-spirit domain expression template operator overloads would be selected
    • use BOOST_SPIRIT_DEBUG_NODES
    • Do not mix using namespaces. There's no need and it will lead to trouble.

    Live On Coliru

    //#define BOOST_SPIRIT_DEBUG
    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/karma.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    
    #include <boost/fusion/adapted.hpp>
    #include <iostream>
    #include <fstream>
    
    struct c_struct {
        int value1;
    };
    
    struct b_struct {
        std::string value1;
        std::vector<c_struct> value2;
    };
    
    struct a_struct {
        std::string value1;
        std::vector<b_struct> value2;
    };
    
    BOOST_FUSION_ADAPT_STRUCT(c_struct, (int, value1))
    BOOST_FUSION_ADAPT_STRUCT(b_struct, (std::string, value1)(std::vector<c_struct>, value2))
    BOOST_FUSION_ADAPT_STRUCT(a_struct, (std::string, value1)(std::vector<b_struct>, value2))
    
    namespace qi    = boost::spirit::qi;
    namespace karma = boost::spirit::karma;
    namespace ascii = boost::spirit::ascii;
    
    template <typename Iterator> struct generator : karma::grammar<Iterator, a_struct(), karma::space_type> {
        generator() : generator::base_type(start) {
            using namespace karma;
            _a_type _pass_along;
            _r1_type _inherited;
    
            start = ruleA;
    
            ruleA %= string [ _pass_along = _1 ] << +ruleB(_pass_along);
            ruleB  = string << +ruleC(_inherited);
            //ruleB  = lit(_inherited) << string << +ruleC(_inherited); // alternative interpretation
            ruleC  = "->" << lit(_inherited) << karma::int_;
    
            BOOST_SPIRIT_DEBUG_NODES((start)(ruleA)(ruleB)(ruleC))
        }
    
        karma::rule<Iterator, a_struct(), karma::space_type> start;
        karma::rule<Iterator, a_struct(), qi::locals<std::string>, karma::space_type> ruleA;
        karma::rule<Iterator, b_struct(std::string const&), karma::space_type> ruleB;
        karma::rule<Iterator, c_struct(std::string const&), karma::space_type> ruleC;
    };
    
    template <typename Iterator> struct grammar : qi::grammar<Iterator, a_struct(), boost::spirit::ascii::space_type> {
        grammar() : grammar::base_type(ruleA) {
            using namespace qi;
            ruleA = ruleString >> lexeme[+qi::lit('.')] >> +ruleB;
            ruleB = ruleString >> ',' >> (ruleC % ',') >> ';';
            ruleC = qi::int_;
    
            ruleString = +qi::char_("a-z");
    
            BOOST_SPIRIT_DEBUG_NODES((ruleA)(ruleB)(ruleC)(ruleString))
        }
        qi::rule<Iterator, a_struct(), boost::spirit::ascii::space_type> ruleA;
        qi::rule<Iterator, b_struct(), boost::spirit::ascii::space_type> ruleB;
        qi::rule<Iterator, c_struct(), boost::spirit::ascii::space_type> ruleC;
    
        qi::rule<Iterator, std::string()/*, boost::spirit::ascii::space_type*/> ruleString;
    };
    
    int main() {
        typedef std::string::const_iterator It;
        std::string const storage("parent ... whee,4,5,6;ahhhh,5,6;"); // We will read the contents here.
    
        grammar<It> grammar;
        a_struct ast;
    
        It iter = storage.begin(), end = storage.end();
        bool r = phrase_parse(iter, end, grammar, ascii::space, ast);
    
        if (r && iter == end) {
            std::cout << "Parsing succeeded" << std::endl;
    
            generator<boost::spirit::ostream_iterator> generator;
    
            std::cout << "'parent whee  ->  4  ->  5  ->  6 ahhhh  ->  5  ->  6 ' // ORIGINAL\n";
            std::cout << "'parent parent whee -> parent 4 -> parent 5 -> parent 6 ahhhh -> parent 5 -> parent 6 ' // DESIRED/EXPECTED\n";
            std::cout << "'" << karma::format_delimited(generator, karma::space, ast) << "' // ACTUAL\n";
        }
    }
    

    Output:

    Parsing succeeded
    'parent whee  ->  4  ->  5  ->  6 ahhhh  ->  5  ->  6 ' // ORIGINAL
    'parent parent whee -> parent 4 -> parent 5 -> parent 6 ahhhh -> parent 5 -> parent 6 ' // DESIRED/EXPECTED
    'parent whee -> parent 4 -> parent 5 -> parent 6 ahhhh -> parent 5 -> parent 6 ' // ACTUAL
    

    If you uncomment the alternative ruleB generator:

    Live On Coliru

    Output:

    Parsing succeeded
    'parent whee  ->  4  ->  5  ->  6 ahhhh  ->  5  ->  6 ' // ORIGINAL
    'parent parent whee -> parent 4 -> parent 5 -> parent 6 ahhhh -> parent 5 -> parent 6 ' // DESIRED/EXPECTED
    'parent parent whee -> parent 4 -> parent 5 -> parent 6 parent ahhhh -> parent 5 -> parent 6 ' // ACTUAL