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. :)