- Parrot 教程
- Parrot - 首页
- Parrot - 概述
- Parrot - 安装
- Parrot - 使用说明
- Parrot - 垃圾回收
- Parrot - 数据类型
- Parrot - 寄存器
- Parrot - 操作
- Parrot - 分支
- Parrot 示例
- Parrot - 示例
- Parrot 资源
- Parrot 快速指南
- Parrot - 有用资源
Parrot 快速指南
什么是Parrot
当我们将程序输入到传统的Perl中时,它首先被编译成内部表示形式,或字节码;然后将此字节码输入到Perl内部几乎独立的子系统中进行解释。因此,Perl的操作有两个不同的阶段。
编译成字节码和
解释字节码。
这并非Perl独有。遵循此设计的其他语言包括Python、Ruby、Tcl,甚至Java。
我们也知道有一个Java虚拟机(JVM),它是一个与平台无关的执行环境,可以将Java字节码转换为机器语言并执行它。如果您理解这个概念,那么您就会理解Parrot。
Parrot是一个虚拟机,旨在高效地编译和执行解释语言的字节码。Parrot是最终Perl 6编译器的目标,并用作Pugs以及其他各种语言(如Tcl、Ruby、Python等)的后端。
Parrot是用最流行的语言“C”编写的。
Parrot安装
在开始之前,让我们下载Parrot的最新副本并将其安装到我们的机器上。
Parrot下载链接可在Parrot CVS快照中找到。下载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. ...
然后会问您一些关于本地配置的问题;对于每一个问题,您几乎总是可以按回车键/Enter键。
最后,系统会提示您键入 - 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“示例”部分中提供的各种示例。您也可以查看Parrot存储库中的examples目录。
Parrot指令格式
Parrot目前可以接受四种形式的执行指令。PIR(Parrot中间表示)旨在由人员编写和编译器生成。它隐藏了一些底层细节,例如参数传递给函数的方式。
PASM(Parrot汇编)位于PIR之下 - 它仍然是人类可读/可写的,并且可以由编译器生成,但是作者必须注意调用约定和寄存器分配等细节。PAST(Parrot抽象语法树)使Parrot能够接受抽象语法树样式的输入 - 这对于编写编译器的人员非常有用。
所有上述输入形式都会在Parrot内部自动转换为PBC(Parrot字节码)。这很像机器码,但是Parrot解释器可以理解。
它并非旨在供人类阅读或编写,但与其他形式不同的是,执行可以立即启动,无需组装阶段。Parrot字节码与平台无关。
指令集
Parrot指令集包括算术和逻辑运算符、比较和分支/跳转(用于实现循环、if...then结构等)、查找和存储全局和词法变量、使用类和对象、调用子程序和方法及其参数、I/O、线程等等。
Parrot中的垃圾回收
与Java虚拟机一样,Parrot也可以让您无需担心内存释放。
Parrot提供垃圾回收。
Parrot程序不需要显式释放内存。
分配的内存将在不再使用(即不再被引用)时释放。
Parrot垃圾收集器定期运行以处理不需要的内存。
Parrot数据类型
Parrot CPU有四种基本数据类型
IV
整数类型;保证足够宽,可以容纳指针。
NV
与体系结构无关的浮点类型。
STRING
抽象的、与编码无关的字符串类型。
PMC
标量。
前三种类型几乎是不言自明的;最后一种类型 - Parrot魔术Cookie,则比较难以理解。
什么是PMC?
PMC代表Parrot魔术Cookie。PMC表示任何复杂的数据结构或类型,包括聚合数据类型(数组、哈希表等)。PMC可以为对其执行的算术、逻辑和字符串操作实现自己的行为,从而允许引入特定于语言的行为。PMC可以内置到Parrot可执行文件中,也可以在需要时动态加载。
Parrot寄存器
当前的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操作
您可以执行各种操作。例如,我们可以打印出寄存器或常量的内容
set I1, 10 print "The contents of register I1 is: " print I1 print "\n"
上述指令将导致寄存器I1的内容是: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分支
没有流程控制,代码会变得有点枯燥;首先,Parrot了解分支和标签。branch操作相当于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。请注意,您不能对不同类型的参数使用这些运算符;您甚至可能需要向op添加后缀_i或_n,以告诉它您正在使用什么类型的参数,尽管到您阅读本文时,汇编器应该能够为您推断出来。
Parrot编程示例
Parrot编程类似于汇编语言编程,您可以有机会在更低的级别上工作。以下是一些编程示例列表,让您了解Parrot编程的各个方面。
经典的Hello world!
创建一个名为hello.pir的文件,其中包含以下代码
.sub _main print "Hello world!\n" end .end
然后键入以下命令运行它
parrot hello.pir
正如预期的那样,这将在控制台上显示文本“Hello world!”,后跟一个换行符(由于\n)。
在上面的示例中,“.sub _main”表示后面的指令构成名为“_main”的子程序,直到遇到“.end”。第二行包含print指令。在这种情况下,我们正在调用接受常量字符串的指令变体。汇编器负责决定为我们使用哪种指令变体。第三行包含“end”指令,这会导致解释器终止。
使用寄存器
我们可以修改hello.pir,首先将字符串Hello world!\n存储在寄存器中,然后使用该寄存器和print指令。
.sub _main set S1, "Hello world!\n" print S1 end .end
在这里,我们已经精确地说明了要使用哪个寄存器。但是,通过将S1替换为$S1,我们可以将选择使用哪个寄存器的任务委托给Parrot。也可以使用=表示法而不是编写set指令。
.sub _main $S0 = "Hello world!\n" print $S0 end .end
为了使PIR更易于阅读,可以使用命名寄存器。这些稍后将映射到实际的编号寄存器。
.sub _main .local string hello hello = "Hello world!\n" print hello end .end
此示例介绍了更多指令和PIR语法。以#开头的行是注释。
求平方和
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
只是另一种更类似于汇编的写法
temp = i * i
和
mul temp, i, i
与
if i <= maxnum goto loop
相同
le i, maxnum, loop
与
total += temp
相同
add total, temp
通常情况下,每当 Parrot 指令修改寄存器内容时,在汇编形式中编写指令时,该寄存器将是第一个寄存器。
与汇编语言通常一样,循环和选择是根据条件分支语句和标签实现的,如上所示。在汇编编程中,使用 goto 并不是一种不好的形式!
斐波那契数列
斐波那契数列定义如下:取两个数,1 和 1。然后重复将数列中最后两个数加在一起构成下一个数:1、1、2、3、5、8、13,依此类推。斐波那契数 fib(n) 是数列中的第 n 个数。这是一个简单的 Parrot 汇编程序,用于查找前 20 个斐波那契数。
# 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++; }
注意: 有趣的是,在 Perl 中打印斐波那契数列最短且无疑最漂亮的方法之一是:`perl -le '$b=1; print $a+=$b while print $b+=$a'`。
递归计算阶乘
在这个例子中,我们定义了一个阶乘函数,并递归调用它来计算阶乘。
.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 子程序。前面略过的一点是为什么子程序的名称都以下划线开头!这样做只是为了表明该标签是全局的,而不是特定子程序的范围。这很重要,因为该标签随后对其他子程序可见。
第一行 .param int n 指定此子程序接受一个整数参数,并且我们希望在子程序的其余部分中使用名称 n 来引用传递给它的寄存器。
除了读取的行之外,大部分后续内容都在之前的示例中见过
result = _fact($I0)
这一行 PIR 实际上代表相当多的 PASM 代码行。首先,寄存器 $I0 中的值被移动到相应的寄存器中,以便它可以作为整数参数被 _fact 函数接收。然后设置其他与调用相关的寄存器,然后调用 _fact。然后,一旦 _fact 返回,_fact 返回的值将被放入名为 result 的寄存器中。
在 _fact 子程序的 .end 之前,使用 .return 指令确保将寄存器中保存的值(名为 result)放入正确的寄存器中,以便调用子程序的代码将其视为返回值。
在 main 中对 _fact 的调用与 _fact 子程序内对 _fact 的递归调用方式相同。唯一剩下的新语法是 :main,它写在 .sub _main 之后。默认情况下,PIR 假设执行从文件中的第一个子程序开始。可以通过标记要从中启动的子程序来更改此行为 :main。
编译成PBC
要将 PIR 编译成字节码,请使用 -o 标志并指定扩展名为 .pbc 的输出文件。
parrot -o factorial.pbc factorial.pir
PIR与PASM
可以使用以下命令将 PIR 转换为 PASM:
parrot -o hello.pasm hello.pir
最终示例的 PASM 代码如下所示:
_main: set S30, "Hello world!\n" print S30 end
PASM 不处理寄存器分配或提供对命名寄存器的支持。它也没有 .sub 和 .end 指令,而是用指令开头的标签替换它们。