Search code examples
c++boost-spirit-qi

expected unqualified-id before 'namespace' error in working with boost spirit qi


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!


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";
    }