Ich versuche einen Lexer in JavaScript zu schreiben, um Token einer einfachen domänenspezifischen Sprache zu finden. Ich begann mit einer einfachen Implementierung, die einfach versucht, nachfolgende Regexps von der aktuellen Position in einer Zeile abzugleichen, um herauszufinden, ob sie mit einem Token-Format übereinstimmt und es dann akzeptiert.
Das Problem ist, dass, wenn etwas in einem solchen Regexp nicht zusammenpasst, der ganze Regexp fehlschlägt, also weiß ich nicht, welches Zeichen genau das verursacht hat.
Gibt es eine Möglichkeit, die Position in der Zeichenfolge herauszufinden, die den regulären Ausdruck fehlgeschlagen hat?
INB4: Ich frage nicht, ob ich meinen regulären Ausdruck debuggen und seine Korrektheit überprüfen soll. Es ist bereits korrekt, stimmt mit den richtigen Strings überein und gibt falsche Strings aus. Ich möchte nur programmgesteuert wissen, wo genau die Regexp nicht mehr übereinstimmte, um die Position eines Zeichens herauszufinden, das in der Benutzereingabe falsch war, und wie viel von ihnen in Ordnung war.
Gibt es einen Weg, es mit nur einfachen Regexs zu tun, statt mit der Implementierung eines voll entwickelten Automaten mit endlichem Zustand fortzufahren?
Kurze Antwort
Es gibt keine "Position in der Zeichenkette, die das verursacht regulärer Ausdruck fehlschlagen ".
Ich werde Ihnen jedoch einen Ansatz zeigen, um die umgekehrte Frage zu beantworten:
Bei welchem Token in der Regex konnte die Engine nicht mit der übereinstimmen Zeichenfolge?
Diskussion
Aus meiner Sicht ist die Frage von the position in the string which caused the regular expression to fail
auf den Kopf gestellt. Während sich der Motor mit der linken Hand und die rechte Hand mit der linken Hand nach unten bewegt, kann ein Regex-Token, das einem Moment sechs Zeichen entspricht, aufgrund von Quantifiern und Backtracking auf die nächsten Null-Zeichen reduziert werden zehn.
Aus meiner Sicht wäre eine richtigere Frage:
Bei welchem Token in der Regex konnte die Engine nicht mit der übereinstimmen Zeichenfolge?
Betrachten Sie zum Beispiel die Regex ^\w+\d+$
und die Zeichenfolge abc132z
.
Die \w+
kann tatsächlich mit der gesamten Zeichenfolge übereinstimmen. Der gesamte Regex schlägt jedoch fehl. Ist es sinnvoll zu sagen, dass die Regex am Ende der Zeichenfolge fehlschlägt? Ich denke nicht. Bedenken Sie das.
Anfangs stimmt \w+
mit abc132z
überein. Dann rückt der Motor zum nächsten Token vor: \d+
. In diesem Stadium rückt der Motor in der Kette zurück und lässt die \w+
die 2z
(so dass die \w+
jetzt nur abc13
entspricht) nach und lässt die \d+
mit 2
übereinstimmen.
In diesem Stadium schlägt die $
-Assion fehl, da z
übrig bleibt. Die Triebwerk-Backtracks, die \w+
überlassen, geben das 3
-Zeichen auf, dann das 1
(so dass das \w+
jetzt nur noch abc
entspricht), was schließlich zulässt, dass \d+
mit 132
übereinstimmt . Bei jedem Schritt versucht die Engine die $
-Assion und schlägt fehl. Je nach Engine-Interna kann mehr Backtracking auftreten: die \d+
geben die 2 und die 3 noch einmal auf, dann gibt% code_% das c und das b auf. Wenn der Motor schließlich aufgibt, stimmt die \w+
nur mit der anfänglichen \w+
überein. Kannst du sagen, dass die Regex "auf der" 3 "scheitert? Auf der" b "?
Nein. Wenn Sie das Regex-Muster von links nach rechts betrachten, können Sie argumentieren, dass es in a
fehlschlägt, weil es das erste Token ist, das wir dem Match nicht hinzufügen konnten. Bedenken Sie, dass es andere Wege gibt, dies zu diskutieren.
Unten, ich gebe dir einen Screenshot, um dies zu visualisieren. Aber zuerst, mal sehen, ob wir die andere Frage beantworten können.
Die andere Frage
Gibt es Techniken, die es uns erlauben, die andere Frage zu beantworten:
Bei welchem Token in der Regex konnte die Engine nicht mit der übereinstimmen Zeichenfolge?
Es hängt von Ihrer Regex ab. Wenn Sie in der Lage sind, Ihre Regex in saubere Komponenten zu zerlegen, können Sie einen Ausdruck mit einer Reihe von optionalen Lookaheads in Capture-Gruppen erstellen, sodass die Übereinstimmung immer erfolgreich ist. Die erste nicht ausgewählte Erfassungsgruppe ist diejenige, die den Fehler verursacht hat.
Javascript ist bei optionalen Lookaheads etwas geizig, aber Sie können etwas wie folgt schreiben:
%Vor%In PCRE, .NET, Python ... könnte man dies kompakter schreiben:
%Vor%Was passiert hier? Jeder Lookahead wird inkrementell auf dem letzten erstellt und fügt jeweils einen Token hinzu. Daher können wir jeden Token separat testen. Der Punkt am Ende ist ein optionaler Vorteil für visuelles Feedback: Wir können in einem Debugger sehen, dass mindestens ein Zeichen übereinstimmt, aber uns interessiert dieser Charakter nicht, wir kümmern uns nur um die Capture-Gruppen.
$
\w+
zu testen, testet daher inkrementell \w+\d+
token \d+
zu testen, deshalb testet sie inkrementell \w+\d+$
token Es gibt drei Capture-Gruppen. Wenn alle drei gesetzt sind, ist das Spiel ein voller Erfolg. Wenn nur Gruppe 3 nicht festgelegt ist (wie bei $
), können Sie sagen, dass abc123a
den Fehler verursacht hat. Wenn Gruppe 1 gesetzt ist, aber nicht Gruppe 2 (wie bei $
), können Sie sagen, dass abc
den Fehler verursacht hat.
Als Referenz: Innenansicht eines Fehlerpfads
Für was es sich lohnt, hier ist eine Ansicht des Fehlerweges vom RegexBuddy Debugger.
Sie können einen negierten Zeichensatz verwenden RegExp
,
%Vor%Ein negierter oder ergänzter Zeichensatz. Das heißt, es passt zu allem das ist nicht in den Klammern eingeschlossen. Sie können einen Bereich von angeben Zeichen mit einem Bindestrich, aber wenn der Bindestrich als erster erscheint oder das letzte in den eckigen Klammern eingeschlossene Zeichen wird als a genommen Literaler Bindestrich, der als normal in den Zeichensatz aufgenommen wird Zeichen.
index
Eigenschaft von String.prototype.match()
Das zurückgegebene Array verfügt über eine zusätzliche Eingabeeigenschaft, die Folgendes enthält: ursprüngliche Zeichenfolge, die analysiert wurde. Außerdem hat es einen Index Eigenschaft, die den nullbasierten Index der Übereinstimmung in der Zeichenfolge.
Zum Beispiel, um index
zu protokollieren, wobei die Ziffer für RegExp
/[^a-zA-z]/
in der Zeichenfolge aBcD7zYx
Gibt es eine Möglichkeit, die Position in der Zeichenfolge herauszufinden, die den regulären Ausdruck fehlgeschlagen hat?
Nein, ist es nicht. Ein Regex stimmt entweder überein oder nicht. Nichts dazwischen.
Partielle Ausdrücke können übereinstimmen, aber das gesamte Muster stimmt nicht. Daher muss die Engine immer den gesamten Ausdruck auswerten:
Nimm die Zeichenfolge Hello my World
und das Muster /Hello World/
. Während jedes Wort einzeln übereinstimmt, schlägt der gesamte Ausdruck fehl. Sie können nicht sagen, ob Hello
oder World
übereinstimmen - unabhängig, beide tun. Auch das Leerzeichen zwischen ihnen ist verfügbar.
Tags und Links javascript regex lexical-analysis