Mit boost :: spirit, wie kann ich verlangen, Teil eines Datensatzes in einer eigenen Zeile zu sein?
-
19-09-2019 - |
Frage
Ich habe einen Rekord-Parser, der eine von mehreren Ausnahmen auslöst, um anzuzeigen, welche Regel fehlgeschlagen.
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 der folgende Strom -insertion 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, die den Textvorschlag aus, um 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 verwendeten zusammen mit ihrem err_t
Wrapper:
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 sucht nach einfachen Sequenzen. Ohne eps
, schlägt die start
Regel und nicht 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 eine std::string
und verwenden position_iterator
Standort der 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 wird 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 führt 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]
erzeugt stattdessen das gleiche Ergebnis.
muss ich etwas fehlt offensichtlich, weil dies eine der banalsten Art von Parsern scheint zu schreiben. Was mache ich falsch?
Lösung
Ja, isst der Skipper die Zeilenumbrüche. lexeme[eol]
hilft nicht, weil entweder die lexeme Richtlinie vor dem Einschalten den Kapitän ruft zu No-Skipper-Modus (siehe hier für weitere Details).
Um Zeilenumbrüche zu vermeiden Skipping, entweder einen anderen Skipper Typen verwenden, oder wickeln Sie die eol
Komponenten in no_skip[eol]
, die lexeme[]
semantisch äquivalent ist, außer es nicht den Skipper nicht aufrufe. Beachten Sie aber, dass no_skip[]
wurde vor kurzem nur hinzugefügt, so dass es nur mit der nächsten Version verfügbar sein wird (Boost-V1.43). Aber es ist in den Boost bereits SVN (siehe hier für den vorläufigen docs).