Как обобщить анализатор духа для получения списков в произвольном порядке?

У меня есть простой парсер, который может анализировать списки целых или строки в кавычках.

Если я сделаю SIMPLE_CASE где я принимаю входные данные:

std::string input1 = "{ INT: 42, 24 STR: \"Smith\", \"John\" }";

он правильно разбирается в my_record, который содержит список целых и список std :: string.

Я хочу изменить этот код, чтобы он был универсальным, чтобы он мог принимать ноль или более списков INT и ноль или более списков STR в произвольном порядке и вставлять их в my_record в правильном порядке. Я хотел бы мой второй, более общий тестовый пример:

std::string input1 = "{ STR: \"Joe\" INT: 42, 24 STR: \"Smith\", \"John\" }";

разобрать как:

client::my_record expected1 { { 42, 24 }, {"Joe", "Smith", "John"} };

Код ниже работает нормально, если я бегу:

/tmp$ g++ -DSIMPLE_CASE -g -std=c++11 sandbox.cpp -o sandbox && ./sandbox

но я не уверен, как заставить общее дело работать при запуске этого:

/tmp$ g++ -g -std=c++11 sandbox.cpp -o sandbox && ./sandbox

Код для sandbox.cpp

#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_object.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/io.hpp>

#include <string>
#include <complex>
#include <algorithm>

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

struct my_record
{
std::vector<int>          m_ints;
std::vector<std::string>  m_strs;

bool operator==( const my_record& other ) const
{
return std::equal( m_ints.begin(), m_ints.end(), other.m_ints.begin() )
&& std::equal( m_strs.begin(), m_strs.end(), other.m_strs.begin() );
}
bool operator!=( const my_record& other ) const
{
return ! operator==( other );
}
friend std::ostream& operator<<( std::ostream& os, const my_record& rec );
};

std::ostream& operator<<( std::ostream& os, const my_record& rec )
{
for( const auto& x : rec.m_ints )
std::cerr << x << ' ';
std::cerr << std::endl;

for( const auto& x : rec.m_strs )
std::cerr << x << ' ';
std::cerr << std::endl;

}
}

BOOST_FUSION_ADAPT_STRUCT(
client::my_record,
(std::vector<int>,          m_ints)
(std::vector<std::string>,  m_strs)
)

namespace client
{
template <typename Iterator>
struct employee_parser : qi::grammar<Iterator, my_record(), ascii::space_type>
{
employee_parser() : employee_parser::base_type(start)
{
using qi::int_;
using qi::lit;
using qi::double_;
using qi::lexeme;
using ascii::char_;

quoted_string %= lexeme['"' >> +(char_ - '"') >> '"'];

#ifdef SIMPLE_CASE
start %=
'{'
>>  int_list
>>  str_list
>>  '}'
;
#else
// not sure how to approach this
start %=
'{'
>>  *(int_list)  // want zero or more of these, in any order
>>  *(str_list)  // want zero or more of these, in any order
>>  '}'
;
#endif

str_list %=
lit( "STR:" ) >> quoted_string % ','
;

int_list %=
lit( "INT:" ) >> int_ % ','
;
}

qi::rule<Iterator, std::string(), ascii::space_type>               quoted_string;
qi::rule<Iterator, std::vector<std::string>(), ascii::space_type>  str_list;
qi::rule<Iterator, std::vector<int>(),         ascii::space_type>  int_list;

qi::rule<Iterator, my_record(), ascii::space_type>                 start;
};
}

static int
TryParse( const std::string& input, const client::my_record& expected )
{
using boost::spirit::ascii::space;
client::my_record                        rec;
auto                                     iter = input.begin(), end = input.end();
client::employee_parser<decltype(iter)>  g;
phrase_parse( iter, end, g, space, rec );
if ( iter!=end )
{
std::cerr << "failed to parse completely" << std::endl;
return -1;
} else if ( rec!=expected ) {
std::cerr << "unexpected result in parse" << std::endl;
std::cerr << rec;
return -1;
}
return 0;
}

int
main(int argc, char* argv[])
{
#ifdef SIMPLE_CASE
client::my_record  expected1 { { 42, 24 }, {"Smith", "John"} }, emp;
std::string        input1 = "{ INT: 42, 24 STR: \"Smith\", \"John\" }";
return TryParse( input1, expected1 );
#else
client::my_record  expected1 { { 42, 24 }, {"Joe", "Smith", "John"} }, emp;
std::string        input1 = "{ STR: \"Joe\" INT: 42, 24 STR: \"Smith\", \"John\" }";
return TryParse( input1, expected1 );
#endif

}

3

Решение

Вы грамматика не так,

    start %=
'{'
>>  *(int_list)  // want zero or more of these, in any order
>>  *(str_list)  // want zero or more of these, in any order
>>  '}'
;

Это значит принять любое количество intс последующим любым числом string, Вы не можете иметь int, string, intили любая другая комбинация.

Вам нужно что-то вроде

    start %=
'{'
>> *( int_list  // want zero or more of these, in any order
| str_list  // want zero or more of these, in any order
)
>>
'}'
;

Но, очевидно, вам нужно добавить это в вашу структуру данных, предупредив, что вам, возможно, придется использовать семантические действия.

ТАКЖЕ:

пока я здесь, я не могу пропустить этот слайд:

    std::ostream& operator<<( std::ostream& os, const my_record& rec )
{
for( const auto& x : rec.m_ints )
std::cerr << x << ' ';
std::cerr << std::endl;

for( const auto& x : rec.m_strs )
std::cerr << x << ' ';
std::cerr << std::endl;

}

должен быть в упор на os лайк:

        for( const auto& x : rec.m_ints )
os << x << ' ';
os << '\n';

Также попытайтесь избежать endlВ операторе вставки потока используйте \n если вам нужна новая строка.

РЕШЕНИЕ:

В конце концов нужно было использовать функции Phoenix, push_back и связыватель.

template<typename Iterator>
struct my_grammar
: qi::grammar<Iterator, my_record(), ascii::space_type> {

my_grammar()
: my_grammar::base_type(start) {

quoted_string %= qi::lexeme['"' >> +(qi::char_ - '"') >> '"'];

start = qi::lit("{")
>>
*( "INT:" >> qi::int_
[
phx::push_back(
phx::at_c<0>(
qi::_val
),
qi::_1
)
] % ","| "STR:" >> quoted_string
[
phx::push_back(
phx::bind(
&my_record::m_strs,
qi::_val
),
qi::_1
)
] % ",")
>>
"}";
}
qi::rule<Iterator, std::string(), ascii::space_type> quoted_string;
qi::rule<Iterator, my_record(),   ascii::space_type>   start;
};

Полный список кода можно увидеть здесь:

http://ideone.com/XW18Z2

4

Другие решения

Альтернативное использование is_container а также push_back_container вместо смысловых действий:

Шаг 1: удалите свой BOOST_FUSION_ADAPT_STRUCT макро.

Шаг 2: измени свой start править.

start %=
'{'
>>  *(int_list // want zero or more of these, in any order
| str_list)  // want zero or more of these, in any order
>>  '}'
;

Шаг 3: Добавьте следующие специализации.

namespace boost { namespace spirit { namespace traits
{
template <>
struct is_container<client::my_record>: mpl::true_//my_record acts as a container
{};

template <>
struct container_value<client::my_record>
{
typedef boost::variant<std::vector<int>,std::vector<std::string>> type;//The elements to add to that container are either vector<int> or vector<string>
};template <>
struct push_back_container<client::my_record,std::vector<int>>//when you add a vector of ints...
{
static bool call(client::my_record& c, std::vector<int> const& val)
{
c.m_ints.insert(c.m_ints.end(),val.begin(), val.end());//insert it at the end of your acumulated vector of ints
return true;
}
};

template <>
struct push_back_container<client::my_record,std::vector<std::string>>//when you add a vector of strings
{
static bool call(client::my_record& c, std::vector<std::string> const& val)//insert it at the end of your acumulated vector of strings
{
c.m_strs.insert(c.m_strs.end(),val.begin(),val.end());
return true;
}
};

}}}

Вот полный код, который был запрошен (компилируется с g ++ 4.7.1 и msvc11, если я создаю ожидаемый результат, используя несколько push_backs):

Обновлен пример для добавления другого вектора-члена адаптированных структур.

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

namespace client
{
struct my_subrec
{
double foo;
double bar;
bool operator==( const my_subrec& other ) const
{
return foo==other.foo && bar==other.bar;
}
};

std::ostream& operator<<( std::ostream& os, const my_subrec& rec )
{
os << rec.foo << "->" << rec.bar;
return os;
}

}

BOOST_FUSION_ADAPT_STRUCT(client::my_subrec,
(double, foo)
(double, bar)
)namespace client
{
namespace qi    = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;struct my_record
{
std::vector<int>          m_ints;
std::vector<std::string>  m_strs;
std::vector<my_subrec>    m_recs;

bool operator==( const my_record& other ) const
{
return std::equal( m_ints.begin(), m_ints.end(), other.m_ints.begin() )
&& std::equal( m_strs.begin(), m_strs.end(), other.m_strs.begin() )
&& std::equal( m_recs.begin(), m_recs.end(), other.m_recs.begin() );
}
bool operator!=( const my_record& other ) const
{
return ! operator==( other );
}
friend std::ostream& operator<<( std::ostream& os, const my_record& rec );
};

std::ostream& operator<<( std::ostream& os, const my_record& rec )
{
for( const auto& x : rec.m_ints )
os << x << ' ';
os << '\n';

for( const auto& x : rec.m_strs )
os << x << ' ';
os << '\n';

for( const auto& x : rec.m_recs )
os << x << ' ';
return os;
}
}

//BOOST_FUSION_ADAPT_STRUCT(
//    client::my_record,
//        (std::vector<int>,          m_ints)
//        (std::vector<std::string>,  m_strs)
//)namespace client
{
template <typename Iterator>
struct employee_parser : qi::grammar<Iterator, my_record(), ascii::space_type>
{
employee_parser() : employee_parser::base_type(start)
{
using qi::int_;
using qi::lit;
using qi::double_;
using qi::lexeme;
using ascii::char_;

quoted_string %= lexeme['"' >> +(char_ - '"') >> '"'];

#ifdef SIMPLE_CASE
start %=
'{'
>>  int_list
>>  str_list
>>  '}'
;
#else
// not sure how to approach this
start %=
'{'
>>  *(int_list // want zero or more of these, in any order
| str_list  // want zero or more of these, in any order
| rec_list)
>>  '}'
;
#endif

str_list %=
lit( "STR:" ) >> quoted_string % ','
;

int_list %=
lit( "INT:" ) >> int_ % ','
;
rec_list =
lit( "REC:" ) >> rec % ','
;
rec = double_ >> lit('-') >> double_
;
}

qi::rule<Iterator, std::string(), ascii::space_type>               quoted_string;
qi::rule<Iterator, std::vector<std::string>(), ascii::space_type>  str_list;
qi::rule<Iterator, std::vector<int>(),         ascii::space_type>  int_list;
qi::rule<Iterator, client::my_subrec(), ascii::space_type> rec;
qi::rule<Iterator, std::vector<client::my_subrec>(),ascii::space_type> rec_list;

qi::rule<Iterator, my_record(), ascii::space_type>                 start;
};
}

namespace boost { namespace spirit { namespace traits
{
template <>
struct is_container<client::my_record>: mpl::true_//my_record acts as a container
{};

template <>
struct container_value<client::my_record>
{
typedef boost::variant<std::vector<int>,std::vector<std::string>,std::vector<client::my_subrec> >type;
//The elements to add to that container are vector<int>, vector<string> or vector<my_subrec>
};template <>
struct push_back_container<client::my_record,std::vector<int>>//when you add a vector of ints...
{
static bool call(client::my_record& c, std::vector<int> const& val)
{
c.m_ints.insert(c.m_ints.end(),val.begin(), val.end());//insert it at the end of your acumulated vector of ints
return true;
}
};

template <>
struct push_back_container<client::my_record,std::vector<std::string>>//when you add a vector of strings
{
static bool call(client::my_record& c, std::vector<std::string> const& val)//insert it at the end of your acumulated vector of strings
{
c.m_strs.insert(c.m_strs.end(),val.begin(),val.end());
return true;
}
};

template <>
struct push_back_container<client::my_record,std::vector<client::my_subrec>>//when you add a vector of subrecs
{
static bool call(client::my_record& c, std::vector<client::my_subrec> const& val)//insert it at the end of your acumulated vector of subrecs
{
c.m_recs.insert(c.m_recs.end(),val.begin(),val.end());
return true;
}
};

}}}

static int
TryParse( const std::string& input, const client::my_record& expected )
{
using boost::spirit::ascii::space;
client::my_record                        rec;
auto                                     iter = input.begin(), end = input.end();
client::employee_parser<decltype(iter)>  g;
phrase_parse( iter, end, g, space, rec );
if ( iter!=end )
{
std::cerr << "failed to parse completely" << std::endl;
return -1;
} else if ( rec!=expected ) {
std::cerr << "unexpected result in parse" << std::endl;
std::cerr << rec;
return -1;
}
std::cout << rec << std::endl;
return 0;
}

int
main(int argc, char* argv[])
{
#ifdef SIMPLE_CASE
client::my_record  expected1 { {42, 24 }, {"Smith", "John"} }, emp;
std::string        input1 = "{ INT: 42, 24 STR: \"Smith\", \"John\" }";
return TryParse( input1, expected1 );
#else
client::my_record  expected1 { { 42, 24,240 }, {"Joe", "Smith", "John"}, {{1.5,2.5}} }, emp;

std::string        input1 = "{ STR: \"Joe\" INT: 42, 24 STR: \"Smith\", \"John\" INT: 240 REC: 1.5-2.5 }";
return TryParse( input1, expected1 );
#endif

}
4