如何在Perl 6中创建语法
在编程中,语法是一组解析文本的规则。它们非常有用,例如,您可以使用语法来检查一个文本字符串是否符合特定的标准。Perl 6原生支持语法——它们非常容易编写,一旦开始使用,您会发现自己在各个地方都在使用它们。
最近我一直在工作于Module::Minter,这是一个用于创建新Perl 6模块基本骨架结构的简单应用程序。我需要一个方法来检查建议的模块名称是否符合Perl 6的命名约定。
模块名称可以描述为由两个冒号分隔的标识符,例如File::Compare。标识符必须以字母字符(a-z)或下划线开头,后跟零个或多个字母数字字符。到目前为止,一切顺利,但并不简单;一些模块名称只有一个标识符而没有冒号,例如Bailador,而其他模块则更复杂,例如HTTP::Server::Async::Plugins::Router::Simple。这似乎是使用语法的合适工作!
定义语法
Perl 6语法由正则表达式组成。我需要两个正则表达式:一个用于匹配标识符,一个用于匹配双冒号分隔符。对于标识符正则表达式,我将使用
<[A..Za..z_]> # begins with letter or underscore
<[A..Za..z0..9]> ** 0..* # zero or more alpanumeric记住我们正在使用Perl 6正则表达式,如果您习惯了Perl 5风格的正则表达式,那么这些内容可能会有所不同。字符类由<[ ... ]>定义,范围使用范围运算符..定义,而不是连字符。这个正则表达式匹配任何以字母或下划线开头的字符,后跟零个或多个字母数字字符。匹配两个冒号很简单
\:\: # colon pairs使用grammar关键字定义语法,后跟语法的名称。我将把这个语法命名为Legal::Module::Name
grammar Legal::Module::Name
{
  ...
}现在我可以将正则表达式作为令牌添加到语法中
grammar Legal::Module::Name
{
  token identifier
  {
    # leading alpha or _ only
    <[A..Za..z_]>
    <[A..Za..z0..9]> ** 0..*
  } 
  token separator
  {
    \:\: # colon pairs
  }
}每个语法都需要一个名为TOP的令牌,这是语法的起点
grammar Legal::Module::Name
{
  token TOP
  { # identifier followed by zero or more separator identifier pairs
    ^ <identifier> [<separator><identifier>] ** 0..* $
  }
  token identifier
  {
    # leading alpha or _ only
    <[A..Za..z_]>
    <[A..Za..z0..9]> ** 0..*
  } 
  token separator
  {
    \:\: # colon pairs
  }
}TOP令牌定义一个有效的模块名称,它以标识符令牌开头,后跟零个或多个分隔符和标识符令牌对。这既易于编写也易于维护——假设我想更改分隔符的规则以包括连字符(‘-’),我只需更新分隔符令牌的正则表达式,效果就会自动传播到TOP令牌定义。
使用语法
现在我已经有了语法,是时候将其付诸实践。parse方法在字符串上运行语法,如果成功,则返回一个匹配对象。此代码解析$proposed_module_name字符串,如果提议的模块名称无效,则打印出错误消息或匹配对象。
my $proposed_module_name = 'Super::New::Module';
my $match_obj = Legal::Module::Name.parse($proposed_module_name);
if $match_obj
{
    say $match_obj;
}
else
{
    say 'Invalid module name!';
}此代码打印
「Super::New::Module」
 identifier => 「Super」
 separator => 「::」
 identifier => 「New」
 separator => 「::」
 identifier => 「Module」从匹配对象中提取内容
我们不仅可以向命令行中输出匹配对象的全部内容,还可以从匹配对象中提取匹配的令牌。这使用的是Perl 6中常用到的引号语法(例如命名正则表达式和散列键)
say $match_obj[0].Str; # Super
say $match_obj[1].Str; # New
say $match_obj[2].Str; # Module
say $match_obj; # all 3 captures动作类
到目前为止,语法可以检测提议的模块名称是否合法,并从匹配对象中轻松提取模块名称的各个部分。Perl 6还允许您添加一个动作类,该类定义了匹配令牌的额外行为。我想添加一个警告,当模块名称具有过多的标识符时,换句话说,它是一个合法的模块名称,但用户可能想要缩短它。首先,我定义动作类本身
class Module::Name::Actions
{
  method TOP($/)
  {
    if $<identifier>.elems > 5
    {
      warn 'Module name has a lot of identifiers, consider simplifying the name';
    }
  }
}如您所见,这是一个普通的Perl 6类定义。我添加了一个名为TOP的方法,它匹配语法中的第一个标记。我使用命名正则表达式语法来计算所有标识符匹配的数量,如果有超过5个,则发出警告。这不会阻止代码运行,但可能会使用户重新考虑他们的模块名称选择。
然后我初始化动作类,并将其作为参数传递给parse。
my $actions = Module::Name::Actions.new; 
my $match_obj = Legal-Module-Name.parse($proposed_module_name, :actions($actions));每当在解析过程中遇到标记时,语法将调用匹配的动作类方法。在这种情况下,每次解析都会调用一次,但我们可以为标识符标记添加额外的长度。查看Module::Minter的源代码,了解如何将语法集成到模块中。
Perl 5中的语法
您也可以在Perl 5中编写语法。对于类似于Perl 6的实现,请查看Regexp::Grammars或Ingy Döt Net的Pegex发行版。对于不同的方法,请查看brian d foy所著的《Mastering Perl》的第一章,其中包含一个JSON语法的示例。
注:这并不完全正确 - 整个名称(包括冒号)都是标识符。
更新: 添加了Regexp::Grammars链接。2015-01-13
本文最初发布在PerlTricks.com上。
标签
反馈
这篇文章有什么问题吗?请在GitHub上打开一个问题或拉取请求来帮助我们。




 
              