Perl spezifische erweiterte reguläre Ausdrücke




[Seminarthemen WS07/08]  [Übersicht]

Perl Entwickler sind nach wie vor innovativ und experementierfreudig.
Eine interessante Möglichkeit ist es, in Regex Perl-Code einzubringen, der während des Matchversuchs ausgeführt wird.



Das dynamisches Regex-Konstrukt

Die Syntax des Konstrukts:

(??{Perlcode})


Der im Konstrukt enthaltene Perlcode wird jedes mal bei Anwendung des Regex und Erreichen des Konstrukts ausgeführt. Das Resultat des Perlcodes ist dann ein Regex-Objekt oder ein String, der als Regex interpretiert wird und dann in dem laufenden Muster verwendet wird. Praktisch ist dieses Konstrukt z.B. für beliebig tief verschachtelte Elemente.

Bespiel:

if ($text =~ m/^(\d+)(??{"X{$1}"})$/x) {
	print "zahl mit folgenden X: ".$text."\n"
}


In diesem Beispiel passt der Regex auf eine Zahl am Zeilenanfang, gefolgt von einem großen "X" in der Anzahl, die die gefundene Zahl vorgibt. Der reguläre Ausdruck passt z.B. auf den String "3XXX".
Zuerst wird die Zahl "3" erkannt. Die Variable im dynamischen Konstrukt hat nun eine "3". Das Ergebnis des dynmischen Teils ist somit "X{3}" und wird in dem laufenden regulären Ausdruck verwendet.

[top]


Das Perl-Codemuster-Konstrukt

Die Syntax des Konstrukts:

(?{Perlcode})


Der Unterschied zum dynamischen Regex-Konstrukt ist, dass der Code keine Rückgabewert liefert. Falls ein Rückgabewert vorhanden ist, wird dieser ignoriert. Er ist aber in der Variablen $^R vorhanden. Der Perlcode dieses Konstrukts wird ausgeführt, sobald die Regex-Maschine die Klammer des Konstrukts erreicht.
Da das Codemuster-Konstrukt beliebigen Perlcode enthalten kann, ist der Einsatzbereich groß. Ein interessanter Einsatzbereich ist z.B. das Debugging.

Beispiel:

if ($zahl =~ m/^(\d+)(??{"X{$1}"})
		(?{$i=$1;
			while($i>=0) {
				print $i."\n";
	  			$i--;
			}
		})$/x) {

	print "zahl mit X: ".$zahl."\n"
}


An dieser Stelle wurde das Beispiel für das dynamische Regex-Konstrukt um das Perl-Codemuster-Konstrukt erweitert. Es wird eine Variable "$i" auf die gefundene Zahl gesetzt und im Perl-Codemuster-Konstrukt in einer Schleife runtergezählt und dabei über "print" ausgegeben.


Alternative zum Perl-Codemuster-Konstrukt in PCRE / Callouts

In anderen Skriptsprachen steht das Perl-Codemuster-Konstrukt nicht zur Verfügung. In Skriptsprachen die PCRE nutzen gibt es jedoch die Möglichkeit eine Alternative zu nutzen, so genannte Callouts. Durch das Setzen einer globalen Variable "pcre_callout", hat die RegExp-Maschine die Möglichkeit, während des Matchens eine externe Funktion aufzurufen. Dazu muss im regulären Ausdruck "(?C)" stehen.

[top]


Overloading: Überladen von Regex-Literalen

Mit Overloading hat man die Möglichkeit den Regex-Dialekt um weitere Metazeichen und Features zu erweitern. Es werden Regex-Literale vorverarbeitet, bevor sie an die Regex-Maschine weitergereicht werden.

Beispiel:

Perl hat kein eigenes Metazeichen für Wortanfang und -ende, nur eins für Wortgrenze. Durch Overloading kann man die neuen Metazeichen \< und \> für Wortanfang und -ende einführen. Diese werden dann im Hintergrund in Ausdrücke umgewandelt, die Perl schon kennt. Zunächst benötigen wir dazu eine Subroutine, für das Ersetzen:

sub RegexLiteralVerarbeiten($)
{
	my ($RegexLiteral) = @_;	#Argument ein String
	$RegexLiteral =~ s/\\</(?<!\\w)(?=\\w)/g;
	$RegexLiteral =~ s/\\>/(?<=\\w)(?!\\w)/g;
	return $RegexLiteral;
}

Der Ersatztext wird wie ein String in Anführungszeichen behandelt, daher muss man "\\w" schreiben, um "\w" zu bekommen.

Die Funktion ersetzt in einem übergebenen String alle "\<" durch "(?<!\w)(?=\w)" und alle "\>" durch (?<=\w)(?!\w) und gibt den veränderten String zurück.

Diese Routine wird in eine Datei MyRegexStuff.pm eingefügt.

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

sub RegexLiteralVerarbeiten($)
{
	my ($RegexLiteral) = @_;	#Argument ein String
	$RegexLiteral =~ s/\\</(?<!\\w)(?=\\w)/g;
	$RegexLiteral =~ s/\\>/(?<=\\w)(?!\\w)/g;
	return $RegexLiteral;
}

1;	#Package muss 1 zurückgeben

Um das Modul in Perl und somit auch die neuen Metazeichen in Perl-Skripten nutzen zu können, muss man es im Bibliothekssuchpfad installieren.
Über "use MyRegexStuff;" kann das Modul dann eingebunden werden.


Ein weiteres Beispiel

In diesem Beispiel Possesive Quantoren in Perl nachbilden. Dazu werden wir die Datei "MyRegexStuff.pm" erweitern.
Wie bereits in einem anderen Abschnitt geschrieben, geben possessive Quantoren keine Zeichen zurück. Dieses Verhalten können wir mit Hilfe von atomaren Klammern nachbilden. Man muss also nur das letzte "+" entfernen und den Ausdruck in eine atomare Klammer setzen. Also aus "Regex*+" wird so z.B. "(?>Regex*)".

Es sind viele Kombinationen mit Quantoren möglich, so kann der Regex-Teil z.B. eine Metazeichen, ein geklammerter Unterausdruck, ein einfaches Zeichen, und Weiteres sein. Um dieses Beispiel einfach zu halten, schauen wir uns die possessiven Quantoren "?+", "*+" und "++" in Kombination mit einem geklammerten Unterausdruck an.

Mit Hilfe des folgenden Regex können Unterausdrücke beliebig tief verschachtelte Klammern enthalten.

$tiefex = qr/ ( [^()] | \( (??{ $tiefex }) \) )* /x;

Erweiterung der Subroutine um die folgenden Zeilen:

sub RegexLiteralVerarbeiten($)
{
	my ($RegexLiteral) = @_;	#Argument ein String
	$RegexLiteral =~ s/\\</(?<!\\w)(?=\\w)/g;
	$RegexLiteral =~ s/\\>/(?<=\\w)(?!\\w)/g;
	$tiefex = qr/ ( [^()] | \( (??{ $tiefex }) \) )* /x;
	$RegexLiteral =~ s/( \( $tiefex \)[*+?] )\+/(?>$1)/gx;
	return $RegexLiteral;
}

Nun können mit Hilfe unseres Moduls auch possessive Quantoren in regulären Ausdrücken verwendet werden.


Probleme beim Überladen von Regex-Literalen

Bei normalen Regex-Literalen funktioniert das Überladen problemlos.
Probleme treten z.B. bei dem folgenden Regex auf

m/($irgendwas)*+)/

Die Funktion "RegexLiteralVerarbeiten" wird hierbei zweimal aufgerufen, einmal mit dem literalen Teil vor der Variableninterpolation und einmal mit dem Teil danach. Wenn der Teil "$irgendwas" ein String ist, der einen Regex enthält, wird dieser Teil nicht von der Overloadroutine behandelt. Da die Funktion keinen ganzen Klammerausdruck erhält, wird der possessive Quantor nie in eine atomare Klammer umgesetzt.
Overloading betrifft nur Regex-Literale. Deshalb werden Variablem mit Strings, die reguläre Ausdrücke enthalten, nie von der Funktion verarbeitet.

Die Overload-Routine keine Möglichkeit festzustellen, welche Modifikatoren für den Regex aktiv sind. Was bei der Verarbeitung wichtig sein könnte.

[top]
Autor: Börge Gabriel