2010-03-11 4 views
6

У меня есть парсер записей, который выдает одно из нескольких исключений, чтобы указать, какое правило не удалось.Использование boost :: spirit, как мне нужно, чтобы часть записи была в отдельной строке?

вводная:

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

Мы используем position_iterator от Spirit.Classic, поэтому следующий оператор в поток вставки удобно.

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

Шаблон err_t факторы вне шаблонного для метания исключения, связанные с различными формами синтаксического анализа недостаточности.

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

Исключения, используемые вместе с их err_t оберток:

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

Грамматика выглядит для простых последовательностей. Без eps правило start выходит из строя, а не a на пустом входе.

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

В my_parse, мы дамп содержимого потока в std::string и использовать position_iterator для отслеживания местоположения синтаксического анализа в.

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

Наконец, основная программа

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

Код выше бросает MissingA:

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

Я думал, шкипер мог бы потребляли символ новой строки, но попытка lexeme[eol] вместо произвел тот же результат ,

Должно быть, мне не хватает чего-то очевидного, потому что это кажется одним из самых тривиальных парсеров для написания. Что я делаю не так?

ответ

7

Да, шкипер ест символы новой строки. lexeme[eol] не помогает ни потому, что директива lexeme вызывает шкипера перед переключением в режим без шкипера (см. here для получения дополнительной информации).

Для того, чтобы избежать пропуска новой строки, либо использовать другой тип шкипера или обернуть eol компоненты в no_skip[eol], который семантически эквивалентен lexeme[], за исключением того, что не вызывает шкипера. Обратите внимание, однако, что только no_skip[] был добавлен, поэтому он будет доступен только с следующей версией (Boost V1.43). Но это уже в Boost SVN (см. here для предварительных документов).