- Parrot 教程
- Parrot - 首頁
- Parrot - 概述
- Parrot - 安裝
- Parrot - 使用說明
- Parrot - 垃圾回收
- Parrot - 資料型別
- Parrot - 暫存器
- Parrot - 操作
- Parrot - 分支
- Parrot 示例
- Parrot - 示例
- Parrot 資源
- Parrot 快速指南
- Parrot - 有用資源
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 指令,而是用指令開頭的標籤替換它們。