2016-05-10 18 views
9

Sto cercando di imparare Boost.Spirit, ma ho trovato una difficoltà.Boost.Spirit.x3 evitare di comprimere due attributi consecutivi dello stesso tipo in un vettore

Sto cercando di analizzare una stringa nella seguente struttura:

struct employee { 
    std::string name; 
    std::string location; 
}; 

e sembra che quando due attributi con lo stesso tipo sono back to back, collassano (logicamente) in un std::vector di quel tipo . A causa di questa regola, il seguente parser

+x3::ascii::alnum >> 
    +x3::space >> 
    +x3::ascii::alnum 

avrebbe l'attributo di std::vector<std::string>.

Ma sto cercando di analizzare questo in quello struct, il che significa che l'attributo ideale per me sarebbe un boost::fusion::tuple<std::string, std::string>, quindi posso adattare la mia struttura ad esso.

La versione completa del codice non lavora (di cui sopra):

// Example program 
#include <iostream> 
#include <string> 

#include <boost/spirit/home/x3.hpp> 
#include <boost/fusion/include/adapt_struct.hpp> 


struct employee { 
    std::string name; 
    std::string location; 
}; 

BOOST_FUSION_ADAPT_STRUCT(employee, 
    (std::string, name), 
    (std::string, location) 
) 

namespace x3 = boost::spirit::x3; 

x3::rule<struct parse_emp_id, employee> const parse_emp = "Employee Parser"; 
auto parse_emp_def = 
    +x3::ascii::alnum >> 
    +x3::space >> 
    +x3::ascii::alnum 
    ; 
BOOST_SPIRIT_DEFINE(parse_emp); 

int main() 
{ 
    std::string input = "Joe Fairbanks"; 

    employee ret; 

    x3::parse(input.begin(), input.end(), parse_emp, ret); 

    std::cout << "Name: " << ret.name << "\tLocation: " << ret.location << std::endl; 
} 

See it live

Questo codice attiva un static_assert dicendomi che il mio attributo non è corretto:

error: static_assert failed "Attribute does not have the expected size." 

Con il comando di

clang++ -std=c++14 test.cpp 

(fallisce anche in GCC).

Quello che ho cercato

Ho trovato una soluzione a questo problema, ma è disordinato, e non riesco a credere che questo è il modo più pulito:

// Example program 
#include <iostream> 
#include <string> 

#include <boost/spirit/home/x3.hpp> 
#include <boost/fusion/include/adapt_struct.hpp> 


struct employee { 
    std::string name; 
    std::string location; 
}; 

namespace x3 = boost::spirit::x3; 

x3::rule<struct parse_emp_id, employee> const parse_emp = "Employee Parser"; 
auto parse_emp_def = 
    x3::eps [ 
    ([](auto& ctx) { 
     x3::_val(ctx) = employee{}; 
    }) 
    ]>> 
    (+x3::ascii::alnum)[ 
    ([](auto& ctx) { 
     x3::_val(ctx).name = x3::_attr(ctx); 
    }) 
    ]>> 
    +x3::space >> 
    (+x3::ascii::alnum)[ 
    ([](auto& ctx) { 
     x3::_val(ctx).location = x3::_attr(ctx); 
    }) 
    ] 
    ; 
BOOST_SPIRIT_DEFINE(parse_emp); 

int main() 
{ 
    std::string input = "Joe Fairbanks"; 

    employee ret; 

    x3::parse(input.begin(), input.end(), parse_emp, ret); 

    std::cout << "Name: " << ret.name << "\tLocation: " << ret.location << std::endl; 
} 

See it live

Non mi piace davvero questa soluzione: rovina la sorprendente espressività dello spirito e la rende super-brutta, anche se voglio aggiungere nuovi campi nella struttura employee, quindi devo aggiungere un lambda in più, invece di aggiornare il mio BOOST_FUSION_ADAPT_STRUCT, che è molto più facile.

Quindi la domanda è: c'è un modo per (si spera) dividere in modo pulito due attributi consecutivi dello stesso tipo da std::vector e in un boost::fusion::vector?

Grazie in anticipo per arrivare così lontano;).

risposta

6

Il problema è che a differenza dei caratteri letterali, x3::space ha un attributo. Quindi non hai un attributo di due sequenze di caratteri separate separate da spazi bianchi, ma piuttosto un attributo di una sequenza di caratteri grandi che include gli spazi bianchi.

The omit directive è ciò che stai cercando e con questa singola aggiunta il tuo "codice non funzionante" funziona.: -]

// Example program 
#include <string> 
#include <iostream> 

#include <boost/fusion/include/adapt_struct.hpp> 
#include <boost/spirit/home/x3.hpp> 

namespace x3 = boost::spirit::x3; 

struct employee { 
    std::string name; 
    std::string location; 
}; 
BOOST_FUSION_ADAPT_STRUCT(employee, name, location) 

x3::rule<struct parse_emp_id, employee> const parse_emp = "Employee Parser"; 
auto parse_emp_def 
    =  +x3::ascii::alnum 
     >> x3::omit[+x3::space] 
     >> +x3::ascii::alnum 
    ; 
BOOST_SPIRIT_DEFINE(parse_emp) 

int main() 
{ 
    std::string const input = "Joe Fairbanks"; 

    employee ret; 
    x3::parse(input.begin(), input.end(), parse_emp, ret); 

    std::cout << "Name: " << ret.name << "\tLocation: " << ret.location << '\n'; 
} 

Online Demo

+2

E 'una buona giornata in cui la risposta ad una domanda spirito è già lì prima di trovare la domanda :) +1 – sehe

+0

@sehe: Hah! Questo è il primo che ti ho battuto su una domanda X3 sicuramente, ma poi, questo è stato molto facile. ; -] – ildjarn

+1

@Russel Suggerirei di usare uno skipper invece di uno spazio bianco esplicito (consultate http://stackoverflow.com/questions/17072987/boost-spirit-skipper-issues/17073965#17073965). [Live on Coliru] (http://coliru.stacked-crooked.com/a/444dad3ed2859a35). – sehe