📜  Parrot-编程示例

📅  最后修改于: 2020-11-03 16:07:58             🧑  作者: Mango


鹦鹉编程类似于汇编语言编程,您有机会在较低级别上工作。以下是编程示例列表,以使您了解Parrot编程的各个方面。

经典的Hello世界!

创建一个名为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寄存器),字符串(对于S寄存器),pmc(对于P寄存器)或PMC类型的名称。

求和平方

本示例介绍了更多指令和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不错的地方!

斐波那契数

斐波那契数列的定义如下:取两个数字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;打印$ 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返回的值放入给定名称结果的寄存器中。

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