<.ident> function/capture 在 perl 6 语法中

<.ident> function/capture in perl6 grammars

在阅读 perl6 (https://github.com/supernovus/exemel/blob/master/lib/XML/Grammar.pm6) 的 Xml 语法时,我在理解以下标记时遇到了一些困难。

token pident {
  <!before \d> [ \d+ <.ident>* || <.ident>+ ]+ % '-'
}

更具体地说 <.ident>,ident 没有其他定义,所以我假设它是一个保留词。虽然我找不到 perl6.org 上的正确定义。有谁知道这是什么意思吗?

一般来说,查找文档的地方是Perl6 documentation. That's part of a regex, and you can find it in the definition of character classes. It matches Perl6 identifiers. What the . in front of ident does is to suppress capture

TL;DR 我将从一个精确且相对简洁的答案开始。这个答案的其余部分是为那些想要了解更多关于内置规则的人准备的and/or,以便深入研究 ident

<.ident> function/capture

因为.<.ident>只匹配,不捕获[1]。对于此答案的其余部分,我通常会省略 .,因为除了捕获方面,它对规则的含义没有影响。

正如您可以在编程语言的声明中调用(又名 "call")一个函数一样,您也可以调用 rule/token/regex/method(此后我通常只使用术语"rule") 在另一条规则的声明中。 <foo> 是用于调用名为 foo 的规则的语法;所以 <ident> 调用一个名为 ident.

的(方法)

在我写这篇文章时,XML::Grammar 语法本身 define/declare 没有名为 ident 的规则。这意味着调用最终被分派到具有该名称的内置声明。

内置 ident 规则与声明为完全相同:

token ident {
    [ <alpha> ]
    [ <alnum> ]*
}

official Predefined character classes doc 应该提供 <alpha><alnum> 的精确定义。或者,相关详细信息也包含在此答案的后面。

底线是 ident 匹配一个或多个 "alphanumeric" 个字符的字符串,除了第一个字符不能是 "number".

因此 abcdef123 都匹配,而 123abc 不匹配。

这个答案的其余部分

对于那些对值得了解的细节感兴趣的人,我写了以下部分:

  • Raku(标准语言和class细节)

  • Rakudo(高级实现)

  • NQP(中级实现)

  • MoarVM(低级实现)

  • ident

  • 的规范和"specification"
  • <ident>、"character class" 和 "identifier"

    [= 的(更正)文档410=]
  • ident vs Raku 标识符

Raku(标准语言和class细节)

XML::Grammar 是用户定义的 Raku 语法。 Raku 语法是 class。 ("Grammars are really just slightly specialized classes".)

一个Raku rule是一个正则表达式是一个方法:

grammar foo { rule ident { ... } }

say foo.^lookup('ident').WHAT; # (Regex)
say Regex ~~ Method;           # True

语法中的 call 规则,如 <ident>,通常是调用 .parse 或类似语法的结果。 .parse 调用根据语法规则匹配输入字符串。

XML::Grammar 中出现的 <ident> 在匹配过程中被评估时,结果是对 XML::Grammar 实例的 ident 方法(规则)调用( .parse 调用会创建其调用者的一个实例(如果它只是一个类型对象)。

因为 XML::Grammar 本身并没有定义那个名称的 rule/method,所以 ident 调用是根据标准方法解析,呃,规则分派的。 (我在这里使用 "rules" 这个词是在通用 non-Raku 特定意义上。啊,语言。)

在 Raku 中,使用 grammar foo { ... } 形式的声明创建的任何 class 自动继承自 Grammar class,后者又继承自 Match class:

say .^mro given grammar foo {} # ((foo) (Grammar) (Match) (Capture) (Cool) (Any) (Mu))

ident 在内置 Match class.

中找到

Rakudo(高级实现)

在 Rakudo 编译器中,the Match class does the role NQPMatchRole

NQPMatchRoleident 的最高级别实现所在。

NQP(中级实施)

NQPMatchRole 是用 nqp 语言编写的,Raku 的一个子集,用于 bootstrap 完整的 Raku,NQP 的核心,一个编译器工具包。

仅从 the ident declaration 中摘录和重新格式化最重要的代码,第一个 字符的匹配归结为:

   nqp::ord($target, $!pos) == 95
|| nqp::iscclass(nqp::const::CCLASS_ALPHABETIC, $target, $!pos)

如果第一个字符是或者一个_95是ASCII码/下划线的 Unicode 代码点) 匹配 NQP 中定义的字符 class 的字符称为 CCLASS_ALPHABETIC.

另外一点重要的代码是:

nqp::findnotcclass( nqp::const::CCLASS_WORD

这匹配字符 class CCLASS_WORD.

中的零个或多个 后续 个字符

一个search of NQP for CCLASS_ALPHABETIC shows several matches. The most useful seems to be an NQP test file。虽然这个文件清楚地表明 CCLASS_WORDCCLASS_ALPHABETIC 的超集,但并没有清楚地表明那些 class 实际匹配什么。

NQP 针对多个 "backends" 或具体的虚拟机。鉴于 Rakudo/NQP doc/tests 这些规则和字符 classes 实际匹配的内容相对较少,因此必须查看其后端之一以验证什么是什么。

MoarVM(低级实现)

MoarVM 是唯一官方支持的后端。

一个search of MoarVM for CCLASS显示永远的比赛。

重要的似乎是 ops.c which includes a switch (cclass) statement,它又包括 MVM_CCLASS_ALPHABETICMVM_CCLASS_WORD 的情况,它们对应于 NQP 的类似命名常量。

根据代码的注释:

CCLASS_ALPHABETIC 当前匹配与完整 Raku 或 NQP <:L> 规则完全相同的字符,即字符 Unicode class 化为 "Letters".

我认为这意味着 <alpha> 等同于 CCLASS_ALPHABETIC_(下划线)的并集。

CCLASS_WORD 匹配相同的加号 <:Nd>,即十进制数字(在任何人类语言中,不仅仅是英语)。

我认为这意味着 Raku / NQP <alnum> 规则等同于 CCLASS_WORD

ident

的规范和"specification"

Raku的官方规范体现在roast[2].

A search of roast for ident 显示多个匹配项。

大多数人只是偶然使用 <ident>,作为测试其他内容的一部分。规范要求它们按所示工作,但您不会通过查看附带用法来理解 <ident> 应该做什么。

三个测试清楚地测试了<ident>本身。其中一个基本上是多余的,剩下两个。我发现这两个匹配项的 6.c6.c.errata 版本之间没有变化:

来自 S05-mass/rx.t:

ok ('2+3 ab2' ~~ /<ident>/) && matchcheck($/, q/mob<ident>: <ab2 @ 4>/), 'capturing builtin <ident>';

ok 测试它的第一个参数 returns True。此调用测试 <ident> 跳过 2+3 并匹配 ab2.

来自 S05-mass/charsets.t:

is $latin-chars.comb(/<ident>/).join(" "), "ABCDEFGHIJKLMNOPQRSTUVWXYZ _ abcdefghijklmnopqrstuvwxyz ª µ º ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö øùúûüýþÿ", 'ident chars';

is 测试它的第一个参数是否匹配它的第二个。此调用测试 ident 规则与由前 256 个 Unicode 代码点(Latin-1 字符集)组成的字符串匹配的内容。

这是此测试的变体,可以更清楚地显示发生的匹配:

say ~$_ for $latin-chars ~~ m:g/<ident>/;

打印:

ABCDEFGHIJKLMNOPQRSTUVWXYZ
_
abcdefghijklmnopqrstuvwxyz
ª
µ
º
ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ
ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö
øùúûüýþÿ

但是 <ident> 将匹配 Latin-1 中超过一百个左右的字符。因此,虽然上述测试涵盖了 <ident> 官方 specified/tested 匹配的内容,但它们显然没有涵盖全部情况。

所以让我们仔细看看可能被认为与 "specification" 有关的 official speculation

首先,我们注意到顶部的警告:

Note: these documents may be out of date.
For Perl 6 documentation see docs.perl6.org;
for specs, see the official test suite.

此警告中的术语 "specs" 是 "specification" 的缩写。如前所述,官方规范测试套件是roast,不是任何人类语言的废话。

(有些人仍然认为这些历史设计文档也是"specifications",并称它们为"specs",但官方观点是"specs",应用于设计文档,应被视为 "speculations" 的缩写,以强调它们不是完全依赖的东西。)

A search for ident in design.raku.org 显示多个匹配项。

最有用的匹配在 the Predefined Subrules section of S05:

These are some of the predefined subrules for any grammar or regex:

  • ident ... 匹配标识符。

呃……

<ident>、"character class" 和 "identifier"

文档的(更正)

来自 Predefined character classes in the official doc:

    Class                             Description
    <ident>                           Identifier. Also a default rule.

这在三个方面具有误导性:

  • ident 不是 a character class。字符 classes 匹配该字符 class 中的一个 单个 字符;如果与量词一起使用,它们只匹配一串这样的字符,每个字符都可以是 class 中的任何字符。相反 <ident> 匹配特定的字符模式。它可能是一个角色,但你无法控制它;规则是贪婪的,匹配符合模式的字符。如果您应用量词,它会控制整个规则的重复,而不是规则的单个匹配项中包含的字符数。

  • 所有内置规则均为默认规则。我认为默认注释是为了强调如果您不喜欢 built-in 模式,您可以编写自己的 ident 规则。这对所有规则都是正确的,尽管覆盖内置的规则通常意义不大,例如规范字符 classes,如 <lower>(小写)。

  • ident 匹配标识符!或者,更准确地说,对于大多数 Raku 标识符,它不会自己这样做。详情见下一节。

ident vs Raku 标识符

my @Identifiers = < $bar %hash Foo Foo::Bar your_ident anothers' my-ident >; 
say (~$/ if m/^<ident>$/ for @Identifiers); # (Foo your_ident)
say (~$/ if m/ <ident> / for @Identifiers); # (bar hash Foo Foo your_ident anothers my)

在NQP的Grammar.nqp中定义的nqp语法中,有:

token identifier { <.ident> [ <[\-']> <.ident> ]* }

在 Rakudo 的 Grammar.nqp 中定义的 Raku 语法中,有一段代码看起来略有不同但效果完全相同:

token apostrophe { <[ ' \- ]> }
token identifier { <.ident> [ <.apostrophe> <.ident> ]* }

因此 <identifier> 匹配包含一个或多个 <ident> 且中间有 <apostrophe> 的模式。

ident 方法在 NQPMatchRole 中,这意味着它是 built-in 用户语法规则命名空间的一部分。

但是 identifier 方法 不是 由 Raku 或 nqp 导出。所以它们是 not 使用规则命名空间的一部分s 的语法。

如果我们编写自己的 indentifier 令牌,我们可以看到它的实际效果:

my token identifier { <.ident> [ <[\-']> <.ident> ]* }
my token sigil { <[$@%&]> }
say (~$/ if m/^ <sigil>? <identifier> $/ for @Identifiers)

显示:

($bar %hash Foo your_ident my-ident)

总结以上内容和其他一些注意事项:

  • <ident> 仅匹配 <identifier> 匹配的 部分 (尽管它们对于简单名称是相同的)。考虑 is-prime。这是一个 Raku 标识符,但包含 两个 <ident> 匹配项(isprime)。

  • <identifier> 仅匹配 "Raku identifiers" 的 部分 (尽管它们对于简单名称是相同的)。考虑 infix:<+>。这有时被称为 Raku 标识符,但需要 <identifier> 匹配和 :<+>.

  • 匹配
  • Raku 标识符本身只是名称的 部分 (尽管对于最简单的名称它们是相同的)。考虑包含 两个 <identifier> 个匹配项的 Foo-Bar::Baz-Qux(每个包含两个 <ident> 个匹配项)。

脚注

[1] 如果您不确定捕获是什么,请参阅 Capturing, Named captures and Subrules

[2] Raku 的官方规范是一个名为 roast 的测试套件 -- Repository O f All S规范T估计。 roast defines a specific version of Raku. When I first wrote this answer there had only been two official branches/versions of roast, and therefore of Raku. The first was 6.c aka 6.Christmas. This was cut on Christmas day 2015 特定分支的最新版本,从那天起就被故意冻结了。第二个是 6.c.errata,它保守地添加了对 6.c 的修正,这些修正被认为足够重要并且向后兼容以包含在(当时)当前官方推荐的 Raku 版本中。 "officially compliant" Raku 编译器通过了一些官方的 roast 分支。 Rakudo 编译器(当时)通过了 6.c.errata。如果您阅读了所有涉及某项功能的测试,例如,roast 的 6.c.errata 分支,那么您将阅读该功能的 官方指定 含义的完整定义6.c.errata 版本的 Raku 语言。