4

The code below (adapted from spirit qi mini_xml example) does not compile. There is an error related to the rule brac that has an attribute of an recursive boost::variant.
However, all commented out versions of brac do compile.

I am very curious to know what makes the simple string parser so special in this case:

#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/phoenix_fusion.hpp>
#include <boost/spirit/include/phoenix_stl.hpp>
#include <boost/spirit/include/phoenix_object.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/variant/recursive_variant.hpp>

#include <string>
#include <vector>

namespace client
{
   namespace fusion = boost::fusion;
   namespace phoenix = boost::phoenix;
   namespace qi = boost::spirit::qi;
   namespace ascii = boost::spirit::ascii;

   struct ast_node;

   typedef boost::variant<
      boost::recursive_wrapper<ast_node>,
      std::string
   > ast_branch;

   struct ast_node
   {
      std::string text;
      std::vector<ast_branch> children;
   };
}

BOOST_FUSION_ADAPT_STRUCT(
      client::ast_node,
      (std::string, text)
      (std::vector<client::ast_branch>, children)
)

namespace client
{
   template <typename Iterator>
      struct ast_node_grammar
      : qi::grammar<Iterator, ast_branch(), ascii::space_type>
      {
         ast_node_grammar()
            : ast_node_grammar::base_type(brac)
         {
            using qi::_1;
            using qi::_val;
            using ascii::char_;
            using ascii::string;

            name %= *char_;

            brac %= string("no way") ;
//            brac = string("works")[_val = _1] ;
//            brac %= string("this") | string("works");
//            brac %= name ; // works
//            brac %= *char_ ; // works
         }
         qi::rule<Iterator, std::string()> name;
         qi::rule<Iterator, ast_branch(), ascii::space_type> brac;
      };
}


int main(int argc, char **argv)
{
   typedef client::ast_node_grammar<std::string::const_iterator> ast_node_grammar;
   ast_node_grammar gram;
   client::ast_branch ast;

   std::string text("dummy");
   using boost::spirit::ascii::space;
   std::string::const_iterator iter = text.begin();
   std::string::const_iterator end = text.end();
   bool r = phrase_parse(iter, end, gram, space, ast);
   return r ? 0 : 1;
}

Part of the error message:

/usr/include/boost/spirit/home/qi/detail/assign_to.hpp:38:17: error: No match for ‘boost::variant<
        boost::recursive_wrapper<client::ast_node>, basic_string<char> 
>::variant(
        const __normal_iterator<const char *, basic_string<char> > &, const __normal_iterator<
            const char *, basic_string<char> > &)’

Thank's in advance.

pdug
  • 45
  • 3

1 Answers1

5

I'd suggest the problem is Attribute compatibility. Contrary to the documentation, the ascii::string parser appears to expose an iterator range instead of a string.

name = string("no way");

is no issue, because the attribute exposed by ascii::string can be coerced into the rule's attribute type without any difficulty.

However, the brac rule's attribute type is ast_branch, which is only a variant with one of it's possible contained types. Therefore, the ast_branch type has several constructors, and it isn't clear to Spirit which one is adequate for this particular conversion.

There are several ways about this (besides the approaches you already showed):

  • use attr_cast

    brac = qi::attr_cast( string("no way") );
    
  • use as_string

    brac = qi::as_string[ string("no way") ];
    
  • use customization points

    namespace boost { namespace spirit { namespace traits {
        template <typename It>
            struct assign_to_attribute_from_iterators<client::ast_branch, It>
            {
                static void call(It const& f, It const& l, client::ast_branch& val)
                {
                    val = std::string(f, l);
                }
            };
    }}}
    

Each of these have the same effect: make Spirit realize what attribute conversion to use.

Here is a full working sample showing all three:

// #define BOOST_SPIRIT_ACTIONS_ALLOW_ATTR_COMPAT
#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/adapted.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/variant/recursive_variant.hpp>

#include <string>
#include <vector>

namespace client
{
   namespace fusion  = boost::fusion;
   namespace phoenix = boost::phoenix;
   namespace qi      = boost::spirit::qi;
   namespace ascii   = boost::spirit::ascii;

   struct ast_node;

   typedef boost::variant<
      boost::recursive_wrapper<ast_node>,
      std::string
   > ast_branch;

   struct ast_node
   {
      std::string text;
      std::vector<ast_branch> children;
   };
}

namespace boost { namespace spirit { namespace traits {
    template <typename It>
        struct assign_to_attribute_from_iterators<client::ast_branch, It>
        {
            static void call(It const& f, It const& l, client::ast_branch& val)
            {
                val = std::string(f, l);
            }
        };
}}}

BOOST_FUSION_ADAPT_STRUCT(
      client::ast_node,
      (std::string, text)
      (std::vector<client::ast_branch>, children)
)

namespace client
{
    template <typename Iterator>
        struct ast_node_grammar : qi::grammar<Iterator, ast_branch(), ascii::space_type>
    {
        ast_node_grammar()
            : ast_node_grammar::base_type(brac)
        {
            using qi::_1;
            using qi::_val;
            using ascii::char_;
            using ascii::string;

            name %= *char_;

            brac = string("works");
            brac = string("works")[_val = _1] ;
            brac %= string("this") | string("works");
            brac %= name ; // works
            brac %= *char_ ; // works

            brac = qi::as_string[ string("no way") ];
            brac = qi::attr_cast( string("no way") );
        }
        qi::rule<Iterator, std::string()> name;
        qi::rule<Iterator, ast_branch(), ascii::space_type> brac;
    };
}


int main(int argc, char **argv)
{
   typedef client::ast_node_grammar<std::string::const_iterator> ast_node_grammar;
   ast_node_grammar gram;
   client::ast_branch ast;

   std::string text("dummy");
   using boost::spirit::ascii::space;
   std::string::const_iterator iter = text.begin();
   std::string::const_iterator end = text.end();
   bool r = phrase_parse(iter, end, gram, space, ast);
   return r ? 0 : 1;
}
sehe
  • 374,641
  • 47
  • 450
  • 633
  • The hint to assign_to_attribute_from_iterators was nice. – pdug Jul 11 '12 at 14:49
  • 2
    @pdug thanks! Welcome to [SO]. Remember to [vote/accept](http://meta.stackexchange.com/questions/5234/how-does-accepting-an-answer-work), so people will know what answers helped the OP – sehe Jul 11 '12 at 21:48
  • I've found this answer using a dart-style google query. That's certainly a documentation bug that `qi::string` generates `std::string`. Though it seems that they know about it, and made `qi::as_string[...]` syntax pretty much for this case. – polkovnikov.ph Jun 21 '15 at 21:48