Общий подход для (эквивалент) "обратных ссылок в классе символов"?

В Perl-выражениях выражения типа \1, \2 и т.д. обычно интерпретируются как "обратные ссылки" ранее захваченным группам, но не так, когда \1, \2 и т.д. появляются внутри символа класс. В последнем случае \ рассматривается как escape-символ (и поэтому \1 является просто 1 и т.д.).

Следовательно, если (например) нужно совместить строку (длиной больше 1), первый символ которой соответствует ее последнему символу, но не появляется нигде в строке, следующее регулярное выражение не будет делать:

/\A       # match beginning of string;
 (.)      # match and capture first character (referred to subsequently by \1);
 [^\1]*   # (WRONG) match zero or more characters different from character in \1;
 \1       # match \1;
 \z       # match the end of the string;
/sx       # s: let . match newline; x: ignore whitespace, allow comments

не будет работать, поскольку он соответствует (например) строке 'a1a2a':

  DB<1> ( 'a1a2a' =~ /\A(.)[^\1]*\1\z/ and print "fail!" ) or print "success!"
fail!

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

Есть ли общий (и, надеюсь, простой) обходной путь?


1 Например, для проблемы в приведенном выше примере я бы использовал что-то вроде

/\A
 (.)              # match and capture first character (referred to subsequently
                  # by \1);
 (?!.*\1\.+\z)    # a negative lookahead assertion for "a suffix containing \1";
 .*               # substring not containing \1 (as guaranteed by the preceding
                  # negative lookahead assertion);
 \1\z             # match last character only if it is equal to the first one
/sx

... где я заменил достаточно прямое (хотя, увы, неправильное) подвыражение [^\1]* в более раннем регулярном выражении с несколько более запрещающим отрицательным прогнозом (?!.*\1.+\z). Это утверждение в основном говорит "сдаваться, если \1 появляется где-то за пределами этой точки (кроме последней позиции)". Кстати, я даю это решение, чтобы проиллюстрировать подобные обходные пути, о которых я упоминал в вопросе. Я не утверждаю, что он особенно хорош.

Ответ 1

Это может быть выполнено с отрицательным просмотром в повторяющейся группе:

/\A         # match beginning of string;
 (.)        # match and capture first character (referred to subsequently by \1);
 ((?!\1).)* # match zero or more characters different from character in \1;
 \1         # match \1;
 \z         # match the end of the string;
/sx

Этот шаблон можно использовать, даже если группа содержит более одного символа.