TL;DR @briandfoy 提供了 an easy to digest answer。但这里有他没有提到的龙。还有漂亮的蝴蝶。这个答案很深入。
问题1:令牌中的{}在做什么?
这是一个代码块1,2,3,4。
它是一个空的,被插入纯粹是为了强制quotebody($<quote>) 中的$<quote> 评估为正则表达式开头的<quote> 捕获的值。
$<quote> 在不插入代码块的情况下不包含正确值的原因是 Rakudo Perl 6 编译器限制或与“匹配变量的发布”相关的错误。
Rakudo 对匹配变量的“发布”
Moritz Lenz 在a Rakudo bug report 中指出“除非认为有必要,否则正则表达式引擎不会发布匹配变量”。
“正则表达式引擎”是指 NQP 中的正则表达式/语法引擎,它是 Rakudo Perl 6 编译器的一部分。3
“匹配变量”是指存储匹配结果捕获的变量:
当前匹配变量$/;
编号子匹配变量$0、$1等;
命名子匹配变量$<foo>。
“发布”是指正则表达式/语法引擎执行所需的操作,以便在正则表达式(标记也是正则表达式)中对任何变量的任何提及都评估为它们应该具有的值重新应该拥有它们。在给定的正则表达式中,匹配变量应该包含一个Match object,对应于在处理该正则表达式的任何给定阶段为它们捕获的内容,如果没有捕获任何内容,则为Nil。
“认为必要”是指正则表达式/语法引擎在匹配过程的每个步骤之后对是否值得进行发布工作做出保守判断。我所说的“保守”是指引擎经常避免发布,因为它会减慢速度并且通常是不必要的。不幸的是,对于何时真正需要发布 有时过于乐观。因此,程序员有时需要通过显式插入代码块来强制发布匹配变量(以及其他变量的其他技术5)进行干预。随着时间的推移,正则表达式/语法引擎可能会在这方面有所改进,从而减少需要手动干预的情况。如果您希望帮助完成此任务,请针对现有的相关错误创建对您很重要的测试用例。5
$<quote> 值的“发布”
命名的捕获$<quote> 就是这里的例子。
据我所知,当直接写入正则表达式时,所有子匹配变量都正确地引用了它们捕获的值,而没有周围的构造。这有效:
my regex quote { <['"]> }
say so '"aa"' ~~ / <quote> aa $<quote> /; # True
我认为6$<quote> 得到了正确的值,因为它被解析为 regex 俚语 构造。4
相比之下,如果 {} 被删除
token string { <quote> {} <quotebody($<quote>)> $<quote> }
那么quotebody($<quote>) 中的$<quote> 将不包含由开头<quote> 捕获的值。
我认为这是因为 $<quote> 在这种情况下被解析为 main 俚语结构。
问题 2a:escaped($quote) 内 <> 将是一个正则表达式函数,对吗?它以$quote 作为参数
这是一个很好的初步近似。
更具体地说,<foo(...)> 形式的正则表达式原子是对 方法 foo 的调用。
所有正则表达式——无论是用token、regex、rule、/.../ 还是任何其他形式声明——都是方法。但是用method 声明的方法是不是正则表达式:
say Method ~~ Regex; # False
say WHAT token { . } # (Regex)
say Regex ~~ Method; # True
say / . / ~~ Method; # True
当遇到<escaped($quote)> 正则表达式原子时,正则表达式/语法引擎不知道或关心escaped 是否是正则表达式,也不关心the details of method dispatch inside a regex or grammar。它只是调用方法分派,调用者设置为 Match 由封闭的正则表达式构造的对象。
调用将控制权交给最终运行该方法的任何对象。事实证明,正则表达式/语法引擎只是递归地回调自身,因为通常这是一个正则表达式调用另一个正则表达式的问题。但不一定如此。
并返回另一个正则表达式
不,<escaped($quote)> 形式的正则表达式原子不会返回另一个正则表达式。
相反,它调用一个将/应该返回Match 对象的方法。
如果调用的方法是正则表达式,P6 将确保正则表达式自动生成并填充 Match 对象。
如果调用的方法不是正则表达式,而只是普通方法,则该方法的代码应该手动创建并返回一个Match 对象。 Moritz 在他对 SO 问题 Can I change the Perl 6 slang inside a method? 的回答中展示了一个示例。
Match 对象返回到驱动正则表达式匹配/语法解析的“正则表达式/语法引擎”。3
然后引擎根据结果决定下一步做什么:
问题2b:如果我想指出“char that is not before quote”,我应该使用. <!before $quote>而不是<!before $quote> .吗??
是的。
但这不是 quotebody 正则表达式所需要的,如果这就是您所说的。
关于后一个主题,在@briandfoy 的回答中,他建议使用“匹配...任何不是引用的内容”结构,而不是消极地向前看 (<!before $quote>)。他的观点是匹配“not a quote”比“are we not before a quote? then match any character”更容易理解。
但是,当引用是一个变量,其值被设置为开始引用的捕获时,这样做绝不是直截了当的。这种复杂性是由于 Rakudo 中的错误造成的。我已经找到了我认为最简单的解决方法,但我认为最好还是坚持使用<!before $quote> .,除非/直到这些长期存在的 Rakudo 错误得到修复。5
token escaped($quote) { '\\' ( $quote | '\\' ) } # I think this is a function;
这是一个令牌,这是一个Regex,这是一个Method,这是一个Routine:
say token { . } ~~ Regex; # True
say Regex ~~ Method; # True
say Method ~~ Routine; # True
正则表达式主体内的代码({ ... } 位)(在这种情况下,代码是 token { . } 中唯一的 .,它是匹配单个字符的正则表达式原子)写在P6 正则表达式“俚语”,而在 method 例程主体内使用的代码是用 P6 主“俚语”编写的。4
使用~
regex tilde (~) operator 是专门为这个问题所涉及的示例中的解析而设计的。它读起来更好,因为它可以立即识别并将开始和结束引号放在一起。更重要的是,它可以在失败时提供人类可理解的错误消息,因为它可以说明它正在寻找什么结束分隔符。
但是,如果您在正则表达式 ~ 运算符(在其任一侧)旁边的正则表达式(其中包含或不包含代码)中插入代码块,则必须考虑一个关键问题。除非您特别希望波浪号将代码块视为自己的原子,否则您将需要对代码块进行分组。例如:
token foo { <quote> ~ $<quote> {} <quotebody($<quote>) }
将匹配一对<quote>s 它们之间没有任何内容。 (然后尝试匹配<quotebody...>。)
相比之下,这是一种在String::Simple::Grammar 语法中复制string 标记的匹配行为的方法:
token string { <quote> ~ $<quote> [ {} <quotebody($<quote>) ] }
脚注
1 2002 年,Larry Wall 写了"It needs to be just as easy for a regex to call Perl code as it is for Perl code to call a regex."。计算机科学家注意到你不能在a traditional regular expression 中间有程序代码。但是 Perls 很久以前就导致了向non-traditional regexes 的转变,P6 得出了合乎逻辑的结论——只需一个简单的{...} 就可以在正则表达式中间插入任意过程代码。语言设计和正则表达式/语法引擎实现3 确保识别正则表达式中的传统样式纯声明性区域,以便可以将正式的正则表达式理论和优化应用于它们,但仍然是任意的正则过程代码也可以插入。简单的用途包括matching logic 和debugging。但天空是极限。
2 正则表达式的第一个过程元素(如果有)终止正则表达式的所谓“声明性前缀”。插入空代码块 ({}) 的一个常见原因是,当它为正则表达式中的给定 longest alternation 提供所需的匹配语义时,故意终止正则表达式的声明性前缀。 (但这不是将其包含在您试图理解的令牌中的原因。)
3 粗略地说,NQP 中的正则表达式/语法引擎对于 P6 就像 PCRE 对于 P5。
一个关键的区别在于,正则表达式语言,连同其相关的正则表达式/语法引擎,以及与之合作的主要语言,在 Rakudo 的情况下是 Perl 6,在控制方面是同等重要的。这是Larry Wall's original 2002 vision for integration between regexes and "rich languages" 的实现。每种语言/运行时都可以调用另一种语言并通过高级 FFI 进行通信。因此,它们可以看起来,可以表现为,并且确实是,协作语言和协作运行时的单一系统。
(P6 设计使得所有语言都可以通过两个互补的 P6 FFIs 以“丰富”的方式进行显式设计或改造,以“丰富”的方式进行合作:元模型 FFI @ 987654338@ 和/或 C 调用约定 FFI NativeCall。)
4 P6 语言实际上是一起使用的子语言(也称为俚语)的集合。当您阅读或编写 P6 代码时,您正在阅读或编写以一种俚语开始但有其他部分编写的源代码。文件的第一行使用主要俚语。假设这类似于英语。正则表达式是用另一种俚语写的;假设这就像西班牙语。所以在the grammar String::Simple::Grammar的情况下,代码以英文开头(use v6;语句),然后递归成西班牙文(在rule TOP {的{之后),即^ <string> $位,然后返回成英文(评论以# Note ...开头)。然后它为<quote> {} <quotebody($<quote>)> $<quote> 递归回西班牙语,在该西班牙语中间,在{} 代码块,它再次递归到另一个级别的英语。这就是英语中的西班牙语中的英语。当然,代码块是空的,所以就像什么都不用英语写/读,然后立即回到西班牙语,但重要的是要理解这种语言/运行时的递归堆叠是 P6 的工作方式,两者都是单一的整体语言/运行时以及与其他非 P6 语言/运行时合作时。
5 在应用两个潜在改进的过程中,我遇到了几个错误,列在本脚注的末尾。 (在 briandfoy 的回答和这个回答中都提到了。)这两个“改进”是使用 ~ 构造,以及“不是引用”构造而不是使用 <!before foo> .。最终结果,以及相关错误的提及:
grammar String::Simple::Grammar {
rule TOP {^ <string> $}
token string {
:my $*not-quote;
<quote> ~ $<quote>
[
{ $*not-quote = "<-[$<quote>]>" }
<quotebody($<quote>)>
]
}
token quote { '"' | "'" }
token quotebody($quote) { ( <escaped($quote)> | <$*not-quote> )* }
token escaped($quote) { '\\' ( $quote | '\\' ) }
}
如果有人知道更简单的方法,我很乐意在下面的评论中听到它。
我最终在 RT 错误数据库中搜索所有正则表达式错误。我知道 SO 不是错误数据库,但我认为注意以下几点对我来说是合理的。 Aiui 前两个直接与发布匹配变量的问题交互。
6 这个问题和我的回答把我推到了我对 P6 雄心勃勃和复杂方面的理解的外部极限。我计划很快深入了解 nqp 和完整 P6 之间的精确交互,以及它们的正则表达式俚语和主要俚语之间的切换,如上文脚注中所述。 (我目前的希望主要在于刚刚购买了commaide。)如果/当我有一些结果时,我会更新这个答案。