1. 1. 前所未有的表现力
  • 笔者
  • 文章
  • 社交链接
  • 1. 正则表达式入门
    1. 1. 1-1 与文件搜索类比
    2. 2. 1-2 正则表达式简史
    3. 3. 1-3 Perl 6 正则表达式的新功能
    4. 4. 1-4 与正则表达式相关联的功能和方法
  • 2. Perl 6的正则表达式
    1. 1. 2-1 惯用法
    2. 2. 2-2 字面量
    3. 3. 2-3 元字符和字符类
      1. 3.1. 2-3-1 转义字符和预定义的字符类
      2. 3.2. 2-3-2 Unicode属性
      3. 3.3. 2-3-3 枚举的字符类和间隔
    4. 4. 2-4 量词
      1. 4.1. 2-4-1 量词的亲和力和节俭
    5. 5. 2-5 替代品(认识这个或那个)
      1. 5.1. 2-5-1 连词(认识到这一点和那个)
    6. 6. 2-6 锚点
    7. 7. 2-7 分组和捕获
      1. 7.1. 2-7-1 分组
      2. 7.2. 2-7-2 捕获
      3. 7.3. 2-7-3 非捕获分组
      4. 7.4. 2-7-4 捕获编号
      5. 7.5. 2-7-5 命名捕获
    8. 8. 2-8 助理规则或规则命名
    9. 9. 2-9 副词
      1. 9.1. 2-9-1 正则表达式副词
        1. 9.1.1. 2-9-1-1 副词“棘轮”:没有回溯
        2. 9.1.2. 2-9-1-2 副词“sigspace”(显着的空格)
        3. 9.1.3. 2-9-1-3 和Perl 5 兼容的副词
      2. 9.2. 2-9-2 认可副词
        1. 9.2.1. 2-9-2-1 副词“continue”(匹配的起始位置)
        2. 9.2.2. 2-9-2-2 副词“exhaustive”(所有匹配)
        3. 9.2.3. 2-9-2-3 副词“global”(所有匹配)
        4. 9.2.4. 2-9-2-4 副词“pos”
        5. 9.2.5. 2-9-2-5 副词“overlap”(带重叠)
    10. 10. 2-10 向前查看和向后查看(断言)
      1. 10.1. 2-10-1 before 断言
      2. 10.2. 2-10-2 after 断言
  • 3. Grammar
    1. 1. 3-1 该“砖”,以建立一个语法
    2. 2. 3-2 创建 Grammar
      1. 2.1. 3-2-1 语法的语法定义
      2. 2.2. 3-2-2 Grammar 的继承
    3. 3. 3-3 使用 Grammar
    4. 4. 3-4 类和对象的股票
      1. 4.1. 3-4-1 在确认期间执行代码
      2. 4.2. 3-4-2 在语法中执行代码的其他方法
    5. 5. 3-5 验证Perl模块名称的 Grammar
      1. 5.1. 3-5-1 验证 Grammar
      2. 5.2. 3-5-2 添加动作对象
    6. 6. 3-6 分析JSON的 Grammar
      1. 6.1. 3-6-1 JSON文档的结构
      2. 6.2. 3-6-2 示例JSON文档
      3. 6.3. 3-6-3 逐步写出JSON Grammar 的元素
        1. 6.3.1. 3-6-3-1 数字
        2. 6.3.2. 3-6-3-2 字符串
        3. 6.3.3. 3-6-3-3 JSON对象
        4. 6.3.4. 3-6-3-4 JSON表
        5. 6.3.5. 3-6-3-5 值
      4. 6.4. 3-6-4 JSON Grammar
      5. 6.5. 3-6-5 添加动作
    7. 7. 3-7 用于分析(伪)XML的 Grammar
    8. 8. 3-8 计算算术表达式(计算器)
    9. 9. 3-9 Grammar:先进的理念和观点
      1. 9.1. 3-9-1 角色构成
      2. 9.2. 3-9-2 设定规则
      3. 9.3. 3-9-3 递归规则和动态变量
      4. 9.4. 3-9-4 所谓原类型的规则
      5. 9.5. 3-9-5 继承和可变语法
      6. 9.6. 3-9-6 语法和语言可扩展性的变化
      7. 9.7. 3-9-7 展望
  • 4. 最佳实践和陷阱
    1. 1. 4-1 格式化代码
    2. 2. 4-2 限制尺寸
      1. 2.1. 4-2-1 识别浮点数
      2. 2.2. 4-2-2 识别复数
      3. 2.3. 4-2-3 识别URL
      4. 2.4. 4-2-4 识别IP地址
      5. 2.5. 4-2-5 识别URL的 Grammar
    3. 3. 4-3 要匹配什么?
    4. 4. 4-4 匹配空白
    5. 5. 4-5 避免递归左陷阱
    6. 6. 4-6 调试正则表达式或Perl 6 Grammar
  • 5. 结论
  • 6. 另见/来源
  • 7. 致谢
  • Perl 6 的正则表达式和文法

    前所未有的表现力 图片不可用

    Perl 5 正则表达式的强大功能使该语言成为分析文本数据的首选工具。从那时起,许多其他编程语言都复制了 Perl 的正则表达式,这部分地削弱了 Perl 在该领域中优于其他语言的优势。

    从 Perl 5 派生的新 Perl 6 语言创建了一个新的文本匹配模型,该模型源自正则表达式,但功能强大且富有表现力,并且从常规正则表达式中删除它已决定给它们一个新名称,正则表达式。

    不仅 Perl 的正则表达式6的机理是它大大高于现有的所有系统的正则表达式更强大,但它被设计成正则表达式相结合,构建 Grammar 语境,也就是说能够实现词汇和语法分析(系统词法和解析)到更复杂的数据,例如 HTML 文本,XML,XHTML,JSON,YAML,其中,不包括简单的情形,都超出正则表达式的范围。这些 Grammar 甚至可以分析所有级别的计算机程序。Perl 6 程序本身是用 Perl 6 自己编写的 Grammar 编译的。

    虽然它们远不是 Perl 6 的唯一创新,但我们相信 Perl 6 正则表达式和 Grammar 将至少与 Perl 的正则表达式一样彻底改变计算机语言,甚至可能更多。他们现在还没有时间这样做。

    本教程的讨论在 Perl 论坛上公开,地址如下: 评论

    笔者

    劳伦特罗森菲尔德

    文章

    发表于 2015年11月6日 - 更新于2018年 11月4日

    PDF, 版离线版, ePub, Azw, Mobi

    社交链接

    Viadeo 叽叽喳喳 Facebook的 在Google+上分享

    1. 正则表达式入门

    在正则表达式(或正则表达式)从数学和将字符串通常已知的形式语言的计算机科学理论概念模式(或模式)来描述一个整体(成品或不)字符串由模式定义的共同特征,根据预定义的语法并且不考虑上下文。的图案(匹配图案匹配)是应用这些模式到文本的样本,以试图找到对应于这些模式的文本片段的过程。

    在你的计算机上安装Perl 6

    如果你想使用 Perl 6,我们建议你在此地址下载 Rakudo Star。有关安装的更多信息,请参阅Perl 5到Perl 6教程的第一部分- 第1部分:语言基础知识

    在我们更新本文档(2018年10月)时,建议你选择 MoarVM 虚拟机。

    1-1 与文件搜索类比

    要在目录中搜索名称以字母 “a” 开头且扩展名为 .txt 的所有文件,可以在系统的提示符下写入:

    1
    2
    3
    ls  a*.txt    # shell Unix, Linux, etc.
    # ou :
    dir a*.txt # console DOS/cmd ou Powershell Windows, VMS, etc.

    使用的命令名称(ls或dir)因操作系统而异,但此处使用的原因相同:a*.txt。更确切地说,它意味着字母 “a”,后跟任意数量的字符,后跟字符串 “.txt ”。

    使用的命令将在屏幕上显示当前目录中名称与模式 a*.txt 相关的所有文件 。过滤文件的名称在目录(或这样夹有图案 DOS/Windows 的术语)是正则表达式的工作原理:该模式所描述的,一般由左到右,一系列的元素必须在目标字符串中遇到(这里是文件名):首先是字母 “a”,然后是任何字符,然后是字符串 “.txt”。

    类比结束于此,因为编程语言中常用的正则表达式的语法赋予元字符 “*” 不同的含义(见第2.4节 )。

    1-2 正则表达式简史

    正则表达式的使用是在1970年由计算机科学家 Ken Thompson,UNIX 的创作者第一个实现的,在 QED 和 ED 编辑器中(其很少使用)和 Unix 命令 grep,现在还很常用。

    例如,在 Unix 或 Linux上,以下 grep 命令显示 text.txt 文件中包含字母“ab”的所有行,后跟任何字符,后跟字母“d”:

    1
    $> grep ab.d texte.txt

    在ab.d中,元字符 “.” 表示“任何单个字符”,上面的命令可以显示文件的以下行:

    1
    2
    3
    4
    5
    abcd
    abvd ...
    ... abyd
    ... xyabcdz ...
    xyab3dgh

    现在仍在广泛使用的其他使用正则表达式的 Unix 实用程序也快速出现了:sed,vi,awk, lex,emacs,egrep 等。

    Perl 语言(Perl 1出现于1987年12月)可能是第一个通用编程语言(除了awk,它不是通用语言,并且非常特别)具有集成的正则表达式,随后其它语言跟随。

    很快,Perl(尤其是自1994年以来的Perl 5)大大扩展了它的正则表达式,以至于他们早已不再是严格意义上的“常规”或“理性”,而是提供另一方面,由于它们被许多编程语言(如Tcl,Python,PHP,Ruby,.NET,Java,JavaScript,Delphi等)复制而增加了表现力。Perl 5 对这些语言的影响是这样的,他们大多使用一种叫做“Perl兼容的正则表达式”(PCRE)库:简而言之,吸引用户,有必要强调的事实,提出的解决方案遵循 Perl 的“扩展正则表达式”的语法。

    1-3 Perl 6 正则表达式的新功能

    使用 Perl 6 时,正则表达式总是使用从模式的左到右相同的渐进式匹配方法,但它们更不规则(严格意义上是原始的非上下文形式语言)并且甚至比那些更强大从Perl 5开始,所以决定放弃术语“正则表达式”来引用它们并将它们命名为正则表达式。随着时间的推移添加新功能最终使 Perl 的正则表达式语法有些蓬松,这在很大程度上是因为 Perl 5 一直专注于尽可能保持向后兼容。

    Perl 6 的正则表达式不再是 Perl 5 的正则表达式

    Perl 的正则表达式(尤其是Perl 5)已经显着影响了许多其他编程语言,以至于它们已成为事实上通常采用的标准(由PCRE库证明)。

    注意,虽然他们保持清晰的相似性,Perl 6 的正则表达式重新制作,不再遵循 Perl 5 建立的标准。这种重新设计使它们更清晰,最重要的是更强大。因此,与PCRE不同,Perl 6 的正则表达式与 Perl 5 的正则表达式不兼容。

    未来将告诉我们这个彻底打破常规的决定是否会被其他语言所遵循,以及 Perl 6 正则表达式是否会成为事实上的标准。我们认为 Perl 6 值得。

    注意 :但是,可以在 Perl 6 程序中使用副词 :P5:Perl5 以使用 Perl 5 正则表达式语法(参见 2.9.1.3。)。然后我们找到与Perl 5兼容的正则表达式。

    Perl 6 决定成为一种新语言并放弃这种向后兼容的要求,它允许相当深入地重写正则表达式的语言,使其大大扩展并使其更加连贯和合乎逻辑。

    此外,下面的许多示例将显示如何构建简单的正则表达式并给正则表达式起个名字,以便它们可以像组装块一样组装成模式。越研究就越复杂。例如,IPv4 地址由四个字节组成(通常由十进制表示的四个整数表示,在0到255之间,由点号分隔)。这是很容易编写的第一个 Perl 6 正则表达式,因为这样的被称为字节,并检查它具有令人满意的数量在适当的条件,则装配一个新的正则表达式匹配那些字节四个,用点号分隔(见第4.2.4节)

    但是,Perl 6 正则表达式带来的真正革命是它们可以构建更强大的实体,即 Grammar(见 §3)。Grammar 是定义语法的形式主义,因此也是一种形式语言。在 Perl 6 中,它采用一组规则的形式,并命名为正则表达式,并允许逐步构建一个文本匹配系统,其结构远远少于正则表达式所能匹配的结构(甚至扩展)因此,Grammar 允许对文本进行词法和句法分析,例如计算机程序的源代码,以便编译它。Perl 6 有自己的 Perl 6 Grammar,用 Perl 6编写。

    正则表达式有时被认为是抽象的,难以理解。正则表达式使用简单的概念是比较难比的条件,了解构造,如果和循环,同时或为Perl语言本身。事实上,学习正则表达式的真正挑战在于理解通常用于表达这些概念的非常简洁,甚至是简洁的符号。事实上,Perl 6 通过提供插入空格,注释等能力,可以大大简化对正则表达式的理解。总是可以继续编写正则表达式非常简洁,有时候有点难以破译,有时候对于简单的问题就足够了,但没有人有义务。在本文档末尾附近格式化代码一章的示例中,显而易见。

    1-4 与正则表达式相关联的功能和方法

    虽然这不是本教程的主题,但使用正则表达式之前简要回顾一些 Perl 6 运算符很有用。

    在 Perl 6 中,基体操作者检查的字符串是否匹配的模式是操作员~~智能匹配(智能操作者匹配)。例如:

    1
    say "Reconnu" if "abcdef" ~~ /ab.d/;    # -> Affiche : Reconnu

    这里,要解析的字符串是“abcdef”和正则表达式/ab.d/的模式。动机得到认可,因为人们可以在动机的四个原子之间建立对应关系(点“ 。 ”匹配一个任意的角色,因此,在这里,它匹配“c”)和角色链的一部分; 该模式描述了链的这一部分。当然,/ ab.d /模式也会匹配出例如字符串“ abwd ef”,“ ab7d ef”或“su abZd a”。

    需要注意的是智能匹配的操作~~这里用来连接字符串,用于在Perl 6的许多其他事情正则表达式模式(例如检查的项目在数组中,或检查变量与类型的兼容性等),但这不是本文档的目的(参见例如 智能匹配操作员)。

    如果要解析的字符串存储在默认变量$ _中,则不需要存在智能匹配运算符,并且可以直接在布尔上下文中评估正则表达式:

    1
    2
    3
    if / ^ab / {
    say "La chaîne $_ commence par les lettres 'ab'";
    }

    可以使用智能匹配操作符的否定形式 !~~

    1
    2
    3
    4
    say "Chaîne 'ab' non trouvée" if "fedcba" !~~ /ab/; 
    # -> Chaîne 'ab' non-trouvée
    # Équivalent à :
    say "Chaîne 'ab' non trouvée" unless "fedcba" ~~ /ab/;

    Perl 6 还允许使用 .match 这种面向对象的语法:

    1
    say "Reconnu" if "abdcef".match(/c.f/);   # -> Reconnu

    正则表达式还允许你进行替换:

    1
    2
    3
    my $chaîne = "abcde";
    $chaîne ~~ s/bc/CB/;
    say $chaîne; # -> affiche aCBde

    还有一个 .subst 方法来执行替换(但不是就地替换 ///):

    1
    2
    my $chaîne = "abcde";
    my $chaîne-modifiée = $chaîne.subst(/cd/, "DC"); # -> abDCe

    但是,可以直接修改 $string 变量(不创建新变量),也可以将其放在赋值的左侧部分:

    1
    2
    my $chaîne = "abcde";
    $chaîne = $chaîne.subst(/cd/, "DC") # -> abDCe

    .subst 方法的第一个参数可以是正则表达式或字符串。

    split 函数和 .split 方法按照分割符将字符串划分为子字符串列表, 其第一个参数也可以使用字符串或正则表达式:

    1
    2
    3
    4
    5
    say split(';', "a;b;c,d").perl;        # ("a", "b", "c,d").Seq
    say split(/\;/, "a;b;c,d").perl; # ("a", "b", "c,d").Seq
    say split(/<[;,]>/, "a;b;c,d").perl; # ("a", "b", "c", "d").Seq
    # Version syntaxe de méthode orientée objet :
    say "a;b;c,d".split(/<[;,]>/).perl; # ("a", "b", "c", "d").Seq

    comb 函数和 .comb 方法返回字符串上模式的匹配(贪婪)列表:

    1
    2
    3
    say join " ", comb /\d+/, "jeu du 7, 14 et 21";    # -> 7 14 21
    # syntaxe de méthode :
    say "3 fois 6 font 18".comb(/\d+/).join(" "); # -> 3 6 18

    在不特定于正则表达式的情况下,建立布尔条件的其他函数或方法可以使用正则表达式模式(或其他内容,例如数字比较)来定义此条件。因此,函数 first 返回满足条件的列表的第一个元素,函数 grep 返回满足条件的列表的所有元素,正则表达式可写为:

    1
    2
    3
    say first /ma/, <jan fév mar avr mai>;             # -> mar
    say grep /ma/, <jan fév mar avr mai>; # -> (mar mai)
    say <jan fév mar avr mai>.grep(/v/); # -> (fév avr)

    given-when构造(Perl 6 的“switch”)也经常使用正则表达式:

    1
    2
    3
    4
    5
    6
    my $var = '42';
    given $var {
    when /^4/ { say "Commence par '4'"; proceed};
    when /2$/ { say "Finit par '2'"; proceed};
    when /^42$/ { say "Réponse à la Grande Question sur l'Univers" }
    }

    2. Perl 6的正则表达式

    2-1 惯用法

    Perl 6 提供了以下用于编写正则表达式的语法结构:

    1
    2
    3
    m/abc/;         # 立即应用于 $_ 的正则表达式
    rx/abc/; # Regex 类型的正则表达式对象
    /abc/; # Regex 类型的正则表达式对象

    前两个语法可以使用除斜杠之外的其他分隔符:

    1
    2
    m{abc};         # 或者 m[abc];
    rx{abc}; # 或者 rx!abc!;

    但请注意,冒号(“:”)不能用作正则表达式的分隔符。普通括号 “( “ 和 “)” 只能作为分隔符,如果左括号由前面的rx 运算符的至少一个空格分隔(这是 Perl 6 的一般规则:括号前面标识符旁边的开头被分析为函数调用的开头):

    1
    2
    my $regex = rx(toto);  # 错误,解释为函数调用
    my $regex = rx (toto); # OK

    通常,默认情况下会忽略模式中的空格,除非使用(显式或隐式)副词 :s:sigspace,请参见第2.9.1.2节 (下文)。

    1
    say "Reconnu" if "abc" ~~ /a b  c /;    # -> "Reconnu"

    正如在 Perl 6 的注释,注释通常以 # 字符开始(通常称为字符英镑错误,英镑是不同的:“ ♯ ‘),除非蜘蛛被转义字符保护’ \“,并转到行的末尾(除非括号用作分隔符,在这种情况下,最好不要尝试将其用作注释开始字符)。多行注释也是可能的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # Commentaires unilignes :
    my $regex = rx {
    abc # chaîne littérale 'abc'
    \d # suivie d'un chiffre
    \w # puis d'un caractère alphanumérique.
    };

    # Commentaire multiligne :
    my $regex = rx {
    abc \d \w #`[ chaîne littérale 'abc' suivie d'un
    chiffre puis d'un caractère
    alphanumérique quelconque.
    ]
    };
    say "Reconnu" if "XYabc6QUVW" ~~ /<$regex>/; # reconnaît 'abc6Q'

    2-2 字面量

    正则表达式的匹配模式的最简单情况是常量字符串。在这种情况下,匹配模式是将模式作为字符串的子字符串查找:

    1
    2
    3
    4
    my $chaîne = "Esperluette est le nom parfois donné au signe &";
    if $chaîne ~~ m/ perl / {
    say '$chaîne contient "perl"'; # -> $chaîne contient "perl"
    }

    所有字母数字字符(Unicode)和带下划线或下划线的字符(“ _ ”)都是字面匹配。所有其他字符(标点符号,符号等)必须由反斜杠转义字符(“ \ ”)保护或引用单引号(或单引号):

    1
    2
    3
    4
    5
    / 'deux mots' /    # reconnaît 'deux mots', espace blanc compris
    / "a:b" / # reconnaît 'a:b', caractère deux-points compris
    / '#' / # reconnaît le caractère croisillon (ou hash)
    /moi\@gmail\.com/ # échappements pour protéger l'@ et le .
    /'moi@gmail.com'/ # équivalent à : /moi\@gmail\.com/

    当受转义字符保护时,字母数字字符通常具有特定含义:例如,元字符\ d表示可以表示任意数字(Unicode)的字符类; 稍后将给出许多例子(特别是在第2.3.1节中 )。

    从左到右搜索字符串,因此,例如,子字符串等于模式:

    1
    2
    3
    4
    5
    6
    7
    if 'abcdefg' ~~ / de / {
    say ~$/; # de -> motif reconnu
    say $/.prematch; # abc -> ce qui précède le motif reconnu
    say $/.postmatch; # fg -> ce qui suit le motif reconnu
    say $/.from; # 3 -> position du début de la reconnaissance
    say $/.to; # 5 -> position de ce qui suit la reconnaissance
    };

    匹配结果存储在变量$ /中(表示匹配对象,即使英文单词有时也意味着“ 匹配类型的对象”,它将在本文档中被“匹配对象”翻译 ),以及也通过承认来提及。如果匹配成功,则结果为Match类型,否则为Nil。

    2-3 元字符和字符类

    甲字符类是不能匹配的单个特定的字符正则表达式语法元素,但一个字符属于范围可能与共同的特征的字符(例如匹配任何数字0至9,或任何小写字母字符)。

    (“ 。 ”)匹配任何简单字符(除非前面有转义字符,在这种情况下它匹配文字点):

    1
    2
    3
    4
    5
    6
    7
    'perl'   ~~ /per./;       # Reconnaît toute la chaîne
    'perl' ~~ / per . /; # Idem (espaces blancs ignorés);
    'perl' ~~ / pe.l /; # Idem: le . reconnaît le r
    'Épelle' ~~ / pe.l/; # Idem: le . reconnaît le premier l

    'perl' ~~ /. per / # Pas de reconnaissance:
    # le . ne reconnaît rien avant la chaîne per

    与Perl 5和许多其正则表达式系统派生自Perl 5的语言不同,点也始终匹配换行符。

    2-3-1 转义字符和预定义的字符类

    反斜杠(或反斜杠)形式的预定义字符类后跟一个字母,例如\ w。如果字母是大写的(\ W),则它是对应于相同小写字母的字符类的否定(即\ W识别\ w无法匹配的任何字符):

    • 字母数字字母(字母,数字和_):\ w(补码:\ W); 匹配例如一个,Ç,Ž,7个字符的Unicode 0041甲LATIN CAPITAL LETTER甲,0031 1一个数字,03B4δGREEK小写字母DELTA或0409ЉCYRILLIC CAPITAL LETTER LJE);
    • 数字字符 :\ d,和\ d(以Unicode意义上,不只是我们的阿拉伯数字单位数字:例如,U + 0E53 3 DIGIT三泰(泰图3)是由\ d识别);
    • 水平空间 l:\ h和\ H(白色空格,制表符,U + 00A0 NO-BREAK SPACE);
    • 垂直空间 :\ n和\ N;
    • 空间(水平或垂直) :\ s和\ S ; 例如,在字符串’包含以m开头的单词’中,表达式/ m \ S + /匹配’word’。
    • 制表(U + 0009):\ t和\ T ;
    • 垂直空间 :\ v和\ V(例如U + 000A LINE FEED,U + 000C CARRIAGE RETURN等)。

    2-3-2 Unicode属性

    上面看到的字符类对常见情况很有用。Unicode 属性的使用允许更系统和更精细的方法。调用语法的格式为 <:property>,其中 “property” 可以是 Unicode 属性的短名称或长名称。Unicode 属性本身的确切含义不是由Perl 定义的,而是由 Unicode 标准定义的。

    以下是最常见的Unicode属性列表:

    简称 长名 意思和评论
    L Letter 字母
    LC Cased_Letter 带大小写的字母(区分大小写)
    Lu Upper_Cased_Letter或Upper 大写字母(大写)
    Ll Lower_Cased_Letter或Lower 小写字母(小写)
    N Number 数字
    Nd Decimal_Number或数字 十进制数(数字)
    Nl LETTER_NUMBER 号码信
    P Punctuation or punct 标点符号
    Pd Dash_Punctuation 标点符号类型破折号
    Ps Open_Punctuation 打开标点符号
    Pe Close_Punctuation 结束标点符号
    S Symbol 符号
    Sm Math_Symbol 数学符号
    Sc CURRENCY_SYMBOL 货币符号(例如$,£或€)
    Z Separator 分隔符
    Zs SPACE_SEPARATOR 空白分隔符
    Zl LINE_SEPARATOR 行分隔符
    Zp Paragraph_Separator 段落分隔符

    例如,<:Lu> 匹配单个大写字母。

    使用 <:!property> 形式获取否定的 Unicode 属性,例如 <:!Lu> 将匹配任何不是大写字母的单个字符。

    可以使用以下固定运算符组合多个属性:

    操作者 备注
    + 设置联盟 或属性之间的逻辑(金)
    \ 设置联盟 或属性之间的逻辑(金)
    设置交叉点 和属性之间的逻辑(和)
    - 设定差异 拥有第一个属性而不是第二个属性
    ^ 交点对称集 或属性之间的独占逻辑(XOR)

    例如,要匹配小写字母或数字,可以写入:<:Ll +:N>或<:Ll +:Number>或甚至<+:Lowercase_Letter +:Number>。

    也可以使用括号对类别和类别组进行分组,例如:

    1
    'perl6' ~~ m{\w+(<:Ll+:N>)}       # 0 => 「6」

    2-3-3 枚举的字符类和间隔

    有时,元字符和预定义的字符类是不够的。幸运的是,通过放置<[…]>任意数量的字符和字符间隔(在这些间隔的边界之间有两个点“ .. ”)来定义一个人自己的字符类很容易,或者没有空格:

    1
    "abacabadabacaba" ~~ / <[ a .. d 1 2 3 ]> / # Vrai

    可以在<…>中使用与Unicode类别(+,|,&,-,^)相同的运算符来组合多个区间定义,甚至可以将它们与上面的Unicode类别; 我们也可以在括号中使用用反斜杠定义的字符类:/ <[\ d] - [13579]> /,顺便说一下,它与/ <[02468]不同。 > /,因为第一个也匹配非阿拉伯数字。

    在开放的V形符号之后用“ - ”符号获得这类字符的否定:

    1
    2
    say 'pas de guillemets' ~~ /  <-[ " ]> + /;  
    # reconnaît les caractères autres que "

    解析用引号分隔的字符串,使用使用字符类否定的模式是很常见的:

    1
    2
    say 'entre guillemets' ~~ / '"' <-[ " ]> * '"'/;
    # un guillemet, suivi de non-guillemets, suivi d'un guillemet

    2-4 量词

    量化器使得有可能匹配,而不是一次,而是固定或可变次数,即它之前的原子。例如,量化器“ + ”试图匹配上述中的一个或多个。

    量化器具有比串联更高的优先级,因此/ ab + /匹配字母a后跟字母b的一倍或多倍。使用撇号反转情况:/‘ab’+ /匹配字符串’ab’,’abab’’ababab’等。

    量词 注释或示例
    + 一个或多个 匹配前面的原子一次或多次,没有上限
    * 0次或更多次 例如,要在a和b之间允许一个或多个可选空格:/ a \ s * b /
    0或1次 例如,对于单个可选字符
    ** min..max min和max之间的任意次数 说Bool(’a’~~ / a 2..5 /); # - > 假说Bool(’aaa’~~ / a 2..5 /); # - >是的
    ** 恰好n次 说Bool(’yyyy’~~ / a 5 /); # - > 假说Bool(’aaaaa’~~ / a 5 /); # - >是的
    量化器修改 为了更容易使用CSV,应用于上述量词之一的%修饰符允许匹配指定重复确认之间必须存在的分隔符。例如:/ a +%’,’/匹配’a,a’或’a,a,a’等,但ni’a,’,ni’a,a,’。

    2-4-1 量词的亲和力和节俭

    默认情况下,+和*量化器是贪婪或贪婪的,也就是说,它们在链中寻找最长的匹配。例如:

    1
    say ~$/ if 'aabaababa' ~~ / .+ b /;      # -> aabaabab

    在这里,子正则表达式。+搜索的任何字符,最长的可能字符串尚未承认正则表达式的其余部分,即原子b,这可能是目标。但经常发生这是初学者的错误,而且目标是匹配“任何角色直到第一个b ”。在这种情况下,优选使用量词非贪婪(或“节俭”),得到通过用问号加上后缀原始计数,给予任+?那是*?。例如:

    1
    say ~$/ if 'aabaababa' ~~ / .+? b /;     # -> aab

    2-5 替代品(认识这个或那个)

    要匹配几种可能性中的一种,必须用“ || ”将它们分开 “; 发现的第一个匹配(从左到右)获胜。例如,初始化文件(config.ini样式)通常具有以下形式:

    1
    2
    [section]
    clef = valeur

    从这样的文件中读取一行时,它可以是一个段或一个键值对。作为第一种方法,用于读取此类文件的正则表达式可以是:

    1
    / '[' \w+ ']' || \S+ \s* '=' \s* \S* /

    也就是说:

    • 括号中的一个词;
    • 是由比白空间的其他字符,接着0或多个空格的字符串,然后等号“=”随后再次可选的空间,随后除白色空间的其他字符的进一步的字符串。

    还有另一种形式的替代方案,使用分隔符“ | (而不是“ || ”)。我们的想法是一样的,但是这是最长的匹配(无论是在第一),其被保留(只要你开始在同一位置),这意味着它们必须全部(可能并行测试)并进行比较。

    以下示例说明了两个运算符之间的区别:

    1
    2
    3
    4
    5
    my $chaîne = "abcdef";
    say ~$/ if $chaîne ~~ /ab || abcde/; # -> ab
    say ~$/ if $chaîne ~~ /ab | abcde/; # -> abcde
    say ~$/ if $chaîne ~~ /ab | bcde/; # -> ab
    # dans ce dernier cas, la reconnaissance la plus à gauche l'emporte

    最长匹配的规则对于属于例如Perl代码的字符串的词法分析特别有用。因此,它允许Perl语法匹配包含连字符的标识符(变量,函数等)(假设它们后跟字母字符):Perl能够匹配由于这种最长匹配规则,这样的标识符(并且例如将其与较少放置在两个不同符号之间的算术运算符区分开)。

    2-5-1 连词(认识到这一点和那个)

    替代方案在由运算符“ | ”分隔的替代项的术语之间建立逻辑或(分离)»或«« | ”。

    还有运算符“ & ”和“ && ” 在它们分开的术语之间建立逻辑和(结合)。如果两个术语都被匹配并且它们匹配相同的子串(匹配的相同开头和相同末尾),则正则表达式只会成功:

    1
    2
    3
    my $regex = rx/a..d & .bcd/;
    say "Reconnu" if "XZabcdZ" ~~ /$regex/; # -> Reconnu
    say ~$/; # -> abcd

    2-6 锚点

    正则表达式引擎尝试通过从左到右搜索来查找字符串中的匹配项。

    1
    2
    3
    say so 'Saperlipopette' ~~ / perl /;   # True 
    # ^^^^
    # (so renvoie une évaluation booléenne, donc, en fait True ou False)

    但这并不总是你想要的。例如,我们可能想要匹配整个字符串,整行,或一个或多个整个单词,或者重视发生匹配的字符串的位置(例如,仅在字符串的开头) 。锚(以及后面第2.10节中讨论的断言 )允许指定匹配将在何处发生。

    必须为整个正则表达式匹配正则表达式的锚点,但锚点不会消耗链中的字符。

    注释或示例
    ^ 字符串的开头 ‘Saperlipopette’~~ / perl /; # True ‘Soperlipopette’~~ / ^ perl /; #False ‘perl’~~ / ^ perl /; #True
    ^^ 行首 ^^匹配字符串的开头或返回字符后面的内容
    $$ 行尾 $$匹配字符串的结尾或后跟新行的字符
    << 限于单词的左侧 换句话说,这个词的开头。或者,更准确地说:左边的非单词字符和右边的单词字符之间的边界。
    >> 限于单词的右侧 或者结尾。例如: ‘Carpe diem’~~ / arpe >> /; # true’Carpe diem’~~ / die >> /; #fake
    $ 字符串的末尾 ‘Carpe diem’~~ / arpe \$/; # false ‘Carpe diem’~~ / diem \$/; #true

    2-7 分组和捕获

    2-7-1 分组

    在常规 Perl 6 代码中(对于正则表达式),圆括号用于对表达式进行分组,通常用于更改执行优先级:

    1
    2
    say 1 + 4 * 2;      # 9, 解析为: 1 + (4 * 2)
    say (1 + 4) * 2; # 10

    我们可以使用相同的想法对正则表达式的元素进行分组:

    1
    2
    / a || b c /        # 匹配 'a' 或 'bc'
    / ( a || b ) c / # 匹配 'ac' 或 'bc'

    相同的分组技术可以应用于量词:

    1
    2
    3
    4
    / a b+ /            # 匹配一个 'a', 后跟一个或多个 'b'
    / (a b)+ / # 匹配一个或多个 'ab' 序列
    / (a || b)+ / # 匹配 'a' 和 'b' 的任何序列
    # 至少一个字符 'b'

    2-7-2 捕获

    圆括号不仅用于分组,它们还用于捕获,也就是说,它们将被匹配的部分存储在可重用变量的括号中,以及对象元素的形式认可。

    1
    2
    3
    4
    5
    6
    my $str =  'nombre 42';
    if $str ~~ /'nombre ' (\d+) / {
    say "The number is $0"; # The number is 42
    # 或
    say "The number is $/[0]"; # The number is 42
    }

    如果有几对括号,它们从左到右编号,从零($0$1$2 等)开始,与 Perl 5 不同,其捕获从$1开始。

    1
    2
    3
    if 'abc' ~~ /(a) b (c)/ {
    say "0: $0; 1: $1"; # 0: a; 1: c
    }

    变量 $0$1 等 实际上是捷径语法。$/ 匹配对象作为一个列表,以至于 $0实际上是语法糖$/[0]$1$/[1] 等等。

    强制匹配对象到列表上下文允许轻松访问所有元素:

    1
    2
    3
    if 'abcdef' ~~ /(a) b (c) (d) e (f)/ {
    say $/.list.join: ', ' # a, c, d, f
    }

    2-7-3 非捕获分组

    圆括号提供双重功能:它们对正则表达式中的元素进行分组,并捕获括号内已匹配的内容。

    为了仅保留分组行为(不捕获),我们可以使用方括号而不是圆括号:

    1
    2
    3
    if 'abc' ~~ / [a||b] (c) / {
    say ~$0; # c
    }

    如果你不需要捕获,那么使用非捕获组有三个优点:开发人员的意图更清晰,更容易计算你需要的捕获组,速度更快一点。

    2-7-4 捕获编号

    上面已经说过,捕获组从左到右编号。这在原则上是正确的,但它有点过于简化。列出以下规则以使本文档尽可能完整,但如果你发现自己经常使用它,那么最好考虑命名捕获(第2.7.5节 )或甚至Sub - 命名规则(第2.8节 )。

    备选分支将捕获的编号重置为0:

    1
    2
    / (x) (y)  || (a) (.) (.) /
    # $0 $1 $0 $1 $2

    例如:

    1
    2
    3
    4
    if 'abc' ~~ /(x)(y) || (a) (.) (.)/ {
    # $0 $1 $2
    say ~$1; # b
    }

    如果备选分支的不同选项(或多或少有点复杂)具有不同数量的捕获,则具有最大捕获编号的那个选项(逻辑上)确定下一个捕获的索引。

    1
    2
    3
    4
    5
    $chaîne = 'abcd';
    if $chaîne / a [ b (.) || (x) (y) ] (.) / {
    # $0 $0 $1 $2
    say ~$2; # d
    }

    捕获可以嵌套,在这种情况下,它们按级别编号。

    1
    2
    3
    4
    if 'abc' ~~ / ( a (.) (.) ) / {
    say "Outer: $0"; # Outer: abc
    say "Inner: $0[0] and $0[1]"; # Inner: b and c
    }

    2-7-5 命名捕获

    可以给捕获命名,而不是对捕获进行编号。命名捕获的通用(有点健谈)方式是:

    1
    2
    3
    if 'abc' ~~ / $<my_name> = [ \w+ ] / {
    say ~$<my_name> # abc
    }

    访问命名快照 $<myname> 实际上是一个快捷方式,可以将匹配对象作为散列值访问,即$/{'my_name'}$/<my_name>

    强制匹配对象在散列上下文中提供了一种访问所有命名快照的简单方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    if 'count=23' ~~ / $<variable>=\w+ '=' $<value>=\w+ / {
    my %h = $/.hash;
    say %h.keys.sort.join: ', '; # OUTPUT: «value, variable␤»
    say %h.values.sort.join: ', '; # OUTPUT: «23, count␤»
    for %h.kv -> $k, $v {
    say "Found value '$v' with key '$k'";
    # outputs two lines:
    # Found value 'count' with key 'variable'
    # Found value '23' with key 'value'
    }
    }

    下一节(子规则或命名规则)提供了访问命名捕获的通常更方便的方法。

    2-8 助理规则或规则命名

    可以将正则表达式放入命名规则中,就像你可以将代码片段放入函数(或子例程)或方法中一样。

    1
    2
    3
    4
    my regex ligne { \N*\n }
    if "abc\ndef" ~~ /<ligne> def/ {
    say "Première ligne: ", $<ligne>.chomp; # Première ligne: abc
    }

    命名正则表达式可以使用语法my regex regexname {正则表达式的主体}声明自己,然后使用调用。此外,调用命名的regex ipso facto会创建一个具有相同名称的命名快照(上例中的$ )。

    但是,如果需要,可以使用以下调用语法<capture_name = regexname>为捕获提供另一个名称 ; 然后,你可以使用符号$ <capture_name>访问捕获。我们举例说明在第3.9.1节的语法MultDiv中 使用此功能角色的组成)。

    如果你不需要捕获,则前缀<.name_regex>的点的正则表达式的名称将删除捕获。

    这里是一个小全码(但仍然还相当有限)的摘录,分析文件类型的config.ini的§ 2.5

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    my regex header { \s* '[' (\w+) ']' \h* \n+ }
    my regex identifier { \w+ }
    my regex kvpair { \s* <key=identifier> '=' <value=identifier> \n+ }
    my regex section {
    <header>
    <kvpair>*
    }

    my $contents = q:to/EOI/;
    [passwords]
    jack=password1
    joy=muchmoresecure123
    [quotas]
    jack=123
    joy=42
    EOI

    my %config;
    if $contents ~~ /<section>*/ {
    for $<section>.list -> $section {
    my %section;
    for $section<kvpair>.list -> $p {
    %section{ $p<key> } = ~$p<value>;
    }
    %config{ $section<header>[0] } = %section;
    }
    }
    say %config.perl;

    # OUTPUT: «{:passwords(${:jack("password1"), :joy("muchmoresecure123")}),
    # :quotas(${:jack("123"), :joy("42")})}»

    命名正则表达式可以分组为语法(见第3节 ),并且通常需要这样做(命名正则表达式的目的正是为了构建语法)。

    有预定义的子规则,或多或少对应于之前看到的字符类,例如:

    • <ident> :标识符;
    • <upper> :单个大写字母;
    • <lower> :单个小写字母;
    • <alpha> :单个字母字符或带下划线的字符(对于没有带下划线字符的Unicode字母字符,请使用 <:alpha> ;
    • <digit> :单个十进制数;
    • <xdigit> :单个十六进制数;
    • <print> :单个可打印字符;
    • <punct> :一个标点字符;
    • <alnum> :单个字母数字字符(相当于<alpha> + <digit>);
    • <wb> :单词边界,零长度断言;
    • <space> :单个空格(与 \s 相同);
    • 在 pattern 之前 :在null length 之前断言,也就是说,验证一个是否处于匹配模式的位置,并且如果成功则返回零大小的已匹配对象(同样存在一个子排除离职后的图案向后)。另见第2.10节)。

    在Synopse S05中可以找到更完整的列表。

    如在下一章讨论的,名为正则表达式或子规则也可以与关键字定义的 token 或 rule(而不是 regex),其改变方式的正则表达式引擎将分析链隐式使用副词:棘轮和/或(视情况而定):sigspace(参见下面的2.9.1.12.9.1.2节)。声明的语法如下:

    1
    2
    3
    my token nom-de-règle { ... }
    # 或:
    my rule nom-de-règle { ... }

    2-9 副词

    该副词(对应于所谓的改性剂在Perl 5 Perl的正则表达式5和包在其他语言如使用PCRE)改变工作方式正则表达式,并允许一些非常方便的快捷键重复的任务。

    有两种副词:正则表达式副词适用于定义正则表达式的地方,以及匹配副词,其中模式匹配字符串。区别有时是模糊的,因为匹配和定义通常是文本近似的,但使用方法匹配的语法有助于澄清差异。

    ‘abc’~~ /../大致相当于’abc’.match(/../),甚至可以用两个不同的行更清楚地写出来:

    1
    2
    3
    4
    my $regex = /../;           # définition, deux caractères quelconques
    if 'abc'.match($regex) { # reconnaissance
    say "'abc' a au moins deux caractères";
    }

    正则表达式副词如:i(忽略大小写,即大写和小写字母之间的区别)进入定义,而匹配副词如:重叠被添加到调用匹配:

    1
    2
    3
    4
    5
    6
    7
    my $regex = /:i . a/;
    for 'baA'.match($regex, :overlap) -> $m {
    say ~$m;
    }
    # Affiche :
    # ba
    # aA

    2-9-1 正则表达式副词

    声明正则表达式时出现的副词是正则表达式的一部分,并影响Perl 6编译器如何将正则表达式转换为二进制代码。

    例如,副词:ignorecase或:i(忽略大小写)告诉编译器忽略大写和小写字母之间的区别。因此,’a’~~ / A /是假,而’a’~~ /:i A /被成功匹配。(有一个变体:ii或:samecase,在替换的情况下,确保替换字符串与匹配的字符串在相同的情况下。)

    正则表达式的副词可以放在正则表达式语句之前或之内,并且只会影响下一个正则表达式的那部分。

    这两个正则表达式是等价的:

    1
    2
    my $rx1 = rx:i/a/;      # avant
    my $rx2 = rx/:i a/; # à l'intérieur

    但这些不是:

    1
    2
    my $rx3 = rx/a :i b/;   # insensible à la casse seulement pour b
    my $rx4 = rx/:i a b/; # complètement insensible à la casse

    括号和括号限制了副词的范围:

    1
    2
    / (:i a b) c /          # reconnaît 'ABc' mais pas 'ABC'
    / [:i a b] c / # reconnaît 'ABc' mais pas 'ABC'

    2-9-1-1 副词“棘轮”:没有回溯

    副词:棘轮或:r告诉正则表达式引擎不要返回(回溯)。英语单词棘轮指非回捕(如棘轮扳手),机械系统,防止设备回去(并迫使从而隐含前进)。

    如果没有这个副词,正则表达式的不同部分将尝试不同的方式来匹配字符串以允许正则表达式的其他部分匹配。例如,使用正则表达式’abc’~~ / w + ./,\ w +部分以消耗整个字符串abc开始,然后在“ 。 以下是。然后有退格(或返回跟踪),也就是说\ w +放弃最后一个字符并且只识别ab,这允许“ 。 “成功确认Ç。删除一个字符(或几个)以开始新的匹配尝试的过程称为回溯(有时回溯)。

    1
    2
    say so 'abc' ~~ / \w+ . /;      # Vrai
    say so 'abc' ~~ / :r \w+ . /; # Faux

    使用这种棘轮可以是优化,因为回溯通常是昂贵的。但兴趣在于,没有回溯的匹配与人类分析他们阅读的文本的方式密切相关。使用正则表达式我的正则表达式标识符{\ w +}和我的正则表达式关键字{if | 别的| endif},我们直观地等待标识符吸收完整的单词,而不需要恢复该单词的结尾以满足以下规则。例如,没有人希望将单词reason解析为单词标识符,后跟if关键字 ; 我们等待而不是因为它被解析为一个标识符,如果解析器等待该单词,则它会失败,而不是解析输入数据与预期的不同。

    我们可以认为回溯一般期望行为分析低级别的一个字符,一个字符串,但研究棘爪(棘轮)通常更符合我们想要做的用于结构化文本的词法或句法分析。

    由于棘轮的行为是如此经常在词法分析器(可取的词法分析器)和句法(解析器),存在一个正则表达式棘轮一个快捷方式:使用所述关键字设置的分则令牌而不是正则表达式 :

    1
    2
    3
    my token truc { .... }
    # raccourci pour :
    my regex truc { :r ... }

    使用关键字规则的子规则(参见 §2.9.1.2。)也会起到防止回溯的作用。

    这些规则的存在令牌和规则的副词:棘轮还算是很少明确使用:它通常最好使用隐含定义子规则作为标记或规则。

    2-9-1-2 副词“sigspace”(显着的空格)

    副词:sigspace其中:s在正则表达式中创建重要的空格(到目前为止,它们不再被忽略):

    1
    2
    3
    say so "J'ai utilisé Photoshop®"  ~~ m:i/   photo shop /; # Vrai
    say so "J'ai utilisé photo shop" ~~ m:i:s/ photo shop /; # Vrai
    say so "J'ai utilisé Photoshop®" ~~ m:i:s/ photo shop /; # Faux

    m:s / photo shop /好像我们写了m / photo <.ws> shop <.ws> /。默认情况下,<。ws>确保单词是分开的,因此“a b”将被<.ws>识别,而不是“ab”。

    正则表达式中的空格根据前面的空格转换为<.ws>或不转换为<.ws>。在上面的示例中,正则表达式开头的空格不会更改为<.ws>,而是字符后面的空格。通常,规则是如果术语可以匹配某些内容,则该术语后面的空格将转换为<.ws>。

    另外,如果结束之后的空间中,但在此之前量词(+,*或?),<.WS>将这个术语的每个匹配后匹配,从而使富+变为[富<。 ws>] +。另一方面,量化器之后的空间表现得像正常有意义的空间,例如“foo +”变为foo + <.ws>。

    最后,这段代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    rx :s {
    ^^
    {
    say "Pas de sigspace après ceci";
    }
    <.assertion_puis_ws>
    caractères_puis_ws+
    caractères_séparés_par_ws *
    [
    | des "trucs" .. .
    | $$
    ]
    :my $toto = "pas de ws après ceci";
    $toto
    }

    变为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    rx {
    ^^ <.ws>
    {
    say "Pas d'espace après ceci";
    }
    <.assertion_puis_ws> <.ws>
    caractères_puis_ws++ <.ws>
    [ caractères_séparés_par_ws <.ws>]* <.ws>
    [
    | des <.ws> "trucs" <.ws> .. <.ws> . <.ws>
    | $$ <.ws>
    ] <.ws>
    :my $toto = "pas de ws après ceci";
    $toto <.ws>
    }

    就像使用token关键字声明的正则表达式隐含adverb :ratchet一样,使用关键字rule声明的正则表达式意味着副词:ratchet和:sigspace。

    语法提供了一种简单的方法来重新定义<.ws>将匹配的内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    grammar Demo {
    token ws {
    <!ww> # reconnaissance si pas à l'intérieur d'un mot
    \h* # reconnaît seulement les espaces horizontaux
    }
    rule TOP { # appelée par Demo.parse;
    a b '.'
    }
    }

    say so Demo.parse("ab."); # Faux (espace requis entre a et b)
    say so Demo.parse("a b."); # Vrai
    say so Demo.parse("a\tb ."); # Vrai
    say so Demo.parse("a\tb\n."); # Faux (\n est un espace vertical)

    在解析某些类型的空白(例如,垂直空间)很重要的文件格式时,通常需要重新定义ws。

    2-9-1-3 和Perl 5 兼容的副词

    副词 :P5:Perl5 允许你在 Perl 6 程序中使用 Perl 5 正则表达式语法:

    1
    2
    3
    4
    5
    next if $ligne =~ m/[aeiou]/   ;    # Classe de caractères Perl 5
    next if $ligne ~~ m:P5/[aeiou]/ ; # Perl 6, avec l'adverbe P5
    next if $ligne ~~ m:Perl5/[aeiou]/; # Perl 6, avec l'adverbe Perl5
    next if $ligne ~~ m/ <[aeiou]> / ; # Perl 6, nouvelle syntaxe des
    # classes de caractères

    这可用于迁移方案或那些还没有习惯 Perl 6 正则表达式的情况,但我们没有更多的是住在我们对这种可能性在本教程旨在介绍Perl的正则表达式6的语法。

    2-9-2 认可副词

    与正则表达式副词(第2.9.1节 )不同,它与正则表达式的声明相关,只有当你想匹配字符串和正则表达式时,匹配副词才有意义。

    它们永远不会出现在正则表达式中,但只能出现在它之外,作为识别m / … /的一部分,或者作为匹配方法的参数。

    2-9-2-1 副词“continue”(匹配的起始位置)

    副词 :continue:c 接受一个参数:正则表达式开始搜索的位置。默认情况下,搜索从字符串的开头开始,但是副词 :c 会修改此行为。如果没有指定位置,则搜索将从位置0开始,除非定义了匹配对象 $\,在这种情况下,搜索将从位置 $/(在前一次匹配结束后的位置)开始。

    1
    2
    3
    4
    given 'a1xa2' {
    say ~m/a./; # a1
    say ~m:c(2)/a./; # a2 (搜索从x的位置开始)
    }

    2-9-2-2 副词“exhaustive”(所有匹配)

    要查找正则表达式的所有可能匹配 - 包括重叠的那些 - 以及从相同位置开始的多个匹配,请使用副词 :exhaustive:ex

    1
    2
    3
    4
    5
    given 'abracadabra' {
    for m:exhaustive/ a .* a / -> $match {
    say ' ' x $match.from, ~$match;
    }
    }

    上面的代码显示以下结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    abracadabra
    abracada
    abraca
    abra
    acadabra
    acada
    aca
    adabra
    ada
    abra

    2-9-2-3 副词“global”(所有匹配)

    不是搜索单个匹配并返回匹配对象,而是可以搜索每个匹配而不重叠,并使用副词 :global:g 将结果作为列表返回 :

    1
    2
    3
    4
    5
    given 'Trois mots ici' {
    my @matches = m:global/\w+/;
    say @matches.elems; # 3
    say ~@matches[2]; # ici
    }

    2-9-2-4 副词“pos”

    副词 :pos:p 锚点匹配字符串中的特定位置:

    1
    2
    3
    4
    5
    given 'abcdef' {
    my $trouvé = m:pos(2)/.*/;
    say $trouvé.from; # 2
    say ~$trouvé; # cdef
    }

    2-9-2-5 副词“overlap”(带重叠)

    要获得多个匹配,包括重叠匹配,但每个起始位置只有一个(最长),可以使用副词 :overlap:ov

    1
    2
    3
    4
    5
    given 'abracadabra' {
    for m:overlap/ a .* a / -> $match {
    say ' ' x $match.from, ~$match;
    }
    }

    这个显示:

    1
    2
    3
    4
    abracadabra
    acadabra
    adabra
    abra

    2-10 向前查看和向后查看(断言)

    断言可以搜索向前或向后匹配,但不会像锚点那样消耗目标字符串(保持在相同位置)。

    2-10-1 before 断言

    要检查模式之前,另一模式出现,它可以使用 before 断言,在下面的形式:<?before>

    因此,要搜索紧跟在字符串 titi 之后的字符串 foo,我们可以使用正则表达式 rx {foo <?before titi>},例如:

    1
    say "tototiti" ~~ rx{ toto <?before titi>};   # -> toto

    相反,如果要搜索未紧跟其他模式的模式,则必须在断言以下形式之前使用否定: <!before pattern>

    例如,要查找不紧跟字符串titi的字符串foo,我们可以使用正则表达式 rx {foo <!before titi>} ; 使用上面示例的字符串,此正则表达式将失败,因为foo后跟titi。

    2-10-2 after 断言

    要检查模式是否出现在另一个模式之后,我们可以使用以下形式之后的断言 :<?after pattern>

    例如,要找到紧跟在字符串titi之前的字符串foo,我们可以使用正则表达式 rx {foo <?after titi>},如下所示:

    1
    say "tititoto" ~~ rx{ <?after titi> toto };   # -> toto

    相反,如果要搜索不是紧接其他模式的模式,则必须使用以下形式的负数: <!after pattern>

    例如,要搜索的字符串FOO没有紧跟字符串蒂蒂,你可以使用正则表达式 rx {<!after> foo} ,其失败,上面的例子中的字符串(这是我们的目标)。

    3. Grammar

    语法是一种强大的工具,用于将文本分解为单个元素,并且通常返回通过解释该文本而创建的数据结构。

    例如,使用Perl 6语法解释和执行Perl 6程序。

    当前Perl 6用户的一个更实用的范围示例是Perl 6 JSON::Tiny模块,它可以反序列化任何有效的JSON文件。执行此反序列化的代码用不到100行简单代码编写,易于扩展。我们在下面描述(§3.6 )这种语法的详细构造能够分析JSON。

    如果你不喜欢在学校学习语法,不要逃避。Perl 6语法实际上是一种对正则表达式进行分组的简单方法,就像类可以将普通代码方法组合在一起一样(而且这个类比远比人们乍看之下想象的要多得多,因为它是稍后会看到)。

    3-1 该“砖”,以建立一个语法

    上面已经看到了编写语法所需要知道和理解的大部分内容,特别是在第2.8 章中子规则或命名规则,以及子章节2.9.1.1。2.9.1.2。以上。这些子规则或命名规则是语法的基本构建块。实际上,语法的主要目的之一是将命名的子表达或正则表达式(正则表达式,标记和规则类型)分组到明确定义的名称空间中,以避免标识符名称的冲突。在代码中的其他地方使用其他正则表达式。

    回想一下, 正则表达式,标记和规则是非常相似的实体,用于以类似于函数或方法的定义的形式声明命名的正则表达式。在本章中,我们现在将它们统称为规则,而不管用于声明它们的关键字。它们声明如下:

    1
    2
    3
    regex ma_regex  { ... }  # regex ordinaire
    token mon_token { ... } # regex avec adverbe :ratchet implicite
    rule ma_règle { ... } # regex avec :ratchet et :sigspace implicites

    记录:

    • 关键字regex表示普通的正则表达式;
    • 关键字令牌隐含了副词:棘轮(“棘轮”),也就是说这种规则不会回溯(没有回溯);
    • 关键字规则隐式指示副词:棘轮(无后退空间)和:sigspace(模式的空格不被忽略)。

    以下是我们定义第一个数字规则的示例,并使用它来定义第二个十进制数 :

    1
    2
    3
    4
    5
    my regex chiffres { \d+ }
    my regex décimal { <chiffres> \. <chiffres> }
    say so " Cet objet coûte 13.45 euros" ~~ /<décimal>/; # -> True
    # (so renvoie une évaluation booléenne, donc, en fait True ou False)
    say ~$/; # -> 13.45

    我们在上面看到规则可以调用另一个规则,就像函数可以调用另一个函数一样。规则也可以递归调用自身。这个关键机制是Perl 6正则表达式的核心功能及其创建语法的能力。

    令牌规则不会回滚:

    1
    2
    my token lettres { abc .+ g };
    say "abcdefg" ~~ /<lettres>/ ?? "Réussit" !! "Échoue"; # -> Échoue

    这条规则在这里失败,因为子模式。+,凭借其贪婪的量词“ + ”消费链中的所有终端,包括最后一个字母“G”,所以该规则不再能匹配“G “最后已经消耗:因为她是不允许回去砸” G“,为了取得成功,离开它的处置模式的最后一个原子,它失败。

    使用非贪婪(或节俭)量化器,修改后的字母规则成功:

    1
    2
    my token lettres { abc .+? g };
    say "abcdefg" ~~ /<lettres>/ ?? "Réussit" !! "Échoue"; # -> Réussit

    使用相同的定义,类型规则规则将失败:

    1
    2
    my rule  lettres { abc .+? g };
    say "abcdefg" ~~ /<lettres>/ ?? "Réussit" !! "Échoue"; # -> Échoue

    因为模式中的空格不再被忽略(并且在链中找不到)。

    三种命名规则的名称,正则表达式,标记和规则,反映了这样一种观点,即在一种非常通用和模糊的方式中,正则表达式是关于通常所期望的。一个正则表达式(低级别分析,逐个字符),而一个令牌通常用于词法分析(将输入文本划分为“单词”或单个词位),并且规则将更频繁分析句法(分析lexemes之间的关系,可能取决于上下文)。这种理论上的任务划分受到许多例外的限制,并且不得在教条上遵循,但在决定不同类型的规则时,要牢记这一点。

    3-2 创建 Grammar

    语法创建了一个合适的命名空间,并引入了关键字语法。

    3-2-1 语法的语法定义

    就像一个类可以对命名的动作或方法进行分组:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class Identité {
    method nom { "Nom = $!nom" }
    method âge { "Âge = $!âge" }
    method adresse { "Adr = $!adresse" }

    method desc {
    print &.nom(), "\n",
    &.âge(), "\n",
    &.adresse(), "\n";
    }

    # etc.
    }

    一个名为规则的语法组:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    grammar Identité {
    rule nom { Nom '=' (\N+) } # chaîne de caractères quelconques
    # autres que des retours à la ligne
    rule adresse { Adr '=' (\N+) } # idem
    rule âge { Age '=' (\d+) } # des chiffres

    rule desc {
    <nom> \n
    <âge> \n
    <adresse> \n
    }

    # etc.
    }

    这里使用其他地方定义的其他规则(名称,年龄和地址)来定义desc规则。这使得逐渐建立越来越高的抽象水平成为可能。

    这将创建一个语法类型对象,其类型表示正在分析的语言,并且可以以扩展语言的形式从中派生其他语法。

    然后,新对象作为调用TOP方法(正则表达式,标记或规则)传递。这个默认的TOP方法可以被另一个传递给命名参数的规则替换:rule,这对于测试语法尤其有用。

    请注意,不再需要使用my运算符声明规则,因为语法创建了必要的命名空间和词法范围。

    3-2-2 Grammar 的继承

    语法可以从另一个(父)语法继承,就像一个类可以从另一个类继承一样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    grammar Message {
    rule texte { <salutation> $<corps>=<ligne>+? <fin> }
    rule salutation { [Salut|Bonjour] $<dest>=\S+? ',' }
    rule fin { [à|'@'] plus ',' $<auteur>=.+ }
    token ligne { \N* \n}
    }

    grammar MessageFormel is Message {
    rule salutation { [Cher|Chère] $<dest>=\S+? ',' }
    rule fin { Bien cordialement ',' $<auteur>=.+ }
    }

    这里,MessageFormel语法继承父语法消息。与类的方法一样,规则继承自父语法(和多态),因此无需重新定义不更改的文本和行规则。我们只重载改变的规则。

    所有语法都是从语法类派生的,语法类除其他外提供了所有语法共有的方法,如.parse和.fileparse,如下所述。

    语法从其他语法继承的能力是一种极其强大的工具,是允许Perl 6语言可扩展性的重要因素(见第3.9节 )。

    3-3 使用 Grammar

    可以通过在该语法上调用.parse方法并可选地将操作对象作为参数传递来解析带有语法的字符串(参见下面的第3.4节 )。同样,.parsefile方法用于解析文件。

    1
    2
    MaGrammaire.parse($chaîne, :actions($objet-action))
    MaGrammar.parsefile($nom-fic, :actions($objet-action))

    .parse和.parsefile方法锚定在文本的开头和结尾,如果未到达文本末尾则失败。

    原则上,有必要使用语法来执行文本的实际词法和句法分析。要提取复杂数据,建议将操作对象与语法结合使用。

    3-4 类和对象的股票

    3-4-1 在确认期间执行代码

    当语法成功解析文本时,它返回一个匹配对象的语法树。这棵树越深(并且它通常变得非常快),并且这棵树中的分支越多,探索这棵树以找到正在寻找的信息就越困难。

    为了避免必须对匹配树进行这种探索,可以提供一个行动对象。在对命名的语法规则进行每次成功分析之后,它会尝试调用此操作对象的方法,该方法具有与规则相同的名称,方法是将其作为刚刚匹配的对象的位置参数提供创建。此方法(如果存在)可以特别用于构建抽象语法树(Astract语法树或AST)或执行将来可能需要的各种其他事物。如果此方法不存在,则忽略此步骤。

    这是一个极简主义,有点人为的语法和动作一起工作的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    use v6;

    grammar GrammaireTest {
    token TOP { ^ \d+ $ }
    }

    class ActionsTest {
    method TOP($/) {
    $/.make(2 + ~$/);
    }
    }

    my $actions = ActionsTest.new;
    my $reconnu = GrammaireTest.parse('40', :$actions);
    say $reconnu; # -> 「40」
    say $reconnu.made; # -> 42

    Actiontest类的$ action对象被实例化,并在调用.parse方法时作为参数传递。当TOP规则匹配出该参数时,语法通过将匹配的对象作为参数传递给它来自动调用TOP方法。

    类Match的make方法使用其参数提供结构$ / .Med(用户决定其内容,但通常是一个抽象的语法树)。

    为了证明参数是一个公认的对象,该示例使用 $/ 作为传递给action方法的参数名称,但这只是一个方便的约定,没有任何内在的必要条件在这里。通过认可的 $ 也会起作用。(但请注意,使用 $/ 可以提供快捷方式 $<capture> 而不是 $/<capture>。)

    这是一个稍微更具体的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    use v6;

    grammar PairesClésValeurs {
    token TOP {
    [<paire> \n+]*
    }
    token ws { \h* }

    rule paire {
    <clé=.identifiant> '=' <valeur=.identifiant>
    }
    token identifiant {
    \w+
    }
    }

    class PairesClésValeursActions {
    method identifiant($/) { $/.make: ~$/ }
    method paire ($/) { $/.make: $<clé>.made => $<valeur>.made }
    method TOP ($/) { $/.make: $<paire>».made }
    }

    my $res = PairesClésValeurs.parse(q:to/EOI/, :actions(PairesClésValeursActions)).made;
    phase=b
    points=42
    Perl=6
    EOI

    for @$res -> $p {
    say "Clef : $p.clé()\tValeur : $p.valeur()";
    }

    其中显示以下结果:

    1
    2
    3
    Clef : phase      Valeur : b
    Clef : points Valeur : 42
    Clef : Perl Valeur : 6

    规则对,其中分析一对由等于字符分隔的标识符,提供的别名规则的两个呼叫标识符来区分渔获的名称和使它们可更容易地且直观地。相应的action-method构造一个Pair类型的对象,并使用已匹配的子对象的属性.made。因此(以及TOP -action方法),它利用了子匹配的动作方法在对应于边界正则表达式之前被调用的事实。因此,以所需顺序调用动作方法。

    TOP动作方法简单地收集由偶数规则的多个匹配生成的所有对象,并将它们作为列表返回。

    另请注意,PairsKeyActionList在此作为类型对象(而不是对象的实例)传递给.parse方法,这是可能的,因为没有一个action-methods使用属性(仅在正确初始化的实例中可用)。

    在某些情况下,可能需要将状态保留在属性中。在这种情况下,它是一个必须传递给.parse方法的实例化对象。

    action-methods还可用于调试顽固语法,例如通过显示解析的中间状态。但是动作方法的基本作用是将简单语法转换为真正的解析工具,生成一个抽象语法树,Perl解释器(或其他工具)可以使用它来生成,例如可执行代码。

    3-4-2 在语法中执行代码的其他方法

    在上面的例子中,action-methods是在一个与语法本身不同的动作中定义的,这通常是任何扩展的语法。

    但是,对于简单的情况,也可以在语法本身中定义方法:

    1
    2
    3
    4
    5
    6
    grammar Toto {
    regex titi { <.configurer> blah blah }
    method configurer {
    # faire quelque chose ici
    }
    }

    是的,语法可以定义方法(他们甚至可以使用角色,我们将在第3.9.1节中详细介绍),它们实际上是类……

    通过在大括号之间插入代码,也可以在规则中执行代码:

    1
    2
    3
    grammar toto {
    regex titi { blah blah { say "Je suis arrivé ici"} blah blah}
    }

    如果匹配出代码块之前的模式部分,则立即执行该块。

    本工程以真理的任何正则表达式(名为或非规则,甚至外的语法),这可以用于调试正则表达式的例子帮助(见 § 4.6。 调试Perl的正则表达式语法或6):

    1
    2
    3
    4
    my regex lettres { a b {say "reconnu ab"} c d}
    say so "abc" ~~ /<lettres>/; # -> reconnu ab / False
    # la regex ci-dessus a reconnu ab et l'affiche et n'a échoué qu'ensuite
    say so "abcde" ~~ /<lettres>/; # -> reconnu ab / True - ici, succès

    但是要小心:如果正则表达式是回溯,则可能会多次执行代码块:

    1
    say so "aaa" ~~ / a { say "Bonjour" } b /;

    这个显示:

    1
    2
    3
    4
    Bonjour
    Bonjour
    Bonjour
    False

    3-5 验证Perl模块名称的 Grammar

    此示例的目的是验证Perl模块名称。

    Perl模块的名称可以分解为由冒号对分隔的标识符:“::”,例如List::UtilList::MoreUtils(这里提供的模块名称示例是模块Perl 5)。标识符必须以字母字符(az)或带下划线的字符开头,后跟零个,一个或多个字母数字字符。

    到目前为止没有什么非常复杂,但这有点复杂,因为有些模块有一个标识符(Memoize),因此没有冒号,而其他模块可能有“扩展”名称:Regexp::Common::Email::Address

    他不是一个很好的语法候选人吗?

    3-5-1 验证 Grammar

    例如,足以定义保证上面的命名规则和分隔符规则的标识符规则,并在语法中正确地组合它们。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    grammar Valide-Nom-Module {
    token TOP { ^ <identifiant> [ <séparateur> <identifiant> ]* $}
    token identifiant {
    <[A..Za..z_]> # 'mot' commençant par un caractère
    # alphabétique ou un caractère souligné
    <[A..Za..z0..9_]>* # 0 ou plusieurs caractères alphanumériques
    }
    rule séparateur { '::' } # paire de caractères deux-points
    }

    我们现在可以使用一些有效的模块名来测试这个语法:

    1
    2
    3
    4
    5
    for  <Super:Nouveau::Module Super.Nouveau.Module 
    Super::6ouveau::Module Super::Nouveau::Module> -> $nom {
    my $reconnu = Valide-Nom-Module.parse($nom);
    say "nom\t", $reconnu ?? $reconnu !! "Nom de module invalide";
    }

    这个显示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    Super:Nouveau::Module   Nom de module invalide
    Super.Nouveau.Module Nom de module invalide
    Super::6ouveau::Module Nom de module invalide
    Super::Nouveau::Module 「Super::Nouveau::Module」
    identifiant => 「Super」
    séparateur => 「::」
    identifiant => 「Nouveau」
    séparateur => 「::」
    identifiant => 「Module」

    只匹配出有效的模块名称,其他三个名称被正确拒绝。

    有时,通过用短划线替换色列对来汇总模块名称。例如,官方名称是Regexp::Common::Email::Address,也可以是Regexp-Common-Email-Address。如果想要验证第二次写作,只需修改分隔符以便它也授权破折号:

    1
    rule séparateur { '::' || \- }    # deux car. deux-points ou tiret

    通过使用名称“Super-New-Module”测试语法,我们获得:

    1
    2
    3
    4
    5
    6
    Super-Nouveau-Module  「Super-Nouveau-Module」
    identifiant => 「Super」
    séparateur => 「-」
    identifiant => 「Nouveau」
    séparateur => 「-」
    identifiant => 「Module」

    修改分隔符规则就足以使修改扩展到所有语法,直到规则TOP。

    3-5-2 添加动作对象

    上述语法可以确定Perl模块名称是否有效。

    我们现在想要在模块名称太长(超过5个标识符)时添加警告; 在这种情况下,模块的名称仍然有效,但建议模块的作者尝试选择较短的名称。

    例如,只需添加如下定义的 Valide-Name-Module-Actions操作类:

    1
    2
    3
    4
    5
    6
    7
    class Valide-Nom-Module-Actions {
    method TOP($/) {
    if $<identifiant>.elems > 5 {
    warn "Nom de module très long! Peut-être le réduire ?\n"
    }
    }
    }

    该类的定义并不特殊,它是一个普通的Perl 6类。重要的特征是这里定义的唯一方法与其中一个语法规则具有相同的名称(在本例中,语法中的输入规则为TOP)。如果标识符的数量超过5,将发送警告,但这不会阻止验证模块的名称。

    调用语法的语法更改如下:

    1
    my $reconnu =  Valide-Nom-Module.parse($nom, :actions(Valide-Nom-Module-Actions));

    如果我们使用模块名称“ Super::New::Module ”(或“ Super-New-Module ”)调用语法,结果与之前相同 ,这是令人放心的。

    但是使用Mary Poppins的模块名称:

    1
    2
    3
    my $nom = "Mon::Module::Super::Cali::Fragi::Listi::Cexpi::Delilicieux";
    my $reconnu = Valide-Nom-Module.parse($nom, :actions(Valide-Nom-Module-Actions));
    say $reconnu if $reconnu;

    我们收到警告:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    > perl6 grammaire_nom_module.pl
    Nom de module très long! Peut-être le réduire ?
    in method TOP at perl6_grammaire_module.pl:15
    「Mon::Module::Super::Cali::Fragi::Listi::Cexpi::Delilicieux」
    identifiant => 「Mon」
    séparateur => 「::」
    identifiant => 「Module」
    séparateur => 「::」
    identifiant => 「Super」
    (...)
    identifiant => 「Delilicieux」

    然后显示已匹配的对象。

    请注意,语法调用直接作为操作类的参数传递给上面。如 3.4.1中所述,如有必要,可以传递此类的实例化对象:

    1
    2
    3
    my $nom = "Mon::Module::Super::Cali::Fragi::Listi::Cexpi::Delilicieux";
    my $actions = Valide-Nom-Module-Actions.new();
    my $reconnu = Valide-Nom-Module.parse($nom, :actions($actions));

    这个显示:

    1
    2
    3
    perl6 grammaire_nom_module.pl
    Nom de module très long! Peut-être le réduire ?
    in method TOP at perl6_grammaire_module.pl:15

    这个验证模块名称的例子非常松散地基于 David Farrell的文章如何在Perl 6中创建语法

    3-6 分析JSON的 Grammar

    JavaScript Object Notation(JSON )是从JavaScript对象表示法派生的文本数据格式。他成为(连同其他如XML和YAML)通常用于序列化的数据结构,例如允许平台或不同语言之间的交流,或将它们存储持久标准(例如,在文件中)。

    3-6-1 JSON文档的结构

    JSON格式具有简单的优点。JSON文档包括两种类型的结构实体:

    • 对象或名称/值对列表(基本上对应于JavaScript对象或Perl散列表);
    • 有序的值列表(表)。

    值本身可以是(递归)对象或数组,如上所述,或基本通用数据:布尔值(true或false),数字,字符串或null(空值或无定义值) 。

    字符串是包含整数(可能为零)unicode字符的序列。数字的格式为带符号的小数,可能包含小数或高功率部分(E表示法)。JSON不区分整数和浮点数。

    3-6-2 示例JSON文档

    作为本教程的一部分,我们将使用以下JSON文档来检查语法的工作原理:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    {
    "prénom": "Martine",
    "nom": "Unetelle",
    "enVie": true,
    "âge": 28,
    "sexe": "F",
    "adresse": {
    "NumRue": "21 rue Pasteur",
    "Ville": "Lyon",
    "CodePostal": "F-69000"
    },
    "NumérosTéléphone": [
    {
    "type": "domicile",
    "num": "04 05 06 07 08"
    },
    {
    "type": "professionnel",
    "num": "04 08 07 06 05"
    },
    {
    "type": "mobile",
    "num": "06 12 34 56 78"
    }
    ],
    "enfants": [],
    "Conjoint": null,
    "Profession": "sage-femme",
    "Hobbies": ["GRS", "surf", "peinture"]
    }

    已经使用可用工具验证了本文档符合JSON标准,并且可以看出它包含上述所有类型数据的示例。

    3-6-3 逐步写出JSON Grammar 的元素

    3-6-3-1 数字

    选择的示例JSON文档仅包含整数,但是数字实体的描述(第3.6.1节)表明必须能够匹配具有例如以下格式的数字:“17”, “-138.27”,“1.2e-3”等

    这可能会导致以下规则:

    1
    2
    3
    4
    5
    6
    token nombre {
    [\+|\-]? # signe optionnel
    [ <[0..9]>* ] # chiffres optionnels (partie entière)
    [ \. <[0..9]>+ ]? # séparateur décimal et partie fractionnaire
    [ <[eE]> [\+|\-]? <[0..9]>+ ]? # exposant optionnel
    }

    3-6-3-2 字符串

    可以想象许多通过模式定义字符串的方法。对于选择的示例JSON文档,以下规则就足够了:

    1
    2
    3
    4
    token chaîne {
    \" <[ \w \s - ]>+ \" # caractères alphanumériques, espaces et
    # tirets, le tout entre des guillemets
    }

    对于真正的JSON解析器,我们可能更喜欢使用负字符类的规则,该字符类不包括任何不属于字符串的内容,例如:

    1
    2
    3
    4
    token chaîne {
    \" <-[\n " \t]>* \" # tous caractères sauf guillemets, retours à
    # la ligne et tabulations, entre guillemets
    }

    应该查看JSON标准的细节,以精确地细化JSON字符串中可接受或不可接受的内容。上面引用的第一条规则足以满足所选的例子。

    3-6-3-3 JSON对象

    JSON对象是键/值对的列表。这些列表由大括号框起,并且这些对用逗号分隔。键/值对是标识符(字符串,上面已定义的规则),后跟冒号,后跟值。这可以翻译如下:

    1
    2
    3
    rule objet       { '{'  <listepaires> '}' }
    rule listepaires { <paire> * % \, }
    rule paire { <chaîne> ':' <valeur> }

    为了理解{<对> *%} ,它是值得记住的修饰语“ % ”施加到量词指定的隔膜必须被重复认可之间存在(见第 2.4。 )。此修饰符可以轻松指定对以逗号分隔。如果没有此修饰符,则必须编写,例如:

    1
    rule listepaires { \s* | [<paire> [',' <paire>]*] }

    这不太方便,可读性也差一点。

    3-6-3-4 JSON表

    表是值列表。列表用方括号括起来,值用逗号分隔:

    1
    2
    rule tableau       { '[' <listeTableaux> ']'}
    rule listeTableaux { <valeur> * % [ \, ] }

    3-6-3-5

    值可以是对象,数组,字符串,数字,布尔值或null。

    1
    2
    3
    token valeur { | <objet> | <tableau> | <chaîne> | <nombre> 
    | true | false | null
    }

    这种语法非常有效,但是目前可以考虑一种有些先进的句法符号,这种符号在本文档中尚未解决,后面将在第3.9.4节中讨论原型规则

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    proto token valeur {*};
    token valeur:sym<nombre> {
    [ '-' | '+' ]?
    [ <[0..9]>* ]
    [ \. <[0..9]>+ ]?
    [ <[eE]> [\+|\-]? <[0..9]>+ ]?
    }
    token chaîne {
    \" <[ \w \s - ]>+ \"
    }
    token valeur:sym<true> { <sym> };
    token valeur:sym<false> { <sym> };
    token valeur:sym<null> { <sym> };
    token valeur:sym<objet> { <objet> };
    token valeur:sym<tableau> { <tableau> };
    token valeur:sym<chaîne> { <chaîne> }

    这种不那么简洁的符号具有促进语法扩展的优点,但是这里的兴趣相当有限,因为JSON标准是严格且相对不可变的。上面引用的第一条规则足以满足所选的例子。

    3-6-4 JSON Grammar

    现在可以组合上面描述的不同元素并添加TOP规则来编写整个语法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    grammar JSON-Grammaire {
    token TOP { ^ \s* [ <objet> | <tableau> ] \s* $ }
    rule objet { '{' <listepaires> '}' }
    rule listepaires { <paire> * % \, }
    rule paire { <chaîne> ':' <valeur> }
    rule tableau { '[' <listeTableaux> ']' }
    rule listeTableaux { <valeur> * % [ \, ] }
    token chaîne { \" (<[ \w \s - ]>+) \" }
    token nombre {
    [\+ | \-]?
    [ <[0..9]>* ]
    [ \. <[0..9]>+ ]?
    [ <[eE]> [\+ | \-]? <[0..9]>+ ]?
    }
    token valeur {
    | <objet> | <tableau> | <chaîne> | <nombre>
    | true | false | null
    }
    }

    要测试,只需调用语法:

    1
    2
    my $reconnu = JSON-Grammaire.parse($chaîne_json);
    say ~$reconnu if $reconnu;

    这将显示包含整个原始JSON的已匹配对象:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    $ perl6 json_grammaire.pl
    {
    "prénom": "Martine",
    "nom": "Unetelle",
    "enVie": true,
    "âge": 28,

    # [ ... affichage abrégé pour des raisons de place ]

    "Profession": "sage-femme",
    "Hobbies": ["GRS", "surf", "peinture"]
    }

    JSON文档已得到充分认可。这个JSON语法完全适用于作为示例选择的JSON文档,并且适用于少于20行。鼓励读者测试它。它还能够通过在JSON文档中引入错误来验证(例如,删除列表的两个值之间的逗号),不再进行匹配。

    有人可能会反对这个语法只涵盖JSON的一个子集。实际情况并非如此。当然不建议在生产环境中使用此语法来解析任何JSON,因为它是作为教学示例完成的,但没有检查JSON标准的更精细细节,但是然而,上面的语法几乎完成了。

    Perl 6 的JSON::Tiny模块的语法可以解析任何有效的JSON文件,因为它可以容纳大约35行。

    3-6-5 添加动作

    该JSON语法的工作,但如果打印是为JSON文档获得认可的对象树中使用例如大约300行文字,因为它提供的是已经认识到了一切的所有细节,而不是不是子模式的子模式。这对于理解语法的作用(例如在发生故障时)非常有用,但是探索这个树来提取数据可能会非常痛苦。

    可以添加一类动作来构建抽象语法树(AST)。下面的类代码部分受到JSON::Tiny模块的action类的启发 :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    class JSON-actions {
    method TOP($/) {
    make $/.values.[0].made;
    };
    method objet($/) {
    make $<listepaires>.made.hash.item;
    }
    method listepaires($/) {
    make $<paire>>>.made.flat;
    }
    method paire($/) {
    make $<chaîne>.made => $<valeur>.made;
    }
    method tableau($/) {
    make $<listeTableaux>.made.item;
    }
    method listeTableaux($/) {
    make [$<valeur>.map(*.made)];
    }
    method chaîne($/) { make ~$0 }
    method nombre($/) { make +$/.Str; }
    method valeur($/) {
    given ~$/ {
    when "true" {make Bool::True;}
    when "false" {make Bool::False;}
    when "null" {make Any;} # équivalent d'undef en Perl5
    default {make $<val>.made}
    }
    }
    }

    此操作类在value方法中使用名为 $<val> 的正则表达式,这使我们稍微修改语法的值规则:

    1
    2
    3
    4
    token valeur {
    | <val=objet> | <val=tableau> | <val=chaîne> | <val=nombre>
    | true | false | null
    }

    现在可以按如下方式调用语法:

    1
    2
    3
    my $j-actions = JSON-actions.new();
    my $reconnu = JSON-Grammaire.parse($chaîne_json, :actions($j-actions));
    say $reconnu.made if $reconnu;

    对象$ reconnu.made现在有一个抽象语法树(AST),一个Perl数据结构6,它现在更容易发掘和利用。下面这棵树的显示略有重新格式化,其唯一目的是提高可读性:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    {
    Conjoint => (Any),
    Hobbies => [GRS surf peinture],
    NumérosTéléphone => [
    {num => 04 05 06 07 08, type => domicile}
    {num => 04 08 07 06 05, type => professionnel}
    {num => 06 12 34 56 78, type => mobile}
    ],
    Profession => sage-femme,
    adresse => {
    CodePostal => F-69000
    Complément_Adr => (Any),
    NumRue => 21 rue Pasteur,
    Ville => Lyon
    },
    enVie => True,
    enfants => [0],
    nom => Unetelle,
    prénom => Martine,
    sexe => F,
    âge => 28
    }

    现在可以轻松访问单个值,例如:

    1
    say $reconnu.made<adresse><CodePostal Ville>;     # -> (F-69000 Lyon)

    3-7 用于分析(伪)XML的 Grammar

    一个语法章在Perl 5到Perl 6的:深化介绍一步一步写语法解析文本尊重XML的一个子集。

    该子集由以下测试套件定义:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    my @tests = (
    [1, 'abc' ], # 1
    [1, '<a></a>' ], # 2
    [1, '..<ab>foo</ab>dd' ], # 3
    [1, '<a><b>c</b></a>' ], # 4
    [1, '<a href="foo"><b>c</b></a>'], # 5
    [1, '<a empty="" ><b>c</b></a>' ], # 6
    [1, '<a><b>c</b><c></c></a>' ], # 7
    [0, '<' ], # 8
    [0, '<a>b</b>' ], # 9
    [0, '<a>b</a' ], # 10
    [0, '<a>b</a href="">' ], # 11
    [1, '<a/>' ], # 12
    [1, '<a />' ], # 13
    );

    其中与第一个字段相关联的字符串等于1的字符串被认为是格式良好的XML字符串,而第一个字段为0的字符串被认为是格式不正确的。

    强烈建议有兴趣的读者阅读本章,其中只引用最终语法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    grammar XML {
    token TOP { ^ <xml> $ };
    token xml { <text> [ <tag> <text> ]* };
    token text { <-[<>&]>* };
    rule tag {
    '<'(\w+) <attributes>*
    [
    | '/>' # a single tag
    | '>'<xml>'</' $0 '>' # an opening and a closing tag
    ]
    };
    token attributes { \w+ '="' <-["<>]>* '"' };
    };

    3-8 计算算术表达式(计算器)

    这是一个使用计算器语法的程序的简单示例,该计算器语法可以评估基本的算术表达式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    use v6.c;

    grammar GrammaireArithmétique {
    token TOP { \s* <nombre> \s* <operation> \s* <nombre> \s*}
    token operation { <[^*+/-]> }
    token nombre { \d+ | \d+\.\d+ | \.\d+ }
    }
    class ActionsArithmétiques {
    method TOP($/) {
    given $<operation> {
    when '*' { $/.make([*] $/<nombre>)}
    when '+' { $/.make([+] $<nombre>)}
    when '/' { $/.make($<nombre>[0] / $<nombre>[1]) }
    when '-' { $/.make([-] $<nombre>) }
    when '^' { $/.make($<nombre>[0] ** $<nombre>[1]) }
    }
    }
    }
    for ' 6*7 ', '46.2 -4.2', '28+ 14.0 ',
    '70 * .6 ', '126 /3', '6.4807407 ^ 2' -> $op {
    my $reconnu = GrammaireArithmétique.parse($op, :actions(ActionsArithmétiques));
    say "$reconnu\t= ", $reconnu.made;
    }

    语法GrammaireArithmétique是特别简单:我们试图匹配号码,然后按算术运算符(四个基本操作幂),其次是另一个号码。

    算术动作库存类本身不产生AST,而只是评估在两个数字项之间执行的算术运算的结果。

    在运行时,这将显示以下结果:

    1
    2
    3
    4
    5
    6
    7
    $ perl6 grammaire_arithm.pl6
    6*7 = 42
    46.2 -4.2 = 42
    28+ 14.0 = 42
    70 * .6 = 42
    126 /3 = 42
    6.4807407 ^ 2 = 42.00000002063649

    我们发现它工作得很好,但我们希望能够处理包含多个操作的更复杂的算术表达式,同时考虑操作之间的通常优先级规则,以及通常用于修改这些优先级的括号。

    特别是,我们想要正确处理以下算术表达式:

    • 3 + 4 + 5;
    • 3 + 4 * 5; #预期的结果是23
    • (3 + 4)* 5; #预期的结果是35

    要实现这种结果,我们必须以不同的方式对待具有不同优先级的运营商。在下面的语法中,表达式(expr)由几个由+或-运算符分隔的术语组成。甲术语由一个原子或多个原子 S按运算符分隔*或/。并且原子是括号中的简单数字或算术表达式。

    这确保符合优先的规则:乘法和除法的分析过程中的加法和减法,因为之前评估,EXPR,考虑长期的个性化,才能完成评估一个表达。类似地,由于带括号的表达式是原子,因此有必要在完成对该术语中包含括号表达式的术语的评估之前评估原子的值。

    语法比以前复杂一点,但至少在外观上仍然相对简单:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    my grammar Calculette {
    rule TOP { <expr> }
    rule expr { <terme> + % <plus-moins-op> }
    token plus-moins-op { [< + - >] }
    rule terme { <atome> + % <mult-div-op> }
    token mult-div-op { [< * / >] }
    rule atome {
    | <nombre> { make +$<nombre> }
    | <expr-parenth> { make $<expr-parenth>.made}
    }
    rule nombre { <signe> ? [\d+ | \d+\.\d+ | \.\d+ ] }
    rule expr-parenth { '(' <expr> ')' }
    token signe { [< + - >] }
    }

    注意,在括号中的表达式的情况下,可以递归地调用expr规则。这种递归调用的可能性是语法能力的重要组成部分,但必须采取一些预防措施来避免进入无限递归的风险; 这些预防措施将在4.5节中详细讨论 避免左递归的陷阱)。

    我们还可以注意到,我们在语法中集成了两个动作(在原子规则中))。我们特别出于实际原因做出了这样的选择:由于原子规则包含非常不同的命名子规则,因此更容易将操作直接集成到子规则的上下文中。如果这些股票被放置在一类股票中,则有必要确定哪些子规则已得到承认。这并不难,但它会使代码更复杂一些。第二个原因是教学法:尽管创建一个单独的一类行动通常会更好(我们将在本例中很快完成此操作),但有必要知道可以偶尔直接集成行动。用语法。对于一个非常简单的语法,它可能是过度工程 而不是仅为一个或两个动作创建一个动作类。

    以下是与此语法相关的操作类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    class CalcActions {
    method TOP ($/) {
    make $<expr>.made
    }
    method expr ($/) {
    $.calculer($/, $<terme>, $<plus-moins-op>)
    }
    method terme ($/) {
    $.calculer($/, $<atome>, $<mult-div-op>)
    }
    method expr-parenth ($/) {
    make $<expr>.made;
    }
    method calculer ($/, $operandes, $operateurs) {
    my $result = (shift $operandes).made;
    while my $op = shift $operateurs {
    my $nombre = (shift $operandes).made;
    given $op {
    when '+' { $result += $nombre; }
    when '-' { $result -= $nombre; }
    when '*' { $result *= $nombre; }
    when '/' { $result /= $nombre; }
    default { die "operateur inconnu "}
    }
    }
    make $result;
    }
    }

    计算方法从左到右确定表达式(由加法或减法运算符分隔的项)和项(由乘法或除法运算符分隔的原子)的值,因为这些运算符在左侧都是关联的。

    可以使用以下代码片段测试此语法和与其关联的操作类:

    1
    2
    3
    4
    5
    6
    for |< 3*4 5/6 3+5 74-32 5+7/3 5*3*2 (4*5) (3*2)+5 4+3-1/5 4+(3-1)/4 >,
    "12 + 6 * 5", " 7 + 12 + 23", " 2 + (10 * 4) ", "3 * (7 + 7)" {
    my $résultat = Calculette.parse($_, :actions(CalcActions));
    # say $résultat;
    printf "%-15s %.3f\n", $/, $résultat.made if $résultat;
    }

    其中显示以下结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    $ perl6 grammaire_arithm_2.pl6
    3*4 12.000
    5/6 0.833
    3+5 8.000
    74-32 42.000
    5+7/3 7.333
    5*3*2 30.000
    (4*5) 20.000
    (3*2)+5 11.000
    4+3-1/5 6.800
    4+(3-1)/4 4.500
    12 + 6 * 5 42.000
    7 + 12 + 23 42.000
    2 + (10 * 4) 42.000
    3 * (7 + 7) 42.000

    有人可能想知道这个程序是否适用于嵌套括号。当我编写这段代码时,我问自己这个问题,我认为在这种情况下它可能无法正常工作,并且我可能需要添加一些东西以正确分析表达式嵌套括号。事实证明它不是,它适用于嵌套括号:

    1
    2
    3
    4
    for "(((2+3)*(5-2))-1)*3", "2 * ((4-1)*((3*7) - (5+2)))"  { 
    my $résultat = Calculette.parse($_, :actions(CalcActions));
    printf "%-30s %.3f\n", $/, $résultat.made if $résultat;
    }

    这个显示正确的结果:

    1
    2
    (((2+3)*(5-2))-1)*3            42.000
    2 * ((4-1)*((3*7) - (5+2))) 84.000

    3-9 Grammar:先进的理念和观点

    3-9-1 角色构成

    在面向对象的编程中,角色对可以由不同类共享的行为(通常是方法)进行分组。角色在技术上与类非常相似,但最大的区别在于不期望直接从角色实例化对象。在OOP中,可以将角色添加到整个类,或仅添加到类的各个对象。将角色功能添加到类中称为角色组合。

    使用关键字do将角色添加到类(或对象) :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    role Personne-details {
    has Adresse $.adresse is rw; # Adresse : type défini préalablement
    has NumString $.telephone-fixe is rw; # idem pour NumString
    method déménage (Adresse $nouvelle-adresse) {
    $.adresse = $nouvelle-adresse;
    }
    # … autres méthodes
    }

    class Personne-privée does Personne-details {
    has Str $.nom;
    has Str $.prénom;
    # autres attributs et méthodes
    }

    角色是在OOP中重用代码的一种非常强大的方法。有关OOB角色组成的更多信息,请参阅图片不可用Perl对象,类和角色教程6 - 面向对象编程教程

    读者已经理解Perl 6的语法最终只是一种特定的类形式,并不会惊讶于我们也可以在语法中添加角色。这些角色最终可能包含OOP中使用的属性和方法,但它们通常包含命名规则。

    通常,角色将是从较小的独立组件汇编语法的好方法。例如,匹配引号(或撇号)中的数字和字符串是一种非常常见的需求,对于分析JSON,YAML,HTML,配置文件或数学表达式非常有用。因此,我们可以想象将角色形式的通用组件添加到更具体的语法(或添加到语法中的其他角色)。

    在下面的示例中,我们定义了第一个角色Integer,它提供了用于匹配有符号或无符号整数的规则; 然后我们使用Integer角色的一些规则定义第二个角色Floating:感谢子句Do Integer并提供匹配浮点数的规则:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    role Entier {
    token non-signé { <[0..9]>+ }
    token signe { [ '+' | '-' ] }
    token entier-signé { <signe>? <non-signé> }
    }

    role Flottant does Entier {
    token exposant { :i e \s* <entier-signé> }
    token point-décimal { '.' }
    token nombre-fractionnaire {
    || <entier-signé> <point-décimal> <entier-signé>
    || <point-décimal> <entier-signé>
    || <entier-signé> <point-décimal>?
    }
    token flottant {
    <nombre-fractionnaire> <exposant>?
    }
    }

    这为我们提供了以下语法分析简单算术表达式中的重用组件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    grammar MultDiv does Flottant {
    rule TOP {
    || <dividende=nombre> '/' <diviseur=nombre> {
    make $<dividende> / $<diviseur>
    }
    || [ <nombre> ]+ % \* { make [*] $<nombre> }

    }
    token nombre { \s* [<entier-signé> | <flottant> ] \s*}
    }
    for '7 * 6', '3*2*7', '126/3', '147.0 / 3.5' -> $expr {
    my $match = MultDiv.parse($expr);
    printf "%-20s %s %d \n", $match, "La réponse est: ", $match.made;
    }

    Floating Does子句将Floating角色中定义的规则和Integer角色都导入到MultDiv语法中。

    显示的结果符合预期:

    1
    2
    3
    4
    5
    $ perl6 grammaire_roles.pl6
    7 * 6 La réponse est: 42
    3*2*7 La réponse est: 42
    126/3 La réponse est: 42
    147.0 / 3.5 La réponse est: 42

    请注意,我们再次将操作直接插入到语法中,并在此处使用为命名规则的捕获提供特定名称的可能性(参见§2.8 )以区分规则提供的捕获在分割的情况下的数字。

    现在我们已经构建了这些软件块,它们是由Integer和Floating角色提供的整数签名和浮动规则,我们也可以重用它们来匹配我们语法中用于解析3.6 JSON的相同类型的实体。 0.4

    1
    2
    3
    4
    5
    6
    7
    8
    9
    grammar JSON-Grammaire does Flottant {
    token TOP { ^ \s* [ <objet> | <tableau> ] \s* $ }
    # … Règles définissant les objets, paires, tableaux, chaînes, etc.
    token nombre { <entier-signé> | <flottant> }
    token valeur {
    | <objet> | <tableau> | <chaîne> | <nombre>
    | true | false | null
    }
    }

    我们发现构建可重用的角色软件库以简化语法编写变得非常容易。

    与继承相关,角色组合具有从编译而不是执行中检测命名冲突的优点。这迫使你解决这些冲突,并使角色组合比继承更可靠和安全,无论是面向对象的编程还是语法创建。

    3-9-2 设定规则

    可以使用参数定义规则(名为类型正则表达式,标记或规则的正则表达式),从而使用参数调用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    my token date($mois) { \d\d? \s+ $mois \s+ \d**4 }
    say "En juin" if "13 juin 2015" ~~ /<date("juin")>/; # -> En juin
    say "En mai" if "13 juin 2015" ~~ /<date("mai")>/; # (échec)

    my $date = "13 $_ 2015" and say $date ~~ /<date("mai")>/
    ?? "Date $date en $_"
    !! "Date $date pas en $_"
    for <mars avril mai juin>;
    # affiche :
    # Date 13 mars 2015 pas en mars
    # Date 13 avril 2015 pas en avril
    # Date 13 mai 2015 en mai
    # Date 13 juin 2015 pas en juin

    这是一个稍微复杂的带有参数规则的更复杂结构的示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    my token mots { <[ \w \s \-]>+ }
    my token entre-crochets { <start("[")> <ident> <end("]")> }
    my token entre-paren { <start("(")> <mots> <end(")")> }
    my token entre-chevrons { <start("<")> <ident> <end(">")> }
    my token start($début) { $début }
    my token end($fin) { $fin }

    say ~$<entre-crochets> if "[Capitaine_Crochet]" ~~ /<entre-crochets>/;
    # Affiche : [Capitaine_Crochet]

    say $<entre-paren> if "[par-parenthèse]" ~~ /<entre-paren>/; # -> ()
    # (échec puisqu'il y a des crochets et non des parenthèses

    say $<entre-paren> if "(par-parenthèse)" ~~ /<entre-paren>/;
    # Affiche :
    # 「(par-parenthèse)」
    # start => 「(」
    # mots => 「par-parenthèse」
    # end => 「)」

    3-9-3 递归规则和动态变量

    语法规则本质上通常是递归的。例如,要匹配嵌套括号,你可能希望编写此样式的递归语法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    grammar G { rule TOP { '(' ~ ')' [ [ $<int>=\d+ ]+| <TOP> ] + } }
    say G.parse("(22 (43 45))");
    # Affiche :

    # 「(22 (43 45))」
    # int => 「22」
    # TOP => 「(43 45)」
    # int => 「43」
    # int => 「45」

    这里,递归调用TOP规则来解析嵌套括号。它可以很容易地分析具有较高嵌套级别的字符串,例如“(22(43(46 45(41)))”。

    将上下文传递给递归调用的规则时,这会变得更加棘手。

    人们可以使用原则参数规则(见第 3.9.2上图)可以更改上下文,但它可能很快就会变得非常冗长。也正是在原则上可以使用全局变量,但是全局变量通常被认为是不好的做法(可能除了一些特殊情况,如环境变量,这是全球性的),因为全局变量经常违反结构化编程的原则,不清楚,不健壮和危险,并且它通常不适用于线程。

    无论是语法还是简单的函数调用或普通的方法,因为它使用递归调用机制,语法可以是艰巨的,当你需要管理多个变量传递回调用函数或函数的返回值。

    动态变量(使用twigil *)解决了这个问题。他们是词法声明,但他们不仅在窝寻求词汇包容性,而且在窝动态的包容性。

    例如,考虑一个计算数字阶乘的简单递归函数,可以写成如下:

    1
    2
    3
    4
    5
    6
    sub fact($n){return 1 if $n <= 1; return $n*fact($n-1)}
    sub callfact(Int $n){
    die "Factorielle non définie pour nombre négatif" if $n < 0;
    say "Factorielle = ", fact($n);
    }
    callfact(5); # -> 120

    假设我们还想计算低于所考虑数的整数之和。参数和多个返回值的通过可能变得相当快速且不可读。以下是将部分和存储在动态变量中的可能语法:

    1
    2
    3
    4
    5
    6
    7
    sub fact($n){$*sum += $n; return 1 if $n <= 1; return $n*fact($n-1);}
    sub callfact(Int $n){
    die "Factorielle non définie pour nombre négatif" if $n < 0;
    my $*sum = 0;
    say "Fact = ", fact($n); say "Somme = ", $*sum;
    }
    callfact(5); # -> Fact = 120 \n Somme = 15

    动态变量 $*sum 在调用函数callfact中声明并初始化为0 。它在词法上是callfact函数的本地,但它在函数事实中是可见的和可修改的,因为事实是由callfact调用的,因此它驻留在callfact的动态范围内。这个变量成为这两个函数的全局变量(可能是它们可能调用的函数),这消除了传递参数或在它们之间返回值的需要,但它仍然是一个变量。词汇变量“私有”,因为它在程序的其余部分是不可见的。

    动态变量不一定是标量,可以类似地定义数组(例如,@*数组)或动态散列。

    当语法变得有点复杂时,动态变量通常可以简化实现。

    语法STD.pm Perl 6的大量使用动态变量,尤其是定义将被传递给函数词法或句法分析上下文,调用的方法或规则,而不需要建立天然气厂的真实通道参数和返回值。例如,有一种动态散列%* LANG定义了不同的“子语言”的Perl 6(基本Perl 6中,正则表达式,Perl的正则表达式5等):

    1
    2
    3
    4
    5
    6
    %*LANG<MAIN>    = ::STD::P6 ;
    %*LANG<Q> = ::STD::Q ;
    %*LANG<Quasi> = ::STD::Quasi ;
    %*LANG<Regex> = ::STD::Regex ;
    %*LANG<P5> = ::STD5 ;
    %*LANG<P5Regex> = ::STD5::Regex ;

    3-9-4 所谓原类型的规则

    同样有可能,由于关键字多,写几个函数或方法的倍数相同的名字,但是编译器可以用不同的签名区分,我们可以写,用的是-clef 原,名称相同的规则,但适用于不同的实体。

    例如,当前的Perl 6语法使用以下构造来定义sigils:

    1
    2
    3
    4
    5
    6
    7
    proto token sigil {*}
    # ...
    token sigil:sym<$> { <sym> }
    token sigil:sym<@> { <sym> }
    token sigil:sym<%> { <sym> }
    token sigil:sym<&> { <sym> }
    # ... une petite dizaine d'autres définitions de sigils

    这将创建一个名为sigil(proto)的组和(在此示例中)属于该组的四个规则(它们属于该组,因为它们具有相同的名称)作为参数接收sym标识符。这些规则中的第一个将sym分配给$,然后在规则的主体中匹配此符号(带有符号 <sym>)。第二条规则与@相同,依此类推。

    如果语法调用规则<sigil>,我们会得到这五个规则的列表,它们之间有一个或逻辑。这与你编写规则的方法大致相同,如下所示:

    1
    token sigil { '$' | '@' | '%' | '&' }

    但是,使用这些原型或protoregex,可以更容易地扩展语法,如下所示(第3.9.6节 )。

    为了§3.6.3.5记忆,已经给出了可能使用原型规则的另一个例子 。

    3-9-5 继承和可变语法

    继承了语法的可能性提供了一种没有料到的表达能力和巨大的前景:它是可能的,例如作为模块的一部分,写语法的“子语法”语法或女儿重载操作符,添加功能甚至修改语法元素,并使用此本地修改语法运行具有相同Perl 6编译器的Perl程序。

    正是由于这种基本机制,Perl 6的语法是动态的,例如,很容易定义自己的运算符(请参阅创建自己的教程操作符从Perl到Perl 6 - Part 2:新奇事物)。然而,没有必要控制继承和语法的可变性,以建立自己的运营商,因为语言提供了最高级别的一个简单的机制来做到这一点,如在运营商的定义因子 “ ! “

    1
    2
    3
    4
    5
    6
    multi sub postfix:<!>(Int $x) {
    my $factorielle = 1;
    $factorielle *= $_ for 2..$x;
    return $factorielle;
    }
    say 5!; # -> imprime 120

    Perl 6宏(在编译时运行的各种函数)也使用Perl 6语法的可变性作为底层机制。

    3-9-6 语法和语言可扩展性的变化

    对语法进行低级修改以扩展语言可能看起来像是白魔法,但实际上并不像看起来那么神秘。

    通过采用允许定义语言符号的proto类型规则(第3.9.4节 ),可以很容易地将新的符号添加到Perl 6的子语法中。例如,假设编译器使用的Perl 6的语法称为“Perl6”(其实名实际上类似于STD:ver ,其中xxx是版本号),我们可以添加sigil“ μ ”:

    1
    2
    3
    grammar NouveauSigilP6 is Perl6 {
    token sigil:sym<µ> { <sym> }
    }

    或者,对于系统使用“ $ ”违背亲欧洲信念的读者,修改现有的印记:

    1
    2
    3
    grammar EuroPerl6 is Perl6 {
    token sigil:sym<$> { '€' }
    }

    语法EuroPerl6现在允许使用的印记“ € ”为标量,但因为它是用相同的参数相同的规则(:印记符号 <$> ,原来的语法,编译器没有)难以知道该怎么做。英国的欧洲怀疑论者可能,如果他们希望通过印记“ £ ”音乐爱好者“ ♪ ”,也是过敏反全球化“ $ ”的锤子和镰刀(“ ☭ ”)或符号变就变。

    3-9-7 展望

    还有许多其他真正令人惊叹和惊人的扩展可能性,但它们更倾向于极客或语言专家,而不是普通用户。在这里探索它们是不可能的,因为它可能需要几十个额外的页面,并且不属于本教程的范围。

    在撰写这些页面的时候,目前几乎没有关于这个主题的文档(实际上关于Perl 6语法,甚至是英语),最丰富的信息来源可能是参考Perl 6和现有模块的标准语法(以及实验)。然而,感兴趣的读者可以通过咨询以下链接加深:Patrick Michaud的The Perl 6标准语法 r和Moritz Lenz的Perl 6的可变语法。不幸的是,这些来源很老,但如果某些细节点可能已经过时,那么概念性讨论仍然非常重要。

    前款规定原文为2015年和可用的文件已经在优秀图书的2017年12月以来的出版物明确扩大解析Perl 6个语法和的正则表达式-一个递归下降到解析由莫里茨伦茨(Apress出版,201页)。如果你读英语,那你有兴趣,请不要犹豫一秒,这真的是我的书也喜欢看,当我开始学习正则表达式,特别是Perl 6语法。

    4. 最佳实践和陷阱

    Perl 6正则表达式和语法单独形成一个真正的编程模型,通常至少部分是新的,因此需要掌握。

    为了帮助读者编写强大的正则表达式和语法,这里有一些好的做法,应用似乎是明智的(从常识来看,这些不是盲目遵循的规则)。这些良好实践的范围从简单的小规模代码格式到对匹配的精细理解,包括帮助避免可能的陷阱和不可读的代码。

    4-1 格式化代码

    当不使用副词:sigspace时,在Perl 6正则表达式中忽略空格(和注释)。这具有插入空格和注释以提高可读性的优点。

    比较这个非常紧凑的正则表达式来匹配浮点数(浮点数):

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

    用它,等价,但更具可读性:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    my regex float {
    <[+-]>? # signe optionnel
    \d* # chiffres de début, optionnels
    '.' # séparateur décimal
    \d+
    [ # exposant optionnel
    e <[+-]>? \d+
    ]?
    }

    (上面这个非常简单的例子只是为了说明格式化,我们之前已经给出了更好的方法来匹配浮点数,我们将把它提高一点。)

    通常,希望:

    • 使用原子周围和群内空间;
    • 将量词直接放在原子之后,不插入空格,并且;
    • 垂直对齐括号和括号打开或关闭。

    同样,垂直条“ | 分离替代方案的不同可能性:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    my regex exemple {
    <début>
    [
    || <choix_1>
    || <choix_2>
    || <choix_3>
    ]+
    <fin>
    }

    4-2 限制尺寸

    正则表达式需要非常少的装饰或船码,因此它们通常比普通代码更紧凑。保持足够小是很重要的。

    当捕获量变高或者我们使用命名捕获量来更好地理解它时,我们应该问自己是否应该更进一步并转到命名的正则表达式。

    4-2-1 识别浮点数

    例如,前一章(第4.1节 )的正则表达式浮点数可以分解为更小的部分:

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

    当正则表达式变得更复杂时,这会有所帮助。例如,如果要在有指数时使小数点(“逗号”)可选:

    1
    2
    3
    4
    5
    6
    7
    my regex float {
    <signe>?
    [
    || <décimal>? '.' <décimal> <exposant>?
    || <décimal> <exposant>
    ]
    }

    这也允许更好地重用代码。上面定义的符号和小数规则(标记)也可以非常简单地定义一个整数:

    1
    2
    3
    4
    my regex entier {
    <signe>?
    <décimal>
    }

    4-2-2 识别复数

    类似地,再次通过重用上面定义的符号和十进制规则(标记),我们可以定义一个复数(用笛卡尔代数表示法),如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    my rule nombre { <float> || <signe>? <décimal> } 
    my regex complexe {
    [
    || [<nombre> \s* <signe> \s* ]? <nombre> \s* 'i'
    || <nombre>
    ]
    }
    say "Reconnu" if '3+4i' ~~ /<complexe>/; # -> Reconnu
    # Accéder aux différents éléments du nombre complexe reconnu :
    say ~$/<complexe>
    if '3.5e-7 + 4.17i' ~~ /<complexe>/; # 3.5e-7 + 4.17i
    say ~$/<complexe><nombre>[0]; # -> 3.5e-7
    say ~$/<complexe><nombre>[1]; # -> 4.17
    say ~$/<complexe><nombre>[0]<float> # -> 3.5e-7
    say ~$/<complexe><signe> # -> +

    很明显,即使不使用语法(至少对于这个非常简单的情况),人们也可以使用命名规则构建真正的乐高游戏的砖块。识别IP地址将给出另一个例子。

    4-2-3 识别URL

    我们将在此考虑简化URL(Web地址)可以是IP地址(由句点分隔的四个数字的系列),也可以是表示协议和域名的字符串,后跟路径访问资源。

    我们可以从尝试识别IP地址开始。

    4-2-4 识别IP地址

    IPv4地址由4个数字组成,由1到3位数字以点分隔。

    正则表达式首次尝试识别IP地址可能是:

    1
    /(\d**1..3) \. (\d**1..3) \. (\d**1..3) \. (\d**1..3)/

    但它至少相当费力和笨拙。

    应用于量化器的%修饰符(参见上面的第2.4节 )允许你指定重复确认之间必须存在的分隔符:

    1
    / (\d ** 1..3) ** 4 % '.' /

    它已经好多了,但遗憾的是从某种观点来看是错误的:这个正则表达式会毫无问题地匹配字符串“125.266.742.12”,这不是有效的IP地址(四个数字中的每一个都必须表示一个字节,因此可以用十进制表示法理解,在0到255之间。这一切当然取决于我们究竟要做什么,上面的正则表达式足以捕获我们正在寻找的东西,但它并不能完全确保输入数据的验证。

    要验证一个字节是否捕获好字节(数字低于255),可以构建一个正则表达式(或令牌)字节,它将检查这些条件,然后使用字节的正则表达式ip :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    my regex octet { 
    || (25 <[0..5]> # 250 à 255
    || 2 <[0..4]> \d # 200 à 249
    || 1 \d**2 # 100 à 199
    || \d**1..2) # 0 à 99
    }
    my regex ip { <octet> ** 4 % '.' }
    say "Reconnu" if "244.7.245.23" ~~ /<ip>/; # -> Reconnu
    say ~$/; # -> 244.7.245.23

    请注意,通过使用代码类型断言(结束),可以显着简化正则表达式字节:

    1
    my regex octet {(\d ** 1..3) <?{0 <= $0 <= 255 }> }

    最后两个版本的正则表达式字节仍然存在一些缺陷,在某些情况下可能会出现在字符串的最后一个八位字节:字符串“244.7.245.263”(不是IP地址)正确,因为最后一个数字大于255),IP正则表达式 将识别IP地址显然是正确的,但可能不需要:“244.7.245.26”。为了避免这个问题,我们可以在字节定义中关于少于三位数字节的部分断言之前添加否定值(<!Before …>):

    1
    2
    3
    4
    5
    6
    7
    my regex octet { 
    || (25 <[0..5]> # 250 à 255
    || 2 <[0..4]> \d # 200 à 249
    || 1\d**2 # 100 à 199
    || \d**1..2) <!before \d>) # 0 à 99
    }
    my regex ip { <octet> ** 4 % '.' }

    同样,简单IP地址的情况非常简单,我们可能不用编写语法,因为上面的正则表达式的组合基本上就足够了。

    但为什么我们要避免语法的定义呢?编写和使用语法并不复杂(只要你习惯了),而不是组装一系列命名规则,并且可以更容易地将语法扩展到其他元素,例如URL。如有必要,这也将受益于与语法相关的额外优势(受限命名空间,继承,方法操作,.parse和.fileparse方法等)

    4-2-5 识别URL的 Grammar

    解析URL的语法(相当基础)可以具有以下形式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    grammar URL {
    token TOP {
    <schéma> '://'
    [<ip> | <nom-domaine> ]
    [ ':' <port>]?
    '/' <chemin>?
    }
    token octet {
    (\d**1..3) <?{ $0 < 256 }>
    }
    token ip {
    <octet> [\. <octet> ] ** 3
    }
    token schéma {
    \w+ # Ce pourrait aussi être : [http | https | ftp | ...]
    }
    token nom-domaine {
    (\w+) ( \. \w+ )*
    }
    token port {
    \d+
    }
    token chemin {
    <[ a..z A..Z 0..9 \-_.!~*'():@&=+$,/ ]>+
    }
    }

    my $cible = URL.parse('http://perl6.org/documentation/');
    say $cible<nom-domaine>; # -> perl6.org

    4-3 要匹配什么?

    通常,输入数据的格式没有明确指定,或者规范(如果存在)是程序员不知道的。对于预期的内容,相当自由或灵活通常是有用的,但仅限于没有模棱两可的风险。

    如果我们以.ini文件为例:

    1
    2
    [section]
    key=value

    部分标题中可以包含哪些内容?只允许一个单词可能限制太多,也许有人会写[两个单词],或者使用破折号,或者只有上帝知道还有什么……而不是问什么是允许的在此标题内,询问不允许的内容可能会有所帮助。

    很明显,禁止关闭钩子,因为[a] b]至少可以说是模棱两可的。从同一观点来看,必须禁止开口钩。哪个可以给我们以下规则:

    1
    token entête { '[' <-[ \[\] ]>+ ']' }

    如果我们分析一行,这似乎运作良好。但如果我们处理整个文件,突然规则分析:

    1
    2
    [ avec un
    retour à la ligne entre deux]

    作为一个正确的标题,这可能不是一个好主意。务实的妥协可能是写:

    1
    token entête { '[' <-[ \[\] \n ]>+ ']' }

    然后,在后处理中,消除空格,制表符等。在节标题的开头和结尾处。

    4-4 匹配空白

    副词:sigspace(或使用规则,而不是正则表达式或令牌)对于分析可能出现在多个位置的空间非常有用。

    如果我们回到ini文件分析的例子,规则可以是:

    1
    my regex kvpair { \s* <clef=identifiant> '=' <val=identifiant> \n+ }

    这原则上有效,但可能不像人们希望的那样灵活。由于用户可以决定在等号周围放置空格,可能应该是:

    1
    my regex kvpair { \s* <clef=identifiant> \s* '=' \s* <val=identifiant> \n+ }

    这很快就会变得不切实际。所以我们可以尝试使用规则而不是正则表达式并写:

    1
    my rule kvpair { <clef=identifiant> '=' <val=identifiant> \n+ }

    但要小心!在值消耗所有可用空间(包括换行符)后隐式匹配空格,因此\ n + final无法匹配。并且通常禁用回溯,它不起作用。

    这是将隐式空间重新定义为输入格式中不重要的空间非常有用的地方,这可以通过重新定义ws 令牌 来完成(但它只能起作用) ‘语法内部):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    grammar IniFormat {
    token ws { <!ww> \h* }
    rule entête { '[' (\w+) ']' \n+ }
    token identifiant { \w+ }
    rule kvpair { \s* <clef=identifiant> '=' <val=identifiant> \n+ }
    token section {
    <entête>
    <kvpair>*
    }

    token TOP {
    <section>*
    }
    }

    # Exemple de fichier ini dans un document "ici même" :
    my $contenu = q:to/FIN_INI/;
    [passwords]
    jean=mdp1
    anne=plusfiable123
    [quotas]
    jean=123
    anne=42
    FIN_INI

    say so IniFormat.parse($contenu);

    除了把所有的正则表达式语法,使他们成为的想法令牌或规则(正则表达式,因为这些不需要反正回溯),以及有趣的新点定义如下:

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

    在隐式空间分析期间调用。它匹配零个或多个不在单词类型的两个字符之间的水平空格(<!Ww>,否定单词中的断言“)。对水平空间的限制是必要的,因为换行符(它们是垂直空格)界定了记录,因此不应该隐式匹配为仅仅空格。

    弯道周围的空间可能仍然存在问题。正则表达式\ n +将无法匹配字符串,例如\ n \ n,因为两个换行符之间有一个空格。要匹配这种输入字符串,可以用 \ n \ s *替换 \ n +。

    4-5 避免递归左陷阱

    所谓的左递归(递归或左),其中如果规则称自己年初模式,不一定消耗分析系统的字符语法可能进入无限循环的情况下(例如,因为像?或*这样的量化器允许匹配空字符串。

    例如,请考虑以下规则:

    避免

    1
    token x { <x>? 'x' }

    有人可能认为这个规则等同于x +模式,但事实并非如此,这个规则将进入无限递归,因为量词?允许它匹配一个空字符串,然后在递归调用自身时不消耗已解析字符串的元素。

    要避免此陷阱,必须确保每个递归调用使正则表达式引擎的光标前进至少一个字符。

    在间接递归的情况下也可能出现左递归情况:例如,如果规则调用规则,它本身调用规则,并且规则调用反过来规则:如果某些字符串可能发生三个规则都不会消耗字符,那么我们再次冒险进入无限递归循环。同样,游行是为了保证这三个规则中至少有一个消耗链中的至少一个字符。

    3.8节中计算器的语法 提出这种间接递归。例如,你可以使用以下规则调用序列找到自己:

    1
    TOP → expr → terme → atome → expr-parenth → expr …

    其中expr规则本身是间接调用的。

    但是在这个语法中没有无限递归的风险,因为这个递归链中的一个规则在它可以调用下一个字符串之前在解析后的字符串中消耗至少一个字符; 更确切地说,规则expr-parenth :

    1
    rule expr-parenth     { '(' <expr> ')' }

    在调用递归expr之前,必须使用已解析字符串的左括号。

    4-6 调试正则表达式或Perl 6 Grammar

    当你开始学习编程时,你会因为愚蠢的小错误而浪费很多时间。凭借经验,你可以学会减少错误并编写更快的代码。

    随着语法(以及更普遍的正则表达式),一切似乎都重新开始:即使是有经验的程序员在处理语法时也会开始犯愚蠢的错误。编写正则表达式以及更多的语法与开发普通程序程序没有太大关系,需要一个新的学习阶段。

    以下是一些帮助编写和调试语法的方法(部分加入了上面已经给出的一些提示):

    • 小规模地继续,按规则统治,并在你去的时候测试规则;

    • 单独测试规则 :如果你的语法不起作用,请逐个测试每个规则以确定规则是错误的,错误命名(或从未调用过),等等。:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      grammar MaGrammaire {
      token TOP {
      ^ [ <commentaire> | <truc> ]* $
      }

      token commentaire {
      '#' \N* $$
      }
      token truc {
      ^^(\S+) \= (\S+) $$
      }
      }

      # Essayer de parser l'ensemble:
      say ?MaGrammaire.parse("#commentaire\ntoto = titi"); # 0
      # La grammaire ne reconnaît pas le test, voyons les règles une à une
      say ?MaGrammaire.parse("#commentaire\n", :rule<commentaire>); # 1 - OK
      say ?MaGrammaire.parse("toto = titi", :rule<truc>); # 0 - KO
      # C'est la règle <truc> qui ne fonctionne pas.
    • 插入显示(打印或说); 只需将它们放在大括号中,这些显示就可以作为普通代码运行(参见 3.4.2。)。让我们回到上一个我们到达的例子(<trick>规则不起作用):

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      grammar MaGrammaire {
      token truc {
      { say "truc: appelé" }
      ^^
      { say "truc: trouvé début de ligne" }
      (\S+)
      { say "truc: trouvé premier identifiant: $0" }
      \=
      { say "truc: trouvé =" }
      (\S+) $$
      }
      }

      say ?MaGrammaire.parse("toto = titi", :rule<truc>);

      # Affichage:
      #
      # truc: appelé
      # truc: trouvé début de ligne
      # truc: trouvé premier identifiant: toto
      # 0
      # C'est le signe égal qui n'est pas reconnu. Pourquoi? À cause
      # de l'espace qui le précède et qui n'est pas dans la règle. Il
      # suffit par exemple de transformer le token en rule
    • 注意回溯 :许多习惯于Perl 5或相关正则表达式系统的程序员都熟悉回溯的使用,非常自然且非常强大,只需要简单的正则表达式。但是在嵌套语法(甚至是一组正则表达式)中很难掌握回溯。大多数词汇和句法问题都可以用不需要(或很少)回溯的方式来表达,因此强烈建议避免在语法中回溯,以及有效性的原因只是作为开发者心理健康的保障。也就是说,使用退格键更容易编写一些模式; 如果你使用它们,请务必将范围限制为唯一需要的正则表达式,

      1
      2
      3
      4
      5
      6
      7
      8
      rule verbatim {
      '[%' ~ '%]' verbatim
      # Autorise le retour arrière à partir d'ici seulement
      :!ratchet
      .*? '[%' endverbatim '%]'
      }
      # Le retour arrière sera activé dans la regex finale, mais, dès
      # qu'une reconnaissance aura été trouvée, on n'en essaiera pas d'autre
    • 最后,让我们提一下 Jonathan Worthington 在 Rakudo/Perl 6 下的优秀语法和正则表达式调试模块。添加一个使用 Regex::Tracer; 在你的代码中,词法范围内的所有语法都会显示颜色详细的调试信息,特别显示调用哪些规则,哪些规则有效以及哪些规则失败。 Advent Calendar Perl 6提供了示例和更多详细信息。

    本节中的信息 4.6 调试正则表达式或Perl 6语法基本上是基于 Moritz Lenz的如何调试Perl 6语法

    5. 结论

    正则表达式和Perl 6级的语法是远不是唯一的新的Perl 6,但他们对自己的完全新的机会开放,既为文字记录功能丰富度分析语言的表现力和可扩展性。我们很可能会发现,经验使用很难在今天进行思考。在我们看来,语法是应该使Perl 6成为一种语言的一部分,这种语言将在20年或更长时间内保持坚定的现代性。

    6. 另见/来源

    关于Perl 6的文档取得了相当大的进展,但有时仍然不完整。

    2Perl的正则表达式6这份文件在很大程度上是官方文档的法国适应的正则表达式(匿名者),这是我们添加了来自下面的Perl 6次的测试中,信息剧情简介 S05和一些官方文档的其他外围文档,此处和那里找到的文章以及个人实验产生的元素。

    当我们最初撰写本文档时,官方英语语法文档(Grammars)在当时(2015年底)非常不完整。在2018年,情况现在好多了。

    在我们发布本文档的第一个版本时,毫无疑问是关于该主题的最完整的文档,无论是法语还是英语。在这种情况下,我们将此文档置于知识共享许可 “署名CC BY”下,以授权(甚至鼓励)任何想要自由恢复内容的人,同时希望尽可能引用来源及其作者。即使导致我们做出这种选择的情况不再具有真正的相关性(官方文档已经说过,我们已经说过,已经有了很大的改进),我们认为没有理由改变我们的想法和因此,我们保留此知识共享许可证 “Attribution CC BY”。

    与我们在2015年撰写本文档的第一版时相比,另一个重大变化是2017年在Perl 6上发布了大量的六本书,从我自己的书,Think Perl 6 - 如何像计算机科学家一样思考(450页,O’Reilly,2017年5月),可以电子格式免费打印和下载。在2018年,这本书已被Luis F. Uceta翻译成西班牙语,可以免费下载:Piensa en Perl 6。在我们写这些专栏的那天(2018年10月底),这本书已经完全翻译,但它仍在等待最后的重读。

    其中一个新的书,发表在2017年12月,是专门讨论正则表达式和语法:用Perl 6的正则表达式和语法解析-一个递归下降到解析,莫里茨伦茨(215页,Apress出版)。我只能非常热情地向所有希望深化这一主题的人推荐这本优秀的书籍。

    7. 致谢

    我感谢官方Perl 6文档的匿名作者,本文档的某些部分部分是法语的免费改编。

    我感谢DjibrilRoland ChastainClaude LeloupCognominal的校对以及他们非常有用的改进建议。还要感谢CosmoKnackiPyramidev,他们在出版后亲切地报道了一些 bug。

    除了 Developpez.com 还有如下教程可供参考: