为了提供强大的正则表达式和 grammar,这里有一些代码布局和可读性的最佳实践,实际匹配的内容,以及避免常见的陷阱。

代码布局

如果没有 :sigspace 副词,在 Raku 正则表达式中空格并不重要。 使用它自己的优势,并插入空格,增加可读性。 此外,必要时插入注释。

比较非常紧凑的写法

  1. my regex float { <[+-]>?\d*'.'\d+[e<[+-]>?\d+]? }

和这种可读性更好的写法:

  1. my regex float {
  2. <[+-]>? # optional sign
  3. \d* # leading digits, optional
  4. '.'
  5. \d+
  6. [ # optional exponent
  7. e <[+-]>? \d+
  8. ]?
  9. }

根据经验, 在原子周围和组的内部使用空白; 将量词直接放在原子之后; 并垂直对齐开口和闭合关方括号和括号。

在括号或方括号内使用替换列表时,请对齐竖线:

  1. my regex example {
  2. <preamble>
  3. [
  4. || <choice_1>
  5. || <choice_2>
  6. || <choice_3>
  7. ]+
  8. <postamble>
  9. }

保持短小

正则代码通常比常规代码更紧凑。 因为他们用这么少的字符就做得那么多,所以保持了正则表达式的简短。

当你可以给正则表达式的一部分命名时,通常最好将它放入一个单独的,命名的正则表达式中。

例如,您可以以前面获取浮点正则表达式为例:

  1. my regex float {
  2. <[+-]>? # optional sign
  3. \d* # leading digits, optional
  4. '.'
  5. \d+
  6. [ # optional exponent
  7. e <[+-]>? \d+
  8. ]?
  9. }

并将其分解为部件:

  1. my token sign { <[+-]> }
  2. my token decimal { \d+ }
  3. my token exponent { 'e' <sign>? <decimal> }
  4. my regex float {
  5. <sign>?
  6. <decimal>?
  7. '.'
  8. <decimal>
  9. <exponent>?
  10. }

这有助于,特别是当正则表达式变得更加复杂时。 例如,您可能希望在存在指数的情况下使小数点可选。

  1. my regex float {
  2. <sign>?
  3. [
  4. || <decimal>? '.' <decimal> <exponent>?
  5. || <decimal> <exponent>
  6. ]
  7. }

要匹配什么

输入数据格式通常没有明确的规范,或者程序员不知道规范。 然后,按照你的期望自由是好的,但只要没有可能的含糊之处。

例如,在 ini 文件中:

  1. [section]
  2. key=value

section 标题内可以有什么内容? 只允许一个词可能限制性太强。 有人可能会写`[two words]`,或使用破折号等。而不是问内部允许什么,可能值得问一下:什么是不允许的?

显然,不允许闭合方括号,因为 [a]b] 是不明确的。 根据同一论点,应禁止开口方括号。 这让我们失望了

  1. token header { '[' <-[ \[\] ]>+ ']' }

如果你只处理一行就没问题。 但是,如果您正在处理整个文件,那么正则表达式会解析

  1. [with a
  2. newline in between]

这可能不是一个好主意。妥协是

  1. token header { '[' <-[ \[\] \n ]>+ ']' }

然后,在后处理中,从 section 标题中删除前导和尾随空格和制表符。

匹配空白

:sigspace 副词(或使用 rule 声明符而不是 tokenregex)非常便于隐式解析可能出现在许多地方的空格。

回到解析 ini 文件的例子,我们有

  1. my regex kvpair { \s* <key=identifier> '=' <value=identifier> \n+ }

这可能不像我们想要的那样的文字,因为用户可能在等号周围放置空格。 那么,我们可以试试这个:

  1. my regex kvpair { \s* <key=identifier> \s* '=' \s* <value=identifier> \n+ }

但那看起来很笨重,所以我们尝试别的东西:

  1. my rule kvpair { <key=identifier> '=' <value=identifier> \n+ }

可是等等! 值之后的隐式空格匹配会占用所有空格,包括换行符,因此 \n+ 没有任何东西可以匹配(并且 rule 也禁用了回溯,因此没有运气)。

因此,将隐式空格的定义重新定义为输入格式中不重要的空白非常重要。

这通过重新定义 token ws 来工作; 但是,它只适用于 grammars

  1. grammar IniFormat {
  2. token ws { <!ww> \h* }
  3. rule header { \s* '[' (\w+) ']' \n+ }
  4. token identifier { \w+ }
  5. rule kvpair { \s* <key=identifier> '=' <value=identifier> \n+ }
  6. token section {
  7. <header>
  8. <kvpair>*
  9. }
  10. token TOP {
  11. <section>*
  12. }
  13. }
  14. my $contents = q:to/EOI/;
  15. [passwords]
  16. jack = password1
  17. joy = muchmoresecure123
  18. [quotas]
  19. jack = 123
  20. joy = 42
  21. EOI
  22. say so IniFormat.parse($contents);

除了将所有正则表达式都放入 grammar 并将其转换为 tokens(因为它们无论如何都不需要回溯),有趣的一点是

  1. token ws { <!ww> \h* }

在进行隐式空格分析的时候会调用该 token。 当它不在两个单词字符之间( <!ww>,”在单词中”的否定断言)和零个或多个水平空格字符之间匹配。 对水平空格的限制很重要,因为换行符(垂直空格)会分隔记录,不应被隐式匹配。

不过,潜伏着一些与空白相关的麻烦。 正则表达式 \n+\n \n 之类的字符串不匹配,因为两个换行符之间有空白。 要允许此类输入字符串,请将 \n+ 替换为 \n\s*