Benannte Unterausdrücke imitieren

(Auszug aus "Reguläre Ausdrücke" von Jeffrey E. F. Friedl)

Trotz der Mängel beim Overloading von Regex-Literalen möchte ich ein größeres Programmbeispiel vorstellen, in dem verschiedene der besonderen Konstrukte von Perl vereinigt sind. In Perl kann man geklammerte Unterausdrücke nicht mit Namen versehen (siehe Benannte Unterausdrücke), aber man kann dieses Feature mit einfangenden Klammern und $^N nachahmen. Die Variable $^N liefert den Text aus der einfangenden Klammer, die zuletzt geschlossen wurde (siehe $^N). (Ich hatte mich extra und offenbar erfolgreich als Perl-Entwickler ausgegeben und die Variable $^N eingeführt, damit man in Perl benannte Klammerausdrücke nachahmen kann.)

Betrachten wir zunächst ein einfaches Beispiel:

˹href\s*=\s*($HttpUrl)(?{ $url = $^N })˼

Hier wird das Regex-Objekt $HttpUrl aus Regex-Objekte aufbauen und verwenden verwendet. Der unterstrichene Teil ist ein Codemuster, das den von $HttpUrl erkannten Text in der Variablen $url abspeichert. In dieser einfachen Situation könnte man statt $^N auch einfach $1 nehmen und auch ohne Weiteres auf das Codemuster verzichten, weil man ja genauso gut nach der Mustersuche $1 verwenden könnte. Nehmen wir aber an, diese einfache Regex würde als Objekt abgespeichert und in einer größeren Regex mehrfach verwendet:

my $UrlSpeichern = qr{
    ($HttpUrl)         # Eine HTTP-URL finden ...
    (?{ $url = $^N })  # ... und in $url abspeichern.
}x;
$text =~ m{
   http \s*=\s* ($UrlSpeichern)
 | src  \s*=\s* ($UrlSpeichern)
}xi;

Unabhängig davon, welche Alternative passt, wird die gefundene URL in jedem Fall in $url abgespeichert. Auch hier gäbe es andere Techniken (zum Beispiel mit der Variablen $+), wenn aber $UrlSpeichern in komplexeren Situationen verwendet wird, werden diese anderen Methoden unübersichtlich und schwierig zu warten. Benannte Klammerausdrücke sind da in der Tat sehr zweckmäßig.


Benannte Unterausdrücke imitieren

package MyRegexStuff;
use strict;
use warnings;
use overload;
sub import { overload::constant('qr' => \&RegexLiteralVerarbeiten) }

my $VerschachtelRegex;            # »Forward«-Deklaration, Regex wird rekursiv verwendet.
$VerschachtelRegex = qr{
 (?>
   (?:  # Weder Klammern noch # noch ein Escape …
        [^()\#\\]+
        # Maskierte Zeichen …
      | (?s: \\. )
        # Kommentar in der Regex …
      | \#.*\n
        # Klammerpaar mit verschachtelten Klammern …
      |  \(  (??{ $VerschachtelRegex })   \)
   )*
 )
}x;

sub RegexUmbauen($);              # »Forward«-Deklaration, wird rekursiv verwendet.
sub RegexUmbauen($)
{
  my $re = shift;                 # Die umzubauende Regex.
  $re =~ s{
      \(\?                        #  "(?"
        < ( (?>\w+) ) >           #     < $1 > $1 ist ein Bezeichner.
        ( $VerschachtelRegex )    #     $2 - möglicherweise verschachtelte Dinge.
      \)                          #  ")"
  }{
    my $id   = $1;
    my $Innerei = RegexUmbauen($2);
    # Wir ersetzen
    #    (?<id>Innerei)
    # durch
    #    (?: (Innerei)  # Passt auf die »Innerei«.
    #        (?{
    #           local($^N{$id}) = $Innerei # Lokalisiertes Element von %^T.
    #         })
    #     )
    "(?:($Innerei)(?{ local(\$^T{'$id'}) = \$^N }))"
  }xeog;
  return $re;  # Umgebaute Regex zurückgeben.
}

sub RegexLiteralVerarbeiten($)
{
  my ($RegexLiteral) = @_;                        # Argument ist ein String.
  # print "VORHER:  $RegexLiteral\n";             # Zum Debuggen auskommentieren.
  my $neu = RegexUmbauen($RegexLiteral);
  if ($neu ne $RegexLiteral)
  {
     my $vorher  = q/(?{ local(%^T) = () })/;     # Temporären Hash lokalisieren.
     my $nachher = q/(?{ %^N = %^T       })/;     # In »wirklichen« Hash umkopieren.
     $RegexLiteral = "$vorher(?:$neu)$nachher";
  }
  # print "NACHHER: $RegexLiteral\n";             # Zum Debuggen auskommentieren.
  return $RegexLiteral;
}

1;

Ein Problem mit der aktuellen Version ist, dass die Variable $url beim Backtracking nicht auf den vorherigen Wert zurückgesetzt wird. Wir müssen also analog zum Vorgehen in local in einem Codemuster eine temporäre local-Variable verwenden und deren Wert bei einem Treffer in eine »normale« Variable umkopieren.

Das Programm im obigen Kasten Benannte Unterausdrücke imitieren zeigt eine Möglichkeit, wie man benannte Klammerausdrücke implementieren kann. Der Benutzer verwendet eine Regex der Art ˹(?<Num>\d+ und bekommt bei einem Treffer die von ˹\d+˼ erkannte Zahl in einem globalen Hash namens %^N, d.h. im Hash-Element $^N{Num}. In zukünftigen Perl-Versionen wird %^N vielleicht zu einer Spezialvariablen, in der aktuellen Version ist es aber eine ganz normale Variable, darum habe ich sie verwendet.

Ich hätte auch einen Namen wie %NamedCapture nehmen können, aber %^N hat zwei Vorteile. Zum einen ähnelt es der Variablen $^N, und zum anderen braucht dieser Hash auch unter use strict nicht mit our deklariert zu werden. Außerdem hoffe ich, dass Perl irgendwann benannte Klammerausdrücke von Haus aus unterstützt, und meiner Meinung nach wäre dann %^N die richtige Idee. In diesem Fall bekäme %^N wohl wie die anderen Treffer-Variablen ($1, @+ usw., siehe Durch das Matching gesteuerte Spezialvariablen) seinen eigenen dynamischen Geltungsbereich. In der hier vorgestellten Lösung ist es eine normale globale Variable ohne automatisches dynamisches Scoping.

Auch der hier vorgestellte Ansatz krankt an den Problemen aller Programme, die Regex-Overloading benutzen: Interpolierte Variablen stören das Overloading.

  

<< zurück vor >>

 

 

 

Tipp der data2type-Redaktion:
Zum Thema Reguläre Ausdrücke bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an:
   

Copyright der deutschen Ausgabe © 2008 by O’Reilly Verlag GmbH & Co. KG
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "Reguläre Ausdrücke" denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

O’Reilly Verlag GmbH & Co. KG, Balthasarstr. 81, 50670 Köln