Question

I am writing a c++ application with several complex structs and

I want to read a string and fill those structs by data provided in that text.

But for easier understanding and debugging, i've wrote easy program with same problem.

This is my code:

#include <string>
#include <iostream>

#define FUSION_MAX_VECTOR_SIZE  30
#define BOOST_PHOENIX_USE_V2_OVER_V3

#include <boost/spirit/home/phoenix/bind/bind_function.hpp>
#include <boost/phoenix/bind.hpp>
#include <boost/fusion/adapted.hpp>
#include <boost/spirit/include/qi.hpp>


namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;
namespace phoenix = boost::phoenix;

using qi::double_;
using qi::char_;
using qi::lexeme;
using qi::int_;
using qi::lit;
using qi::_1;
using ascii::space;
using phoenix::ref;
using qi::parser;


class Test 
{
    // Class fields
    std::string test_str;
    public:

        Test(std::string& sample_str)
        {
            test_str = sample_str;
        }

        struct fruit
        {
            std::string name;
            std::string color;
        };
        BOOST_FUSION_ADAPT_STRUCT
            (
             fruit,
             (std::string, name)
             (std::string, color)
            );      
        struct person
        {
            std::string name;
            int age;
        };

        BOOST_FUSION_ADAPT_STRUCT
            (
             person,
             (std::string, name)
             (int, age)
            );

        void received_person(person& p)
        {
            std::cout << p.name << " with age"<< p.age<< " has been seen!"<<std::endl;
        }
        void received_fruit(fruit& f)
        {
            std::cout << f.name<<" is "<<f.color<<std::endl;
        }

        template <typename Iterator>
            struct MyGrammar : boost::spirit::qi::grammar<Iterator, void()>
        {
            MyGrammar() : MyGrammar::base_type(my_item)
            {
                my_item = *(fruit[ boost::phoenix::bind(&received_fruit, boost::spirit::_1 )]
                        |
                        _person[ boost::phoenix::bind(&received_person, boost::spirit::_1 )]
                        );

                 _person = qi::lit('(') >> *(qi::char_ - ',') >> ',' >> qi::int_ >> ')';
                _fruit = qi::lit('[') >> *(qi::char_ - ',') >> ',' >> *(qi::char_) >> ']';


            }
            qi::rule<Iterator, void()> my_item;
            qi::rule<Iterator, person()> _person;
            qi::rule<Iterator, fruit()> _fruit;
        };

        void run()
        {
            typedef std::string::const_iterator iterator;
            MyGrammar <std::string::const_iterator> my_grammar;
            std::string::const_iterator begin = test_str.begin();
            std::string::const_iterator end = test_str.end();
            bool result_ = qi::parse(begin, end, my_grammar) && begin == end;
        }
};


int main()
{
    std::string input("(jane, 23000)(david, 19)(mary, 30)[yello,100][green, 60.6][red, 30.5]");

    Test test(input);
    test.run();

    return 0;
}

g++ compiles this code and generates this error:

expected unqualified-id before 'namespace' 

i know this code can be written without using class, but i want to use class in main project. many thanks in advance!

Was it helpful?

Solution

Addressing the issues, first:

  1. BOOST_FUSION_ADAPT_STRUCT needs to be at global namespace

    The macro should be used at global scope, and struct_name should be the fully namespace qualified name of the struct to be adapted. [documentation]

  2. you needed a skipper to allow for whitespace as in your sample input

    • this also implies you need qi::phrase_parse to supply a skipper
    • this also implies that begin == end was redundant (because qi::parse always consumes the whole input on success AFAIR)
    • this also implies you need qi::lexeme to control when you expect whitespace as part of the parsed content
  3. you needed the callbacks to be static

  4. you needed to modify the _fruit rule to read until the closing ]

With these taken care of, your code works: See it Live on Coliru

Bonus material

Just in case you didn't know, you don't have to jump through all these fusion/phoenix hoops if you don't want. You'd usually parse into datastructures first, and then process there.

See this demonstrated Live On Coliru too.

E.g., data structures:

namespace ast {
    struct fruit {
        std::string name;
        std::string color;
    };
    struct person {
        std::string name;
        int age;
    };

    typedef boost::variant<fruit, person> record;
    typedef std::vector<record> records;

    // for our demo output:
    static std::ostream& operator<<(std::ostream& os, fruit const& f);
    static std::ostream& operator<<(std::ostream& os, person const& p);
}

And the code that handles it (note improved status reporting):

int main()
{
    typedef std::string::const_iterator It;
    std::string const input("(jane, 23000)(david, 19)(mary, 30)[yello,100][green, 60.6][red, 30.5]");

    MyGrammar <It> my_grammar;

    It begin(input.begin()), end(input.end());

    ast::records data;
    if (qi::phrase_parse(begin, end, my_grammar, qi::space, data))
    {
        std::cout << "Parse success\n";
        for (auto const& record : data)
            std::cout << record << "\n";
    }
    else
        std::cout << "Parse failed\n";

    if (begin != end)
        std::cout << "Remaining unparsed: '" << std::string(begin, end) << "'\n";
}

Now, the clincher is, the grammar becamse simpler too (no more phoenix; I assumed that names/colors should not contain ,,] or )):

    _text   = qi::lexeme [ *~qi::char_(",)]") ];
    _person = qi::lit('(') >> _text >> ',' >> qi::int_ >> ')';
    _fruit  = qi::lit('[') >> _text >> ',' >> _text >> ']';

    _start  = *(_fruit | _person);

Here's the full code listing:

#include <string>
#include <iostream>
#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/qi.hpp>

namespace qi = boost::spirit::qi;

namespace ast {
    struct fruit {
        std::string name;
        std::string color;

        friend std::ostream& operator<<(std::ostream& os, fruit const& f) { return os << f.name << " is " << f.color; }
    };
    struct person {
        std::string name;
        int age;

        friend std::ostream& operator<<(std::ostream& os, person const& p) { return os << p.name << " with age" << p.age << " has been seen!"; }
    };

    typedef boost::variant<fruit, person> record;
    typedef std::vector<record> records;
}

BOOST_FUSION_ADAPT_STRUCT(ast::fruit,
    (std::string, name)
    (std::string, color))

BOOST_FUSION_ADAPT_STRUCT(ast::person,
    (std::string, name)
    (int, age))

template <typename Iterator, typename Skipper = qi::space_type>
    struct MyGrammar : qi::grammar<Iterator, ast::records(), Skipper>
{
    MyGrammar() : MyGrammar::base_type(_start)
    {
        _text   = qi::lexeme [ *~qi::char_(",)]") ];
        _person = qi::lit('(') >> _text >> ',' >> qi::int_ >> ')';
        _fruit  = qi::lit('[') >> _text >> ',' >> _text >> ']';

        _start  = *(_fruit | _person);
    }

    qi::rule<Iterator, ast::records(), Skipper> _start;
    qi::rule<Iterator, ast::person(),  Skipper> _person;
    qi::rule<Iterator, ast::fruit(),   Skipper> _fruit;
    qi::rule<Iterator, std::string()>           _text;
};

int main()
{
    typedef std::string::const_iterator It;
    std::string const input("(jane, 23000)(david, 19)(mary, 30)[yello,100][green, 60.6][red, 30.5]");

    MyGrammar <It> my_grammar;

    It begin(input.begin()), end(input.end());

    ast::records data;
    if (qi::phrase_parse(begin, end, my_grammar, qi::space, data))
    {
        std::cout << "Parse success\n";
        for (auto& r : data)
            std::cout << r << "\n";
    }
    else
        std::cout << "Parse failed\n";

    if (begin != end)
        std::cout << "Remaining unparsed: '" << std::string(begin, end) << "'\n";
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top