"B" 模块提供了访问语法分析树的方法, 其它的一些模块(“后端”)则对这个树进行操作。一些把它(语法树)以字节码的形式输出,还有以C源代码形式的输出的,后者以半可读的文本形式输出的。另一些遍历整棵语法树以建立一个关于所使用的子程序,格式及变量的交叉引用表。还有另外一些检查你的代码,看看有没有模棱两可的构造。另一些则重新将语法树导出成Perl代码,可以起代码美化或是消除混乱的代码的作用。
因为 "B" 模块的最初目的是提供一种能将Perl程序转为对应C代码的方法,接着就能把它变成可执行文件了,所以 "B" 模块和它的那些后端模块就被认为是“编译器”了,即使它们实际上没有做任何编译方面的事。这个编译器的各个部分精确的说应该是个“翻译器”,或者一个“检视器”,但是用Perl的人们想要一个“编译选项”而不是一个叫做“检视器”的小玩艺。你能怎么办呢?
这篇文章的主要内容是讲Perl编译器的用法:它包含的模块,怎样使用那些最重要的后端模块,它们有什么问题,如何让它们工作。
Layout 布局
编译器的后端放在 "B::" 里面,而前端(就是你,编译器的使用者,有时候要与之交互的)是 O 模块。一些后端(如 "B::C"))提供了一些程序(如perlcc)来隐藏模块的复杂性。
这里是一些值得知道的重要后端,并附有它们目前的状态,用0到10的整数表示。(状态0表示目前该部分功能只是有一个框架,还没有实现;状态10则表示如果还有Bug的话,我们会感到很奇怪的):
O模块默认让 -c 开关有效,这防止Perl在编译完代码后运行程序。这也是为什么所有的后端在产生任何输出前都会打印一句:
myperlprogram syntax OK
The Cross Referencing Back End 交叉引用后端
交叉引用后端(B::Xref)生成一个关于你的程序的报表,把各个申明以及子程序,变量(包括格式)的使用情况存入文件中去。举例来说,这有一段摘自对pod2man程序分析后生成的报表(该程序是Perl自带的一个例程):
Subroutine clear_noremap Package (lexical) $ready_to_print i1069, 1079 Package main $& 1086 $. 1086 $0 1086 $1 1087 $2 1085, 1085 $3 1085, 1085 $ARGV 1086 %HTML_Escapes 1085, 1085
这里展示了"clear_noremap" 子程序中变量的使用情况。就像变量 $ready_to_print 是 my() (词法) 的一个变量,在第1069行被引入( 原文用的词是introduced,也就是在 my() 中第一次被定义的意思 ),然后在第1079行该变量被使用了。从主包(main package)中来的变量 $& 又在第1086行被使用, 等等。
行号前面可能会有一个字母作为前缀,它们的意思是:
交叉引用中最为有用的选项就是把报表存入不同的文件,例如要把关于 myperlprogram 的报表存入文件 report 中:
$ perl -MO=Xref,-oreport myperlprogram
The Decompiling Back End 反编译后端
反编译后端将把你的Perl语法树重新变成源代码。生成的源代码会按照某种格式组织,所以这个后端可以用来消除代码中的混乱部分。此后端的基本使用方法如下:
$ perl -MO=Deparse myperlprogram
你也许马上会发现Perl并不知道如何给你的代码分段。你要自己手动添入新行来把这大断的代码分开。然而现在,让我们看看代码只有一行时情况怎样,这个后端会做些什么:
$ perl -MO=Deparse -e '$op=shift||die "usage: $0 code [...]";chomp(@ARGV=<>)unless@ARGV; for(@ARGV){$was=$_;eval$op; die$@ if$@; rename$was,$_ unless$was eq $_}' -e syntax OK $op = shift @ARGV || die("usage: $0 code [...]"); chomp(@ARGV = <ARGV>) unless @ARGV; foreach $_ (@ARGV) { $was = $_; eval $op; die $@ if $@; rename $was, $_ unless $was eq $_; }
这个后端也有几条选项控制生成的代码,举例说,你可以把缩进的尺寸设在4(最大)到2之间:
$ perl -MO=Deparse,-si2 myperlprogram
-p 开关控制在常常可以不加圆括号的地方加上它们:
$ perl -MO=Deparse -e 'print "Hello, world\n"' -e syntax OK print "Hello, world\n"; $ perl -MO=Deparse,-p -e 'print "Hello, world\n"' -e syntax OK print("Hello, world\n");
要知道更多,请参考 B::Deparse
Lint 后端
lint 后端 (B::Lint) 检察程序中不好的程序风格。一个程序认为的不好风格可能对另外一个程序员来说是用起来很有效的工具,所以有选项让你设定哪些东东将会受到检查。
要运行一个风格检查器检察你的代码:
$ perl -MO=Lint myperlprogram
要取消对上下文和没有定义的子程序的检查:
$ perl -MO=Lint,-context,-undefined-subs myperlprogram
要知道更多的选项信息,请看 B::Lint
The Simple C Back End 简化的C后端
这个模块用来把你的Perl程序的内部编译状态存储到一个C代码文件中去,而生成的C代码就可以被特定平台上的C编译器转换成一个可执行文件了。最后的程序还会和Perl解释器的库文件静态链接起来,所以它不会节省你的磁盘空间(除非你的Perl是用共享的库文件创建的)或是程序大小,然而,另一方面,程序启动起来会快一些。
"perlcc" 工具缺省是生成以下的可执行文件。
perlcc myperlprogram.pl
The Bytecode Back End 字节码后端
这个模块只有在你能够找到一种方法来装入并运行它生成的字节码时才会显得有用。ByteLoader模块提供了这项功能。
要把Perl转换成可执行的字节码,你可以使用 "perlcc" 的 "-B" 开关:
perlcc -B myperlprogram.pl
字节码是和机器类型无关的,所以一旦你编译了一个模块或是程序,它就可以像Perl源代码一样具有可移植性。(假设那个模块或者程序的使用者有一个足够新的Perl解释器来对字节码进行解码)
有一些选项用来控制要生成的字节码的性质和关于优化方面的参数,要知道这些选项的详细情况,请参考 B::Bytecode
The Optimized C Back End 优化的C后端
优化的C后端按照语法树中运行期代码的路径将你的Perl程序转换成等效的(但是被优化了的)C代码文件。这个C程序会直接对Perl的数据结构进行操作,而且也会链接Perl的解释器的库文件,以支持 eval(), "s///e", "require" 等等。
"perlcc" 工具使用 -O 开关生成这种可执行文件。要编译一个Perl程序(以".pl" 或者".p" 结尾):
perlcc -O myperlprogram.pl
从Perl模块创建一个共享库文件(以 ".pm" 结尾):
perlcc -O Myperlmodule.pm
$ perl -MO=Deparse myperlprogram
这与在这个Perl程序中使用 "use O 'Deparse'" 相同。
$ perl -MO=Showlex,mysub myperlprogram
要得到一份关于 my() 中的变量在文件myperlprogram中的使用情况的列表:
$ perl -MO=Showlex myperlprogram
[BROKEN]
这个模块对正在编写自己的后端程序,或正在深入Perl内部机制的人们来说是非常有用的。对普通程序员来说则没什么用。
优化的 C 后端会为一些不该为之输出的模块(比如说 DirHandle)输出代码。而且它不太可能正确地处理正在执行的子程序外部的goto语句(goto &sub is OK)。目前 "goto LABEL" 语句在这个后端中完全不会工作。他还会生成让C 编译器头痛无比的巨大的初始化函数。如果把这个初始化函数分割开是能得到比目前更好的效果的。另外的问题包括:处理无符号的数学问题时不能正确工作;一些操作码如果按照默认的操作码机制处理也会有非正常的结果。
BEGIN{} 块会在编译你的代码的时候被执行。所有的在BEGIN{} 中初始化的外部状态,如打开的文件,初始的数据库连结等等,会有不正确的表现。为了解决这个问题,Perl中又提供了一个 INIT{} 块来对应程序编译之后,正式运行之前要执行的那段代码。执行的顺序是:BEGIN{}, (后端编译程序可能这时会保存状态), INIT{}, 程序运行, END{}。
跋
本页面中文版由中文 man 手册页计划提供。
中文 man 手册页计划:https://github.com/man-pages-zh/manpages-zh