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"。 第二行包含打印指令。 在这种情况下,我们调用接受常量字符串的指令的变体。 汇编器负责决定我们使用哪种指令变体。 第三行包含"end"指令,该指令导致解释器终止。
使用寄存器
我们可以修改 hello.pir 以首先将字符串 Hello world!\n 存储在寄存器中,然后将该寄存器与打印指令一起使用。
.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
'.local' 指令表示指定的寄存器仅在当前编译单元内部(即 .sub 和 .end 之间)需要。 '.local' 后面是一个类型。 它可以是 int(对于 I 寄存器)、float(对于 N 寄存器)、string(对于 S 寄存器)、pmc(对于 P 寄存器)或 PMC 类型的名称。
平方求和
此示例介绍了更多指令和 PIR 语法。 以 # 开头的行是注释。
.sub _main # 说出要求和的平方数。 .local int maxnum maxnum = 10 # 我们将使用一些命名寄存器。 # 注意我们如何声明许多 # 同一类型的寄存器在一行上。 .local int i, total, temp total = 0 # 循环求和。 i = 1 loop: temp = i * i total += temp inc i if i <= maxnum goto loop # 输出结果。 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 的一种不错的形式!
斐波那契数
斐波那契数列的定义如下:取两个数字 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,同时打印 $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 sub 的 .end 之前,使用 .return 指令来确保寄存器中保存的值; 命名结果被放入正确的寄存器中,以便调用 sub 的代码将其视为返回值。
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 指令,而是用指令开头的标签替换它们。