Herkömmliche Parser verbrauchen ihre gesamte Eingabe und erzeugen einen einzelnen Analysebaum. Ich suche einen, der einen kontinuierlichen Stream verbraucht und eine Analyse-Gesamtstruktur erzeugt. [ Bearbeiten: Siehe Diskussion in Kommentaren dazu, warum diese Verwendung dieses Begriffs unkonventionell sein kann. ] Mein Bauch sagt, dass ich nicht die erste Person sein kann, die einen solchen Parser benötigt (oder für nötig hält), aber ich habe monatelang erfolglos hin und her gesucht.
Ich erkenne, dass ich möglicherweise vom XY-Problem gefangen bin. Mein letztendlicher Zweck ist es, einen Textstrom zu analysieren, den größten Teil davon zu ignorieren und einen Strom von Analysebäumen aus den erkannten Abschnitten zu erzeugen.
Meine Frage ist also bedingt: Wenn eine Klasse von Parsern mit diesen Merkmalen existiert, wie heißt sie? Und wenn nicht, warum nicht? Was ist die Alternative? Vielleicht fehlt mir eine Möglichkeit, konventionelle Parser dazu zu bringen, das zu tun, was ich will.
Antworten:
Ein Parser, der ein (Teil-) Ergebnis zurückgibt, bevor die gesamte Eingabe verbraucht wurde, wird als inkrementeller Parser bezeichnet . Inkrementelles Parsen kann schwierig sein, wenn lokale Mehrdeutigkeiten in einer Grammatik vorliegen, die erst später in der Eingabe entschieden werden. Eine andere Schwierigkeit besteht darin, die Teile des Analysebaums vorzutäuschen, die noch nicht erreicht wurden.
Ein Parser, der eine Gesamtheit aller möglichen Analysebäume zurückgibt, dh für jede mögliche Ableitung einer mehrdeutigen Grammatik einen Analysebaum zurückgibt, heißt ... Ich bin mir nicht sicher, ob diese Dinge noch einen Namen haben. Ich weiß, dass der Marpa-Parser-Generator dazu in der Lage ist, aber jeder Parser auf Earley- oder GLR-Basis sollte dies tun können.
Sie scheinen jedoch nichts davon zu wollen. Sie haben einen Stream mit mehreren eingebetteten Dokumenten, zwischen denen sich Müll befindet:
Sie möchten anscheinend einen Parser, der den Müll überspringt und (träge) eine Folge von ASTs für jedes Dokument ausgibt. Dies könnte betrachtet wird ein inkrementelles Parser in seinem allgemeinsten Sinne zu sein. Aber Sie würden tatsächlich eine Schleife wie folgt implementieren:
Die
parse_docment
Funktion wäre dann ein herkömmlicher, nicht inkrementeller Parser. Es gibt eine kleinere Schwierigkeit, sicherzustellen, dass Sie genug vom Eingabestream gelesen haben, um eine erfolgreiche Analyse durchzuführen. Wie dies gehandhabt werden kann, hängt von der Art des verwendeten Parsers ab. Zu den Möglichkeiten gehören das Erweitern eines Puffers bei bestimmten Analysefehlern oder die Verwendung der verzögerten Tokenisierung.Lazy Tokenization ist aufgrund Ihres Eingabestreams wahrscheinlich die eleganteste Lösung. Anstatt in einer Lexer-Phase eine feste Liste von Token zu erstellen, fordert der Parser träge das nächste Token von einem Lexer-Rückruf an [1] . Der Lexer würde dann so viel von dem Strom verbrauchen, wie benötigt wird. Auf diese Weise kann der Parser nur fehlschlagen, wenn das reale Ende des Streams erreicht ist oder wenn ein realer Analysefehler aufgetreten ist (dh wir haben mit dem Parsen begonnen, während wir uns noch im Müll befanden).
[1] Ein Callback-gesteuerter Lexer ist auch in anderen Kontexten eine gute Idee, da dies einige Probleme bei der Suche nach dem längsten Token vermeiden kann .
Wenn Sie wissen, nach welcher Art von Dokumenten Sie suchen, können Sie das Überspringen so optimieren, dass es nur an vielversprechenden Stellen endet. Beispielsweise beginnt ein JSON-Dokument immer mit dem Zeichen
{
oder[
. Daher ist Müll eine beliebige Zeichenfolge, die diese Zeichen nicht enthält.quelle
NO_MATCH
undUNDERFLOW
) aus, mit denen ich unterscheiden kann, ob ich die Stream-Position erhöhen oder auf weitere Eingaben warten soll.Es gibt keinen bestimmten Namen für einen Parser, der dies ausführt. Aber ich werde einen Algorithmus hervorheben, der dies tut: Parsen mit Derivaten .
Es verbraucht Eingabe, ein Token nach dem anderen. Am Ende der Eingabe wird ein Analysewald erstellt. Alternativ können Sie auch den gesamten Parsing-Wald abrufen, während Sie sich mitten im Parsing befinden ( partielles Parsing ).
Beim Parsen mit Derivaten werden kontextfreie Grammatiken verarbeitet, und es wird eine Analysegesamtstruktur für mehrdeutige Grammatiken erstellt.
Es ist eine elegante Theorie, aber sie steckt erst in den Kinderschuhen und ist nicht weit verbreitet. Matt Might hat eine Liste von Links zu verschiedenen Implementierungen in Scala / Racket / etc.
Die Theorie ist leichter zu erlernen, wenn Sie mit der Erkennung von Derivaten beginnen (dh mit der Ermittlung von Derivaten von Sprachen mit dem Ziel, Eingaben zu erkennen, um festzustellen, ob sie gültig sind oder nicht) und dann das Programm so ändern, dass sie mit Derivaten analysiert werden ( Das heißt, ändern Sie es, anstatt Ableitungen von Sprachen zu nehmen , Ableitungen von Parsern zu nehmen und eine Analyse- Gesamtstruktur zu berechnen.
quelle
Weit davon entfernt, ideal zu sein, aber ich habe es mehr als einmal gesehen: Versuche bei jeder Eingabezeile zu analysieren. Wenn dies fehlschlägt, behalten Sie die Zeile bei und fügen Sie die nächste hinzu. Im Pseudocode:
Das große Problem ist, dass Sie in einigen Sprachen nicht wissen können, ob ein Ausdruck vollständig ist, bevor Sie die nächste Zeile lesen. In diesem Fall könnten Sie anscheinend den nächsten lesen und prüfen, ob es sich um einen gültigen Anfang oder eine gültige Fortsetzung handelt. Dafür benötigen Sie jedoch die genaue Sprachsyntax
Schlimmer noch, in diesen Sprachen ist es nicht schwer, einen pathologischen Fall zu erstellen, der erst am Ende der Datei analysiert werden kann, selbst wenn es sich nicht um eine einzelne lange Aussage handelt.
quelle
In einer Nussschale
Es scheint, dass die schnelle Lösung für Ihr Problem darin besteht, ein REGEX oder einen FSA (Finite-State-Automaten) zu definieren, der alle möglichen Anfänge von Dokumenten erkennt (Fehlalarme sind zulässig, die eigentlich keinem Dokument entsprechen würden). Sie können es dann bei Ihrer Eingabe sehr schnell ausführen, um die nächste Stelle zu identifizieren, an der ein Dokument mit wenigen Fehlern beginnen könnte. Es kann einige fehlerhafte Positionen für einen Dokumentstart verursachen, diese werden jedoch vom Parser erkannt und abgebrochen.
So Finite State Automaton kann der Parser Name sein , das Sie gesucht haben. :)
Das Problem
Es ist immer schwierig, ein praktisches Problem zu verstehen, besonders wenn das Vokabular viele Interpretationen hat. Das Wort Parsing Forest wurde (afaik) für das kontextfreie Parsen (CF) mehrdeutiger Sätze mit mehreren Parsingbäumen geprägt. Es kann etwas verallgemeinert werden, um ein Satzgitter oder andere Arten von Grammatik zu analysieren. Daher waren alle Antworten zu Earley, GLR, Marpa und abgeleiteten Parsern (es gibt viele andere) in diesem Fall nicht relevant.
Aber das haben Sie anscheinend nicht im Sinn. Sie möchten eine eindeutige Zeichenfolge analysieren, bei der es sich um eine Folge eindeutiger Dokumente handelt, und einen Analysebaum für jedes Dokument oder eine strukturierte Darstellung abrufen , da Sie nicht genau angeben, wie die Syntax Ihrer Dokumente definiert ist und woher sie stammt eine formale sprachliche Sichtweise. Was Sie haben, sind ein Algorithmus und Tabellen, die den Parsing-Job ausführen, wenn sie am Anfang eines Dokuments gestartet werden. So sei es.
Das eigentliche Problem ist, dass Ihr Dokumentenstrom erheblichen Müll enthält, der die Dokumente voneinander trennt. Und es scheint, dass es Ihre Schwierigkeit ist, diesen Müll schnell genug zu scannen. Ihre derzeitige Technik besteht darin, am Anfang zu beginnen und zu versuchen, ab dem ersten Zeichen zu scannen und beim nächsten Zeichen mit dem Neustart fortzufahren, wenn dies fehlschlägt, bis Sie ein gesamtes Dokument gescannt haben. Anschließend wiederholen Sie die Eingabe ab dem ersten Zeichen nach dem gerade gescannten Dokument.
Dies ist auch die von @amon im zweiten Teil seiner Antwort vorgeschlagene Lösung .
Dies ist möglicherweise keine sehr schnelle Lösung (ich kann sie nicht testen), da es unwahrscheinlich ist, dass der Code des Parsers so optimiert ist, dass er am Anfang eines Dokuments sehr effizient gestartet wird. Bei normaler Verwendung wird dies nur einmal ausgeführt, sodass es aus Optimierungssicht kein Hot Spot ist. Daher ist Ihr mäßiges Glück mit dieser Lösung nicht zu überraschend.
Was Sie also wirklich brauchen, ist ein Algorithmus, der schnell den Anfang eines Dokuments findet, das mit einer Menge Müll beginnt. Und Sie haben Glück: Es gibt solche Algorithmen. Und ich bin mir sicher, dass Sie es wissen: Es heißt Suche nach einer REGEX.
Die einfache Lösung
Sie müssen lediglich die Spezifikation Ihrer Dokumente analysieren, um herauszufinden, wie diese Dokumente beginnen. Ich kann Ihnen nicht genau sagen, wie, da ich nicht sicher bin, wie ihre Syntaxspezifikation formal organisiert ist. Möglicherweise beginnen sie alle mit einem Wort aus einer endlichen Liste, möglicherweise gemischt mit Satzzeichen oder Zahlen. Das müssen Sie überprüfen.
Sie müssen lediglich einen Finite-State-Automaten (FSA) oder für die meisten Programmierer einen regulären Ausdruck (REGEX) definieren, der die ersten Zeichen eines Dokuments erkennt: Je mehr, desto besser, aber nicht unbedingt sehr groß (da dies Zeit und Raum beanspruchen kann). Dies sollte ausgehend von der Spezifikation Ihrer Dokumente relativ einfach zu bewerkstelligen sein und kann wahrscheinlich automatisch mit einem Programm durchgeführt werden, das die Spezifikation Ihrer Dokumente liest.
Sobald Sie Ihren regulären Ausdruck erstellt haben, können Sie ihn in Ihrem Eingabestream ausführen, um wie folgt sehr schnell zum Anfang Ihres ersten (oder nächsten) Dokuments zu gelangen:
Ich nehme an:
-
docstart
ist eine Regex, die dem Anfang aller Dokumente entspricht.-
search(regex, stream)
ist eine Funktion, diestream
nach einer passenden Teilzeichenfolge suchtregex
. Wenn er zurückkehrt, wird der Stream ab dem Beginn des ersten übereinstimmenden Teilstrings auf sein Suffix reduziert, oder für den leeren Stream wird keine Übereinstimmung gefunden.-
parse(stream)
Versucht, ein Dokument vom Anfang des Streams zu analysieren (was davon übrig ist), und gibt den Analysebaum in einem beliebigen Format zurück oder schlägt fehl. Bei der Rückkehr wird der Stream an der Position unmittelbar nach dem Ende des analysierten Dokuments auf sein Suffix reduziert. Es ruft eine Ausnahme auf, wenn das Parsen fehlschlägt.Beachten Sie, dass das Entfernen des ersten Zeichens erforderlich ist, damit bei der nächsten Suche nicht wieder dieselbe Übereinstimmung gefunden wird.
Natürlich ist die Verkürzung des Streams ein Bild. Es kann nur ein Index für den Stream sein.
Ein letzter Hinweis ist, dass Ihr Regex nicht zu genau sein muss, solange er alle Anfänge erkennt. Wenn gelegentlich eine Zeichenfolge erkannt wird, die nicht der Anfang eines Dokuments sein kann (falsch positiv), sind die Kosten für einen nutzlosen Anruf beim Parser die einzige Strafe.
Das kann also möglicherweise dazu beitragen, den regulären Ausdruck zu vereinfachen, falls dies nützlich ist.
Über die Möglichkeit einer schnelleren Lösung
Die obige Lösung sollte in den meisten Fällen ziemlich gut funktionieren. Wenn Sie jedoch wirklich viel Müll und Terabyte an Dateien zu verarbeiten haben, gibt es möglicherweise andere Algorithmen, die schneller ausgeführt werden.
Die Idee leitet sich aus dem Boyer-Moore-Algorithmus für die Suche nach Zeichenfolgen ab . Dieser Algorithmus kann einen Stream extrem schnell nach einer einzelnen Zeichenfolge durchsuchen, da er eine Strukturanalyse der Zeichenfolge verwendet, um das Lesen des größten Teils des Streams zu überspringen und Fragmente zu überspringen, ohne sie überhaupt anzusehen. Es ist der schnellste Suchalgorithmus für eine einzelne Zeichenfolge.
Die Schwierigkeit besteht darin, dass die Anpassung an reguläre Ausdrücke und nicht an einzelne Zeichenfolgen sehr heikel erscheint und je nach den Funktionen des zu untersuchenden regulären Ausdrucks möglicherweise nicht so gut funktioniert. Dies kann wiederum von der Syntax der zu analysierenden Dokumente abhängen. Aber vertraue mir nicht zu sehr, da ich keine Zeit hatte, die gefundenen Dokumente sorgfältig zu lesen.
Ich überlasse Ihnen ein oder zwei Hinweise, die ich im Internet gefunden habe, darunter einen, der anscheinend ein referiertes Forschungspapier ist , aber Sie sollten dies als spekulativer, möglicherweise als recherchierender Hinweis betrachten, der nur in Betracht gezogen werden sollte, wenn Sie starke Leistungsprobleme hatten. Und es gibt wahrscheinlich kein Regalprogramm, das das macht.
quelle
Was Sie beschreiben, kann als SAX vs. SOM bezeichnet werden.
SAX - (Simple API for XML) ist eine Ereignissequenzzugriffs-Parser-API, die von der XML-DEV-Mailingliste für XML-Dokumente entwickelt wurde.
SOM - (XML Schema Object Model) Direktzugriff auf die Darstellung einer XML-Datei im Speicher
Es gibt Implementierungen beider Typen in C # und Java und wahrscheinlich viele weitere. Normalerweise ist eine XSD oder DTD optional.
Die Freude von SAX ist, dass es wenig Speicherplatz benötigt, was für große XML-Dateien großartig ist. Der Nachteil ist, dass der Direktzugriff mit SAX entweder nicht vorhanden oder langsam ist und die Entwicklungszeit in der Regel erheblich länger ist als mit SOM. Das offensichtliche Problem bei SOM sind möglicherweise große RAM-Anforderungen.
Diese Antwort gilt nicht für alle Plattformen und alle Sprachen.
quelle