正则表达式是定义特定文本模式的字符序列,通常是希望在大量文本中找到的模式。

在理论计算机科学和形式语言理论中,正则表达式用于描述所谓的正则语言。自从 20 世纪 50 年代问世以来,正则表达式的实际实现(例如在文本编辑器的文本搜索和替换功能中)已经超出了其严格的科学定义。为了承认这一点,并试图消除歧义,Raku 中的正则表达式通常被称为Regex(来自:regular expression),这个术语在其他编程语言中也很常见。

在 Raku 中,正则表达式是用领域特定语言编写的,即一种子语言或俚语。本页介绍了这种语言,并解释了如何在称为模式匹配的过程中使用正则表达式在字符串中搜索文本模式。

词法约定§

从根本上说,Raku 正则表达式非常类似于子例程:两者都是代码对象,就像你可以有匿名子例程和命名子例程一样,你也可以有匿名正则表达式和命名正则表达式。

无论匿名还是命名,正则表达式都由Regex 对象表示。然而,构造匿名和命名Regex 对象的语法不同。因此,我们将依次讨论它们。

匿名正则表达式定义语法§

可以使用以下方法之一构造匿名正则表达式

rx/pattern/;          # an anonymous Regex object; 'rx' stands for 'regex' 
/pattern/;            # an anonymous Regex object; shorthand for 'rx/.../' 
 
regex { pattern };    # keyword-declared anonymous regex; this form is 
                      # intended for defining named regexes and is discussed 
                      # in that context in the next section

rx/ / 形式比裸简写形式 / / 具有两个优点。

首先,它允许使用除斜杠以外的其他分隔符,这可以提高正则表达式定义的可读性

rx{ '/tmp/'.* };      # the use of curly braces as delimiters makes this first 
rx/ '/tmp/'.* /;      # definition somewhat easier on the eyes than the second

虽然选择范围很广,但并非所有字符都可以用作替代正则表达式分隔符

  • 不能使用空格或字母数字字符作为分隔符。正则表达式定义语法中的空格通常是可选的,除非需要与函数调用语法区分(将在后面讨论)。

  • 括号可以用作替代正则表达式分隔符,但 rx 和开始分隔符之间必须有一个空格。这是因为紧跟括号的标识符始终被解析为子例程调用。例如,在 rx() 中,调用运算符 () 调用子例程 rx。然而,rx ( abc ) 形式确实定义了一个Regex 对象。

  • 使用冒号作为分隔符将与副词 的使用冲突,副词采用 :adverb 的形式;因此,禁止使用冒号。

  • 哈希字符 # 不能用作分隔符,因为它被解析为注释 的开头,一直运行到行尾。

其次,rx 形式允许你在 rx 和开始分隔符之间插入正则表达式副词,以修改整个正则表达式的定义。这等效于在正则表达式的开头插入副词,但可能更清晰

rx:r:s/pattern/;            # :r (:ratchet) and :s (:sigspace) adverbs, defining 
                            # a ratcheting regex in which whitespace is significant 
rx/:r:s pattern/;           # Same, but possibly less readable

虽然匿名正则表达式本身没有命名,但可以通过将它们放在一个命名的变量中来有效地赋予它们一个名称,之后就可以在嵌入正则表达式之外和在嵌入正则表达式中通过插值来引用它们。

my $regex = / R \w+ /;
say "Zen Buddhists like Raku too" ~~ $regex# OUTPUT: «「Raku」␤» 
 
my $regex = /pottery/;
"Japanese pottery rocks!" ~~ / <$regex> /;  # Interpolation of $regex into /.../ 
say $/;                                     # OUTPUT: «「pottery」␤»

命名正则表达式定义语法§

可以使用regex声明器来构建命名正则表达式,如下所示

regex R { pattern };        # a named Regex object, named 'R'

rx形式不同,您不能选择首选分隔符:花括号是必需的。在这方面,应该注意的是,使用regex形式定义命名正则表达式的语法类似于定义子例程

my sub   S { /pattern/ };   # definition of Sub object (returning a Regex) 
my regex R {  pattern  };   # definition of Regex object

这强调了Regex对象表示代码而不是数据的事实。

&S ~~ Code;                 # OUTPUT: «True␤» 
 
&R ~~ Code;                 # OUTPUT: «True␤» 
&R ~~ Method;               # OUTPUT: «True␤»   (A Regex is really a Method!) 

与使用rx形式定义匿名正则表达式不同,使用regex关键字定义命名正则表达式不允许在开始分隔符之前插入副词。相反,用于修改整个正则表达式模式的副词可以首先包含在花括号中。

regex R { :i pattern };     # :i (:ignorecase), renders pattern case insensitive

或者,作为一种简写,也可以(并且建议)使用regex声明器的ruletoken变体来定义Regex,当:ratchet:sigspace副词是感兴趣的时。

regex R { :r pattern };     # apply :r (:ratchet) to entire pattern 

以及,或者

token R { pattern };        # same thing: 'token' implies ':r' 

或者

regex R { :r :s pattern };  # apply :r (:ratchet) and :s (:sigspace) to pattern 

使用这种替代方法

rule  R { pattern };        # same thing: 'rule' implies ':r:s' 

命名正则表达式可以用作其他正则表达式的构建块,因为它们是可以在其他正则表达式中使用<regex-name>语法调用的方法。当它们以这种方式使用时,它们通常被称为子规则;有关其使用的更多详细信息,请参见此处Grammar是子规则的自然栖息地,但许多常见的预定义字符类也作为命名正则表达式实现。

正则表达式可读性:空格和注释§

正则表达式中的空格将被忽略,除非使用:sigspace副词使空格在语法上具有意义。

除了空格之外,还可以像在代码中一样在正则表达式中使用注释来提高其可理解性。这对于单行注释多行/嵌入式注释都是如此。

my $regex =  rx/ \d ** 4            #`(match the year YYYY) 
                 '-'
                 \d ** 2            # ...the month MM 
                 '-'
                 \d ** 2 /;         # ...and the day DD 
 
say '2015-12-25'.match($regex);     # OUTPUT: «「2015-12-25」␤»

匹配语法§

有多种方法可以将字符串与正则表达式匹配。无论选择哪种语法,成功的匹配都会产生一个Match对象。如果匹配不成功,结果将是Nil。无论哪种情况,匹配操作的结果都可通过特殊的匹配变量$/获得。

将字符串与匿名正则表达式/pattern/或命名正则表达式R匹配的最常见方法包括以下方法

  • 智能匹配:"string" ~~ /pattern/,或 "string" ~~ /<R>/

    智能匹配字符串与Regex执行字符串与Regex的正则表达式匹配。

    say "Go ahead, make my day." ~~ / \w+ /;   # OUTPUT: «「Go」␤» 
     
    my regex R { me|you };
    say "You talkin' to me?" ~~ / <R> /;       # OUTPUT: «「me」␤ R => 「me」␤» 
    say "May the force be with you." ~~ &R ;   # OUTPUT: «「you」␤»

    最后两个语句的不同输出表明,这两种对命名正则表达式进行智能匹配的方式并不相同。差异的产生是因为匿名正则表达式/ /中的方法调用<R>Match对象中安装了一个所谓的'命名捕获',而对命名Regex本身的智能匹配则没有。

  • 显式主题匹配:m/pattern/,或 m/<R>/

    匹配运算符m/ /立即将主题变量$_m之后的正则表达式匹配。

    与正则表达式定义的rx/ /语法一样,匹配运算符可以在m和开始的正则表达式分隔符之间使用副词,并且可以使用除斜杠以外的其他分隔符。但是,虽然rx/ /语法只能与影响正则表达式编译的正则表达式副词一起使用,但m/ /语法还可以与确定正则表达式引擎如何执行模式匹配的匹配副词一起使用。

    以下示例说明了m/ // /语法的主要区别

    my $match;
    $_ = "abc";
    $match = m/.+/say $matchsay $match.^name# OUTPUT: «「abc」␤Match␤» 
    $match =  /.+/say $matchsay $match.^name# OUTPUT: «/.+/␤Regex␤»
  • 接收器和布尔上下文中的隐式主题匹配

    如果在接收器上下文中使用 Regex 对象,或者在将其强制转换为 Bool 的上下文中使用,则主题变量 $_ 会自动与之匹配。

    $_ = "dummy string";        # Set the topic explicitly 
     
    rx/ s.* /;                  # Regex object in sink context matches automatically 
    say $/;                     # OUTPUT: «「string」␤» 
     
    say $/ if rx/ d.* /;        # Regex object in Boolean context matches automatically 
                                # OUTPUT: «「dummy string」␤»
  • 匹配方法:"string".match: /pattern/,或 "string".match: /<R>/

    match 方法类似于上面讨论的 m/ / 运算符。在字符串上调用它,并使用 Regex 作为参数,将字符串与 Regex 进行匹配。

  • 解析语法:grammar-name.parse($string)

    虽然解析 语法 不仅仅是将字符串与正则表达式进行匹配,但这种强大的基于正则表达式的文本解构工具不能从这种常见模式匹配方法概述中省略。

    如果您觉得您的需求超出了简单的正则表达式所能提供的范围,请查看此 语法教程,将正则表达式提升到一个新的水平。

字面量和元字符§

正则表达式使用字面量和元字符来描述要匹配的模式。字母数字字符和下划线 _ 构成字面量:这些字符匹配自身,不匹配其他任何字符。其他字符充当元字符,因此可能具有特殊含义,无论是单独使用(例如点 .,用作通配符)还是与其他字符一起构成更大的元语法结构(例如 <?before ...>,定义一个前瞻断言)。

在最简单的形式中,正则表达式仅包含字面量

/Cześć/;           # "Hello" in Polish 
/こんばんは/;        # "Good afternoon" in Japanese 
/Καλησπέρα/;       # "Good evening" in Greek

如果您希望正则表达式字面匹配一个或多个通常充当元字符的字符,则这些字符必须使用反斜杠进行转义,或者使用单引号或双引号进行引用。

反斜杠充当开关。它将单个元字符转换为字面量,反之亦然。

/ \# /;             # matches the hash metacharacter literally 
/ \w /;             # turns literal 'w' into a character class (see below) 
/Hallelujah\!/;     # matches string 'Hallelujah!' incl. exclamation mark

即使元字符在 Raku 中(尚未)具有特殊含义,也需要对其进行转义(或引用)以确保正则表达式编译并字面匹配该字符。这允许保持字面量和元字符之间的清晰区分。因此,例如,要匹配逗号,这将起作用

/ \, /;             # matches a literal comma ','

而这将失败

/ ,  /;             # !! error: an as-yet meaningless/unrecognized metacharacter 
                    # does not automatically match literally 

虽然转义的反斜杠对其后的单个字符起作用,但单个元字符和元字符序列都可以通过在单引号或双引号中引用它们来转换为字面匹配的字符串。

/ "abc" /;          # quoting literals does not make them more literal 
/ "Hallelujah!" /;  # yet, this form is generally preferred over /Hallelujah\!/ 
 
/ "two words" /;    # quoting a space renders it significant, so this matches 
                    # the string 'two words' including the intermediate space 
 
/ '#!:@' /;         # this regex matches the string of metacharacters '#!:@'

但是,引用并不一定将每个元字符都转换为字面量。这是因为引用遵循 Raku 的正常 插值规则。特别是,「…」 引用不允许任何插值;单引号('…'‘…’)允许反斜杠转义单引号和反斜杠本身;双引号("…"“…”)允许插值变量和 {…} 形式的代码块。因此,所有这些都有效

/ '\\\'' /;          # matches a backslash followed by a single quote: \' 
/ 「\'」 /;       # also matches a backslash followed by a single quote 
my $x = 'Hi';
/ "$x there!" /;     # matches the string 'Hi there!' 
 
/ "1 + 1 = {1+1}" /# matches the string '1 + 1 = 2'

而这些示例说明了您想要避免的错误

/ '\' /;             # !! error: this is NOT the way to literally match a 
                     # backslash because now it escapes the second quote 
 
/"Price tag $0.50"/# !! error: "$0" is interpreted as the first positional 
                     # capture (which is Nil), not as '$0' 

字符串从左到右进行搜索,因此只要字符串的一部分与正则表达式匹配就足够了。

if 'Life, the Universe and Everything' ~~ / and / {
    say ~$/;            # OUTPUT: «and␤» 
    say $/.prematch;    # OUTPUT: «Life, the Universe ␤» 
    say $/.postmatch;   # OUTPUT: « Everything␤» 
    say $/.from;        # OUTPUT: «19␤» 
    say $/.to;          # OUTPUT: «22␤» 
};

匹配结果始终存储在 $/ 变量中,并且也从匹配中返回。如果匹配成功,它们都是 Match 类型;否则两者都是 Nil 类型。

通配符§

正则表达式中未转义的点 . 匹配任何单个字符。

因此,这些都匹配

'raku' ~~ /rak./;       # matches the whole string 
'raku' ~~ / rak . /;    # the same; whitespace is ignored 
'raku' ~~ / ra./;     # the . matches the k 
'raker' ~~ / rak. /;    # the . matches the e

而这并不匹配

'raku' ~~ / . rak /;

因为目标字符串中 rak 之前没有字符可以匹配。

值得注意的是,. 也匹配逻辑换行符 \n

my $text = qq:to/END/ 
  Although I am a
  multi-line text,
  I can be matched
  with /.*/.
  END
  ;
 
say $text ~~ / .* /;
# OUTPUT: «「Although I am a␤multi-line text,␤I can be matched␤with /.*/.␤」»

字符类§

反斜杠字符类§

存在 \w 形式的预定义字符类。它的否定用大写字母 \W 表示。

\n\N§

\n 匹配逻辑换行符。\N 匹配单个非逻辑换行符的字符。

逻辑换行符的定义遵循 Unicode 对行边界定义,特别是包括:换行符 (LF) \U+000A、垂直制表符 (VT) \U+000B、换页符 (FF) \U+000C、回车符 (CR) \U+000D 以及 Microsoft Windows 风格的换行符序列 CRLF。

正则表达式中 \n 的解释独立于由 换行符编译指示 控制的变量 $?NL 的值。

\t\T§

\t 匹配单个制表符/制表符字符,U+0009\T 匹配单个非制表符字符。

请注意,这里不包括像 U+000B VERTICAL TABULATION 这样的特殊制表符。

\h\H§

\h 匹配单个水平空白字符。\H 匹配单个非水平空白字符。

水平空白字符的示例包括

U+0020 SPACE
U+00A0 NO-BREAK SPACE
U+0009 CHARACTER TABULATION
U+2001 EM QUAD

垂直空白,例如换行符,被明确排除在外;这些可以使用 \v 匹配;\s 匹配任何类型的空白。

\v\V§

\v 匹配单个垂直空白字符。\V 匹配单个非垂直空白字符。

垂直空白字符的示例

U+000A LINE FEED
U+000B VERTICAL TABULATION
U+000C FORM FEED
U+000D CARRIAGE RETURN
U+0085 NEXT LINE
U+2028 LINE SEPARATOR
U+2029 PARAGRAPH SEPARATOR

使用 \s 匹配任何类型的空白,而不仅仅是垂直空白。

\s\S§

\s 匹配单个空白字符。\S 匹配单个非空白字符。

say $/.prematch if 'Match the first word.' ~~ / \s+ /;
# OUTPUT: «Match␤»

\d\D§

\d 匹配单个十进制数字(Unicode 通用类别数字,十进制数字Nd);相反,\D 匹配单个十进制数字字符。

'ab42' ~~ /\d/ and say ~$/;     # OUTPUT: «4␤» 
'ab42' ~~ /\D/ and say ~$/;     # OUTPUT: «a␤»

请注意,不仅阿拉伯数字(通常用于拉丁字母)匹配 \d,而且其他脚本中的十进制数字也匹配。

十进制数字的示例包括

U+0035 5 DIGIT FIVE
U+0BEB ௫ TAMIL DIGIT FIVE
U+0E53 ๓ THAI DIGIT THREE
U+17E5 ៥ KHMER DIGIT FIVE

另请注意,“十进制数字”是一个比“数字”更窄的类别,因为(Unicode)数字不仅包括十进制数字(Nd),还包括字母数字(Nl)和其他数字(No)。Unicode 数字的示例,这些数字不是十进制数字,包括

U+2464 ⑤ CIRCLED DIGIT FIVE
U+2476 ⑶ PARENTHESIZED DIGIT THREE
U+2083 ₃ SUBSCRIPT THREE

要匹配所有数字,可以使用 Unicode 属性 N

say '' ~~ /<:N>/ # OUTPUT: «「⑤」␤»

\w\W§

\w 匹配单个单词字符,即字母(Unicode 类别 L)、数字或下划线。\W 匹配单个非单词字符。

单词字符的示例

0041 A LATIN CAPITAL LETTER A
0031 1 DIGIT ONE
03B4 δ GREEK SMALL LETTER DELTA
03F3 ϳ GREEK LETTER YOT
0409 Љ CYRILLIC CAPITAL LETTER LJE

\c\C§

\c 接受一个由方括号分隔的参数,该参数是 Unicode 字符在 Unicode 字符数据库 (UCD) 中的名称,并匹配该特定字符。例如

'a.b' ~~ /\c[FULL STOP]/ and say ~$/;    # OUTPUT: «.␤»

\C 匹配单个非命名 Unicode 字符的字符。

请注意,“字符”一词在此处是指 UCD 的意义,但由于 Raku 使用 NFG,组合代码点和它们所附着的基本字符通常不会单独匹配。例如,如果您将 "ü" 组成 "u\x[0308]",这很好用,但匹配可能会让您感到意外

say "u\x[0308]" ~~ /\c[LATIN SMALL LETTER U]/;    # OUTPUT: «Nil␤»

要匹配未修改的字符,可以使用 :ignoremark 副词。

\x\X§

\x 接受一个由方括号分隔的参数,该参数是表示要匹配字符的 Unicode 代码点的十六进制表示形式。例如

'a.b' ~~ /\x[2E]/ and say ~$/;    # OUTPUT: «.␤»

\X 匹配单个非给定 Unicode 代码点的字符。

此外,\x\X 可以不带方括号使用,在这种情况下,xX 之后的所有有效十六进制数字将被消耗。这意味着所有这些都是等效的

/\x2e/ and /\x002e/ and /\x00002e/

但这种格式可能很模糊,因此在非平凡表达式中强烈建议使用周围的空白。

有关组合代码点的更多规定,请参阅 \c\C

预定义字符类§

简写描述
<alpha>字母字符加下划线 (_)
<digit>\d十进制数字
<xdigit>十六进制数字 [0-9A-Fa-f]
<alnum>\w<alpha> 加 <digit>
<punct>标点符号和符号(仅 ASCII 之外的标点符号)
<graph><alnum> 加 <punct>
<space>\s空白字符
<cntrl>控制字符
<print><graph> 加 <space>,但没有 <cntrl>
<blank>\h水平空白字符
<lower><:Ll>小写字符
<upper><:Lu>大写字符

左侧列中预定义的字符类都是 <name> 形式,暗示它们是作为内置 命名正则表达式 实现的。因此,它们受通常的捕获语义的约束。这意味着,如果字符类以 <name> 语法(即如左侧列中所示)调用,它不仅会匹配,还会捕获,在生成的 Match 中安装一个相应命名的 '命名捕获'。如果只需要匹配而不捕获,可以通过使用包含前导点的调用语法来抑制捕获:<.name>

预定义正则表达式§

除了内置字符类之外,Raku 还提供作为命名正则表达式定义的内置 锚点零宽度断言。这些包括 wb(词边界)、ww(在词内)和 same(下一个和上一个字符相同)。有关详细信息,请参见 锚点零宽度断言 部分。

Raku 还提供以下两个预定义标记(即不会 回溯 的正则表达式)

标记正则表达式等效项描述
<ws><!ww> \s*分隔单词的空白字符(包括零,例如在 EOF 处)
<ident><.alpha> \w*基本标识符(不支持 ' 或 -)。

Unicode 属性§

到目前为止提到的字符类大多是为了方便起见;另一种方法是使用 Unicode 字符属性。这些属性采用 <:property> 的形式,其中 property 可以是 Unicode 通用类别名称的简短名称或长名称。这些使用对语法。

要匹配 Unicode 属性,可以使用智能匹配或 uniprop

"a".uniprop('Script');                 # OUTPUT: «Latin␤» 
"a" ~~ / <:Script<Latin>/;           # OUTPUT: «「a」␤» 
"a".uniprop('Block');                  # OUTPUT: «Basic Latin␤» 
"a" ~~ / <:Block('Basic Latin')> /;    # OUTPUT: «「a」␤»

这些是用于匹配的 Unicode 通用类别

简短
LLetter
LCCased_Letter
LuUppercase_Letter
LlLowercase_Letter
LtTitlecase_Letter
LmModifier_Letter
LoOther_Letter
MMark
MnNonspacing_Mark
McSpacing_Mark
MeEnclosing_Mark
NNumber
NdDecimal_Number 或 digit
NlLetter_Number
NoOther_Number
PPunctuation 或 punct
PcConnector_Punctuation
PdDash_Punctuation
PsOpen_Punctuation
PeClose_Punctuation
PiInitial_Punctuation
PfFinal_Punctuation
PoOther_Punctuation
SSymbol
SmMath_Symbol
ScCurrency_Symbol
SkModifier_Symbol
SoOther_Symbol
ZSeparator
ZsSpace_Separator
ZlLine_Separator
ZpParagraph_Separator
COther
CcControl 或 cntrl
CfFormat
CsSurrogate
CoPrivate_Use
CnUnassigned

例如,<:Lu> 匹配单个大写字母。

它的否定是:<:!property>。因此,<:!Lu> 匹配单个非大写字母的字符。

类别可以一起使用,使用中缀运算符

运算符含义
+集合并
\-集合差

要匹配小写字母或数字,请编写 <:Ll+:N><:Ll+:Number><+ :Lowercase_Letter + :Number>

也可以用括号将类别和类别集分组;例如

say $0 if 'raku9' ~~ /\w+(<:Ll+:N>)/ # OUTPUT: «「9」␤»

枚举字符类和范围§

有时预先存在的通配符和字符类是不够的。幸运的是,定义自己的字符类相当简单。在 <[ ]> 中,您可以放置任意数量的单个字符和字符范围(用两个点表示端点),可以包含或不包含空格。

"abacabadabacaba" ~~ / <[ a .. c 1 2 3 ]>* /;
# Unicode hex codepoint range 
"ÀÁÂÃÄÅÆ" ~~ / <[ \x[00C0] .. \x[00C6] ]>* /;
# Unicode named codepoint range 
"αβγ" ~~ /<[\c[GREEK SMALL LETTER ALPHA]..\c[GREEK SMALL LETTER GAMMA]]>*/;
# Non-alphanumeric 
'$@%!' ~~ /<[ ! @ $ % ]>+/  # OUTPUT: «「$@%!」␤»

如上面最后一行所示,在 <[ ]> 中,您不需要像在 <[ ]> 之外的正则表达式文本中那样对大多数非字母数字字符进行引用或转义。但是,您需要转义在 <[ ]> 中具有特殊含义的字符集,例如 \[]

要转义在 <[ ]> 中具有特殊含义的字符,请在字符前面加上一个 \

say "[ hey ]" ~~ /<-[ \] \[ \s ]>+/# OUTPUT: «「hey」␤»

您不能在 <[ ]> 中引用特殊字符 - ' 只是匹配一个字面上的 '

< > 中,您可以使用 +- 来添加或删除多个范围定义,甚至可以混合使用上面的一些 Unicode 类别。您也可以在 [ ] 之间编写字符类的反斜杠形式。

/ <[\d] - [13579]> /;
# starts with \d and removes odd ASCII digits, but not quite the same as 
/ <[02468]> /;
# because the first one also contains "weird" unicodey digits

您也可以在列表中包含 Unicode 属性

/<:Zs + [\x9] - [\xA0] - [\x202F] >/
# Any character with "Zs" property, or a tab, but not a "no-break space" or "narrow no-break space"

要否定一个字符类,请在左尖括号后放置一个 -

say 'no quotes' ~~ /  <-[ " ]> + /;  # <-["]> matches any character except "

解析引号分隔字符串的常见模式涉及否定字符类

say '"in quotes"' ~~ / '"' <-[ " ]> * '"'/;

此正则表达式首先匹配一个引号,然后匹配任何不是引号的字符,然后再次匹配一个引号。*+ 在上面示例中的含义将在下一节关于量词中解释。

就像您可以使用 - 来表示集合差和单个值的否定一样,您也可以在前面显式地放置一个 +

/ <+[123]> /  # same as <[123]>

量词§

量词使前面的原子匹配可变次数。例如,a+ 匹配一个或多个 a 字符。

量词比连接绑定得更紧密,因此 ab+ 匹配一个 a 后跟一个或多个 b。对于引号来说,情况有所不同,因此 'ab'+ 匹配字符串 abababababab 等。

一个或多个:+§

+ 量词使前面的原子匹配一次或多次,没有上限。

例如,要匹配 key=value 形式的字符串,您可以编写这样的正则表达式

/ \w+ '=' \w+ /

零个或多个:*§

* 量词使前面的原子匹配零次或多次,没有上限。

例如,要允许 ab 之间有可选的空格,您可以编写

/ a \s* b /

零个或一个:?§

? 量词使前面的原子匹配零次或一次。

例如,要匹配 dogdogs,您可以编写

/ dogs? /

通用量词:** min..max§

要对原子进行任意次数的量化,请使用 ** 量词,它在右侧接受一个 Int 或一个 Range,指定匹配的次数。如果指定了 Range,则端点指定匹配的最小和最大次数。

say 'abcdefg' ~~ /\w ** 4/;      # OUTPUT: «「abcd」␤» 
say 'a'       ~~ /\w **  2..5/;  # OUTPUT: «Nil␤» 
say 'abc'     ~~ /\w **  2..5/;  # OUTPUT: «「abc」␤» 
say 'abcdefg' ~~ /\w **  2..5/;  # OUTPUT: «「abcde」␤» 
say 'abcdefg' ~~ /\w ** 2^..^5/# OUTPUT: «「abcd」␤» 
say 'abcdefg' ~~ /\w ** ^3/;     # OUTPUT: «「ab」␤» 
say 'abcdefg' ~~ /\w ** 1..*/;   # OUTPUT: «「abcdefg」␤» 

仅支持量词右侧的基本字面语法,以避免与其他正则表达式构造的歧义。如果您需要使用更复杂的表达式,例如由变量组成的 Range,请将 Range 括在花括号中

my $start = 3;
say 'abcdefg' ~~ /\w ** {$start .. $start+2}/# OUTPUT: «「abcde」␤» 
say 'abcdefg' ~~ /\w ** {π.Int}/;              # OUTPUT: «「abc」␤» 

负值被视为零

say 'abcdefg' ~~ /\w ** {-Inf}/;     # OUTPUT: «「」␤» 
say 'abcdefg' ~~ /\w ** {-42}/;      # OUTPUT: «「」␤» 
say 'abcdefg' ~~ /\w ** {-10..-42}/# OUTPUT: «「」␤» 
say 'abcdefg' ~~ /\w ** {-42..-10}/# OUTPUT: «「」␤» 

如果结果值为 InfNaN,或者结果 Range 为空、非数值、包含 NaN 端点,或最小有效端点为 Inf,则将抛出 X::Syntax::Regex::QuantifierValue 异常

(try say 'abcdefg' ~~ /\w ** {42..10}/  )
    orelse say ($!.^name$!.empty-range);
    # OUTPUT: «(X::Syntax::Regex::QuantifierValue True)␤» 
(try say 'abcdefg' ~~ /\w ** {Inf..Inf}/)
    orelse say ($!.^name$!.inf);
    # OUTPUT: «(X::Syntax::Regex::QuantifierValue True)␤» 
(try say 'abcdefg' ~~ /\w ** {NaN..42}/ )
    orelse say ($!.^name$!.non-numeric-range);
    # OUTPUT: «(X::Syntax::Regex::QuantifierValue True)␤» 
(try say 'abcdefg' ~~ /\w ** {"a".."c"}/)
    orelse say ($!.^name$!.non-numeric-range);
    # OUTPUT: «(X::Syntax::Regex::QuantifierValue True)␤» 
(try say 'abcdefg' ~~ /\w ** {Inf}/)
    orelse say ($!.^name$!.inf);
    # OUTPUT: «(X::Syntax::Regex::QuantifierValue True)␤» 
(try say 'abcdefg' ~~ /\w ** {NaN}/)
    orelse say ($!.^name$!.non-numeric);
    # OUTPUT: «(X::Syntax::Regex::QuantifierValue True)␤» 

修改后的量词:%%%§

为了更容易匹配诸如逗号分隔的值之类的东西,您可以将 % 修饰符附加到上述任何量词,以指定必须出现在每个匹配项之间的分隔符。例如,a+ % ',' 将匹配 aa,aa,a,a 等。

%%% 相似,区别在于它可以选择性地匹配尾随分隔符。这意味着除了 aa,a 之外,它还可以匹配 a,a,a,

量词与 % 相互作用并控制可以成功匹配的总重复次数,因此 a* % ',' 也匹配空字符串。如果您想匹配以逗号分隔的单词,您可能需要嵌套一个普通量词和一个修改后的量词

say so 'abc,def' ~~ / ^ [\w+** 1 % ',' $ /;  # OUTPUT: «False␤» 
say so 'abc,def' ~~ / ^ [\w+** 2 % ',' $ /;  # OUTPUT: «True␤» 

防止回溯::§

防止 回溯 的一种方法是使用 ratchet 副词,如 下面 所述。另一种更细粒度的防止正则表达式回溯的方法是将 : 修饰符附加到量词

my $str = "ACG GCT ACT An interesting chain";
say $str ~~ /<[ACGT\s]>+ \s+ (<[A..Z a..z \s]>+)/;
# OUTPUT: «「ACG GCT ACT An interesting chain」␤ 0 => 「An interesting chain」␤» 
say $str ~~ /<[ACGT\s]>+\s+ (<[A..Z a..z \s]>+)/;
# OUTPUT: «Nil␤» 

在第二种情况下,An 中的 A 已经被模式“吸收”,阻止了模式第二部分(在 \s+ 之后)的匹配。通常我们想要相反的结果:防止回溯以精确匹配我们正在寻找的内容。

在大多数情况下,您会出于效率原因想要防止回溯,例如这里

say $str ~~ m:g/[(<[ACGT]> **: 3) \s*]+ \s+ (<[A..Z a..z \s]>+)/;
# OUTPUT: 
# «(「ACG GCT ACT An interesting chain」 
# «0 => 「ACG」␤» 
# «0 => 「GCT」␤» 
# «0 => 「ACT」␤» 
# «1 => 「An interesting chain」)␤» 

虽然在这种情况下,从 ** 后面删除 : 会使其表现完全相同。最佳用途是创建不会回溯的标记

$_ = "ACG GCT ACT IDAQT";
say  m:g/[(\w+:) \s*]+ (\w+$$/;
# OUTPUT: 
# «(「ACG GCT ACT IDAQT」␤» 
# «0 => 「ACG」␤» 
# «0 => 「GCT」␤» 
# «0 => 「ACT」␤» 
# «1 => 「IDAQT」)␤»

如果没有 : 紧随 \w+,捕获的ID 部分将只是 T,因为模式将继续匹配所有内容,只留下一个字母来匹配行末的 \w+ 表达式。

贪婪量词与节俭量词:?§

默认情况下,量词请求贪婪匹配

'abababa' ~~ /.* a/ && say ~$/;   # OUTPUT: «abababa␤» 

您可以将 ? 修饰符附加到量词以启用节俭匹配

'abababa' ~~ /.*? a/ && say ~$/;   # OUTPUT: «aba␤» 

您也可以为通用量词启用节俭匹配

say '/foo/o/bar/' ~~ /\/.**?{1..10}\//;  # OUTPUT: «「/foo/」␤» 
say '/foo/o/bar/' ~~ /\/.**!{1..10}\//;  # OUTPUT: «「/foo/o/bar/」␤»

可以使用 ! 修饰符显式请求贪婪匹配。

交替:||§

要匹配几个可能的备选方案之一,请用 || 分隔它们;第一个匹配的备选方案获胜。

例如,ini 文件具有以下形式

[section]
key = value

因此,如果您解析 ini 文件的单行,它可以是节或键值对,并且正则表达式将是(作为第一近似值)

/ '[' \w+ ']' || \S+ \s* '=' \s* \S* /

也就是说,要么是方括号包围的单词,要么是非空格字符的字符串,后跟零个或多个空格,后跟等号 =,再次后跟可选空格,后跟另一个非空格字符的字符串。

空字符串作为第一个分支被忽略,以允许您一致地格式化分支。您可以将前面的示例写成

/
|| '[' \w+ ']'
|| \S+ \s* '=' \s* \S*
/

即使在非回溯上下文中,交替运算符 || 也会按顺序尝试所有分支,直到第一个分支匹配。

最长交替:|§

简而言之,在由 | 分隔的正则表达式分支中,最长的标记匹配获胜,与正则表达式中的文本顺序无关。但是,| 的实际作用远不止于此。它不会在完成整个匹配后决定哪个分支获胜,而是遵循 最长标记匹配 (LTM) 策略

简而言之,| 的作用如下:

  • 首先,选择具有最长声明式前缀的分支。

say "abc" ~~ /ab | a.* /;                 # OUTPUT: «⌜abc⌟␤» 
say "abc" ~~ /ab | a {} .* /;             # OUTPUT: «⌜ab⌟␤» 
say "if else" ~~ / if | if <.ws> else /;  # OUTPUT: «「if」␤» 
say "if else" ~~ / if | if \s+   else /;  # OUTPUT: «「if else」␤»

如上所示,a.* 是一个声明式前缀,而 a {} .*{} 处终止,因此其声明式前缀为 a。请注意,非声明式原子会终止声明式前缀。如果您想在 rule 中应用 |,这一点非常重要,因为 rule 会自动启用 :s,而 <.ws> 会意外地终止声明式前缀。

  • 如果出现平局,则选择具有最高特异性的匹配项。

say "abc" ~~ /a. | ab { print "win" } /;  # OUTPUT: «win「ab」␤»

当两个备选方案在相同长度上匹配时,通过特异性来打破平局。也就是说,ab 作为精确匹配,比使用字符类的 a. 更接近。

  • 如果仍然出现平局,则使用其他平局决胜符。

say "abc" ~~ /a\w| a. { print "lose" } /# OUTPUT: «⌜ab⌟␤»

如果上面的平局决胜符不起作用,则文本上更早的备选方案优先。

有关更多详细信息,请参阅 LTM 策略

带引号的列表是 LTM 匹配项§

在正则表达式中使用带引号的列表等效于指定列表元素的最长匹配备选方案。因此,以下匹配

say 'food' ~~ /< f fo foo food >/;      # OUTPUT: «「food」␤»

等效于

say 'food' ~~ / f | fo | foo | food /;  # OUTPUT: «「food」␤»

请注意,第一个 < 后的空格在这里很重要:<food> 调用名为 food 的命名规则,而 < food >< food> 指定具有单个元素 'food' 的带引号的列表。

如果第一个分支为空字符串,则会忽略它。这使您可以一致地格式化正则表达式

/
| f
| fo
| foo
| food
/

数组也可以插入到正则表达式中以实现相同的效果

my @increasingly-edible = <f fo foo food>;
say 'food' ~~ /@increasingly-edible/;   # OUTPUT: «「food」␤»

这在下面 正则表达式插值 中有进一步的说明。

连接:&&§

如果所有以 && 分隔的段都匹配目标字符串的相同子字符串,则匹配成功。这些段从左到右进行评估。

这对于增强现有正则表达式很有用。例如,如果您有一个匹配带引号的字符串的正则表达式 quoted,那么 / <quoted> && <-[x]>* / 匹配不包含字符 x 的带引号的字符串。

请注意,您不能使用前瞻轻松获得相同的行为,也就是说,正则表达式不会消耗字符,因为前瞻不会在带引号的字符串停止匹配时停止查找。

say 'abc' ~~ / <?before a> && . /;    # OUTPUT: «Nil␤» 
say 'abc' ~~ / <?before a> . && . /;  # OUTPUT: «「a」␤» 
say 'abc' ~~ / <?before a> . /;       # OUTPUT: «「a」␤» 
say 'abc' ~~ / <?before a> .. /;      # OUTPUT: «「ab」␤» 

|| 一样,空第一个分支会被忽略。

连接:&§

与正则表达式中的 && 非常相似,如果所有以 & 分隔的段都匹配目标字符串的相同部分,则匹配成功。

&(与 && 不同)被认为是声明式的,从概念上讲,所有段都可以并行评估,或者以编译器选择的任何顺序评估。

||& 一样,空第一个分支会被忽略。

锚点§

正则表达式在整个字符串中搜索匹配项。有时这不是您想要的。锚点仅在字符串的特定位置匹配,从而将正则表达式匹配锚定到该位置。

字符串开头和结尾§

^ 锚点仅在字符串开头匹配

say so 'karakul'  ~~ /  raku/;    # OUTPUT: «True␤» 
say so 'karakul'  ~~ /^ raku/;    # OUTPUT: «False␤» 
say so 'rakuy'    ~~ /^ raku/;    # OUTPUT: «True␤» 
say so 'raku'     ~~ /^ raku/;    # OUTPUT: «True␤»

$ 锚点仅在字符串结尾匹配

say so 'use raku' ~~ /  raku  /;   # OUTPUT: «True␤» 
say so 'use raku' ~~ /  raku $/;   # OUTPUT: «True␤» 
say so 'rakuy'    ~~ /  raku $/;   # OUTPUT: «False␤»

您可以将两个锚点组合在一起。

say so 'use raku' ~~ /^ raku $/;   # OUTPUT: «False␤» 
say so 'raku'     ~~ /^ raku $/;   # OUTPUT: «True␤»

请记住,^ 匹配字符串的开头,而不是的开头。同样,$ 匹配字符串的结尾,而不是的结尾。

以下是一个多行字符串

my $str = chomp q:to/EOS/; 
   Keep it secret
   and keep it safe
   EOS
 
# 'safe' is at the end of the string 
say so $str ~~ /safe   $/;   # OUTPUT: «True␤» 
 
# 'secret' is at the end of a line, not the string 
say so $str ~~ /secret $/;   # OUTPUT: «False␤» 
 
# 'Keep' is at the start of the string 
say so $str ~~ /^Keep   /;   # OUTPUT: «True␤» 
 
# 'and' is at the start of a line -- not the string 
say so $str ~~ /^and    /;   # OUTPUT: «False␤»

行首和行尾§

^^ 锚点匹配逻辑行的开头。也就是说,匹配字符串的开头,或者换行符之后。但是,它不会匹配字符串的结尾,即使它以换行符结尾。

$$ 锚点匹配逻辑行的结尾。也就是说,匹配换行符之前,或者当最后一个字符不是换行符时匹配字符串的结尾。

要理解以下示例,重要的是要知道 q:to/EOS/...EOS heredoc 语法会删除与 EOS 标记相同的级别的前导缩进,因此第一行、第二行和最后一行没有前导空格,第三行和第四行各有两位前导空格。

my $str = q:to/EOS/; 
    There was a young man of Japan
    Whose limericks never would scan.
      When asked why this was,
      He replied "It's because I always try to fit
    as many syllables into the last line as ever I possibly can."
    EOS
 
# 'There' is at the start of string 
say so $str ~~ /^^ There/;        # OUTPUT: «True␤» 
 
# 'limericks' is not at the start of a line 
say so $str ~~ /^^ limericks/;    # OUTPUT: «False␤» 
 
# 'as' is at start of the last line 
say so $str ~~ /^^ as/;            # OUTPUT: «True␤» 
 
# there are blanks between start of line and the "When" 
say so $str ~~ /^^ When/;         # OUTPUT: «False␤» 
 
# 'Japan' is at end of first line 
say so $str ~~ / Japan $$/;       # OUTPUT: «True␤» 
 
# there's a . between "scan" and the end of line 
say so $str ~~ / scan $$/;        # OUTPUT: «False␤» 
 
# matched at the last line 
say so $str ~~ / '."' $$/;        # OUTPUT: «True␤»

词边界§

要匹配任何词边界,请使用 <?wb>。这类似于其他语言中的 \b。要匹配相反的情况,即任何不与单词相邻的字符,请使用 <!wb>。这类似于其他语言中的 \B\b\B 将从 Raku 的 6.d 版本开始抛出 X::Obsolete 异常。

这两个都是零宽度正则表达式元素。

say "two-words" ~~ / two<|w>\-<|w>words /;    # OUTPUT: «「two-words」␤» 
say "twowords" ~~ / two<!|w><!|w>words /;     # OUTPUT: «「twowords」␤»

左词边界和右词边界§

<< 匹配左词边界。它匹配左侧存在非单词字符或字符串开头,右侧存在单词字符的位置。

>> 匹配右词边界。它匹配左侧存在单词字符,右侧存在非单词字符或字符串结尾的位置。

这两个都是零宽度正则表达式元素。

my $str = 'The quick brown fox';
say so ' ' ~~ /\W/;               # OUTPUT: «True␤» 
say so $str ~~ /br/;              # OUTPUT: «True␤» 
say so $str ~~ /<< br/;           # OUTPUT: «True␤» 
say so $str ~~ /br >>/;           # OUTPUT: «False␤» 
say so $str ~~ /own/;             # OUTPUT: «True␤» 
say so $str ~~ /<< own/;          # OUTPUT: «False␤» 
say so $str ~~ /own >>/;          # OUTPUT: «True␤» 
say so $str ~~ /<< The/;          # OUTPUT: «True␤» 
say so $str ~~ /fox >>/;          # OUTPUT: «True␤»

您也可以使用变体 «»

my $str = 'The quick brown fox';
say so $str ~~ /« own/;          # OUTPUT: «False␤» 
say so $str ~~ /own »/;          # OUTPUT: «True␤»

要查看 <|w>«» 之间的区别

say "stuff here!!!".subst(:g, />>/'|');   # OUTPUT: «stuff| here|!!!␤» 
say "stuff here!!!".subst(:g, /<</'|');   # OUTPUT: «|stuff |here!!!␤» 
say "stuff here!!!".subst(:g, /<|w>/'|'); # OUTPUT: «|stuff| |here|!!!␤»

锚点总结§

锚点是零宽度正则表达式元素。因此它们不会占用输入字符串中的字符,也就是说,它们不会推进正则表达式引擎尝试匹配的当前位置。一个好的思维模型是,它们匹配输入字符串中两个字符之间,或者第一个字符之前,或者最后一个字符之后。

锚点描述示例
^字符串开头"⏏two\nlines"
^^行开头"⏏two\n⏏lines"
$字符串结尾"two\nlines⏏"
$$行结尾"two⏏\nlines⏏"
<< 或 «左词边界"⏏two ⏏words"
>> 或 »右词边界"two⏏ words⏏"
<?wb>任何词边界"⏏two⏏ ⏏words⏏~!"
<!wb>不是词边界"t⏏w⏏o w⏏o⏏r⏏d⏏s~⏏!"
<?ww>词内"t⏏w⏏o w⏏o⏏r⏏d⏏s~!"
<!ww>不在词内"⏏two⏏ ⏏words⏏~⏏!⏏"

零宽度断言§

零宽度断言可以帮助您实现自己的锚点:它将另一个正则表达式转换为锚点,使它们不消耗输入字符串中的任何字符。有两种变体:前瞻和后顾断言。

从技术上讲,锚点也是零宽度断言,它们可以向前和向后查看。

环视断言§

环视断言,在更简单的形式中需要一个字符类,可以双向工作。它们匹配,但它们不消耗字符。

my regex key {^^ <![#-]> \d+ }
say "333" ~~ &key;                  # OUTPUT: «「333」␤» 
say '333$' ~~ m/ \d+ <?[$]>/;       # OUTPUT: «「333」␤» 
say '$333' ~~ m/^^ <?[$]> . \d+ /;  # OUTPUT: «「$333」␤» 

它们可以是肯定的或否定的:![] 是否定的,而 ?[] 是肯定的;方括号将包含将要匹配的字符或反斜杠转义的字符类。

您可以使用直接在感叹号或问号之前定义的字符类和 Unicode 属性,将它们转换为环视断言。

say '333' ~~ m/^^ <?alnum> \d+ /;          # OUTPUT: «「333」␤» 
say '333' ~~ m/^^ <?:Nd> \d+ /;            # OUTPUT: «「333」␤» 
say '333' ~~ m/^^ <!:L> \d+ /;             # OUTPUT: «「333」␤» 
say '333' ~~ m/^^ \d+ <!:Script<Tamil>/# OUTPUT: «「33」␤» 

在前两种情况下,相应的字符类匹配但不消耗第一个数字,然后表达式消耗该数字;在第三种情况下,负向先行断言的行为相同。在第四个语句中,最后一个数字匹配但未消耗,因此匹配仅包含前两位数字。

先行断言§

要检查模式是否出现在另一个模式之前,请使用 before 断言的先行断言。它具有以下形式

<?before pattern>

因此,要搜索紧跟字符串 bar 的字符串 foo,请使用以下正则表达式

/ foo <?before bar> /

例如

say "foobar" ~~ / foo <?before bar> /;  # OUTPUT: «foo␤»

但是,如果您要搜索紧跟某些模式的模式,则需要使用负向先行断言,它具有以下形式

<!before pattern>

在以下示例中,所有不紧跟 barfoo 都会匹配

say "foobaz" ~~ / foo <!before bar> /;  # OUTPUT: «foo␤»

先行断言也可以与其他模式一起使用,例如字符范围、插值变量下标等。在这种情况下,使用 ?! 来表示否定形式就足够了。例如,以下几行都产生相同的结果

say 'abcdefg' ~~ rx{ abc <?before def> };        # OUTPUT: «「abc」␤» 
say 'abcdefg' ~~ rx{ abc <?[ d..f ]> };          # OUTPUT: «「abc」␤» 
my @ending_letters = <d e f>;
say 'abcdefg' ~~ rx{ abc <?@ending_letters> };   # OUTPUT: «「abc」␤»

元字符也可以用在先行或后行断言中。

say "First. Second" ~~ m:g/ <?after ^^ | "." \s+> <:Lu>\S+ /
# OUTPUT: «(「First.」 「Second」)␤»

先行断言的一个实际用途是在替换中,您只想替换特定上下文中的正则表达式匹配项。例如,您可能只想替换紧跟单位(如kg)的数字,而不是其他数字

my @units = <kg m km mm s h>;
$_ = "Please buy 2 packs of sugar, 1 kg each";
s:g[\d+ <?before \s* @units>] = 5 * $/;
say $_;         # OUTPUT: «Please buy 2 packs of sugar, 5 kg each␤»

由于先行断言不是匹配对象的一部分,因此不会替换单位。

后行断言§

要检查模式是否出现在另一个模式之后,请使用 after 断言的后行断言。它具有以下形式

<?after pattern>

因此,要搜索紧接字符串 foo 的字符串 bar,请使用以下正则表达式

/ <?after foo> bar /

例如

say "foobar" ~~ / <?after foo> bar /;   # OUTPUT: «bar␤»

但是,如果您要搜索紧接某些模式的模式,则需要使用负向后行断言,它具有以下形式

<!after pattern>

因此,所有在 foo 之前没有 barbar 都会被匹配

say "fotbar" ~~ / <!after foo> bar /;    # OUTPUT: «bar␤»

这些与先行断言一样,是零宽度断言,它们不消耗字符,例如这里

say "atfoobar" ~~ / (.**3) .**<?after foo> bar /;
# OUTPUT: «「atfoobar」␤ 0 => 「atf」␤»

我们捕获了 bar 之前的 5 个字符中的前 3 个,但前提是 bar 之前是 foo。断言是零宽度的这一事实使我们能够使用断言中的一部分字符进行捕获。

分组和捕获§

在常规(非正则表达式)Raku 中,您可以使用圆括号将事物分组在一起,通常是为了覆盖运算符优先级

say 1 + 4 * 2;     # OUTPUT: «9␤», parsed as 1 + (4 * 2) 
say (1 + 4* 2;   # OUTPUT: «10␤»

正则表达式中也提供了相同的分组功能

/ a || b c /;      # matches 'a' or 'bc' 
/ ( a || b ) c /;  # matches 'ac' or 'bc'

相同的分组适用于量词

/ a b+ /;          # matches an 'a' followed by one or more 'b's 
/ (a b)+ /;        # matches one or more sequences of 'ab' 
/ (a || b)+ /;     # matches a string of 'a's and 'b's, except empty string

未量化的捕获会生成一个 Match 对象。当捕获被量化(除了使用 ? 量词)时,捕获会变成一个 Match 对象列表。

捕获§

圆括号不仅分组,而且还捕获;也就是说,它们使组内匹配的字符串作为变量可用,也作为结果 Match 对象的元素可用

my $str =  'number 42';
if $str ~~ /'number ' (\d+/ {
    say "The number is $0";         # OUTPUT: «The number is 42␤» 
    # or 
    say "The number is $/[0]";      # OUTPUT: «The number is 42␤» 
}

圆括号对从零开始,从左到右编号。

if 'abc' ~~ /(a) b (c)/ {
    say "0: $0; 1: $1";             # OUTPUT: «0: a; 1: c␤» 
}

$0$1 等语法是简写。这些捕获可以通过将匹配对象 $/ 用作列表从匹配对象 $/ 中规范地获得,因此 $0 实际上是 $/[0] 的语法糖。

将匹配对象强制转换为列表提供了一种以编程方式访问所有元素的简单方法

if 'abc' ~~ /(a) b (c)/ {
    say $/.list.join: ''  # OUTPUT: «a, c␤» 
}

非捕获分组§

正则表达式中的圆括号扮演着双重角色:它们将正则表达式元素分组,并捕获子正则表达式匹配的内容。

要仅获得分组行为,可以使用方括号 [ ... ],默认情况下它们不会捕获。

if 'abc' ~~ / [a||b] (c) / {
    say ~$0;                # OUTPUT: «c␤» 
}

如果您不需要捕获,使用非捕获 [ ... ] 分组可以提供以下好处

  • 它们更清晰地传达了正则表达式的意图,

  • 它们使计算匹配的捕获组变得更容易,并且

  • 它们使匹配速度更快。

捕获数字§

上面已经说过,捕获从左到右编号。虽然原则上是正确的,但这也是一种过度简化。

以下规则列出是为了完整性。当您发现自己经常使用它们时,值得考虑使用命名捕获(以及可能的子规则)来代替。

交替重置捕获计数

/ (x) (y)  || (a) (.) (./
# $0  $1      $0  $1  $2

示例

if 'abc' ~~ /(x)(y) || (a)(.)(.)/ {
    say ~$1;        # OUTPUT: «b␤» 
}

如果两个(或更多)交替具有不同数量的捕获,则具有最多捕获的交替将决定下一个捕获的索引

if 'abcd' ~~ / a [ b (.|| (x) (y) ] (./ {
    #                 $0     $0  $1    $2 
    say ~$2;            # OUTPUT: «d␤» 
}

捕获可以嵌套,在这种情况下,它们按级别编号;级别 0 可以使用捕获变量,但它将成为一个列表,其余级别将作为该列表的元素。

if 'abc' ~~ / ( a (.) (.) ) / {
    say "Outer: $0";                # OUTPUT: «Outer: abc␤» 
    say "Inner: $0[0] and $0[1]";   # OUTPUT: «Inner: b and c␤» 
}

这些捕获变量仅在正则表达式外部可用。

# !!WRONG!! The $0 refers to a capture *inside* the second capture 
say "11" ~~ /(\d) ($0)/# OUTPUT: «Nil␤» 

为了使它们在正则表达式内部可用,您需要在匹配之后插入一个代码块;如果没有什么有意义的事情要做,这个代码块可以为空。

# CORRECT: $0 is saved into a variable outside the second capture 
# before it is used inside 
say "11" ~~ /(\d{} :my $c = $0; ($c)/# OUTPUT: «「11」␤ 0 => 「1」␤ 1 => 「1」␤» 
say "Matched $c"# OUTPUT: «␤Matched 1␤» 

此代码块发布正则表达式内部的捕获,以便它可以分配给其他变量或用于后续匹配。

say "11" ~~ /(\d{} $0/# OUTPUT: «「11」␤ 0 => 「1」␤» 

:my 有助于在正则表达式内部和外部限定 $c 变量的范围;在这种情况下,我们可以在下一句话中使用它来显示在正则表达式内部匹配的内容。这可以用于调试正则表达式内部,例如

my $paragraph="line\nline2\nline3";
$paragraph ~~ rx| :my $counter = 0; ( \V* { ++$counter } ) *%% \n |;
say "Matched $counter lines"# OUTPUT: «Matched 3 lines␤»

由于 :my 块只是声明,因此匹配变量 $/ 或编号匹配(如 $0)将无法在其中使用,除非它们之前通过插入空块(或任何块)发布

"aba" ~~ / (a) b {} :my $c = $//;
say $c# OUTPUT: «「ab」␤ 0 => 「a」␤» 

任何其他代码块也将显示变量并使它们在声明中可用。

"aba" ~~ / (a) {say "Check so far "~$/} b :my $c = ~$0/;
# OUTPUT: «Check so far a␤» 
say "Capture $c"# OUTPUT: «Capture a␤» 

:our 与类中的 our 类似,可以在 Grammar 中使用,以声明可以通过其完全限定名称从语法外部访问的变量。

grammar HasOur {
    token TOP {
        :our $our = 'Þor';
        $our \s+ is \s+ mighty
    }
}
 
say HasOur.parse('Þor is mighty'); # OUTPUT: «「Þor is mighty」␤» 
say $HasOur::our;                  # OUTPUT: «Þor␤» 

解析成功后,我们使用 $our 变量的 FQN 名称来访问其值,该值只能是 Þor

命名捕获§

除了对捕获进行编号外,您还可以为它们命名。命名捕获的通用方法(略显冗长)如下所示

if 'abc' ~~ / $<myname> = [ \w+ ] / {
    say ~$<myname>      # OUTPUT: «abc␤» 
}

上面的示例中的方括号通常不会捕获,但现在将使用给定的名称捕获其分组。

对命名捕获的访问 $<myname> 是对匹配对象作为哈希进行索引的简写,换句话说:$/{ 'myname' }$/<myname>

我们也可以在上面的示例中使用圆括号,但它们的工作方式与方括号完全相同。捕获的组只能通过其名称作为匹配对象的键访问,而不能通过其在列表中的位置使用 $/[0]$0 访问。

命名捕获也可以使用常规捕获组语法嵌套。

if 'abc-abc-abc' ~~ / $<string>=( [ $<part>=[abc] ]* % '-' ) / {
    say ~$<string>;          # OUTPUT: «abc-abc-abc␤» 
    say ~$<string><part>;    # OUTPUT: «abc abc abc␤» 
    say ~$<string><part>[0]; # OUTPUT: «abc␤» 
}

将匹配对象强制转换为哈希可以轻松地以编程方式访问所有命名捕获。

if 'count=23' ~~ / $<variable>=\w+ '=' $<value>=\w+ / {
    my %h = $/.hash;
    say %h.keys.sort.join: '';        # OUTPUT: «value, variable␤» 
    say %h.values.sort.join: '';      # OUTPUT: «23, count␤» 
    for %h.kv -> $k$v {
        say "Found value '$v' with key '$k'";
        # outputs two lines: 
        #   Found value 'count' with key 'variable' 
        #   Found value '23' with key 'value' 
    }
}

获取命名捕获的更便捷方法是使用命名正则表达式,如 子规则 部分所述。

捕获标记:<( )>§

<( 符号表示匹配整体捕获的开始,而相应的 )> 符号表示其结束。<( 类似于其他语言中的 \K,用于丢弃在 \K 之前找到的任何匹配项。

say 'abc' ~~ / a <( b )> c/;            # OUTPUT: «「b」␤» 
say 'abc' ~~ / <(<( b )> c)>/;        # OUTPUT: «「bc」␤»

如上例所示,您可以看到 <( 设置起点,)> 设置终点;由于它们实际上是相互独立的,因此最内层的起点获胜(与 b 相连的起点),最外层的终点获胜(与 c 相连的终点)。

替换§

正则表达式还可以用于将一段文本替换为另一段文本。您可以将它用于任何目的,从更正拼写错误(例如,将 'Perl Jam' 替换为 'Pearl Jam')到重新格式化 ISO8601 日期,从 yyyy-mm-ddThh:mm:ssZmm-dd-yy h:m {AM,PM} 等等。

就像搜索和替换编辑器的对话框一样,s/ / / 运算符有两侧,左侧和右侧。左侧是您的匹配表达式所在的位置,右侧是您要替换它的内容。

词法约定§

替换的写法类似于匹配,但替换运算符既有正则表达式匹配的区域,也有要替换的文本。

s/replace/with/;           # a substitution that is applied to $_ 
$str ~~ s/replace/with/;   # a substitution applied to a scalar 

替换运算符允许使用除斜杠之外的其他分隔符。

s|replace|with|;
s!replace!with!;
s,replace,with,;

请注意,冒号 : 或平衡分隔符(如 {}())不能用作替换分隔符。冒号与副词冲突,例如 s:i/Foo/bar/,而其他分隔符用于其他目的。

如果您使用平衡的括号、方括号或圆括号,则替换将按以下方式工作。

s[replace] = 'with';

右侧现在是一个(未引用的)Raku 表达式,其中 $/ 可用作当前匹配项。

$_ = 'some 11 words 21';
s:g[ \d+ ] =  2 * $/;
.say;                    # OUTPUT: «some 22 words 42␤»

m// 运算符一样,替换部分中的正则表达式部分会忽略空格。

替换字符串字面量§

最简单的替换是字符串字面量。您要替换的字符串位于替换运算符的左侧,您要替换它的字符串位于右侧;例如。

$_ = 'The Replacements';
s/Replace/Entrap/;
.say;                    # OUTPUT: «The Entrapments␤»

字母数字字符和下划线是字面匹配,就像它的同类 m// 运算符一样。所有其他字符必须使用反斜杠 \ 转义或包含在引号中。

$_ = 'Space: 1999';
s/Space\:/Party like it's/;
.say                        # OUTPUT: «Party like it's 1999␤»

请注意,匹配限制通常只适用于替换表达式的左侧,但某些特殊字符或它们的组合可能需要在右侧(RHS)中转义。例如。

$_ = 'foo';
s/foo/\%(/;
.say        # OUTPUT: «%(␤»

或者转义 ( 以获得相同的结果。

s/foo/%\(/;
.say        # OUTPUT: «%(␤»

但单独使用任一字符都不需要转义。正斜杠需要转义,但转义字母数字字符会导致它们被忽略。(注意:此 RHS 限制是最近才发现的,这还不是所有需要转义 RHS 的字符或字符对的详尽列表。)

默认情况下,替换只对第一个匹配项进行。

$_ = 'There can be twly two';
s/tw/on/;                     # replace 'tw' with 'on' once 
.say;                         # OUTPUT: «There can be only two␤»

通配符和字符类§

任何可以进入 m// 运算符的内容都可以进入替换运算符的左侧,包括通配符和字符类。当您要匹配的文本不是静态的时,这非常方便,例如尝试匹配字符串中间的数字。

$_ = "Blake's 9";
s/\d+/7/;         # replace any sequence of digits with '7' 
.say;             # OUTPUT: «Blake's 7␤»

当然,您可以使用任何 +*? 修饰符,它们的行为与在 m// 运算符的上下文中一样。

捕获组§

就像在匹配运算符中一样,捕获组允许在左侧使用,匹配的内容填充 $0..$n 变量和 $/ 对象。

$_ = '2016-01-23 18:09:00';
s/ (\d+)\-(\d+)\-(\d+/today/;   # replace YYYY-MM-DD with 'today' 
.say;                             # OUTPUT: «today 18:09:00␤» 
"$1-$2-$0".say;                   # OUTPUT: «01-23-2016␤» 
"$/[1]-$/[2]-$/[0]".say;          # OUTPUT: «01-23-2016␤»

这些变量中的任何一个,$0$1$/,都可以在运算符的右侧使用,这样你就可以操作你刚刚匹配的内容。这样你就可以将日期的 YYYYMMDD 部分分离出来,并将其重新格式化为 MM-DD-YYYY 的顺序。

$_ = '2016-01-23 18:09:00';
s/ (\d+)\-(\d+)\-(\d+/$1-$2-$0/;    # transform YYYY-MM-DD to MM-DD-YYYY 
.say;                                 # OUTPUT: «01-23-2016 18:09:00␤»

也可以使用命名捕获。

$_ = '2016-01-23 18:09:00';
s/ $<y>=(\d+)\-$<m>=(\d+)\-$<d>=(\d+/$<m>-$<d>-$<y>/;
.say;                                 # OUTPUT: «01-23-2016 18:09:00␤»

由于右侧实际上是一个 Raku 插值字符串,你可以将时间从 HH:MM 重新格式化为 h:MM {AM,PM},如下所示。

$_ = '18:38';
s/(\d+)\:(\d+)/{$0 % 12}\:$1 {$0 < 12 ?? 'AM' !! 'PM'}/;
.say;                                 # OUTPUT: «6:38 PM␤»

使用上面的模运算符 % 可以使示例代码保持在 80 个字符以内,但与 $0 < 12 ?? $0 !! $0 - 12 相同。当与解析器表达式语法的强大功能结合使用时,这种语法 **真正** 地体现了你在这里看到的,你可以使用“正则表达式”来解析几乎所有文本。

常用副词§

可以在正则表达式中应用的副词的完整列表可以在本文档的其他地方找到 (副词部分),但最常见的可能是 :g:i

  • 全局副词 :g

通常,在一个给定的字符串中只进行一次匹配,但添加 :g 修饰符会覆盖这种行为,以便在所有可能的地方进行替换。替换是非递归的;例如

$_ = q{I can say "banana" but I don't know when to stop};
s:g/na/nana,/;    # substitute 'nana,' for 'na' 
.say;             # OUTPUT: «I can say "banana,nana," but I don't ...␤»

这里,na 在原始字符串中被找到了两次,并且每次都进行了替换。替换只应用于原始字符串。结果字符串不受影响。

  • 不区分大小写副词 :i

通常,匹配区分大小写。s/foo/bar/ 仅匹配 'foo',而不匹配 'Foo'。但是,如果使用副词 :i,则匹配将不区分大小写。

$_ = 'Fruit';
s/fruit/vegetable/;
.say;                          # OUTPUT: «Fruit␤» 
 
s:i/fruit/vegetable/;
.say;                          # OUTPUT: «vegetable␤»

有关这些副词实际执行的操作的更多信息,请参阅本文档的 副词部分

这些只是你可以使用替换运算符应用的几种转换。现实世界中一些更简单的用法包括从日志文件中删除个人数据、将 MySQL 时间戳编辑为 PostgreSQL 格式、更改 HTML 文件中的版权信息以及清理 Web 应用程序中的表单字段。

顺便说一下,正则表达式新手经常会感到不知所措,并认为他们的正则表达式需要匹配行中的所有数据,包括他们想要匹配的数据。只需编写足够匹配你正在查找的数据的代码,不多不少。

S/// 非破坏性替换§

say S/.+ d/new/ with 'old string';      # OUTPUT: «new string␤» 
S:g/« (.)/$0.uc()/.say for <foo bar ber># OUTPUT: «Foo␤Bar␤Ber␤»

S/// 使用与 s/// 运算符相同的语义,只是它保留原始字符串不变,并返回结果字符串,而不是 $/$/ 仍然设置为与 s/// 相同的值)。

注意:由于结果是作为返回值获得的,因此将此运算符与 ~~ 智能匹配运算符一起使用是错误的,并且会发出警告。要对不是 $_ 的变量执行替换,请使用 givenwith 或任何其他方式将其别名为 $_。或者,使用 .subst 方法

波浪号用于嵌套结构§

~ 运算符是用于匹配具有特定终止符的嵌套子规则的助手。它被设计为放置在一对开始和结束分隔符之间,如下所示

/ '(' ~ ')' <expression> /

但是,它主要忽略左参数,并在接下来的两个原子(可以被量化)上操作。它对这两个原子的操作是“扭转”它们,以便它们实际上以相反的顺序匹配。因此,上面的表达式,乍一看,只是另一种写法

/ '(' <expression> ')' /

使用 ~ 可以使分隔符更靠近,但除此之外,当它重写原子时,它还会插入用于设置内部表达式以识别终止符的装置,并在内部表达式未在所需的结束原子处终止时生成适当的错误消息。因此,它确实也关注左分隔符,并且实际上将我们的示例重写为更类似于

$<OPEN> = '(' <SETGOAL: ')'> <expression> [ $GOAL || <FAILGOAL> ]

FAILGOAL 是一种特殊方法,可以由用户定义,并在解析失败时调用

grammar A { token TOP { '[' ~ ']' \w+  };
            method FAILGOAL($goal{
                die "Cannot find $goal near position {self.pos}"
            }
}
 
say A.parse: '[good]';  # OUTPUT: «「[good]」␤» 
A.parse: '[bad';        # will throw FAILGOAL exception 
CATCH { default { put .^name''.Str } };
# OUTPUT: «X::AdHoc: Cannot find ']'  near position 4␤»

请注意,即使没有开始分隔符,也可以使用此结构来设置对结束结构的期望

"3)"  ~~ / <?> ~ ')' \d+ /;  # OUTPUT: «「3)」» 
"(3)" ~~ / <?> ~ ')' \d+ /;  # OUTPUT: «「3)」»

这里 <?> 成功匹配空字符串。

正则表达式捕获的顺序是原始的

"abc" ~~ /a ~ (c) (b)/;
say $0# OUTPUT: «「c」␤» 
say $1# OUTPUT: «「b」␤»

递归正则表达式§

您可以使用 <~~> 在正则表达式中递归调用当前正则表达式。这对于匹配嵌套数据结构非常有用。例如,考虑以下正则表达式

/ '(' <-[()]>* ')' || '('<-[()]>* <~~> <-[()]>* ]* ')' /

这表示“匹配**要么**一个左括号,后跟零个或多个非括号字符,后跟一个右括号,**要么**一个左括号,后跟零个或多个非括号字符,后跟另一个此正则表达式的匹配,后跟零个或多个非括号字符,后跟一个右括号”。此正则表达式允许您匹配任意多个嵌套括号,如下所示

my $paren = rx/ '(' <-[()]>* ')' || '('<-[()]>* <~~> <-[()]>* ]* ')' /;
say 'text' ~~ $paren;                            # OUTPUT: «Nil␤» 
say '(1 + 1) = 2' ~~ $paren;                     # OUTPUT: «「(1 + 1)」␤» 
say '(1 + (2 × 3)) = 7' ~~ $paren;               # OUTPUT: «「(1 + (2 × 3))」␤» 
say '((5 + 2) × 6) = 42 (the answer)' ~~ $paren  # OUTPUT: «「((5 + 2) × 6)」␤»

请注意,上面显示的最后一个表达式不会匹配到最后的 ),就像使用 /'('.*')'/ 时一样,也不会只匹配到第一个 )。相反,它正确地匹配到与第一个左括号配对的右括号,这种效果在没有递归正则表达式的情况下很难复制。

在使用递归正则表达式(与任何其他递归数据结构一样)时,您应该注意避免无限递归,这会导致您的程序挂起或崩溃。

子规则§

就像您可以将代码片段放入子例程一样,您也可以将正则表达式片段放入命名规则中。

my regex line { \N*\n }
if "abc\ndef" ~~ /<line> def/ {
    say "First line: "$<line>.chomp;      # OUTPUT: «First line: abc␤» 
}

命名正则表达式可以使用 my regex named-regex { body here } 声明,并使用 <named-regex> 调用。同时,调用命名正则表达式会安装一个具有相同名称的命名捕获。

要为捕获指定与正则表达式不同的名称,请使用语法 <capture-name=named-regex>。如果不需要捕获,则可以使用前导点或与号来抑制它:<.named-regex>(如果它是同一个类或语法中声明的方法),<&named-regex>(对于同一个词法上下文中声明的正则表达式)。

以下是解析 ini 文件的更完整代码

my regex header { \s* '[' (\w+']' \h* \n+ }
my regex identifier  { \w+ }
my regex kvpair { \s* <key=identifier> '=' <value=identifier> \n+ }
my regex section {
    <header>
    <kvpair>*
}
 
my $contents = q:to/EOI/; 
    [passwords]
        jack=password1
        joy=muchmoresecure123
    [quotas]
        jack=123
        joy=42
EOI
 
my %config;
if $contents ~~ /<section>*/ {
    for $<section>.list -> $section {
        my %section;
        for $section<kvpair>.list -> $p {
            %section{ $p<key> } = ~$p<value>;
        }
        %config{ $section<header>[0} = %section;
    }
}
say %config.raku;
 
# OUTPUT: «{:passwords(${:jack("password1"), :joy("muchmoresecure123")}), 
#           :quotas(${:jack("123"), :joy("42")})}»

命名正则表达式可以并且应该在语法中分组。预定义子规则的列表在此

正则表达式插值§

您可以使用保存该模式的变量,而不是使用文字模式进行正则表达式匹配。然后,可以将此变量“插值”到正则表达式中,以便它在正则表达式中的出现被它保存的模式替换。使用这种插值方式的优势在于,模式不必在 Raku 程序的源代码中硬编码,而是可以是可变的,并在运行时生成。

有四种不同的方法可以将变量作为模式插值到正则表达式中,可以概括如下

语法描述
$variable按字面意义插值变量的字符串化内容。
$(code)在正则表达式中运行 Raku 代码,并按字面意义插值字符串化的返回值。
<$variable>将变量的字符串化内容作为正则表达式插值。
<{code}>在正则表达式中运行 Raku 代码,并将字符串化的返回值作为正则表达式插值。

您可以使用 @ 符号而不是 $ 符号进行数组插值。请参阅以下内容了解其工作原理。

让我们从前两种语法形式开始:$variable$(code)。这些形式将按字面意义插值变量的字符串化值或代码的字符串化返回值,前提是相应的值不是 Regex 对象。如果该值是 Regex,则不会对其进行字符串化,而是将其作为正则表达式插值。“按字面意义”表示严格按字面意义,即:就像相应字符串化值是用基本 Q 字符串 Q[...] 引用一样。因此,字符串化值本身不会进行任何进一步的插值。

对于$variable,这意味着以下内容

my $string   = 'Is this a regex or a string: 123\w+False$pattern1 ?';
my $pattern1 = 'string';
my $pattern2 = '\w+';
my $number   = 123;
my $regex    = /\w+/;
 
say $string.match: / 'string' /;                      #  [1] OUTPUT: «「string」␤» 
say $string.match: / $pattern1 /;                     #  [2] OUTPUT: «「string」␤» 
say $string.match: / $pattern2 /;                     #  [3] OUTPUT: «「\w+」␤» 
say $string.match: / $regex /;                        #  [4] OUTPUT: «「Is」␤» 
say $string.match: / $number /;                       #  [5] OUTPUT: «「123」␤»

在这个例子中,语句[1][2]是等价的,旨在说明正则表达式插值的简单情况。由于正则表达式中未转义/未引用的字母字符按字面意义匹配,因此语句[1]中的单引号在功能上是多余的;它们只是为了强调前两个语句之间的对应关系而包含在内。语句[3]明确地表明,$pattern2持有的字符串模式按字面意义解释,而不是作为正则表达式。如果它被解释为正则表达式,它将匹配$string的第一个词,即「Is」,如语句[4]所示。语句[5]展示了如何使用字符串化的数字作为匹配模式。

这段代码示例了$(code)语法的使用

my $string   = 'Is this a regex or a string: 123\w+False$pattern1 ?';
my $pattern1 = 'string';
my $pattern3 = 'gnirts';
my $pattern4 = '$pattern1';
my $bool     = True;
my sub f1    { return Q[$pattern1] };
 
say $string.match: / $pattern3.flip /;                #  [6] OUTPUT: «Nil␤» 
say $string.match: / "$pattern3.flip()" /;            #  [7] OUTPUT: «「string」␤» 
say $string.match: / $($pattern3.flip) /;             #  [8] OUTPUT: «「string」␤» 
say $string.match: / $([~] $pattern3.comb.reverse) /#  [9] OUTPUT: «「string」␤» 
say $string.match: / $(!$bool/;                     # [10] OUTPUT: «「False」␤» 
 
say $string.match: / $pattern4 /;                     # [11] OUTPUT: «「$pattern1」␤» 
say $string.match: / $(f1) /;                         # [12] OUTPUT: «「$pattern1」␤»

语句[6]不能按预期工作。对于人类读者来说,点.似乎代表了方法调用运算符,但由于点不是有效的普通标识符字符,并且考虑到正则表达式上下文,编译器将把它解析为匹配任何字符的正则表达式通配符.。这种明显的歧义可以通过多种方式解决,例如通过使用正则表达式中的简单字符串插值,如语句[7]所示(注意,这里包含调用运算符()是关键),或者通过使用上述表格中的第二种语法形式,如语句[8]所示,在这种情况下,匹配模式string首先作为flip方法调用的返回值出现。由于一般的 Raku 代码可以在$( )的括号内运行,因此也可以通过更多努力来实现相同的效果,例如在语句[9]中。语句[10]说明了如何按字面意义匹配代码返回值(布尔值False)的字符串化版本。

最后,语句[11][12]展示了$pattern4的值和f1的返回值不会进行进一步的插值。因此,一般来说,在可能的字符串化之后,$variable$(code)提供了对变量或返回值的严格字面匹配。

现在考虑上述表格中的后两种语法形式:<$variable><{code}>。这些形式将对变量的值或代码的返回值进行字符串化,并将其作为正则表达式进行插值。如果相应的值是Regex,则将其作为正则表达式进行插值

my $string         = 'Is this a regex or a string: 123\w+$x ?';
my $pattern1       = '\w+';
my $number         = 123;
my sub f1          { return /s\w+/ };
 
say $string.match: / <$pattern1>  /;                  # OUTPUT: «「Is」␤» 
say $string.match: / <$number>    /;                  # OUTPUT: «「123」␤» 
say $string.match: / <f1 }>     /;                  # OUTPUT: «「string」␤»

重要的是,“作为正则表达式进行插值”意味着在目标正则表达式中进行插值/插入,而无需保护性引用。因此,如果变量$variable1的值本身是$variable2的形式,则在目标正则表达式/.../中对<$variable1><{ $variable1 }>进行评估将导致目标正则表达式采用/$variable2/的形式。如上所述,此正则表达式的评估将随后触发对$variable2的进一步插值

my $string    = Q[Mindless \w+ $variable1 $variable2];
my $variable1 = Q[\w+];
my $variable2 = Q[$variable1];
my sub f1     { return Q[$variable2] };
 
# /<{ f1 }>/ ==> /$variable2/ ==> / '$variable1' / 
say $string.match: / <f1 }>     /# OUTPUT: «「$variable1」␤» 
 
# /<$variable2>/ ==> /$variable1/ ==> / '\w+' / 
say $string.match: /<$variable2>/;   # OUTPUT: «「\w+」␤» 
 
# /<$variable1>/ ==> /\w+/ 
say $string.match: /<$variable1>/;   # OUTPUT: «「Mindless」␤»

当数组变量被插值到正则表达式中时,正则表达式引擎会将其视为正则表达式元素的|替代(参见关于嵌入列表的文档,上面)。单个元素的插值规则与标量相同,因此字符串和数字按字面意义匹配,而Regex对象则作为正则表达式匹配。与普通的|插值一样,最长的匹配成功

my @a = '2'23, rx/a.+/;
say ('b235' ~~ /  b @a /).Str;      # OUTPUT: «b23␤»

如果您有一个表达式计算结果为列表,但您不想先将其分配给一个以@为前缀的变量,您可以使用@(code)对其进行插值。在这个例子中,两个正则表达式是等价的

my %h = => 1=> 2;
my @a = %h.keys;
say S:g/@(%h.keys)/%h{$/}/ given 'abc';    # OUTPUT: «12c> 
say S:g/@a/%h{$/}/ given 'abc';            # OUTPUT: «12c>

在正则表达式中使用哈希是保留的。

正则表达式布尔条件检查§

特殊运算符<?{}>允许评估布尔表达式,该表达式可以在正则表达式继续之前对匹配进行语义评估。换句话说,可以在布尔上下文中检查正则表达式的一部分,因此即使匹配从语法角度来看成功,也可以使整个匹配无效(或允许它继续)。

特别是,<?{}>运算符需要True值才能允许正则表达式匹配,而其否定形式<!{}>需要False值。

为了演示上述运算符,请考虑以下示例,它涉及简单的 IPv4 地址匹配

my $localhost = '127.0.0.1';
my regex ipv4-octet { \d ** 1..<?{ True }> }
$localhost ~~ / ^ <ipv4-octet> ** 4 % "." $ /;
say $/<ipv4-octet>;   # OUTPUT: «[「127」 「0」 「0」 「1」]␤» 

octet 正则表达式匹配由一个到三个数字组成的数字。每个匹配都由 <?{}> 的结果驱动,固定值为 True 表示正则表达式匹配必须始终被视为有效。作为反例,使用特殊常量值 False 将使匹配无效,即使正则表达式从语法角度来看是匹配的。

my $localhost = '127.0.0.1';
my regex ipv4-octet { \d ** 1..<?{ False }> }
$localhost ~~ / ^ <ipv4-octet> ** 4 % "." $ /;
say $/<ipv4-octet>;   # OUTPUT: «Nil␤» 

从上面的例子可以看出,可以改进语义检查,例如确保每个八位字节都是有效的 IPv4 八位字节。

my $localhost = '127.0.0.1';
my regex ipv4-octet { \d ** 1..<?{ 0 <= $/.Int <= 255 }> }
$localhost ~~ / ^ <ipv4-octet> ** 4 % "." $ /;
say $/<ipv4-octet>;   # OUTPUT: «[「127」 「0」 「0」 「1」]␤» 

请注意,不需要在线评估正则表达式,也可以调用正则方法来获取布尔值。

my $localhost = '127.0.0.1';
sub check-octet ( Int $o ){ 0 <= $o <= 255 }
my regex ipv4-octet { \d ** 1..<?{ &check-octet$/.Int ) }> }
$localhost ~~ / ^ <ipv4-octet> ** 4 % "." $ /;
say $/<ipv4-octet>;   # OUTPUT: «[「127」 「0」 「0」 「1」]␤» 

当然,<!{}><?{}> 的否定形式,相同的布尔值评估可以以否定形式重写。

my $localhost = '127.0.0.1';
sub invalid-octetInt $o ){ $o < 0 || $o > 255 }
my regex ipv4-octet { \d ** 1..<!{ &invalid-octet$/.Int ) }> }
$localhost ~~ / ^ <ipv4-octet> ** 4 % "." $ /;
say $/<ipv4-octet>;   # OUTPUT: «[「127」 「0」 「0」 「1」]␤» 

副词§

副词修改正则表达式的运作方式,并为某些类型的重复任务提供便捷的快捷方式,它们是由一个或多个字母组成的,前面带有冒号 :

所谓的正则表达式副词在定义正则表达式的位置应用;此外,匹配副词在正则表达式与字符串匹配的位置应用,而替换副词仅在替换中应用。

这种区别通常会变得模糊,因为匹配和声明通常在文本上很接近,但使用匹配的方法形式,即 .match,可以使区别变得清晰。

say "Abra abra CADABRA" ~~ m:exhaustive/:i a \w+ a/;
# OUTPUT: «(「Abra」 「abra」 「ADABRA」 「ADA」 「ABRA」)␤» 
my $regex = /:i a \w+ a /;
say "Abra abra CADABRA".match($regex,:ex);
# OUTPUT: «(「Abra」 「abra」 「ADABRA」 「ADA」 「ABRA」)␤»

在第一个例子中,匹配副词 (:exhaustive) 与正则表达式副词 (:i) 相邻,事实上,“定义”和“匹配”是同时进行的;但是,通过使用 match,可以清楚地看到 :i 仅在定义 $regex 变量时使用,而 :ex:exhaustive 的缩写)作为匹配时的参数使用。事实上,匹配副词甚至不能在正则表达式的定义中使用。

my $regex = rx:ex/:i a \w+ a /;
# ===SORRY!=== Error while compiling (...)␤Adverb ex not allowed on rx 

:i 这样的正则表达式副词进入定义行,而像 :overlap(可以缩写为 :ov)这样的匹配副词附加到匹配调用。

my $regex = /:i . a/;
for 'baA'.match($regex:overlap-> $m {
    say ~$m;
}
# OUTPUT: «ba␤aA␤»

正则表达式副词§

在正则表达式声明时出现的副词是实际正则表达式的一部分,并影响 Raku 编译器如何将正则表达式转换为二进制代码。

例如,:ignorecase (:i) 副词告诉编译器忽略大小写字母之间的区别。

所以 'a' ~~ /A/ 是 false,但 'a' ~~ /:i A/ 是一个成功的匹配。

正则表达式副词可以出现在正则表达式声明之前或内部,并且只影响正则表达式之后的部分,从词法上讲。请注意,出现在正则表达式之前的正则表达式副词必须出现在引入正则表达式到解析器的内容之后,例如 'rx' 或 'm' 或一个裸的 '/'。这不是有效的

my $rx1 = :i/a/;      # adverb is before the regex is recognized => exception 

但这些是有效的

my $rx1 = rx:i/a/;     # before 
my $rx2 = m:i/a/;      # before 
my $rx3 = /:i a/;      # inside

这两个正则表达式是等效的

my $rx1 = rx:i/a/;      # before 
my $rx2 = rx/:i a/;     # inside

而这两个不是

my $rx3 = rx/:i b/;   # matches only the b case insensitively 
my $rx4 = rx/:i a b/;   # matches completely case insensitively

方括号和圆括号限制了副词的作用域。

/ (:i a b) c /;         # matches 'ABc' but not 'ABC' 
/ [:i a b] c /;         # matches 'ABc' but not 'ABC'

当两个副词一起使用时,它们在前面保留冒号。

"þor is Þor" ~~ m:g:i/þ/;  # OUTPUT: «(「þ」 「Þ」)␤»

这意味着当 : 之后有多个字符在一起时,它们对应于同一个副词,如 :ov:P5

忽略大小写§

:ignorecase:i 副词指示正则表达式引擎忽略大小写字母之间的区别。

有关示例,请参见 正则表达式副词部分

忽略标记§

:ignoremark:m 副词指示正则表达式引擎只比较基本字符,并忽略额外的标记,例如组合重音。

say so 'a' ~~ rx/ä/;                # OUTPUT: «False␤» 
say so 'a' ~~ rx:ignoremark /ä/;    # OUTPUT: «True␤» 
say so '' ~~ rx:ignoremark /o/;    # OUTPUT: «True␤» 

棘轮§

:ratchet:r 副词会导致正则表达式引擎不回溯(参见 回溯)。助记符:棘轮 只能朝一个方向移动,不能回溯。

没有这个副词,正则表达式的部分将尝试不同的方式匹配字符串,以便让正则表达式的其他部分能够匹配。例如,在 'abc' ~~ /\w+ ./ 中,\w+ 首先吞噬整个字符串 abc,但随后 . 失败。因此 \w+ 放弃一个字符,只匹配 ab,而 . 可以成功匹配字符串 c。这种放弃字符(或在交替的情况下,尝试不同的分支)的过程称为回溯。

say so 'abc' ~~ / \w+ . /;        # OUTPUT: «True␤» 
say so 'abc' ~~ / :r \w+ . /;     # OUTPUT: «False␤»

棘轮可以是一种优化,因为回溯成本很高。但更重要的是,它与人类解析文本的方式密切相关。如果你有一个正则表达式 my regex identifier { \w+ }my regex keyword { if | else | endif },你直观地期望 identifier 吞噬整个单词,而不是放弃其结尾给下一个规则,如果下一个规则否则失败。

例如,你不希望单词 motif 被解析为标识符 mot 后跟关键字 if。相反,你希望 motif 被解析为一个标识符;如果解析器随后期望一个 if,最好是它应该失败,而不是以你意想不到的方式解析输入。

由于棘轮行为在解析器中通常是可取的,因此有一个声明棘轮正则表达式的快捷方式

my token thing { ... };
# short for 
my regex thing { :r ... };

Sigspace§

:sigspace:s 副词会改变正则表达式中未加引号的空格的行为。

没有 :sigspace,正则表达式中的未加引号的空格通常会被忽略,以使正则表达式对程序员更易读。当 :sigspace 存在时,未加引号的空格可能会转换为 <.ws> 子规则调用,具体取决于它在正则表达式中的位置。

say so "I used Photoshop®"   ~~ m:i/   photo shop /;  # OUTPUT: «True␤» 
say so "I used a photo shop" ~~ m:i:s/ photo shop /;  # OUTPUT: «True␤» 
say so "I used Photoshop®"   ~~ m:i:s/ photo shop /;  # OUTPUT: «False␤» 

m:s/ photo shop / 的作用与 m/ photo C«<.ws>» shop <.ws> / 相同。默认情况下,<.ws> 确保单词之间有分隔符,因此 a b^& 将匹配中间的 <.ws>,但 ab 不会

say so "ab" ~~ m:s/<.ws> b/;     # OUTPUT: «False␤» 
say so "a b" ~~ m:s/<.ws> b/;    # OUTPUT: «True␤» 
say so "^&" ~~ m:s/'^' <.ws> '&'/# OUTPUT: «True␤» 

第三行匹配,因为 ^& 不是一个单词。有关 <.ws> 规则工作原理的更多说明,请参阅 WS 规则描述

正则表达式中的空格何时转换为 <.ws> 取决于空格之前的部分。在上面的示例中,正则表达式开头的空格不会转换为 <.ws>,但字符之后的空格会。一般来说,规则是,如果一个术语可能匹配某些内容,那么它之后的空格将转换为 <.ws>

此外,如果空格位于一个术语之后,但在量词(+*?)之前,<.ws> 将在术语的每次匹配之后被匹配。因此,foo + 变成 [ foo <.ws> ]+。另一方面,量词之后的空格充当正常的有效空格;例如,“foo+ ” 变成 foo+ <.ws>。另一方面,量词和 %%% 量词修饰符之间的空格不是有效的。因此,foo+ % , 不会变成 foo+ <.ws>% ,(无论如何这都是无效的);相反,这两个空格都不是有效的。

总而言之,这段代码

rx :s {
    ^^
    {
        say "No sigspace after this";
    }
    <.assertion_and_then_ws>
    characters_with_ws_after+
    ws_separated_characters *
    [
    | some "stuff" .. .
    | $$
    ]
    :my $foo = "no ws after this";
    $foo
}

变成

rx {
    ^^ <.ws>
    {
        say "No space after this";
    }
    <.assertion_and_then_ws> <.ws>
    characters_with_ws_after+ <.ws>
    [ws_separated_characters <.ws>]* <.ws>
    [
    | some <.ws> "stuff" <.ws> .. <.ws> . <.ws>
    | $$ <.ws>
    ] <.ws>
    :my $foo = "no ws after this";
    $foo <.ws>
}

如果正则表达式是用 rule 关键字声明的,那么 :sigspace:ratchet 副词都是隐含的。

语法提供了一种简单的方法来覆盖 <.ws> 的匹配内容

grammar Demo {
    token ws {
        <!ww>       # only match when not within a word 
        \h*         # only match horizontal whitespace 
    }
    rule TOP {      # called by Demo.parse; 
        a b '.'
    }
}
 
# doesn't parse, whitespace required between a and b 
say so Demo.parse("ab.");                 # OUTPUT: «False␤» 
say so Demo.parse("a b.");                # OUTPUT: «True␤» 
say so Demo.parse("a\tb .");              # OUTPUT: «True␤» 
 
# \n is vertical whitespace, so no match 
say so Demo.parse("a\tb\n.");             # OUTPUT: «False␤»

在解析某些空格(例如垂直空格)有效的文件格式时,建议覆盖 ws

Perl 兼容性副词§

:Perl5:P5 副词将正则表达式解析和匹配切换为 Perl 正则表达式的方式。

so 'hello world' ~~ m:Perl5/^hello (world)/;   # OUTPUT: «True␤» 
so 'hello world' ~~ m/^hello (world)/;         # OUTPUT: «False␤» 
so 'hello world' ~~ m/^ 'hello ' ('world')/;   # OUTPUT: «True␤»

当然,推荐使用常规行为,它在 Raku 中更具惯用性,但当需要与 Perl 兼容时,:Perl5 副词可能很有用。

匹配副词§

与绑定到正则表达式声明的正则表达式副词相反,匹配副词只有在将字符串与正则表达式匹配时才有意义。

它们永远不会出现在正则表达式内部,只能出现在外部——要么作为 m/.../ 匹配的一部分,要么作为匹配方法的参数。

位置副词§

位置副词使表达式仅匹配指定位置的字符串

my $data = "f fo foo fooo foooo fooooo foooooo";
say $data ~~ m:nth(4)/fo+/;   # OUTPUT: «「foooo」␤» 
say $data ~~ m:1st/fo+/;      # OUTPUT: «「fo」␤» 
say $data ~~ m:3rd/fo+/;      # OUTPUT: «「fooo」␤» 
say $data ~~ m:nth(1,3)/fo+/# OUTPUT: «(「fo」 「fooo」)␤»

如您所见,副词参数也可以是列表。实际上,:nth 副词与其他副词之间没有区别。您只需根据可读性选择它们。从 6.d 开始,您还可以使用 JunctionSeqRange,甚至是无限的,作为参数。

my $data = "f fo foo fooo foooo fooooo foooooo";
say $data ~~ m:st(1|8)/fo+/;  # OUTPUT: «True␤»

在这种情况下,其中一个存在(1),因此它返回 True。请注意,我们使用了 :st。如上所述,它在功能上等效,尽管显然不如使用 :nth 可读,因此建议使用最后一种形式。

计数§

:x 计数副词使表达式多次匹配,类似于 :g 副词,但仅限于副词表达式给定的限制,一旦达到指定的匹配次数就停止。该值必须是 NumericRange

my $data = "f fo foo fooo foooo fooooo foooooo";
$data ~~ s:x(8)/o/X/# f fX fXX fXXX fXXoo fooooo foooooo

继续§

:continue 或简写 :c 副词接受一个参数。该参数是正则表达式应开始搜索的位置。默认情况下,它从字符串的开头开始搜索,但 :c 会覆盖它。如果未为 :c 指定位置,则默认为 0,除非设置了 $/,在这种情况下,它默认为 $/.to

given 'a1xa2' {
    say ~m/a./;         # OUTPUT: «a1␤» 
    say ~m:c(2)/a./;    # OUTPUT: «a2␤» 
}

注意::pos 不同,使用 :continue() 的匹配将尝试在字符串中进一步匹配,而不是失败

say "abcdefg" ~~ m:c(3)/e.+/# OUTPUT: «「efg」␤» 
say "abcdefg" ~~ m:p(3)/e.+/# OUTPUT: «False␤»

详尽§

要查找正则表达式的所有可能匹配项——包括重叠的匹配项——以及从同一位置开始的多个匹配项,请使用 :exhaustive(简写 :ex)副词。

given 'abracadabra' {
    for m:exhaustive/ a .* a / -> $match {
        say ' ' x $match.from~$match;
    }
}

上面的代码产生以下输出

    abracadabra
    abracada
    abraca
    abra
       acadabra
       acada
       aca
         adabra
         ada
           abra

全局§

不要只搜索一个匹配项并返回 Match,而是搜索所有非重叠匹配项,并将它们返回到 List 中。为此,请使用 :global 副词

given 'several words here' {
    my @matches = m:global/\w+/;
    say @matches.elems;         # OUTPUT: «3␤» 
    say ~@matches[2];           # OUTPUT: «here␤» 
}

:g:global 的简写。

Pos§

将匹配项锚定在字符串中的特定位置

given 'abcdef' {
    my $match = m:pos(2)/.*/;
    say $match.from;        # OUTPUT: «2␤» 
    say ~$match;            # OUTPUT: «cdef␤» 
}

:p:pos 的简写。

注意::continue 不同,使用 :pos() 锚定的匹配项将失败,而不是尝试在字符串中进一步匹配

say "abcdefg" ~~ m:c(3)/e.+/# OUTPUT: «「efg」␤» 
say "abcdefg" ~~ m:p(3)/e.+/# OUTPUT: «False␤»

重叠§

要获取多个匹配项,包括重叠匹配项,但每个起始位置只有一个(最长的),请指定 :overlap(简写 :ov)副词

given 'abracadabra' {
    for m:overlap/ a .* a / -> $match {
        say ' ' x $match.from~$match;
    }
}

产生

    abracadabra
       acadabra
         adabra
           abra

替换副词§

您可以将匹配副词(例如 :global:pos 等)应用于替换。此外,还有一些副词只对替换有意义,因为它们将属性从匹配的字符串转移到替换字符串。

相同大小写§

:samecase:ii 替换副词意味着对替换部分的正则表达式使用 :ignorecase 副词,此外还将大小写信息传递到替换字符串

$_ = 'The cat chases the dog';
s:global:samecase[the] = 'a';
say $_;                 # OUTPUT: «A cat chases a dog␤» 

在这里您可以看到第一个替换字符串 a 被大写了,因为匹配字符串的第一个字符串也是大写字母。

相同标记§

:samemark:mm 副词暗示 regex 的 :ignoremark,此外,将匹配字符的标记复制到替换字符串中

given 'äộñ' {
    say S:mm/ a .+ /uia/;           # OUTPUT: «üị̂ã␤» 
}

Samespace§

:samespace:ss 替换修饰符暗示 regex 的 :sigspace 修饰符,此外,将匹配字符串中的空格复制到替换字符串中

say S:samespace/./c d/.raku given "a b";      # OUTPUT: «"c d"␤» 
say S:samespace/./c d/.raku given "a\tb";     # OUTPUT: «"c\td"␤» 
say S:samespace/./c d/.raku given "a\nb";     # OUTPUT: «"c\nd"␤» 

ss/.../.../ 语法形式是 s:samespace/.../.../ 的简写。

回溯§

Raku 在评估正则表达式时默认使用 回溯。回溯是一种允许引擎尝试不同匹配的技术,以使正则表达式的每个部分都能成功。这很昂贵,因为它需要引擎通常尽可能多地吃掉第一个匹配项,然后向后调整以确保所有正则表达式部分都有机会匹配。

理解回溯§

为了更好地理解回溯,请考虑以下示例

my $string = 'PostgreSQL is an SQL database!';
say $string ~~ /(.+)(SQL) (.+$1/# OUTPUT: «「PostgreSQL is an SQL」␤» 

上面的例子中发生的事情是,字符串必须与单词 SQL 的第二次出现匹配,吃掉之前的所有字符,并留下其余的字符。

由于可以在正则表达式中执行一段代码,因此也可以在正则表达式本身中检查 Match 对象

my $iteration = 0;
sub show-capturesMatch $m ){
    my Str $result_split;
    say "\n=== Iteration {++$iteration} ===";
    for $m.list.kv -> $i$capture {
        say "Capture $i = $capture";
        $result_split ~= '[' ~ $capture ~ ']';
    }
 
    say $result_split;
}
 
$string ~~ /(.+)(SQL) (.+$1 .+ { show-captures$/ );  }/;

show-captures 方法将转储 $/ 的所有元素,产生以下输出

=== Iteration 1 ===
Capture 0 = Postgre
Capture 1 = SQL
Capture 2 =  is an
[Postgre][SQL][ is an ]

显示字符串已在 SQL 的第二次出现周围被分割,即第一个捕获的重复($/[1])。

有了它,现在就可以看到引擎如何回溯以找到上面的匹配:将 show-captures 放在正则表达式中间就足够了,特别是在第一个捕获 $1 的重复之前,以查看它的作用

my $iteration = 0;
sub show-capturesMatch $m ){
    my Str $result-split;
    say "\n=== Iteration {++$iteration} ===";
    for $m.list.kv -> $i$capture {
        say "Capture $i = $capture";
        $result-split ~= '[' ~ $capture ~ ']';
    }
 
    say $result-split;
}
 
$string ~~ / (.+)(SQL) (.+{ show-captures$/ );  } $1 /;

输出将更加详细,并将显示几个迭代,最后一个是获胜的。以下是输出的摘录

=== Iteration 1 ===
Capture 0 = PostgreSQL is an
Capture 1 = SQL
Capture 2 =  database!
[PostgreSQL is an ][SQL][ database!]

=== Iteration 2 ===
Capture 0 = PostgreSQL is an
Capture 1 = SQL
Capture 2 =  database
[PostgreSQL is an ][SQL][ database]

...

=== Iteration 24 ===
Capture 0 = Postgre
Capture 1 = SQL
Capture 2 =  is an
[Postgre][SQL][ is an ]

在第一次迭代中,PostgreSQLSQL 部分保留在单词中:这不是正则表达式要求的,因此需要进行另一次迭代。第二次迭代将向后移动,特别是向后移动一个字符(从而删除最后的 !),并尝试再次匹配,导致失败,因为 SQL 仍然保留在 PostgreSQL 中。经过几次迭代后,最终结果是匹配。

值得注意的是,最后一次迭代是第24次,这个数字正好是从字符串末尾到第一个 SQL 出现的字符数

say $string.chars - $string.index: 'SQL'# OUTPUT: «23␤» 

由于从字符串的最末尾到 SQL 的第一个 S 有 23 个字符,因此回溯引擎将需要 23 个“无用”的匹配才能找到正确的匹配,也就是说,它将需要 24 步才能获得最终结果。

回溯是一种昂贵的机制,因此可以在那些只能向前找到匹配的情况下禁用它。

关于上面的例子,禁用回溯意味着正则表达式将没有机会匹配

say $string ~~ /(.+)(SQL) (.+$1/;      # OUTPUT: «「PostgreSQL is an SQL」␤» 
say $string ~~ / :r (.+)(SQL) (.+$1/;  # OUTPUT: «Nil␤» 

事实上,如迭代 1 输出所示,正则表达式引擎的第一个匹配将是 PostgreSQL is an SQLdatabase,它没有为匹配单词 SQL 的另一个出现留下任何空间(作为正则表达式中的 $1)。由于引擎无法向后移动并更改路径以匹配,因此正则表达式失败。

值得注意的是,禁用回溯不会阻止引擎尝试多种匹配正则表达式的方法。考虑以下略微更改的示例

my $string = 'PostgreSQL is an SQL database!';
say $string ~~ / (SQL) (.+$1 /# OUTPUT: «Nil␤» 

由于没有指定单词 SQL 之前的字符,因此引擎将与最右边的单词 SQL 匹配,并从那里向前移动。由于没有剩余的 SQL 重复,因此匹配失败。同样,可以检查引擎执行的操作,在正则表达式中引入一段转储代码

my $iteration = 0;
sub show-capturesMatch $m ){
    my Str $result-split;
    say "\n=== Iteration {++$iteration} ===";
    for $m.list.kv -> $i$capture {
        say "Capture $i = $capture";
        $result-split ~= '[' ~ $capture ~ ']';
    }
 
    say $result-split;
}
 
$string ~~ / (SQL) (.+{ show-captures$/ ); } $1 /;

这将产生一个相当简单的输出

=== Iteration 1 ===
Capture 0 = SQL
Capture 1 =  is an SQL database!
[SQL][ is an SQL database!]

=== Iteration 2 ===
Capture 0 = SQL
Capture 1 =  database!
[SQL][ database!]

即使使用 :r 副词来防止回溯,也不会改变任何东西

my $iteration = 0;
sub show-capturesMatch $m ){
    my Str $result-split;
    say "\n=== Iteration {++$iteration} ===";
    for $m.list.kv -> $i$capture {
        say "Capture $i = $capture";
        $result-split ~= '[' ~ $capture ~ ']';
    }
 
    say $result-split;
}
 
$string ~~ / :r (SQL) (.+{ show-captures$/ ); } $1 /;

输出将保持不变

=== Iteration 1 ===
Capture 0 = SQL
Capture 1 =  is an SQL database!
[SQL][ is an SQL database!]

=== Iteration 2 ===
Capture 0 = SQL
Capture 1 =  database!
[SQL][ database!]

这表明禁用回溯并不意味着禁用匹配引擎的多个可能的迭代,而是禁用向后匹配调整。

回溯控制§

Raku 提供了一些工具来控制回溯。首先,您可以使用 :ratchet 正则表达式副词来开启回溯(或使用 :!ratchet 来关闭它)。有关详细信息,请参阅 回溯。请注意,与所有正则表达式副词一样,您可以使用方括号来限制 :ratchet 的作用域。因此,在以下代码中,回溯对第一个量词 (\S+) 启用,对第二个量词 (\s+) 禁用,并对第三个量词 (\d+) 重新启用。

'A  42' ~~  rx/\S+ [:r \s+ [:!r \d+ ] ] . /  # OUTPUT: «「A  42」␤» 

:ratchettokenrule 中默认启用;有关更多详细信息,请参阅 语法

Raku 还提供三个正则表达式元字符来控制单个原子的回溯。

禁用回溯::§

: 元字符会禁用前一个原子的回溯。因此,/ .*: a/ 不会匹配 " a",因为 .* 匹配整个字符串,没有留下任何内容供 a 匹配,而无需回溯。

启用贪婪回溯::!§

:! 元字符会为前一个原子启用贪婪回溯,即提供在 :ratchet 未生效时使用的回溯行为。:!! 贪婪量词修饰符 密切相关;但是,与只能在量词之后使用的 ! 不同,:! 可以用在任何原子之后。例如,:! 可以用在交替之后

'abcd' ~~ /:ratchet [ab | abc]   cd/;  # OUTPUT: «Nil␤» 
'abcd' ~~ /:ratchet [ab | abc]:! cd/;  # OUTPUT: «「abcd」␤» 

启用节俭回溯::?§

:? 元字符的工作原理与 :! 完全相同,只是它启用了节俭回溯。因此,它与 ? 节俭量词修饰符 密切相关;同样,:? 可以用在非量词原子之后。这包括 ? 将是 零或一 量词(而不是提供回溯控制)的上下文

my regex numbers { \d* }
 
'4247' ~~ /:ratchet <numbers>?  47/;  # OUTPUT: «Nil␤» 
'4247' ~~ /:ratchet <numbers>:? 47/;  # OUTPUT: «「4247」␤» 

关于子正则表达式回溯的说明§

::!:? 控制它们当前正则表达式中的回溯行为,也就是说,它们会导致原子表现得好像 :ratchet 在当前正则表达式中设置不同。但是,这些元字符和 :!ratchet 都不能导致非回溯子正则表达式(包括规则或标记)回溯;子正则表达式已经无法回溯。另一方面,它们可以阻止子正则表达式回溯。为了扩展我们之前的示例

my regex numbers { \d* }
 
# By default <numbers> backtracks 
'4247' ~~ / <numbers>  47/;  # OUTPUT: «「4247」␤» 
# : can disable backtracking over <numbers> 
'4247' ~~ / <numbers>: 47/;  # OUTPUT: «Nil␤» 
 
my regex numbers-ratchet {:ratchet \d* }
 
# <numbers-ratchet> never backtracks 
'4247' ~~ /   <numbers-ratchet>   47/;  # OUTPUT: «Nil␤» 
# :! can't make it 
'4247' ~~ /   <numbers-ratchet>:! 47/;  # OUTPUT: «Nil␤» 
# Neither can setting :!ratchet 
'4247' ~~ /:!r <numbers-ratchet>  47/;  # OUTPUT: «Nil␤» 

$/ 在每次匹配正则表达式时都会更改§

值得注意的是,每次使用正则表达式时,返回的 Match(即 $/)都会重置。换句话说,$/ 始终引用最后匹配的正则表达式

my $answer = 'a lot of Stuff';
say 'Hit a capital letter!' if $answer ~~ / <[A..Z>]> /;
say $/;  # OUTPUT: «「S」␤» 
say 'hit an x!' if $answer ~~ / x /;
say $/;  # OUTPUT: «Nil␤» 

$/ 的重置独立于匹配正则表达式的范围

my $answer = 'a lot of Stuff';
if $answer ~~ / <[A..Z>]> / {
   say 'Hit a capital letter';
   say $/;  # OUTPUT: «「S」␤» 
}
say $/;     # OUTPUT: «「S」␤» 
 
if True {
  say 'hit an x!' if $answer ~~ / x /;
  say $/;   # OUTPUT: «Nil␤» 
}
 
say $/;     # OUTPUT: «Nil␤» 

同样的概念也适用于命名捕获

my $answer = 'a lot of Stuff';
if $answer ~~ / $<capital>=<[A..Z>]> / {
   say 'Hit a capital letter';
   say $/<capital># OUTPUT: «「S」␤» 
}
 
say $/<capital>;    # OUTPUT: «「S」␤» 
say 'hit an x!' if $answer ~~ / $<x>=x /;
say $/<x>;          # OUTPUT: «Nil␤» 
say $/<capital>;    # OUTPUT: «Nil␤» 

最佳实践和陷阱§

正则表达式:最佳实践和陷阱 提供了有关如何在编写正则表达式和语法时避免常见陷阱的有用信息。