2010-03-11 13 views
6

Tengo un analizador de registros que arroja una de varias excepciones para indicar qué regla falló.Usando boost :: spirit, ¿cómo puedo exigir que parte de un registro esté en su propia línea?

Información preliminar:

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

Utilizamos el position_iterator de Spirit.Classic, por lo que el siguiente operador de la corriente de inserción es práctico.

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

La plantilla err_t factores fuera el repetitivo para lanzar las excepciones asociadas con diferentes formas de insuficiencia de análisis.

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

Las excepciones se utilizan junto con sus envolturas 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>(); 

La gramática mira para secuencias simples. Sin eps, la regla start falla en lugar de a en una entrada vací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; 
}; 

En my_parse, que volcar el contenido de la corriente en un std::string y utilizar position_iterator a rastrear la ubicación del análisis.

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

Por último, el programa principal

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

El código anterior tiros MissingA:

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

pensé que el capitán podría haber consumido la nueva línea, pero intentar lexeme[eol] vez producido el mismo resultado .

Debo perderme algo obvio porque este parece ser uno de los analizadores sintácticos más triviales para escribir. ¿Qué estoy haciendo mal?

Respuesta

7

Sí, el patrón se come los caracteres de nueva línea. lexeme[eol] tampoco ayuda porque la directiva lexeme invoca al patrón antes de cambiar al modo sin patrón (consulte here para obtener más información).

Para evitar saltarse saltos de línea, o bien utilizar un tipo de patrón diferente, o envolver los eol componentes en no_skip[eol], que es semánticamente equivalente a lexeme[], excepto que no invoca el patrón. Sin embargo, tenga en cuenta que no_skip[] se ha agregado recientemente, por lo que estará disponible solo con la próxima versión (Boost V1.43). Pero ya está en Boost SVN (vea here para los documentos preliminares).

Cuestiones relacionadas