Perl 6

Die Zukunft des Programmierens

Regexes und Rules

Perl 5-Regex

Aus Gründen der Rückwärtskompatibilität sind Perl 5-"Regular Expressions", kurz regexes, immer noch vorhanden, allerdings hat sich die Schreibweise für die Modifier ein wenig verändert:

# bisher: $str =~ m/^foo\d{3}$/i;
# jetzt:
$str ~~ m:P5:i/^foo\d{3}$/;

Modifier werden also am Anfang geschrieben, aus =~ wird der Smart Match Operator ~~. Die "Innereien" der regex bleiben größtenteils unverändert, nur dass aus $0 jetzt $1 geworden ist (usw), da der Programmname in $*PROGRAM_NAME verfübar ist und nicht mehr in $0.

Rules

Rules sind der mächtigere und durchdachtere Nachfolger von regexes. Vieles hat sich geändert, aber einiges ist auch gleich geblieben:

  • Jedes mit einem Backslash \ escapete Sonderzeichen steht genau für das das escapte Zeichen.
  • Capturing Groups stehen immer noch in runden Klammern ( ... ).
  • Alternativen werden immer noch durch | getrennt.
  • Die Quantifier +, * und ? behalten ihre Bedeutung bei, auch die "non-greedy"-Varianten mit nachgestelltem Fragezeichen (wie +?).

Die Änderungen überwiegen bei weitem. Das hier ist nur eine kleine Zusammenstellung der Änderungen, die sicher nicht Vollständig ist.

  • Alle Zahlen und Buchstaben bekommen durch ein vorgestellten Backslash eine Sonderbedeutung, alles andere verliert durch einen Backslash seine Sonderbedeutung.
  • Leerzeichen, Tabs und New Lines werden per Default ignoriert (so wie unter p5 mit /x).
  • Den /s-Switch (single line) gibt es nicht mehr, ^ und $ passen immer auf Anfang bzw. Ende des Strings, ^^ und $$ auf logische Zeilenanfänge und -Enden (und auch Anfang/Ende des Strings.
  • Der Punkt . matched jetzt jedes beliebige Zeichen, auch Zeilenende, \N matched alles außer dem Zeilenende.
  • Gruppierungen können jetzt mit einzlenen Anführungszeichen erfolgen, d.h. m/foo*/ matched foooo, während m/'foo'*/ zu foofoofoo passt.
  • Analog zum "normalen" Perl 6 gibt es auch doppelte Anführungszeichen, in denen auch interpoliert wird: "...".
  • Non-Capturing Groups, früher (?: ... ) stehen jetzt in eckigen Klammern [ ... ].
  • Code in geschweiften Klammern wird als sog. Capture ausgeführt: m/(\S+) { say "Text gefunden"; push @text_chunks, $0 }/ . Nach einer Closure werden $0 etc. zurückgesetzt.
  • Variablen in Regexes werden per Default nicht mehr als Regex, sonder also String interpoliert, d.h. my $f = "."; $text ~~ m/$f/ passt nicht mehr auf beliebige Zeichen, sondern auf den Punkt. Interpolation geht noch mit <$re>.
  • Wenn man ein Array in eine Regex einsetzt, werden die Elemente als Alternativen angesehen, d.h. m/@cmd/ ist äquivalent zu m/ [ @cmd[0] | @cmd[1] | ... ].

< ... > ist magisch

Teile von Regexes, die in spitze Klammern < ... > eingeschlossen sind, können sehr verschiedene Dinge bedeuten, je nach dem, was das erste Zeichen ist - und der Rest...

Wenn direkt innerhalb der spitzen Klammern eckige Klammern stehen, steht das für eine Klasse von Zeiche, so wie früher [...] (nur dass jetzt Leerzeichen ignoriert werden):

regex buchstabe { <[a..z A..Z]> };

Wenn einfach nur Buchstaben und Ziffern darin stehen, wird das Konstrukt als unter-Regex interpretiert. Damit kann man regexes verschachteln:

regex buchstabe  { <[a-zA-Z]> };
regex zahl       { <[0-9]> };
regex identifier { <buchstabe> [ <buchstabe> | <zahl> ]* };
regex sigil      { <[$@%]> };
regex p5_var     { <sigil> <identifier>};

Es gibt auch schon fertige Regexes, so steht <alpha> für Buchstaben, <sp> für das Leerzeichen und <ws> für ein "magisches" Leerzeichen, d.h. für \s+ zwischen alphanumerischen Zeichen und für \s* sonst.

Auch den von uns gerade definierten identifier gibt es als <ident> vorgefertigt.

Fängt das Konstrukt mit <?{ an, so wird das als Assertion, also Bedingung gewertet. Folgende Regex matched ein- bis dreistellige Zahlen von 0 bis 255:

regex byte { (\d**{1..3}) <?{ $0 < 256 }> };

Das Match-Objekt

Wenn man eine Regex mit dem Smart Match Operator ~~ auf einen String los lässt, ist das Ergebnis ein Match-Objekt:

my $match = "foo123" ~~ m/ <ident> /;
# wenn man es nicht zuweist wird es in $/ gespeichert.

# wie sieht diese Magie aus?
say $match.perl;
# liefert:

Match.new(
  ok => Bool::True, 
  from => 0, 
  to => 6, 
  str => "foo123", 
  sub_pos => (), 
  sub_named =>
    { "ident" =>
        Match.new(
          ok => Bool::True, 
          from => 0, 
          to => 6, 
          str => "foo123", 
          sub_pos => (), 
          sub_named => {}
        )
    }
)


# im Boolean-Kontext:
if $match {
    say "Identifier found";
}

# im String-Kontext liefert es den gematchen String:
print ~$match;
# spuckt "foo123" aus

# Die Position, an der ein Treffer gefunden wurde:
$match.from .. $match.to;

Ist das magisch? Es kommt sogar noch besser: Wenn man Capturing Groups ( ... ) in der regex hat, kann man das Match-Objekt als Array verwenden, das die verschiedenen Captures als Elemente enthält:

my $m = "bar" ~~ m/(.) a (r)/;
say $m[0];  # ist 'b'
say $m[1];  # ist 'r'

Und wenn man ein capture in spitzen Klammern, z.B. <ident> verwendet hat, kann man das Match-Objekt als Hash benutzen, und auf die Captures mit ihrem Namen zugreifen:

regex sigil { '@' | '$' | '%' };
my $m = ' $foo bar' ~~ m/ <sigil> <ident>/;
say $m<sigil>;      # gibt $
say $m<ident>;      # gibt foo

Text ersetzen

Die alte Form des Text-Ersetzens mit s/// geht auch unter Perl 6, neue Formen sind dazu gekommen:

my $s1 = "klein";
$s1 tr ~~ s/^kl/k/;
# $s1 tr eq "kein"
# oder: $s.subst(m/^kl/, "k");

#neu (entspricht dem p5 /e Flag): 
my $s2 = "7 drunken nights";
$s2 ~~ s[(\d+)] = 2 * $0;
# jetzt "14 drunken nights"

# alle Flags kommen an den Anfang:
my $s3 = "35 beers, 7 drunken nights";
$s3 ~~ s:g[(\d+)] = 2 * $0;

Man beachte im letzten Beispiel, dass das keine normale Zuweisung ist, sondern dass die rechte Seite bei jedem Match neu ausgwertet wird.