Об использовании пустого регулярного выражения в Perl ( m// )

На днях делал отсечение элементов списка не подходящих под регулярку введённую пользователем:

...
my $re = get_text_in_filter();
@list = grep { /$re/i } @list;
...

$re был пустой строкой и в @list должны были остаться все элементы.

image-loader.svg

Так и происходило при первом проходе, а при втором регулярка не пропускала ни одного элемента списка.

Я предположил, что $re или @list изменились и распечатал их датапринтером:

p my $x=[$re, qr/$re/, $list[0], $list[0] =~ qr/$re/? "ok":"no")];

# 1-й проход:
[
    [0] "",
    [1]   (modifiers: u),
    [2] "Application",
    [3] "ok"
]
# 2-й проход:
[
    [0] "",
    [1]   (modifiers: u),
    [2] "Application",
    [3] "no"
]

С точки зрения датапринтера они не изменились. «В таком случае, возможно, в переменных какой-то флаг был установлен», продолжал рассуждать я. И сдампил $x дэвэл-пиком:

pop @$x;
use Devel::Peek;
Dump($x);

# 1-й проход:
SV = IV(0x56228695d7f8) at 0x56228695d808
  REFCNT = 1
  FLAGS = (ROK)
  RV = 0x56228708a930
  SV = PVAV(0x56228712b618) at 0x56228708a930
    REFCNT = 1
    FLAGS = ()
    ARRAY = 0x562287095590
    FILL = 2
    MAX = 3
    FLAGS = (REAL)
    Elt No. 0
    SV = PV(0x56228708c200) at 0x562286bfdbb0
      REFCNT = 1
      FLAGS = (POK,pPOK)
      PV = 0x562286fd9930 ""\0
      CUR = 0
      LEN = 10
    Elt No. 1
    SV = IV(0x5622870ac428) at 0x5622870ac438
      REFCNT = 1
      FLAGS = (ROK)
      RV = 0x562286c0ef68
      SV = REGEXP(0x56228710d4a0) at 0x562286c0ef68
        REFCNT = 1
        FLAGS = (OBJECT,POK,FAKE,pPOK)
        PV = 0x5622870c04d0 "(?^u:)"
        CUR = 6
        LEN = 0
        STASH = 0x562285ffd230  "Regexp"
        COMPFLAGS = 0x100 ()
        EXTFLAGS = 0x80000100 (NULL)
        ENGINE = 0x7f817615d380 (STANDARD)
        INTFLAGS = 0x0 ()
        NPARENS = 0
        LASTPAREN = 0
        LASTCLOSEPAREN = 0
        MINLEN = 0
        MINLENRET = 0
        GOFS = 0
        PRE_PREFIX = 5
        SUBLEN = 0
        SUBOFFSET = 0
        SUBCOFFSET = 0
        SUBBEG = 0x0
        MOTHER_RE = 0x562287123810
        SV = REGEXP(0x56228710d3e0) at 0x562287123810
          REFCNT = 2
          FLAGS = (POK,pPOK)
          PV = 0x5622870c04d0 "(?^u:)"
          CUR = 6
          LEN = 10
          COMPFLAGS = 0x100 ()
          EXTFLAGS = 0x80000100 (NULL)
          ENGINE = 0x7f817615d380 (STANDARD)
          INTFLAGS = 0x0 ()
          NPARENS = 0
          LASTPAREN = 0
          LASTCLOSEPAREN = 0
          MINLEN = 0
          MINLENRET = 0
          GOFS = 0
          PRE_PREFIX = 5
          SUBLEN = 0
          SUBOFFSET = 0
          SUBCOFFSET = 0
          SUBBEG = 0x0
          MOTHER_RE = 0x0
          PAREN_NAMES = 0x0
          SUBSTRS = 0x562287042a00
          PPRIVATE = 0x562287130bb0
          OFFS = 0x5622870153f0
          QR_ANONCV = 0x0
          SAVED_COPY = 0x0
        PAREN_NAMES = 0x0
        SUBSTRS = 0x562287042970
        PPRIVATE = 0x562287130bb0
        OFFS = 0x562287095090
        QR_ANONCV = 0x0
        SAVED_COPY = 0x0
    Elt No. 2
    SV = PVNV(0x5622870402a0) at 0x562286bfdd00
      REFCNT = 1
      FLAGS = (POK,IsCOW,pPOK)
      IV = 0
      NV = 0
      PV = 0x562287130eb0 "Application"\0
      CUR = 11
      LEN = 13
      COW_REFCNT = 3
      
# 2-й проход:
SV = IV(0x56228695d7f8) at 0x56228695d808
  REFCNT = 1
  FLAGS = (ROK)
  RV = 0x562287058898
  SV = PVAV(0x562287139a90) at 0x562287058898
    REFCNT = 1
    FLAGS = ()
    ARRAY = 0x5622871cb700
    FILL = 2
    MAX = 3
    FLAGS = (REAL)
    Elt No. 0
    SV = PV(0x56228713b690) at 0x56228712e378
      REFCNT = 1
      FLAGS = (POK,pPOK)
      PV = 0x562287113620 ""\0
      CUR = 0
      LEN = 10
    Elt No. 1
    SV = IV(0x5622871c7be0) at 0x5622871c7bf0
      REFCNT = 1
      FLAGS = (ROK)
      RV = 0x5622871419b8
      SV = REGEXP(0x56228710d860) at 0x5622871419b8
        REFCNT = 1
        FLAGS = (OBJECT,POK,FAKE,pPOK)
        PV = 0x5622870c04d0 "(?^u:)"
        CUR = 6
        LEN = 0
        STASH = 0x562285ffd230  "Regexp"
        COMPFLAGS = 0x100 ()
        EXTFLAGS = 0x80000100 (NULL)
        ENGINE = 0x7f817615d380 (STANDARD)
        INTFLAGS = 0x0 ()
        NPARENS = 0
        LASTPAREN = 0
        LASTCLOSEPAREN = 0
        MINLEN = 0
        MINLENRET = 0
        GOFS = 0
        PRE_PREFIX = 5
        SUBLEN = 0
        SUBOFFSET = 0
        SUBCOFFSET = 0
        SUBBEG = 0x0
        MOTHER_RE = 0x562287123810
        SV = REGEXP(0x56228710d3e0) at 0x562287123810
          REFCNT = 2
          FLAGS = (POK,pPOK)
          PV = 0x5622870c04d0 "(?^u:)"
          CUR = 6
          LEN = 10
          COMPFLAGS = 0x100 ()
          EXTFLAGS = 0x80000100 (NULL)
          ENGINE = 0x7f817615d380 (STANDARD)
          INTFLAGS = 0x0 ()
          NPARENS = 0
          LASTPAREN = 0
          LASTCLOSEPAREN = 0
          MINLEN = 0
          MINLENRET = 0
          GOFS = 0
          PRE_PREFIX = 5
          SUBLEN = 0
          SUBOFFSET = 0
          SUBCOFFSET = 0
          SUBBEG = 0x0
          MOTHER_RE = 0x0
          PAREN_NAMES = 0x0
          SUBSTRS = 0x562287042a00
          PPRIVATE = 0x562287130bb0
          OFFS = 0x5622870153f0
          QR_ANONCV = 0x0
          SAVED_COPY = 0x0
        PAREN_NAMES = 0x0
        SUBSTRS = 0x5622871b0af0
        PPRIVATE = 0x562287130bb0
        OFFS = 0x562287093f70
        QR_ANONCV = 0x0
        SAVED_COPY = 0x0
    Elt No. 2
    SV = PVNV(0x562287040580) at 0x5622871cdc18
      REFCNT = 1
      FLAGS = (POK,IsCOW,pPOK)
      IV = 0
      NV = 0
      PV = 0x562286fe39d0 "Application"\0
      CUR = 11
      LEN = 13
      COW_REFCNT = 3

meld показал что различия только в указателях на память, флаги у значений оказались одинаковыми:

image-loader.svg

«В таком случае дело в какой-то внутренней переменной perl-а, которая влияет на поведение пустой регулярки», решил я.

Я уж думал поднять исходники perl-а, но для начала загуглил. Оказалось, что данное поведение регулярок описано в perlop. А именно у оператора матчинга (=~): оказывается, при использовании регулярка запоминается perl-ом, а пустая регулярка матчит как предыдущая. Что видно на следующем примере:

$x = "abc";
$y = "xby";
$x =~ /b/; print $`;  # -> a
$y =~ //; print $`;   # -> x

Причём предыдущая регулярка может быть использована где угодно:

sub f1 { $_[0] =~ /b/; print $`; }
sub f2 { f1(@_) }
$x = "abc";
$y = "xby";
f2($x);             # -> a
$y =~ //; print $`; # -> x

То есть между двумя проходами отбора элементов списка где-то была использована регулярка, которая запомнилась и была использована пустой регуляркой. Проблема в том, что пустая это регулярка или нет perl определяет не на стадии компилляции. То есть он вначале интерполирует регулярку, а потом уже судит:

$x = "abc";
$y = "xby";
$x =~ /b/; print $`;                # -> a
my $re = ""; $y =~ /$re/; print $`; # -> x

Но если переменная $re не пустая:

$x = "abc";
$y = "xby";
$x =~ /b/; print $`;                 # -> a
my $re = "y"; $y =~ /$re/; print $`; # -> xb

В perlop предлагается ставить () при использовании с переменной которая может быть пустой:

$x = "abc";
$_ = "xby";
$x =~ /b/;
my $re = ""; $y =~ /()$re/; print $`; # ->
print $'; # -> xby

Из особенностей пустой регулярки ещё нужно сказать о том, что она использует предыдущую регулярку не только в матчинге, но и при замене и не работает так в split (а просто бъёт строку на символы):

$x = "abc";
$_ = "xby";
$x =~ /b/;
s//*/;
print;  # -> x*y
$, = ", "; print split //, "abc"; # -> a, b, c

И ещё, что с эскейпингом /\Q$re\E/ она тоже считается пустой:

$x = "abc";
$y = "xby";
$x =~ /b/; print $`; # -> a
my $re = ""; $y =~ /\Q$re\E/; print $`; # -> x

perl таит в себе множество тайн и когда уже думаешь, что знаешь о нём всё, оказывается, что ты лишь в начале долгого и увлекательного путешествия в мир кратких выражений.

Список использованной литературы

  1. Пустой паттерн / https://perldoc.perl.org/perlop#The-empty-pattern-//

  2. Проблема интерполяции паттерна / https://raku.org/archive/rfc/144.html

  3. Повторяющиеся шаблоны поиска подстроки нулевой длины / https://metacpan.org/dist/POD2-RU/view/lib/POD2/RU/perlre.pod#Повторяющиеся-шаблоны-поиска-подстроки-нулевой-длины

© Habrahabr.ru