=head1 NAME
Perl 交互环境(REPL read eval print loop)
=head1 DESCRIPTION
Python 带有一个 REPL 可以很方便的做计算器,或者做点稍微复杂的事情,作为 Perler
很是羡慕。Perl 源代码中带有个 psh 但是功能太简单了,就像它说的是“穷人的REPL”。
至于
perl -d 也不喜欢。于是搜了下 CPAN 结果没有找到合适的就自动动手写了个,
很简单。
=head2 Loop
REPL 最核心的就是 eval 函数和循环,Perl 都有只需要写个美化的界面如提示符什么的。直接看代码:
sub prompt {
my $pat = '[%03d]=> ';
state $cnt = 0;
printf $pat, $cnt++;
}
这个是打印提示符的,带了行号。顺便 show 了下 state 关键字的用法。
sub main {
chdir 'd:/' or die $!;
no strict;
for(prompt; <>; prompt) {
chomp;
my @output = eval;
say "\n=> ", @output if @output;
print $@ if $@;
say '';
}
}
主函数,首先切换到默认
文件夹这个看个人喜欢我觉得挺方便。因为REPL中我们都懒得
声明变量,而且正常情况下都用全局变量所以就关闭了 strict。这里关于变量作用域
的问题很多,搞不懂的看仙子的教程。
循环很简单,打印提示符-> 读取一行代码-> list context eval -> B<打印返回值>。
至于这里的“一行代码”其实是由 $/ 决定的,所以不用担心要输入多行代码的问题。
只打印出返回值,所以下面的命令可以返回字符串来做命令提示。
=head2 辅助工具
上面是 psh 的全部了,我们还要添加些辅助功能否则和直接写 C
就没
多少区别了。
首先解决读入多行代码的问题:
sub ml { $/ = "\n\n"; '[multi line mode ok]' }
sub sl { $/ = "\n"; '[line mode ok]' }
在 REPL 中直接输入 ml 就切换为了多行模式就可以输入多行代码,最后用连续2个换行
就表示程序结束了。
改变当前目录也很常用,加上:
sub ls { print "\n", `ls`; ''} # win 下用 dir 或自己写个 ls
sub cd { chdir shift; cwd }
基本的提示信息:
sub h { 'bla bla bla' }
sub help { h() }
=head2 数据可视化
进行RE匹配的时候,构建复杂数据结构的时候经常需要将数据结果打印出来,但是
Perl 不会自动展开 reference 所以 Data::Dumper 就成了我的最爱。
use Data::Dumper ();
*D = \&Data::Dumper::Dumper;
这里把 Data::Dumper::Dumper 在 package main 中取了个别名,用一个字母 D 表示
是因为它实在太常用了。同样道理 yaml 格式有时也很常用,特别是很多匿名 hashref
的时候用 Data::Dumper 打印出来太长太乱了,这时候用 YAML 就非常舒服了。
use YAML ();
sub yaml { return "---- yaml ----\n", YAML::Dump @_ }
很简单,这里不用别名是为了打印输出时排版更漂亮。
有兴趣 hack Perl 内部数据结构的同学肯定很熟悉 Devel::Peek::Dump 同样默认加入。
use Devel::Peek ();
*DD = \&Devel::Peek::Dump;
=head2 测试模块
配合上面的辅助功能很容易测试 Perl 语言特性或者陷阱,但是有时候我们想要测试
类或者包的特性的时候就有点力不从心了,因为一般都需要个文件来帮忙。否则在
REPL 中反复输入都要累死了,但是 REPL 有点好处就是交互方便比 Test::More 简单
太多了。
下面我们有了 .pm 文件比如叫 T.pm 吧。放到当前目录用 use T; 来加载它,发现功能
不完善修改下,再 use T; 却发现不行了,提示说已经加载,头大啊难道还要再启用新
进程?
其实这是 Perl 的 require 加载机制决定了,加载的文件会放到 %INC 中,每次再
use 或 require 会检查加载没有,大家明白所在了吧(带有 XS 扩展的模块不行)。
目标就是删除 %INC 中的 'T.pm'。
sub re_use {
my $module = shift;
$module =~ s/\s//g;
$module =~ s/::/\//g;
return if $module eq '';
my $count = 0;
my @packages;
while (my($k, $v) = each %INC) {
if ($k =~ m<^$module(?=[./])>) { #并不只删除此模块,是一系列模块
$count ++;
push @packages, $k;
delete $INC{$k};
}
}
return "delete $count package\n\n", join "\n", @packages;
}
注释那里这么说其实是因为模块命名都是有规律的,比如 Moose 所有辅助模块都在
Moose 文件夹下,这里一并删除就全部更新了。
这样我们就可以方便的写个模块,加载测试下-> 修改-> re_use 'Module_name' ->
加载继续测试。
=head2 来点高级的
Perl 的语法有时让人很迷惑,默认变量、作用域什么的,论坛上很多同学都问题多多。
用 B::Deparse 有时不失为一种很好的解决办法。这里只是简单封闭下,引号问题很多,
如果内容比较多还是老老实实用命令行解决吧。
sub terse {
my $code = shift;
return "---- B::Terse ----\n", `perl -MO=Terse -E "$code"`;
}
sub deparse {
my $code = shift;
return "---- B::Deparse ----\n", `perl -MO=Deparse -E "$code"`;
}
=head2 完善我们的计算器
为了方便做计算或测试,我们可以随意导入模块。有些不常用的也可以在 REPL 中动态
加载 比如 use LWP; 之类的。这里贴出最有用的
use List::Util qw(first max maxstr min minstr reduce shuffle sum);
use Scalar::Util qw(blessed dualvar isweak weaken refaddr reftype
looks_like_number);
让退出更友善点
$SIG{INT} = sub {say "\n*** goodbye ***"; sleep 0.5; exit};
=head1 总结
厌倦了总是在命令行 perl -E 就写了这么个小工具,慢慢的添加了很多小工具,这里
介绍了大部分有助于学习 Perl 语言的功能。因为只是简单的 eval 而且用的时候
经常是全局变量,如果遇到什么离奇的事我只能祝你 good luck 了。
至于应用其实它不仅仅是个简单的计算器、语言测试工具。因为可以动态加载模块,
我们完全可以创建一个文件,处理数据,输出;用 LWP 下载网页,处理,打印想要的
内容等等。配合着 Perl 完整的系统调用封闭和大量模块,它就是个 shell。
给小鸟们提示下如果在 main 函数前声明了上面这些辅助函数,调用的时候就不需要
带括号了,更像命令了。