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 指令,而是用指令開頭的標籤替換它們。

廣告
© . All rights reserved.