如何在 perl6 语法中匹配十六进制数组



我有一个像"39 3A 3B 9:;"这样的字符串,我想提取"39, 3A, 3B">

我试过了

my $a = "39 3A 3B  9:;";
grammar Hex {
token TOP { <hex_array>+ .* }
token hex_array { <[0..9 A..F]> " " }
};
Hex.parse($a);

但这似乎行不通。 即使这样似乎也行不通。

my $a = "39 3A 3B ";
grammar Hex {
token TOP { <hex_array>+ }
token hex_array { <[0..9 A..F]> " " }
};
Hex.parse($a);

我确实尝试了语法::跟踪器TOP和hex_array都失败

TOP
|  hex_array
|  * FAIL
* FAIL

P6正则表达式中的<[abcdef...]>是匹配一个字符意义上的"字符类"。1

获取所需内容的惯用方法是使用**量词:

my $a = "39 3A 3B ";
grammar Hex {
token TOP { <hex_array>+ }
token hex_array { <[0..9 A..F]>**1..2 " " }
};
Hex.parse($a);

这个答案的其余部分是关于为什么以及如何使用rule的"奖励"材料。

当然,您可以通过在任意单个令牌中包含空格模式来完全自由地匹配空格情况,就像您在hex_array令牌中包含" "一样。

但是,最好在适当的时候改用rule- 这是大多数时候。

首先,使用ws代替 " "、s*等。

让我们删除第二个token中的空格并将其移动到第一个:

token TOP { [ <hex_array> " " ]+ }
token hex_array { <[0..9 A..F]>**1..2 }

我们添加了方括号([...])将hex_array和空格组合在一起,然后将+量词应用于组合原子。这是一个简单的更改,语法继续像以前一样工作,像以前一样匹配空格,只是现在空间不会被hex_array令牌捕获。

接下来,让我们切换到使用内置wstoken

token TOP { [ <hex_array> <.ws> ]+ }

默认<ws>在理想的方式下比s*更普遍有用。2如果默认ws没有执行您需要的操作,则可以指定自己的ws令牌。

我们使用<.ws>而不是<ws>因为,像s*一样,使用<.ws>避免了额外的空白捕获,这可能会使解析树混乱并浪费内存。

人们通常希望在将标记串在一起的更高级别的解析规则中的几乎每个令牌之后<.ws>类似的东西。但是,如果只是这样明确地写,它将是非常重复和分散注意力<.ws>[ ... <.ws> ]样板。为了避免这种情况,有一个内置的快捷方式,用于隐式表达为您插入样板的默认假设。此快捷方式是rule声明符,而声明符又使用:sigspace

使用rule(使用:sigspace)

ruletoken完全相同,只是它在模式开始时打开:sigspace

rule  {           <hex_array>+ }
token { :sigspace <hex_array>+ } # exactly the same thing

如果没有:sigspace(默认情况下为tokens 和regexs),模式中的所有文字空格(除非您引用它们)都将被忽略。对于单个token的可读模式,这通常是可取的,因为它们通常指定要匹配的文字内容。

但是一旦:sigspace生效,原子之后的空间就变得"重要"——因为它们被隐式地转换为<.ws>[ ... <.ws> ]调用。这对于指定令牌或子规则序列的可读模式是可取的,因为这是避免所有这些额外调用混乱的自然方法。

下面的第一种模式将匹配一个或多个hex_array标记,它们之间或末尾没有匹配空格。最后两个将匹配一个或多个hex_array,不带空格,然后在最后有或没有空格:

token TOP {           <hex_array>+ }
#          ^ ignored ^            ^ ignored
token TOP { :sigspace <hex_array>+ }
#          ^ ignored ^            ^ significant
rule TOP  {           <hex_array>+ }
#          ^ ignored ^            ^ significant

铌。副词(如:sigspace)不是原子。紧挨着第一个原子之前的空间(在上面,<hex_array>之前的空间)永远不会重要(无论:sigspace是否有效)。但此后,如果:sigspace有效,则模式中所有未引号的间距都是"显着的"——也就是说,它被转换为<.ws>[ ... <.ws> ]

在上面的代码中,第二个标记和规则将匹配单个hex_array,后面有空格,因为紧跟在+之后和}之前的空格意味着模式被重写为:

token TOP { <hex_array>+ <.ws> }

但是,如果您的输入有多个hex_array令牌,它们之间有一个或多个空格,则此重写的令牌将不匹配。相反,你会想写:

rule TOP { <hex_array> + }
# ignored ^           ^ ^ both these spaces are significant

改写为:

token TOP { [ <hex_array> <.ws> ]+ <.ws> }

这将与您的输入相匹配。

结论

所以,在所有这些明显的复杂性之后,这实际上只是我详尽无遗的精确,我建议你可以将原始代码编写为:

my $a = "39 3A 3B ";
grammar Hex {
rule TOP { <hex_array> + }
token hex_array { <[0..9 A..F]>**1..2 }
};
Hex.parse($a);

这将比您的原始版本更灵活地匹配(我认为这将是一件好事,尽管当然对于某些用例可能不是),并且对于大多数 P6er 来说可能更容易阅读。

最后,为了强调如何避免rule的三个陷阱中的两个,另请参阅在perl6语法中松懈空白的最佳方法是什么?。(第三个问题是你是否需要在原子和量词之间放置一个空格,就像上面<hex_array>+之间的空间一样。

脚注

1如果要匹配多个字符,请将合适的量词附加到字符类。这是一种明智的方式,也是维基百科中"角色类"的假设行为。不幸的是,P6 文档目前混淆了这个问题,例如将真正的字符类和其他与预定义字符类标题下的多个字符匹配的规则混为一谈。

2默认ws规则旨在匹配单词,其中"单词"是字母(Unicode 类别 L)、数字 (Nd) 或下划线的连续序列。在代码中,它指定为:

regex ws { <!ww> s* }

ww是一个"字内"测试。所以<!ww>的意思不是在一个"词"内。<ws>总是会在s*会成功的地方成功——除了,与s*不同,它不会在一个词的中间成功。(像任何其他用*量子化的原子一样,一个普通s*总是匹配的,因为它匹配任意数量的空间,包括根本没有空间。

如果你不需要使用语法,你可以这样做:

my $a = "39 3A 3B  9:;";
say $a.split(/s+/).grep: * ~~ /<< <[0..9 A..F]> ** 2 >>/;

正则表达式将匹配这些 2 位六位字符串。无论如何,您的语法问题可能在于您使用的空格数量;从这个意义上说,他们非常严格。

最新更新