2010-03-11 3 views
6

Ich habe einen Datensatz-Parser, der eine von mehreren Ausnahmen auslöst, um anzuzeigen, welche Regel fehlgeschlagen ist.Wie kann ich mithilfe von boost :: spirit einen Teil eines Datensatzes in einer eigenen Zeile anzeigen?

Titelei:

#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; 

Wir verwenden die position_iterator von Spirit.Classic, so dass die folgenden Stream-Ausgabe-Operator ist praktisch.

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

Die Vorlage err_t Faktoren aus dem Textvorschlag für die Ausnahmen mit verschiedenen Formen von Parse Versagen zu werfen.

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()); 
    } 
}; 

Die Ausnahmen zusammen mit ihren err_t Wrapper verwendet:

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>(); 

Die Grammatik für einfache Sequenzen aussieht. Ohne eps schlägt die start Regel statt a auf einem leeren Eingang.

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

In my_parse, Dump wir den Inhalt des Stroms in einen std::string und verwenden position_iterator der Lage des Parse zu verfolgen.

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

schließlich das Hauptprogramm

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

Der obige Code wirft MissingA:

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

Ich dachte, der Kapitän könnte die Newline verbraucht haben, aber versuchen lexeme[eol] stattdessen das gleiche Ergebnis erzeugt .

Ich muss etwas offensichtlich fehlen, weil dies eine der trivialsten Art von Parser zu schreiben scheint. Was mache ich falsch?

Antwort

7

Ja, der Skipper isst die Newline-Zeichen. lexeme[eol] hilft auch nicht, weil die Lexem-Direktive den Skipper vor dem Wechsel in den Nicht-Skipper-Modus aufruft (siehe here für weitere Details).

Um Skipping newlines zu vermeiden, entweder einen anderen Skipper Typen verwenden, oder wickeln die eol Komponenten in no_skip[eol], was zu lexeme[] semantisch äquivalent ist, außer dass es den Skipper nicht aufrufe tut. Beachten Sie jedoch, dass no_skip[] erst kürzlich hinzugefügt wurde, so dass es nur mit der nächsten Version (Boost V1.43) verfügbar sein wird. Aber es ist in der Boost SVN bereits (siehe here für die vorläufigen Dokumente).