2

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.

sehe
  • 374,641
  • 47
  • 450
  • 633
Skeen
  • 4,614
  • 5
  • 41
  • 67
  • Questions: 1. Are you sure you want to use semantic actions for such simple things as exposing a character attribute? 2. What is `qi::token`? – sehe Aug 27 '13 at 08:27
  • Also possibly interesting: [“Semantic actions are evil”?](http://stackoverflow.com/questions/8259440/boost-spirit-semantic-actions-are-evil/8259585#8259585) – sehe Aug 27 '13 at 08:28
  • @sehe: The arguments to qi::token is an enum, which defines my tokens from the lexer. I know the usage of semantic actions for this, is currently overkill, however this is just a toy example, to get the basics before I start implementing my full fledged parser! Where I was planning on recursively building my AST. – Skeen Aug 27 '13 at 08:55
  • 1
    Using a lexer changes the picture, though only a little. Be sure to specify the attribute type in `token_def` and also in the `mpl::vector<>` list of supported token-attributes. That said, you still ***need*** to make the rule declarations `qi::rule` for it to work at all. – sehe Aug 27 '13 at 08:59
  • I just changed my parser rule to `char()`, and the compiler now wants `build_paren` to have the following prototype (as I'd like); `void print_paren(boost::fusion::vector3>>, std::string, boost::iterator_range<__gnu_cxx::__normal_iterator>>> vec)`, however the character is always `0`! – Skeen Aug 27 '13 at 09:01
  • The minimal code example is here; http://coliru.stacked-crooked.com/view?id=79e256230f9fef74b6626fe2f1a1d28d-93e6c6235a92d0c233f44beab03470ad – Skeen Aug 27 '13 at 09:09
  • Ouch. That looks sooo painful. Why are you using semantic actions? That looks... horrible. Anyways, just [assign a value to `_val`](http://coliru.stacked-crooked.com/view?id=a64acaa86be70fa9768b085ca6b0f64e-93e6c6235a92d0c233f44beab03470ad) (note the use of `std::string::iterator`). (See the comment as well) – sehe Aug 27 '13 at 09:40
  • If I was going to process the character, and then return a new one, how would I go about getting it the matched character as an argument? - http://coliru.stacked-crooked.com/view?id=132db957ccfb4a2448581753d501fa7c-93e6c6235a92d0c233f44beab03470ad – Skeen Aug 27 '13 at 11:17
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/36323/discussion-between-sehe-and-skeen) – sehe Aug 27 '13 at 11:31

1 Answers1

2

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_ >> ')' ]);
}
sehe
  • 374,641
  • 47
  • 450
  • 633