Advanced Regular Expressions

19.1 Extended Mode and Readability

my $email_re = qr{
    \A
    (?<user>   [\w.+-]+ )      # local part
    @
    (?<domain> [\w-]+ (?: \. [\w-]+ )+ )   # domain with at least one dot
    \z
}x;

my $date_re = qr/(?<y>\d{4}) - (?<m>\d\d) - (?<d>\d\d)/x;
my $combined = qr/Date: \s* $date_re/x;     # compose regexes

19.2 Lookaround Assertions

'price: $100'   =~ /(?<=\$)\d+/;            # lookbehind: digits preceded by $
'foobar'        =~ /foo(?=bar)/;            # lookahead
'foobaz'        =~ /foo(?!bar)/;            # negative lookahead
'xbar'          =~ /(?<!foo)bar/;           # negative lookbehind
my $strong = qr/^(?=.*[A-Z])(?=.*[a-z])(?=.*\d).{8,}$/;   # password rule via lookaheads
(my $n = '1234567') =~ s/(?<=\d)(?=(?:\d{3})+$)/,/g;     # "1,234,567"

19.3 Backtracking Control, Atomic Groups and Performance

Avoid catastrophic backtracking ((a+)+b on long strings). Use anchors, specific classes, and possessive/atomic constructs.

use re 'debug'; shows how a regex compiles and executes. use re qw(regexp_pattern is_regexp); gives introspection.

/(?>\d+)\./                 # atomic group: no backtracking into it
/\d++\./                    # possessive quantifier (same effect)
/a(*SKIP)(*FAIL)|b/         # backtracking control verbs: skip "a" contexts

19.4 Recursion, Conditionals and Code

my $balanced = qr/ \( (?: [^()]++ | (?R) )* \) /x;      # recursive: balanced parentheses
say 'balanced' if '(a(b)c)' =~ /^$balanced$/;

my $re = qr/ (?<pair> \[ (?: [^\[\]]++ | (?&pair) )* \] ) /x;   # named recursion

/^(<)?\w+(?(1)>)$/          # conditional: if group 1 matched, require ">"

my $cnt = 0;
'aaa' =~ /(?:a(?{ $cnt++ }))*/;       # embedded code (use sparingly)

19.5 The \G Anchor and Lexing

/c keeps pos() after a failed match. \G anchors to where the last match ended. Together they make a simple lexer.

my $input = 'x = 42 + y';
my @tokens;
pos($input) = 0;
while (pos($input) < length $input) {
    if    ($input =~ /\G\s+/gc)          { next }
    elsif ($input =~ /\G([a-z]\w*)/gc)   { push @tokens, [IDENT => $1] }
    elsif ($input =~ /\G(\d+)/gc)        { push @tokens, [NUM   => $1] }
    elsif ($input =~ /\G([=+\-*\/])/gc)  { push @tokens, [OP    => $1] }
    else  { die "Unexpected char at " . pos($input) }
}

19.6 Regex Modules

Regexp::Common (ready-made patterns: numbers, IPs, URIs), Regexp::Assemble / Regexp::Trie (combine many words efficiently), Regexp::Grammars (full grammars in regex), Parse::RecDescent / Marpa::R2 (parsers), re::engine::PCRE2 (swap the engine).