5 Stimmen

einen C++-Istream "kopieren"?

Für mein eigenes kleines Parser-Framework versuche ich, die folgende Funktion zu definieren (in etwa):

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. */
}

Dann könnte ich etwas wie folgt schreiben: (vielleicht nicht das beste Beispiel)

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

Für jede Hilfe sind wir sehr dankbar.

3voto

Björn Pollex Punkte 72424

Dafür sind Streams nicht gedacht. Sie sollten die Daten, die Sie analysieren möchten, in einen Puffer lesen und diesen Puffer dann (vorzugsweise als Iteratorbereich) an die Funktionen übergeben, die die Daten analysieren. Dies könnte etwa so aussehen:

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

Zum Lesen aus einer istream in einen Puffer, können Sie eine istream_iterator :

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

Dadurch wird der gesamte Datenstrom in den Vektor eingelesen, wenn er erstellt wird.

3voto

sbi Punkte 211669

Leider gibt es nur eine sehr minimale und rudimentäre Unterstützung für die Rückgabe von Streams.

Das letzte Mal, als ich dies brauchte, schrieb ich meine eigenen Reader-Klassen, die einen Stream umhüllten, aber einen Puffer hatten, in den man Dinge zurücklegen konnte, und nur aus dem Stream lasen, wenn dieser Puffer leer war. Diese hatten Möglichkeiten, einen Zustand zu erhalten, und man konnte einen Zustand festschreiben oder zu einem früheren Zustand zurückkehren.
Die Standardaktion im Destruktor der Zustandsklasse war ein Rollback, so dass man weiter parsen konnte, ohne sich groß Gedanken über die Fehlerbehandlung zu machen, denn eine Ausnahme würde den Zustand des Parsers einfach bis zu einem Punkt zurücksetzen, an dem eine andere Grammatikregel versucht wurde (ich glaube, das nennt man Backtracking.) Hier eine Skizze:

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

    // ...
};

Das sollte Ihnen eine Vorstellung davon geben. Es fehlt die Umsetzung, aber das war einfach und sollte leicht zu ändern sein. Außerdem hatte der echte Code viele praktische Funktionen wie Lesefunktionen, die eine abgegrenzte Zeichenkette lasen, eine Zeichenkette konsumierten, wenn sie eines von mehreren gegebenen Schlüsselwörtern war, eine Zeichenkette lasen und sie in einen per Template-Parameter gegebenen Typ konvertierten, und solche Sachen.

Die Idee war, dass eine Funktion den Fehlerindex auf die Anfangsposition setzt, den Parse-Status speichert und so lange versucht zu analysieren, bis sie entweder erfolgreich ist oder in eine Sackgasse läuft. Im letzteren Fall würde sie einfach eine Ausnahme auslösen. Dies würde die parse_state Objekte auf dem Stapel, wobei der Zustand bis zu einer Funktion zurückgerollt wird, die die Ausnahme abfangen und entweder etwas anderes versuchen oder einen Fehler ausgeben kann (wozu get_error_string() kommt rein.)

Wenn man einen wirklich schnellen Parser will, ist diese Strategie vielleicht falsch, aber dann sind auch Streams oft zu langsam. OTOH, das letzte Mal, als ich so etwas verwendet habe, habe ich einen XPath-Parser erstellt, der auf einem proprietären DOM arbeitet, das zur Darstellung von Szenen in einem 3D-Renderer verwendet wird. Und das war no der XPath-Parser, der von den Leuten, die höhere Frameraten erreichen wollen, in die Kritik geraten ist. :)

2voto

Anthony Williams Punkte 64334

Das Wiedereinsetzen der Zeichen ist schwierig. Einige Streams unterstützen unget() y putback(somechar) , aber es gibt keine Garantie dafür, wie viele Zeichen Sie wiederherstellen können (wenn überhaupt).

Eine zuverlässigere Methode besteht darin, die Zeichen in einen Puffer zu lesen und diesen zu parsen, oder die beim ersten Parsing-Versuch gelesenen Zeichen zu speichern und diesen Puffer beim zweiten Parsing-Versuch zu verwenden.

1voto

Alexandre C. Punkte 53706

Sie können einige interessante Dinge tun mit streambuf Strommitglieder. Insbesondere haben Sie direkten Zugriff auf die Zeiger der Puffer.

Sie haben jedoch keine Garantie für die Größe der Puffer.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X