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 指令,而是用指令开头的标签替换它们。