📜  Parrot-快速指南

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


当我们将程序输入常规的Perl中时,首先将其编译为内部表示形式或字节码;然后,该字节码将被馈送到Perl内部几乎独立的子系统中进行解释。因此,Perl的操作分为两个不同的阶段:

  • 编译为字节码和

  • 字节码的解释。

这不是Perl独有的。遵循此设计的其他语言包括Python,Ruby,Tcl甚至Java。

我们也知道有一个Java虚拟机(JVM),它是一个平台无关的执行环境,可以将Java字节码转换为机器语言并执行。如果您了解此概念,那么您将了解Parrot。

Parrot是一种虚拟机,旨在有效地编译和执行用于解释语言的字节码。 Parrot是最终Perl 6编译器的目标,并用作Pugs以及Tcl,Ruby, Python等各种其他语言的后端。

鹦鹉是使用最流行的语言“ C”编写的。

鹦鹉安装

在开始之前,让我们下载最新的Parrot副本并将其安装在我们的计算机上。

Parrot CVS Snapshot中提供了Parrot下载链接。下载最新版本的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,......

到您阅读本文时,可能会有更多的测试,其中一些已跳过的测试可能不会跳过,但请确保所有测试均不会失败!

一旦安装了鹦鹉可执行文件,就可以检查鹦鹉“示例”部分中给出的各种类型的示例。您也可以在鹦鹉库中检出examples目录。

鹦鹉指令格式

Parrot当前可以接受以四种形式执行的指令。 PIR(鹦鹉中间表示)旨在由人们编写并由编译器生成。它隐藏了一些底层细节,例如将参数传递给函数的方式。

PASM(鹦鹉汇编)是PIR之下的一个级别-它仍然是人类可读/可写的,可以由编译器生成,但是作者必须注意诸如调用约定和寄存器分配之类的细节。 PAST(Parrot抽象语法树)使Parrot可以接受抽象语法树样式的输入-对于编写编译器的人很有用。

以上所有形式的输入都会在Parrot中自动转换为PBC(鹦鹉字节码)。这很像机器代码,但是Parrot解释器可以理解。

它并非旨在使人可读或可写,但与其他表单不同的是,可以立即开始执行而无需组装阶段。鹦鹉字节码与平台无关。

指令集

Parrot指令集包括算术和逻辑运算符,比较和分支/跳转(用于实现循环,如果…然后构造等),查找和存储全局变量和词法变量,使用类和对象,沿其调用子例程和方法以及它们的参数,I / O,线程等。

鹦鹉的垃圾回收

与Java虚拟机一样,Parrot也使您不必担心内存分配不足。

  • 鹦鹉提供垃圾收集。

  • 鹦鹉程序不需要显式释放内存。

  • 当不再使用已分配的内存(即不再被引用)时,将释放该内存。

  • 鹦鹉垃圾收集器会定期运行以照顾不需要的内存。

鹦鹉数据类型

Parrot CPU具有四种基本数据类型:

  • IV

    整数类型;保证足够宽以容纳指针。

  • 内华达州

    与体系结构无关的浮点类型。

  • 抽象的,独立于编码的字符串类型。

  • PMC

    标量。

前三种类型很容易解释。最后一种-鹦鹉魔术饼干,稍微难懂。

什么是PMC?

PMC代表鹦鹉魔术饼干。 PMC代表任何复杂的数据结构或类型,包括聚合数据类型(数组,哈希表等)。 PMC可以针对在其上执行的算术,逻辑和字符串运算实现自己的行为,从而允许引入特定于语言的行为。 PMC可以内置在Parrot可执行文件中,也可以在需要时动态加载。

鹦鹉寄存器

当前的Perl 5虚拟机是堆栈机。它通过将操作之间的值保持在堆栈上来进行通信。操作将值加载到堆栈上,执行所需的任何操作,然后将结果放回到堆栈上。这很容易使用,但是很慢。

要将两个数字加在一起,您需要执行三个堆栈推入和两个堆栈弹出。更糟糕的是,堆栈必须在运行时增长,这意味着仅在您不想分配内存时才分配内存。

因此,Parrot将打破虚拟机的既定传统,并使用寄存器架构,更类似于真实硬件CPU的架构。这还有另一个优点。我们可以使用所有现有文献,了解如何为我们的软件CPU编写基于寄存器的CPU的编译器和优化器!

鹦鹉具有每种类型的专业寄存器: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 op都具有相同的格式:运算符的名称,目标寄存器以及操作数。

鹦鹉操作

您可以执行多种操作。例如,我们可以打印出寄存器或常量的内容:

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知道分支和标签。分支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;

鹦鹉算子

我们提供全方位的数字比较器:eq,ne,lt,gt,le和ge。请注意,您不能在不同类型的参数上使用这些运算符;您甚至可能需要在操作符中添加后缀_i或_n,以告诉您所使用的参数类型,尽管在您阅读本文时,汇编程序应为您神清。

鹦鹉编程实例

鹦鹉编程类似于汇编语言编程,您有机会在较低级别上工作。以下是编程示例列表,以使您了解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指令,而是在指令开头用标签替换它们。