2010-03-11 15 views
6

Mam parser rekordów, który zgłasza jeden z kilku wyjątków, aby wskazać, która reguła nie powiodła się.Korzystając z boost :: spirit, w jaki sposób wymagać, aby część rekordu znajdowała się na osobnej linii?

względu przedni:

#include <iostream> 
#include <sstream> 
#include <stdexcept> 
#include <string> 

#include <boost/spirit/include/qi.hpp> 
#include <boost/spirit/include/phoenix.hpp> 
#include <boost/spirit/include/classic_position_iterator.hpp> 

using namespace boost::spirit; 
using namespace boost::spirit::ascii; 
using namespace boost::spirit::qi; 
using namespace boost::spirit::qi::labels; 

using boost::phoenix::function; 
using boost::phoenix::ref; 
using boost::spirit::qi::eol; 
using boost::spirit::qi::fail; 
using boost::spirit::qi::lit; 
using boost::spirit::qi::on_error; 

using BOOST_SPIRIT_CLASSIC_NS::file_position; 
using BOOST_SPIRIT_CLASSIC_NS::position_iterator; 

używamy position_iterator z Spirit.Classic, więc ten operator strumieniowo wstawiania jest przydatna.

std::ostream& 
operator<<(std::ostream& o, const file_position &fp) 
{ 
    o << fp.file << ": " << fp.line << ',' << fp.column; 
    return o; 
} 

Szablon err_t czynniki zewnątrz boilerplate do rzucania wyjątków związanych z różnymi formami niepowodzenia przetworzenia.

template <typename Exception> 
struct err_t { 
    template <typename, typename, typename> 
    struct result { typedef void type; }; 

    template <typename Iterator> 
    void operator() (info const &what, Iterator errPos, Iterator last) const 
    { 
    std::stringstream ss; 
    ss << errPos.get_position() 
     << ": expecting " << what 
     << " near '" << std::string(errPos, last) << "'\n"; 
    throw Exception(ss.str()); 
    } 
}; 

Wyjątki stosowane wraz z ich err_t owijarki:

class MissingA : public std::runtime_error { 
    public: MissingA(const std::string &s) : std::runtime_error(s) {} 
}; 

class MissingB : public std::runtime_error { 
    public: MissingB(const std::string &s) : std::runtime_error(s) {} 
}; 

class MissingC : public std::runtime_error { 
    public: MissingC(const std::string &s) : std::runtime_error(s) {} 
}; 

function<err_t<MissingA> > const missingA = err_t<MissingA>(); 
function<err_t<MissingB> > const missingB = err_t<MissingB>(); 
function<err_t<MissingC> > const missingC = err_t<MissingC>(); 
function<err_t<std::runtime_error> > const other_error = 
    err_t<std::runtime_error>(); 

Gramatyka wygląda na prostych sekwencjach. Bez eps, reguła start zawodzi, a nie a na pustym wejściu.

template <typename Iterator, typename Skipper> 
struct my_grammar 
    : grammar<Iterator, Skipper> 
{ 
    my_grammar(int &result) 
    : my_grammar::base_type(start) 
    , result(result) 
    { 
    a = eps > lit("Header A") > eol; 
    b = eps > lit("Header B") > eol; 
    c = eps > lit("C:") > int_[ref(result) = _1] > eol; 
    start = a > b > c; 

    a.name("A"); 
    b.name("B"); 
    c.name("C"); 

    on_error<fail>(start, other_error(_4, _3, _2)); 
    on_error<fail>(a, missingA(_4, _3, _2)); 
    on_error<fail>(b, missingB(_4, _3, _2)); 
    on_error<fail>(c, missingC(_4, _3, _2)); 
    } 

    rule<Iterator, Skipper> start; 
    rule<Iterator, Skipper> a; 
    rule<Iterator, Skipper> b; 
    rule<Iterator, Skipper> c; 
    int &result; 
}; 

W my_parse, możemy zrzucić zawartość strumienia w std::string i używać position_iterator śledzenie położenia analizowania tych.

int 
my_parse(const std::string &path, std::istream &is) 
{ 
    std::string buf; 
    is.unsetf(std::ios::skipws); 
    std::copy(std::istream_iterator<char>(is), 
      std::istream_iterator<char>(), 
      std::back_inserter(buf)); 

    typedef position_iterator<std::string::const_iterator> itertype; 
    typedef my_grammar<itertype, boost::spirit::ascii::space_type> grammar; 
    itertype it(buf.begin(), buf.end(), path); 
    itertype end; 

    int result; 
    grammar g(result); 

    bool r = phrase_parse(it, end, g, boost::spirit::ascii::space); 
    if (r && it == end) { 
    std::cerr << "success!\n"; 
    return result; 
    } 
    else { 
    file_position fpos = it.get_position(); 
    std::cerr << "parse failed at " << fpos << '\n'; 
    return -9999; 
    } 
} 

Wreszcie główny program

int main() 
{ 
    std::stringstream ss; 
    ss << "Header A\n" 
    << "Header B\n" 
    << "C: 3\n"; 

    int val = my_parse("path", ss); 
    std::cout << "val = " << val << '\n'; 

    return 0; 
} 

Powyższy kod rzuca MissingA:

terminate called after throwing an instance of 'MissingA' 
    what(): path: 2,1: expecting near 'Header B 
C: 3 
'

Myślałem, że sternik mógł strawił przełamane, ale próby lexeme[eol] zamiast wyprodukował sam rezultat .

Muszę zabraknąć czegoś oczywistego, ponieważ wydaje się to jeden z najbardziej trywialnych rodzajów parserów do napisania. Co ja robię źle?

Odpowiedz

7

Tak, skipper zje znaki nowej linii. lexeme[eol] również nie pomaga, ponieważ dyrektywa w języku leksymaguje kapitana przed przełączeniem w tryb braku szyfru (więcej informacji można znaleźć w artykule here).

W celu uniknięcia pomijania nowe linie, można użyć innego rodzaju sternik lub owijania elementów eol do no_skip[eol], który jest równoważny znaczeniowo z lexeme[] wyjątkiem tego, że nie odwołuje się do szypra. Zauważ jednak, że tylko no_skip[] został ostatnio dodany, więc będzie dostępny tylko z następną wersją (Boost V1.43). Ale jest już w Boost SVN (zob. here dla wstępnych dokumentów).

Powiązane problemy