c++boostboost-spiritboost-spirit-qiboost-spirit-lex

Boost::spirit get value from non-terminal


I have this, in my boost::spirit grammar;

paren = (qi::token(LEFT_PAREN) >> character >> qi::token(RIGHT_PAREN)) [ build_paren ]
          ;
character = qi::token(CHARACTER) [ build_character]
          ;

Where these are defined as;

qi::rule<Iterator> paren;
qi::rule<Iterator, char> character;

The function build_paren, has the following prototype (found via compiler cast error);

void build_paren(boost::fusion::vector2<boost::iterator_range<__gnu_cxx::__normal_iterator<char*, std::basic_string<char>>>, boost::iterator_range<__gnu_cxx::__normal_iterator<char*, std::basic_string<char>>>> v)

Here the vector, holds two strings, respectively "(\0" and ")\0", this is as I would expect, however how do I get the char matched in character?

Really, the prototype I'd like for my build_paran function is;

void build_paren(std::string left_paren, char character, std::string right_paren)

Or alternatively, the same, however with the char argument as the last one in the list.


Solution

  • You don't have to work that hard :)

    Spirit has automatic attribute propagation. Actually, I'd say that is it's main selling feature. So you can:

    char parsed_char;
    bool ok = qi::phrase_parse(f,l, '(' >> qi::char_("0-9") >> ')', qi::space, parsed_char);
    

    This will simply bind the exposed attribute of the char_ parser component to the attribute reference (parsed_char) passed into the variadic parsing API (phrase_parse).

    Below is a generalized demonstration, showing the many ways in which you can influence what exactly gets exposed. Exactly what gets exposed is documented with the parser directives, e.g. here, for the '%' list parser.

    For your specific question, you'd want to simply:

    qi::rule<Iterator, char()> character;
    qi::rule<Iterator, char()> parenthesized;
    
    character     = qi::char_("0-9a-z_"); // or qi::alnum, qi::graph, qi::alpha etc...
    parenthesized = '(' >> character >> ')';
    

    Note importantly, you need to say qi::rule<Iterator, char()> instead of qi::rule<Iterator, char>!

    Demonstrations

    See it Live on Coliru:

    #include <boost/spirit/include/qi.hpp>
    #include <cassert>
    
    namespace qi = boost::spirit::qi;
    
    template<typename ParseExpr, typename... Attr>
    void test(const std::string& input, const ParseExpr& p, Attr&... attrs)
    {
        auto f = input.begin(), 
             l = input.end();
    
        bool ok = qi::phrase_parse(f,l, p, qi::space, attrs...);
    
        if (!ok)
            std::cerr << "parse failed at: '" << std::string(f,l) << "'\n";
    
        if (f!=l) 
            std::cerr << "trailing unparsed: '" << std::string(f,l) << "'\n";
    }
    
    int main()
    {
        char parsed_char1, parsed_char2;
        int parsed_int;
        std::string parsed_str;
    
        test("( 0 )",                        // input
             '(' >> qi::char_("0-9") >> ')', // parser/grammar
             parsed_char1                     // output
        );
        assert(parsed_char1 == '0');
    
        test("( q 123 )", 
                '(' >> qi::graph >> qi::int_ >> ')', 
                parsed_char1, 
                parsed_int);
        assert(parsed_char1 == 'q');
        assert(parsed_int == 123);
    
        // parsing strings: with the skipper
        test("( hello world )", 
            '(' >> *~qi::char_(")") >> ')', 
            parsed_str = "");
        assert(parsed_str == "helloworld");
    
        // parsing strings: qi::char_ exposes the char
        test("( hello world )", 
            qi::char_('(') >>  *~qi::char_(")") >> qi::char_(')'), 
            parsed_char1, parsed_str = "", parsed_char2);
        assert(parsed_char1 == '(');
        assert(parsed_str == "helloworld");
        assert(parsed_char2 == ')');
    
        // parsing strings: qi::char_ exposes the char, chars get 'combined' into attribute
        test("( hello world )", 
            qi::char_('(') >>  *~qi::char_(")") >> qi::char_(')'), 
            parsed_str = "");
        assert(parsed_str == "(helloworld)");
    
        // parsing strings: as a lexeme
        test("( hello world )", 
            '(' >> qi::lexeme [ *~qi::char_(")") ] >> ')', 
            parsed_str = "");
        assert(parsed_str == "hello world ");
    
        // parsing strings: as bigger lexeme
        test("( hello world )", 
            qi::lexeme [ '(' >>  *~qi::char_(")") >> ')' ], 
            parsed_str = "");
        assert(parsed_str == " hello world ");
    
        // parsing anything as "raw" - exposes an iterator pair, but still 'converts' to a string!
        test("( hello 42 false )", 
            qi::raw [ '(' >>  qi::lexeme[*qi::graph] >> qi::int_ >> qi::bool_ >> ')' ], 
            parsed_str = "");
        assert(parsed_str == "( hello 42 false )");
    
        // note: this would fail to parse, because with the skipper, *qi::graph would eat "42 false )" as well:
        std::cout << "next parse should fail:\n";
        test("( hello 42 false )", qi::raw [ '(' >>  *qi::graph >> qi::int_ >> qi::bool_ >> ')' ]);
    }