Perl 6 终于即将面世。在本文中,Ted 将向您介绍 Perl 6 语言的语法和正则表达式,并将它们与当前可用的 Perl 5 Parse::RecDescent 模块进行对比。认识 Perl 正则表达式的新特性,并学会如何具体使用新奇而又功能强大的 Perl 脚本语言。
对所有 Perl 编程人员而言,Perl 6 项目是一个热门话题。Perl 一直是一门不断发展的语言,几乎从任何可以想像得到的角度都可以确定,Perl 6 确实是由 Perl 5 进化而来(不过,您也可以说它们的起源相同)。Perl 6 将运行于 Parrot 之上,Parrot 是一种通用虚拟机,不但可以加载和解释 Perl 6 字节码,还可以加载和解释其他许多语言。
不要让将来的问题困扰着您。如果您曾经用了几个月的时间来观察某个建筑物的建筑过程,您就会知道,选好地基后要进行挖掘,金属骨架似乎总是矗立着。工人们来来往往,工作一直在进行,但是表面看到的却总是陈旧的、丑陋的、生锈的金属。然后,若干天以后,突然间,建筑物就完成了。Perl 6 项目当前正是处于那个长期的中间阶段,表面看到的只是生锈的金属,而工人们正在深入地下进行幕后工作。如果您想洞悉项目的进展,那么可以去查看最新的 Parrot 发行版本以及 Perl 6 每周的更新(请参阅参考资料中的链接)。
本文将向您介绍 Perl 6 语言的语法和正则表达式,并将这些与当前可用的 Perl 5 Parse::RecDescent 模块进行对比。如果以前对 Perl 5 有所了解,熟悉 Parse::RecDescent,并且有词法分析(lexing)和句法分析(parsing)方面的经验,那么这些会对您掌握本文有很大帮助,此外,本文是为那些对 Perl 6 语法和正则表达式感兴趣的所有 Perl 编程人员撰写的。
Perl 6 正则表达式和语法概述
首先需要声明一件事:Perl 将通过使用 :p5 修饰符来支持 Perl 5 正则表达式。对于那些对 Perl 6 正则表达式不感兴趣或者不想转到这方面来的人而言,这是一个福音。此外,Perl 6 正则表达式可能(但不是必须)与 Perl 5 中对应的正则表达式有本质上的区别。
在需要时,Perl 6 正则表达式可以被复用。在匹配单一的词时,复用正则表达式是很荒谬的;但在解析配置文件时,几乎必须要复用正则表达式(这取决于配置文法的复杂度、发生修改的频率等)。
在 Perl 5 中,Regexp::Common 模块(请参阅参考资料)已经在尝试复用正则表达式,但是,因为 Perl 5 不允许复用正则表达式,所以不得不将它们封装在一个模块接口中。 Perl 6 完全支持这种复用。
尽管您可以编写类似 Perl 5 正则表达式的模糊而晦涩的 Perl 6 正则表达式,但在默认情况下,允许启用空格注解;所以,虽然在 Perl 5 中您可以用“hello there”本身来匹配“hello there”,但在 Perl 6 中,您必须将其改为 /hello <sp there/。这样就可以在正则表达中将条件清晰地分离开来。
更重要的是,在语法(grammars)内部使用正则表达式时,Perl 6 正则表达式必须不那么晦涩。编程人员会发现(我希望如此, Larry Wall 也是),对清单 2 的理解与维护要比对清单 1 的容易得多:
清单 1. 没有语法的正则表达式
# note this is just a language example, not an accurate name matcher
# Perl 6 <[A-Z] is equivalent to the Perl 5 [A-Z]
# Perl 6 :w modifier surrounds all tokens with "automagic" whitespace,
# which basically means it will match what most people would call
# "words"
$name = m:w/ <[A-Z]<[a-z]+ <[A-Z]<[a-z]+ /;
清单 2. 在语法中作为规则的正则表达式
# note this is just a language example, not an accurate name matcher
grammar English
{
rule name :w { <singlename <singlename };
rule singlename { <[A-Z]<[a-z]+ };
};
清单 2 不仅更容易读懂,而且维护起来也更容易。例如,Perl 6 本身已经定义了 <upper 和 <lower 规则,这使事情变得更为简单:
清单 3. 在语法中作为规则的经过改进的正则表达式
# note this is just a language example, not an accurate name matcher
grammar Names
{
rule name :w { <singlename <singlename };
rule singlename { <upper<lower+ };
};
瞧!使用 <upper 和 <lower 之后,我们就复用了代码。此外,我们现在还可以处理 Unicode 名称,而这之前,我们只能局限于处理从 A 到 Z 开头的名称。代码复用是一项出色的技术。
在进行更进一步的维护时,几乎总是需要对名称中的破折号或其他名称(比如 Don Quixote de la Mancha)进行修正(举例来说)。同样,在将对个别规则的更改隔离出来,或者在需要时创建一个新规则的时候,您会注意到这是多么简单。
语法(Grammars) 是相当简单的概念。它们是具有专用名称空间(namespace)和专用子例程的程序包;每一个子例程被称为一个规则。语法可以继承其他语法。这样就使得编程人员既可以复用其他人的代码,也可以编写能够复用的代码。从 Perl 模块的 CPAN 存档文件(archive)获得的成功中可以明显地看出这种复用的价值。Perl 6 语法在规则中使用正则表达式,然后可以将这些规则用于其他规则之中。
对比 Parse::RecDescent 与 Perl 6 的语法
熟悉 Parse::RecDescent 的人都知道,它是一个功能强大的工具。Parse::RecDescent 是一个 Perl 5 模块,只使用很少代码就可以生成非常强大的语法。这些语法与 Perl 6 的语法是否非常相似呢?是这样的,Parse::RecDescent 的作者 Damian Conway 深入参与了 Perl 6 的许多工作。因此,很多在 Parse::RecDescent 中证明好用的思想都被应用到 Perl 6 中也就不足为奇了。它们的一些语法有很多类似之处。
Parse::RecDescent(此后称之为 P::RD)使用 new() 模块文法来创建新的语法。每个 P::RD 语法成为 P::RD 类中的一个对象,语法中的每一个规则都可以作为用来执行动作的方法。P::RD 语法可以将动作(action)与每一个规则关联起来,将其作为解析过程中的一个完整部分。在 Perl 5 本身中,解析是一个事件,而使用了扩展语法的动作则是达成目标途径中的不幸牺牲品(roadkill),那些扩展的语法被证明是造成迷惑的罪魁祸首。这一区别使得 P::RD 比 Perl 5 正则表达式更为有效,原因在于当检测到匹配对象时,它会使某些事情发生。
Perl 6 语法吸取了 P::RD 的经验,它意识到了这些动作的实用性,现在,这些动作已经成为其首要的组成部分。每发现一个匹配对象,就会执行一个动作(代码块)。即使匹配对象的内容可能已经被修改也是如此!此外,这些动作的语法与 P::RD 中的语法同样简单。
清单 4. 包含动作的 Parse::RecDescent 语法
# small extract from my cfperl.pl program's global parser
my $parse_global = new Parse::RecDescent (q{
input:
blank | comment | class | section
comment: /^\s*/ '#' { 1; }
blank: /^\s*$/ { 1; }
section: /\w+/ ':'
{ $::current_section = $item[1];
$::current_classes = 'any'; 1;
}
class: compound_class '::'
{ $::current_classes = $item{compound_class}; 1; }
compound_class: /[-!.|\w]+/
});
$parse_global-input("TEXT GOES HERE");
上面的语法只有一个规则,即 input,它将匹配 blank、comment、class 或者 section 规则。这些规则中的每一个规则都有一个定义,它们可以是独立的或是基于另一个规则的,也可以同时具备这两种特性。
注意像普通的代码块那样封装在大括号 { } 内的动作。对于一个片断(section),动作将全局变量 $current_section 设置为正在进行匹配的片断,并重新设置 $current_classes 全局变量。对于类,动作将全局的 $current_classes 变量设置为匹配的条目。
这个语法在 Perl 6 中会是什么样的呢?
清单 5. 清单 4 语法的 Perl 6 译本
# this may be buggy - it's certainly untested
# every input is known to be one line, without newline characters
grammar Global
{
rule input { <blank | <comment | <class | <section }
rule blank { ^^ \s* $$ }
rule comment { ^^ \s* \# }
rule section
{ (\w+) \s* \:
{
$::current_section = $1;
$::current_classes = 'any';
}
}
rule class { (<compound_class) \s* \:\:
{
$::current_classes = $1;
}
}
rule compound_class { <[-!.|\w]+ }
}
Perl 5 的正则表达式
如果您对 Perl 5 正则表达式非常熟悉,那么可以跳过这一节。
所有 Perl 5 编程人员都熟悉 Perl 5 正则表达式。在进行匹配时,要用 m// 操作符来标识这些正则表达式(有时是可选的),而当匹配并替换时,则要用 s/// 操作符来标识它们。在特定的情况下,/ 字符可以由其他字符取代,并且有一些特定的操作符,它们与正则表达式有几分类似,不过这样的操作符不多(例如 tr///)。Perl 5 正则表达式要指明的或者是“寻找此内容”,或者是“寻找此内容,并