在 raku 的语法中使用 'after' 作为 lookbehind
Using 'after' as lookbehind in a grammar in raku
我正在尝试使用 raku 语法进行匹配,但因 'after' 而失败。我已将我的问题归结为以下片段:
grammar MyGrammar {
token TOP {
<character>
}
token character {
<?after \n\n>LUKE
}
}
say MyGrammar.subparse("\n\nLUKE");
这 returns #<failed match>
作为 MyGrammar.subparse 和 Nil
作为 MyGrammar.parse.
但是如果我 运行 在 REPL 中匹配:
"\n\nLUKE" ~~ /<?after \n\n>LUKE/
我得到匹配「LUKE」
所以有些事情我不明白,我不确定是什么。有什么指点吗?
<?after ...>
不推进匹配游标
这里重要的是 <?after \n\n>
是 "zero width" assertion。
如果匹配光标位于被匹配字符串中 "\n\n"
的紧邻右侧,则匹配,但不会使匹配光标前进。
为什么 ~~ / ... /
版本匹配
regex/grammar 引擎自动为您推进匹配光标。
普通正则表达式风格的匹配与传统正则表达式一样工作。特别是,它应该匹配正在匹配的字符串中的 anywhere,除非您显式添加锚点,例如 ^
(字符串开头)and/or $
(字符串结尾)。
更明确地说,匹配引擎将首先尝试匹配正在匹配的字符串的第一个字符位置。然后,如果失败,它会自动在字符串中向前移动一个字符,然后再次尝试从正则表达式模式的开头进行匹配。
所以所有这些也将匹配并给出相同的结果:
"\n\nLUKE" ~~ /LUKE/; # 「LUKE」
"\n\nLUKE" ~~ /LUKE $/; # 「LUKE」
"LUKE" ~~ /^ LUKE $/; # 「LUKE」
"\n\nLUKE" ~~ / <?after \n\n>LUKE $/; # 「LUKE」
为什么语法版本不匹配
语法应从输入字符串的开头开始匹配。否则失败。
更明确地说,.parse
在解析的开始和结束处具有隐式 ^
和 $
锚点,而 .subparse
具有隐式 ^
在开头。
如果匹配游标未能通过第一个字符,则解析失败。您的语法不会使匹配光标超过第一个字符,因此失败。
( <?after \n\n>
如果匹配则不仅不会使游标前进,而且它甚至从一开始就不会匹配——因为在字符串的开头,匹配游标仅在 [=51 之后=]nothing。如果你改为写 <?after ''>
,那么它总是会成功,但仍然不会使光标前进,所以如果这是你所做的唯一更改,语法仍然会失败。)
当我们使用语法解析字符串时,匹配锚定在字符串的开头。使用 parse
解析输入需要我们使用所有字符串。还有一个 subparse
,它允许我们不消耗所有输入,但这仍然锚定在字符串的开头。
相比之下,像 /<?after \n\n>LUKE/
这样的正则表达式将 扫描 整个字符串,尝试匹配字符串中每个位置的模式,直到它找到一个位置它匹配(或到达字符串的末尾并放弃)。这就是它起作用的原因。但是请注意,如果您的目标是不捕获 \n\n
,那么您可以将正则表达式编写为 /\n\n <( LUKE/
,其中 <(
表示从哪里开始捕获。至少在目前的Rakudo编译器实现上,这种方式效率更高
在没有更多上下文的情况下建议如何编写语法并不容易(我猜这是从一个更大的问题中提取的)。例如,您可以在语法的开头使用空格:
grammar MyGrammar {
token TOP {
\s+ <character>
}
token character {
<?after \n\n>LUKE
}
}
say MyGrammar.subparse("\n\nLUKE");
或者使用字符中的 \n\n
但将其从与 <(
的匹配中排除,如前所述。
目前的答案都很好,但让我更详细地解释一下误解的根源。
要点是,在这里您将作为语法一部分的标记与独立的正则表达式进行比较。它们使用相同的语言、正则表达式,但它们并不相同。可以用正则匹配,代入和提取信息; token 的 objective 纯粹是提取信息;从具有常规结构的字符串中,我想要一个部分并且只是那个部分。
我假设您对 LUKE 部分感兴趣,并且您正在使用 <after
来表达“不,这不是我感兴趣的”,或者“跳过这个,只给我货物”。 Jonathan 已经说过一种方法,可能是最好的方法:
grammar MyGrammar {
token TOP {
<character>
}
token character {
\n \n <( LUKE
}
}
say MyGrammar.subparse("\n\nLUKE");
不仅会数学,而且只会抓拍LUKE:
「
LUKE」
character => 「LUKE
跳过那个。但是,语法不匹配,它们提取。所以你可能希望分隔符也出现在语法中,而不值得一遍又一遍地重复它们。此外,一般语法旨在自上而下使用。所以这样做:
grammar MyGrammar {
token TOP {
<separator><character>
}
token separator { \n \n }
token character { <[A..Z]>+ }
}
say MyGrammar.parse("\n\nLUKE");
character
标记现在更通用(尽管它可能会使用一些空格,我不知道。同样,也许您对分隔符不感兴趣。只需使用点来忽略它. 只是因为你不感兴趣并不意味着你不必解析它,语法给了你一个方法:
grammar MyGrammar {
token TOP {
<.separator><character>
}
token separator { \n \n }
token character { <[A..Z]>+ }
}
say MyGrammar.parse("\n\nLUKE");
这个给出了相同的结果:
「
LUKE」
character => 「LUKE」
归根结底,语法和正则表达式有不同的用例,因此相同 objective 的解决方案也不同。以正确的方式思考它们可以为您提供有关如何构建它们的提示。
我正在尝试使用 raku 语法进行匹配,但因 'after' 而失败。我已将我的问题归结为以下片段:
grammar MyGrammar {
token TOP {
<character>
}
token character {
<?after \n\n>LUKE
}
}
say MyGrammar.subparse("\n\nLUKE");
这 returns #<failed match>
作为 MyGrammar.subparse 和 Nil
作为 MyGrammar.parse.
但是如果我 运行 在 REPL 中匹配:
"\n\nLUKE" ~~ /<?after \n\n>LUKE/
我得到匹配「LUKE」
所以有些事情我不明白,我不确定是什么。有什么指点吗?
<?after ...>
不推进匹配游标
这里重要的是 <?after \n\n>
是 "zero width" assertion。
如果匹配光标位于被匹配字符串中 "\n\n"
的紧邻右侧,则匹配,但不会使匹配光标前进。
为什么 ~~ / ... /
版本匹配
regex/grammar 引擎自动为您推进匹配光标。
普通正则表达式风格的匹配与传统正则表达式一样工作。特别是,它应该匹配正在匹配的字符串中的 anywhere,除非您显式添加锚点,例如 ^
(字符串开头)and/or $
(字符串结尾)。
更明确地说,匹配引擎将首先尝试匹配正在匹配的字符串的第一个字符位置。然后,如果失败,它会自动在字符串中向前移动一个字符,然后再次尝试从正则表达式模式的开头进行匹配。
所以所有这些也将匹配并给出相同的结果:
"\n\nLUKE" ~~ /LUKE/; # 「LUKE」
"\n\nLUKE" ~~ /LUKE $/; # 「LUKE」
"LUKE" ~~ /^ LUKE $/; # 「LUKE」
"\n\nLUKE" ~~ / <?after \n\n>LUKE $/; # 「LUKE」
为什么语法版本不匹配
语法应从输入字符串的开头开始匹配。否则失败。
更明确地说,.parse
在解析的开始和结束处具有隐式 ^
和 $
锚点,而 .subparse
具有隐式 ^
在开头。
如果匹配游标未能通过第一个字符,则解析失败。您的语法不会使匹配光标超过第一个字符,因此失败。
( <?after \n\n>
如果匹配则不仅不会使游标前进,而且它甚至从一开始就不会匹配——因为在字符串的开头,匹配游标仅在 [=51 之后=]nothing。如果你改为写 <?after ''>
,那么它总是会成功,但仍然不会使光标前进,所以如果这是你所做的唯一更改,语法仍然会失败。)
当我们使用语法解析字符串时,匹配锚定在字符串的开头。使用 parse
解析输入需要我们使用所有字符串。还有一个 subparse
,它允许我们不消耗所有输入,但这仍然锚定在字符串的开头。
相比之下,像 /<?after \n\n>LUKE/
这样的正则表达式将 扫描 整个字符串,尝试匹配字符串中每个位置的模式,直到它找到一个位置它匹配(或到达字符串的末尾并放弃)。这就是它起作用的原因。但是请注意,如果您的目标是不捕获 \n\n
,那么您可以将正则表达式编写为 /\n\n <( LUKE/
,其中 <(
表示从哪里开始捕获。至少在目前的Rakudo编译器实现上,这种方式效率更高
在没有更多上下文的情况下建议如何编写语法并不容易(我猜这是从一个更大的问题中提取的)。例如,您可以在语法的开头使用空格:
grammar MyGrammar {
token TOP {
\s+ <character>
}
token character {
<?after \n\n>LUKE
}
}
say MyGrammar.subparse("\n\nLUKE");
或者使用字符中的 \n\n
但将其从与 <(
的匹配中排除,如前所述。
目前的答案都很好,但让我更详细地解释一下误解的根源。
要点是,在这里您将作为语法一部分的标记与独立的正则表达式进行比较。它们使用相同的语言、正则表达式,但它们并不相同。可以用正则匹配,代入和提取信息; token 的 objective 纯粹是提取信息;从具有常规结构的字符串中,我想要一个部分并且只是那个部分。
我假设您对 LUKE 部分感兴趣,并且您正在使用 <after
来表达“不,这不是我感兴趣的”,或者“跳过这个,只给我货物”。 Jonathan 已经说过一种方法,可能是最好的方法:
grammar MyGrammar {
token TOP {
<character>
}
token character {
\n \n <( LUKE
}
}
say MyGrammar.subparse("\n\nLUKE");
不仅会数学,而且只会抓拍LUKE:
「
LUKE」
character => 「LUKE
跳过那个。但是,语法不匹配,它们提取。所以你可能希望分隔符也出现在语法中,而不值得一遍又一遍地重复它们。此外,一般语法旨在自上而下使用。所以这样做:
grammar MyGrammar {
token TOP {
<separator><character>
}
token separator { \n \n }
token character { <[A..Z]>+ }
}
say MyGrammar.parse("\n\nLUKE");
character
标记现在更通用(尽管它可能会使用一些空格,我不知道。同样,也许您对分隔符不感兴趣。只需使用点来忽略它. 只是因为你不感兴趣并不意味着你不必解析它,语法给了你一个方法:
grammar MyGrammar {
token TOP {
<.separator><character>
}
token separator { \n \n }
token character { <[A..Z]>+ }
}
say MyGrammar.parse("\n\nLUKE");
这个给出了相同的结果:
「
LUKE」
character => 「LUKE」
归根结底,语法和正则表达式有不同的用例,因此相同 objective 的解决方案也不同。以正确的方式思考它们可以为您提供有关如何构建它们的提示。