Ermitteln Sie die Position, an der ein regulärer Ausdruck fehlgeschlagen ist

7

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?

    
SasQ 23.05.2014, 22:59
quelle

3 Antworten

19

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.

  1. Gruppe 1 testet das Token $
  2. Gruppe 2 scheint \w+ zu testen, testet daher inkrementell \w+\d+ token
  3. Gruppe 3 scheint \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.

    
zx81 23.05.2014 23:41
quelle
1

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

übereinstimmt

%Vor%
    
guest271314 18.06.2016 17:25
quelle
0
  

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.

    
dognose 23.05.2014 23:27
quelle