在Perl中实现“你是指”功能

几周前,Yuki Nishijima发布了一个巧妙的Ruby gem,名为“Did You Mean”,它拦截失败的方法调用,并在异常消息中建议最接近的匹配(正确)方法。我想创建一个Perl中的等效模块,于是带着对AUTOLOAD
的有限理解,我开始创建Devel::DidYouMean。
使用模块
Devel::DidYouMean现在在CPAN上可用,您可以在命令行中安装它
$ cpan Devel::DidYouMean
要使用此模块,只需像使用任何其他模块一样使用use
# script.pl
use Devel::DidYouMean;
print substring('have a nice day', 0, 6);
此代码调用一个内置函数“substring”,该函数不存在。运行上述代码我们得到一个更有帮助的错误消息
Undefined subroutine 'substring' not found in main. Did you mean substr? at script.pl line 4.
它是如何工作的
正如我在引言中所暗示的,DidYouMean.pm使用AUTOLOAD
函数定义了一个子程序,该函数可以捕获丢失的子程序调用。但默认情况下,此子程序仅在Devel::DidYouMean命名空间内存在,因此它仅在发生丢失方法调用时才会触发,例如Devel::DidYouMean->some_method;
。这并不很有用!所以我使用了一些符号表黑魔法在运行时将模块加载到每个命名空间
CHECK {
# add autoload to main
*{ main::AUTOLOAD } = Devel::DidYouMean::AUTOLOAD;
# add to every other module in memory
for (keys %INC)
{
my $module = $_;
$module =~ s/\//::/g;
$module = substr($module, 0, -3);
$module .= '::AUTOLOAD';
# skip if the package already has an autoload
next if defined *{ $module };
*{ $module } = Devel::DidYouMean::AUTOLOAD;
}
}
在分析此代码时,你可能想知道那个奇怪的CHECK
块是做什么的。这确保了代码在程序编译阶段完成后加载,从而降低了程序在DidYouMean.pm导出其AUTOLOAD
子程序之后加载另一个模块的风险。Perl定义了几个命名的代码块(你可能熟悉BEGIN
)。使用检查块的缺点是,如果使用require
而不是use
加载模块,则此块根本不会执行。
然后,代码将AUTOLOAD
子程序添加到主(执行程序的命名空间)和符号表中每个其他命名空间。我从Mastering Perl的“动态子程序”章节中获得了此语法。
自动加载的子程序代码很长,所以这里不再重复。在高级上,它从$AUTOLOAD
中提取失败调用的子程序名称,并使用Text::Levenshtein模块,计算失败调用的子程序名称与调用命名空间中每个可用子程序之间的Levenshtein距离。然后它发出一个错误,显示一个带有匹配子程序列表的通常未定义子程序错误消息。
结论
尽管模块“工作”,但将子程序导出到内存中的每个命名空间感觉有些笨拙。我考虑过但无法实现的替代方法是在END
块中定义代码,然后在程序以“未知子程序”错误结束进行检查。这个挑战在于,在最后阶段,Perl已经使错误变量$!
无效,因此很难知道程序为什么会结束(绑定$!
可能解决这个问题)。如果你对这个挑战感兴趣,代码库托管在GitHub上,欢迎提交pull requests :) 该模块文档中有更多Devel::DidYouMean的使用示例。
更新:Devel::DidYouMean现在使用信号处理方法,并且完全避免使用AUTOLOAD,2014-11-09
本文最初发布在PerlTricks.com。
标签
反馈
这篇文章有什么问题吗?请通过在GitHub上创建问题或拉取请求来帮助我们。