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
   # 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; 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.