2010-09-23 8 views
5

Por mi propio marco pequeño analizador, estoy tratando de definir (algo así como) la siguiente función:"copia de carbono" a C++ istream?

template <class T> 
// with operator>>(std::istream&, T&) 
void tryParse(std::istream& is, T& tgt) 
{ 
    is >> tgt /* , *BUT* store every character that is consumed by this operation 
    in some string. If afterwards, is.fail() (which should indicate a parsing 
    error for now), put all the characters read back into the 'is' stream so that 
    we can try a different parser. */ 
} 

entonces podría escribir algo como esto: (quizás no es el mejor ejemplo)

/* grammar: MyData  = <IntTriple> | <DoublePair> 
      DoublePair = <double> <double> 
      IntTriple = <int> <int> <int> */ 
class MyData 
{ public: 
    union { DoublePair dp; IntTriple it; } data; 
    bool isDoublePair; 
}; 

istream& operator>>(istream& is, MyData& md) 
{ 
    /* If I used just "is >> md.data.it" here instead, the 
     operator>>(..., IntTriple) might consume two ints, then hit an 
     unexpected character, and fail, making it impossible to read these two 
     numbers as doubles in the "else" branch below. */ 
    tryParse(is, md.data.it); 
    if (!is.fail()) 
     md.isDoublePair = false; 
    else 
    { 
     md.isDoublePair = true; 
     is.clear(); 
     is >> md.data.dp; 
    } 
    return is; 
} 

Cualquier ayuda es muy apreciada.

+0

Las transmisiones no son la herramienta adecuada para esto, debido a su falta de recuperación adecuada. Al diseñar analizadores simples en línea como este (de lo contrario, intente 'boost :: spirit'), las funciones de análisis realmente deberían tener un par de iteradores. Es fácil retrotraer (solo guarde el valor del iterador antes de un analizador de retroceso). –

Respuesta

3

Desafortunadamente, las transmisiones solo tienen un soporte de retorno muy simple y rudimentario.

Las últimas veces que necesité esto, escribí mis propias clases de lectores que incluían una secuencia, pero tenía un búfer para volver a poner las cosas y leer de la transmisión solo cuando ese búfer está vacío. Estas tenían formas de obtener un estado, y usted podría comprometer un estado o revertir a un estado anterior.
La acción predeterminada en el destructor de la clase de estado era retrotraerse, de forma que pudiera analizar más adelante sin pensar demasiado en el manejo de errores, porque una excepción simplemente revertiría el estado del analizador hasta un punto donde se intentó una regla gramatical diferente. (Creo que esto se llama retroceder.) Aquí hay un boceto:

class parse_buffer { 
    friend class parse_state; 
public: 
    typedef std::string::size_type index_type; 

    parse_buffer(std::istream& str); 

    index_type get_current_index() const; 
    void set_current_index(index_type) const; 

    std::string get_next_string(bool skip_ws = true) const; 
    char get_next_char(bool skip_ws = true); 
    char peek_next_char(bool skip_ws = true); 

    std::string get_error_string() const; // returns string starting at error idx 
    index_type get_error_index() const; 
    void set_error_index(index_type); 

    bool eof() const; 

    // ... 
}; 

class parse_state { 
public: 
    parse_state(parse_buffer&); 
    ~parse_state(); 

    void commit(); 
    void rollback(); 

    // ... 
}; 

Esto debería hacerte una idea. No tiene ninguna implementación, pero fue sencillo y debería ser fácil de rehacer. Además, el código real tenía muchas funciones convenientes como leer funciones que leen una cadena delimitada, consumir una cadena si era una de varias palabras clave determinadas, leer una cadena y convertirla a un tipo dado por parámetro de plantilla, y cosas como esta.

La idea era que una función establezca el índice de error en su posición inicial, guarde el estado de análisis e intente analizar hasta que tenga éxito o se ejecute en un callejón sin salida. En este último caso, lanzaría una excepción. Esto destruiría los objetos parse_state en la pila, reduciendo el estado hasta una función que podría detectar la excepción y probar algo más, o emitir un error (que es donde entra get_error_string())

Si quieres un Analizador realmente rápido, esta estrategia puede ser incorrecta, pero a menudo las transmisiones también se ralentizan. OTOH, la última vez que utilicé algo así, hice un analizador XPath que opera en un DOM propietario, que se usa para representar escenas en un renderizador 3D.Y fue no el analizador XPath que obtuvo todo el calor de los chicos tratando de obtener mayores velocidades de cuadro. :)

+0

Eso suena bastante interesante. ¿Todavía tienes el código y es de código abierto? ¿Puedo echarle un vistazo? – rainmaker

+0

¡Oh, qué bueno! Entonces, parse_state :: rollback() solo llama a set_current_index (index_on_my_creation) en su parse_buffer? y commit() hace - uhm - nada, dejando el índice parse_buffer donde está? Ah, pero commit() podría decirle a parse_buffer que no vamos a deshacer() antes de la posición actual y por lo tanto es seguro (al menos para este parse_state) olvidar todo antes de esa posición ... Sí, creo que empiezo comprender. Voy a intentar esto, ¡muchas gracias! – rainmaker

+0

@rainmaker: Sí, ya entendiste la idea. Otra cosa que se comete es establecer el índice de error en la posición actual, de modo que los errores de análisis posteriores arrojen el índice actual como el origen del texto defectuoso. Recuerdo vagamente que esto tenía sus rincones y grietas (¿no también 'parse_state' tendría que almacenar el antiguo índice de error?), Pero hace varios años que utilicé este esquema, así que, si no figuraba el código anterior, ¿sería necesario? haz esto de nuevo, no tendría más que lo anterior para comenzar desde cualquiera de los dos. ':)' – sbi

3

Esto no es para lo que están destinadas las transmisiones. Debería leer los datos que desea analizar en un búfer y luego transferir ese búfer (preferiblemente como un rango de iterador) a las funciones que lo analizan. Esto podría ser algo como esto:

template <class T, class U> 
bool tryParse(U & begin, U & end, T & target) { 
    // return true if parse was successful, false otherwise 
} 

Para leer de un istream en un búfer, se puede utilizar un istream_iterator:

std::vector<char> buffer(std::istream_iterator<char>(is), std::istream_iterator<char>()); 

Esto lee toda la corriente en el vector cuando se crea.

2

Poner los personajes de vuelta es complicado. Algunas transmisiones admiten unget() y putback(somechar), pero no hay garantía de cuántos caracteres puede perder (si corresponde).

Una manera más confiable es leer los caracteres en un búfer y analizarlos, o almacenar los caracteres leídos en el primer intento de análisis y usar ese búfer al analizar por segunda vez.

+0

+1 para analizar un buffer separado. –

1

Puede hacer algunas cosas interesantes con streambuf miembros de transmisión. En particular, tiene acceso directo a los punteros de los buffers.

Sin embargo, no tiene garantía sobre el tamaño de los buffers.