Parrot - 快速指南
What is Parrot
当我们将程序提供给传统的Perl时,它首先被编译成内部表示或字节码; 然后将这个字节码送入Perl中几乎独立的子系统进行解释。 因此,Perl的操作有两个不同的阶段:
编译为字节码和
解释字节码。
这不是Perl独有的。 遵循此设计的其他语言包括Python,Ruby,Tcl甚至Java。
我们还知道有一个Java虚拟机(JVM)是一个独立于平台的执行环境,它将Java字节码转换为机器语言并执行它。 如果您了解这个概念,那么您将了解Parrot。
Parrot是一个虚拟机,旨在有效地编译和执行解释语言的字节码。 Parrot是最终Perl 6编译器的目标,用作Pugs的后端,以及各种其他语言,如Tcl,Ruby,Python等。
Parrot是用最流行的语言“C”编写的。
Parrot Installation
在我们开始之前,让我们下载最新的Parrot副本并将其安装在我们的机器上。
Parrot下载链接可在Parrot CVS Snapshot中找到 。 下载最新版本的Parrot并按照以下步骤进行安装:
解压缩并解压缩下载的文件。
确保您的计算机上已安装Perl 5。
现在执行以下操作:
% cd parrot
% perl Configure.pl
Parrot Configure
Copyright (C) 2001 Yet Another Society
Since you're running this script, you obviously have
Perl 5 -- I'll be pulling some defaults from its configuration.
...
然后,系统会询问您有关本地配置的一系列问题; 你几乎总能按每一个回报/输入。
最后,您将被告知键入 - make test_prog, Parrot将成功构建测试解释器。
现在你应该进行一些测试; 所以输入'make test',你应该看到如下的读数:
perl t/harness
t/op/basic.....ok,1/2 skipped:label constants unimplemented in
assembler
t/op/string....ok, 1/4 skipped: I'm unable to write it!
All tests successful, 2 subtests skipped.
Files=2, Tests=6,......
当你读到这个时,可能会有更多的测试,其中一些跳过可能不会跳过,但要确保它们都不会失败!
一旦安装了parrot可执行文件,您可以查看Parrot“Examples”部分中给出的各种类型的示例。 您还可以查看parrot存储库中的examples目录。
Parrot Instructions Format
Parrot目前可以接受以四种形式执行的指令。 PIR(Parrot Intermediate Representation)旨在由人编写并由编译器生成。 它隐藏了一些低级细节,例如参数传递给函数的方式。
PASM(Parrot Assembly)是一个低于PIR的级别 - 它仍然是人类可读/可写的并且可以由编译器生成,但作者必须处理诸如调用约定和寄存器分配之类的细节。 PAST(Parrot抽象语法树)使Parrot能够接受抽象语法树样式输入 - 对编写编译器的人很有用。
所有上述形式的输入都在Parrot内自动转换为PBC(Parrot Bytecode)。 这很像机器代码,但是Parrot解释器可以理解。
它不是人类可读的或人类可写的,但与其他形式不同,执行可以立即开始而无需组装阶段。 Parrot字节码与平台无关。
指令集
Parrot指令集包括算术和逻辑运算符,比较和分支/跳转(用于实现循环,如果...然后构造等),查找和存储全局变量和词法变量,使用类和对象,调用子例程和方法与他们的参数,I/O,线程等。
Garbage Collection in Parrot
与Java虚拟机一样,Parrot也让您免于担心内存解除分配。
Parrot提供垃圾收集。
Parrot程序不需要明确释放内存。
分配的内存将在不再使用时释放,即不再引用。
Parrot垃圾收集器定期运行以处理不需要的内存。
Parrot Datatypes
Parrot CPU有四种基本数据类型:
IV
整数类型; 保证足够宽以容纳指针。
NV
独立于体系结构的浮点类型。
STRING
一种抽象的,与编码无关的字符串类型。
PMC
一个标量。
前三种类型几乎是不言自明的; 最终类型 - Parrot魔法饼干,稍微难以理解。
什么是PMC?
PMC代表Parrot Magic Cookie。 PMC表示任何复杂的数据结构或类型,包括聚合数据类型(数组,哈希表等)。 PMC可以对其执行的算术,逻辑和字符串操作实现自己的行为,允许引入特定于语言的行为。 PMC可以内置到Parrot可执行文件中,也可以在需要时动态加载。
Parrot Registers
当前的Perl 5虚拟机是一台堆栈机器。 它通过将操作保持在堆栈上来传递操作之间的值。 操作将值加载到堆栈上,执行他们需要执行的操作并将结果放回堆栈。 这很容易使用,但速度很慢。
要将两个数字相加,您需要执行三次堆栈推送和两次堆栈弹出。 更糟糕的是,堆栈必须在运行时增长,这意味着在您不想分配内存时分配内存。
因此,Parrot将打破虚拟机的既定传统,并使用寄存器架构,更类似于真实硬件CPU的架构。 这有另一个好处。 我们可以使用所有现有的文献来介绍如何为我们的软件CPU编写基于寄存器的CPU的编译器和优化器!
Parrot为每种类型提供专业寄存器:32个IV寄存器,32个NV寄存器,32个字符串寄存器和32个PMC寄存器。 在Parrot汇编程序中,它们分别命名为I1 ... I32,N1 ... N32,S1 ... S32,P1 ... P32。
现在让我们看看一些汇编程序。 我们可以使用set运算符设置这些寄存器:
set I1, 10
set N1, 3.1415
set S1, "Hello, Parrot"
所有Parrot操作都具有相同的格式:运算符的名称,目标寄存器,然后是操作数。
Parrot Operations
您可以执行各种操作。 例如,我们可以打印出寄存器或常量的内容:
set I1, 10
print "The contents of register I1 is: "
print I1
print "\n"
以上说明将导致The contents of register I1 is: 10
我们可以对寄存器执行数学运算:
# Add the contents of I2 to the contents of I1
add I1, I1, I2
# Multiply I2 by I4 and store in I3
mul I3, I2, I4
# Increment I1 by one
inc I1
# Decrement N3 by 1.5
dec N3, 1.5
我们甚至可以执行一些简单的字符串操作
set S1, "fish"
set S2, "bone"
concat S1, S2 # S1 is now "fishbone"
set S3, "w"
substr S4, S1, 1, 7
concat S3, S4 # S3 is now "wishbone"
length I1, S3 # I1 is now 8
Parrot Branches
代码在没有流量控制的情况下变得有点无聊; 对于初学者来说,Parrot知道分支和标签。 分支op等同于Perl的goto:
branch TERRY
JOHN: print "fjords\n"
branch END
MICHAEL: print " pining"
branch GRAHAM
TERRY: print "It's"
branch MICHAEL
GRAHAM: print " for the "
branch JOHN
END: end
它还可以执行简单的测试以查看寄存器是否包含真值:
set I1, 12
set I2, 5
mod I3, I2, I2
if I3, REMAIND, DIVISOR
REMAIND: print "5 divides 12 with remainder "
print I3
branch DONE
DIVISOR: print "5 is an integer divisor of 12"
DONE: print "\n"
end
这是Perl中的样子,用于比较:
$i1 = 12;
$i2 = 5;
$i3 = $i1 % $i2;
if ($i3) {
print "5 divides 12 with remainder ";
print $i3;
} else {
print "5 is an integer divisor of 12";
}
print "\n";
exit;
Parrot运算符
我们有全系列的数字比较器:eq,ne,lt,gt,le和ge。 请注意,您不能在不同类型的参数上使用这些运算符; 你可能甚至需要将后缀_i或_n添加到op中,告诉它你正在使用什么类型的参数,虽然汇编程序应该为你理解这一点,当你读到它时。
Parrot Programming Examples
Parrot编程与汇编语言编程类似,您有机会在较低级别工作。 以下是编程示例列表,可让您了解Parrot编程的各个方面。
Classic Hello world!
创建一个名为hello.pir的文件,其中包含以下代码:
.sub _main
print "Hello world!\n"
end
.end
然后键入以下命令运行它:
parrot hello.pir
正如所料,这将显示文本'Hello world!' 在控制台上,后跟一个新行(由于\ n)。
在上面的例子中,'。sub_main'声明后面的指令组成了一个名为'_main'的子程序,直到遇到'.end'。 第二行包含打印指令。 在这种情况下,我们调用接受常量字符串的指令的变体。 汇编程序负责决定使用哪种指令变体。 第三行包含'end'指令,该指令使解释器终止。
使用寄存器 (Using Registers)
我们可以修改hello.pir,首先将字符串Hello world!\ n存储在寄存器中,然后将该寄存器与print指令一起使用。
.sub _main
set S1, "Hello world!\n"
print S1
end
.end
这里我们准确说明了要使用的寄存器。 但是,通过用$ S1替换S1,我们可以将选择使用哪个寄存器委托给Parrot。 也可以使用=表示法而不是写入设置指令。
.sub _main
$S0 = "Hello world!\n"
print $S0
end
.end
为了使PIR更具可读性,可以使用命名寄存器。 这些稍后映射到实数编号的寄存器。
.sub _main
.local string hello
hello = "Hello world!\n"
print hello
end
.end
'.local'指令表示只在当前编译单元内(即.sub和.end之间)需要命名寄存器。 以下'.local'是一种类型。 这可以是int(对于I寄存器),float(对于N寄存器),字符串(对于S寄存器),pmc(对于P寄存器)或PMC类型的名称。
平方和 (Summing squares)
此示例介绍了一些更多指令和PIR语法。 以#开头的行是注释。
.sub _main
# State the number of squares to sum.
.local int maxnum
maxnum = 10
# Some named registers we'll use.
# Note how we can declare many
# registers of the same type on one line.
.local int i, total, temp
total = 0
# Loop to do the sum.
i = 1
loop:
temp = i * i
total += temp
inc i
if i <= maxnum goto loop
# Output result.
print "The sum of the first "
print maxnum
print " squares is "
print total
print ".\n"
end
.end
PIR提供了一些语法糖,使它看起来比装配更高。 例如:
temp = i * i
只是另一种编写更多汇编的方式:
mul temp, i, i
和:
if i <= maxnum goto loop
是相同的:
le i, maxnum, loop
和:
total += temp
是相同的:
add total, temp
通常,每当Parrot指令修改寄存器的内容时,这将是以汇编形式写入指令时的第一个寄存器。
与汇编语言一样,循环和选择是根据条件分支语句和标签实现的,如上所示。 汇编编程是一个使用goto不是一个坏形式的地方!
斐波纳契数 (Fibonacci Numbers)
Fibonacci系列的定义如下:取两个数字,1和1.然后重复将系列中的最后两个数字加在一起,形成下一个数字:1,1,2,3,5,8,13等等。 斐波纳契数fib(n)是该系列中的第n个数。 这是一个简单的Parrot汇编程序,它找到前20个Fibonacci数:
# Some simple code to print some Fibonacci numbers
print "The first 20 fibonacci numbers are:\n"
set I1, 0
set I2, 20
set I3, 1
set I4, 1
REDO: eq I1, I2, DONE, NEXT
NEXT: set I5, I4
add I4, I3, I4
set I3, I5
print I3
print "\n"
inc I1
branch REDO
DONE: end
这是Perl中的等效代码:
print "The first 20 fibonacci numbers are:\n";
my $i = 0;
my $target = 20;
my $a = 1;
my $b = 1;
until ($i == $target) {
my $num = $b;
$b += $a;
$a = $num;
print $a,"\n";
$i++;
}
NOTE:作为一个很好的兴趣点,在Perl中打印Fibonacci系列的最短和最美的方法之一是perl -le'$ b = 1; 打印$ a + = $ b,同时打印$ b + = $ a'。
递归地计算阶乘 (Recursively computing factorial)
在这个例子中,我们定义了一个阶乘函数,并递归地调用它来计算阶乘。
.sub _fact
# Get input parameter.
.param int n
# return (n > 1 ? n * _fact(n - 1) : 1)
.local int result
if n > 1 goto recurse
result = 1
goto return
recurse:
$I0 = n - 1
result = _fact($I0)
result *= n
return:
.return (result)
.end
.sub _main :main
.local int f, i
# We'll do factorial 0 to 10.
i = 0
loop:
f = _fact(i)
print "Factorial of "
print i
print " is "
print f
print ".\n"
inc i
if i <= 10 goto loop
# That's it.
end
.end
我们先来看一下_fact sub。 之前掩盖的一点是为什么子程序的名称都以下划线开头! 这仅仅是为了表明标签是全局的而不是作用于特定子例程的方式。 这很重要,因为标签随后可见于其他子程序。
第一行.param int n指定此子例程采用一个整数参数,并且我们想要引用它传入的寄存器,名称为n,用于其余的子。
除了读行之外,前面的例子中已经看到了以下大部分内容:
result = _fact($I0)
这条单线PIR实际上代表了几行PASM。 首先,寄存器$ I0中的值被移入适当的寄存器,以便由_fact函数作为整数参数接收。 然后设置其他调用相关寄存器,然后调用_fact。 然后,一旦_fact返回,_fact返回的值将被放入给定名称结果的寄存器中。
在_fact sub的.end之前,.return指令用于确保寄存器中保存的值; 命名结果被放入正确的寄存器中,以便通过调用sub的代码将其视为返回值。
在main中对_fact的调用与在sub _fact本身内对_fact的递归调用的方式相同。 新语法的唯一剩余部分是:main,写在.sub _main之后。 默认情况下,PIR假定执行从文件中的第一个子开始。 可以通过将sub标记为以:main开头来更改此行为。
Compiling to PBC
要将PIR编译为字节码,请使用-o标志并指定扩展名为.pbc的输出文件。
parrot -o factorial.pbc factorial.pir
PIR vs. PASM
通过运行PIR可以将PIR转换为PASM:
parrot -o hello.pasm hello.pir
最后一个例子的PASM如下所示:
_main:
set S30, "Hello world!\n"
print S30
end
PASM不处理寄存器分配或提供对命名寄存器的支持。 它也没有.sub和.end指令,而是在指令开头用标签替换它们。