オウム-クイックガイド
プログラムを従来のPerlにフィードすると、最初に内部表現またはバイトコードにコンパイルされます。このバイトコードは、Perl内のほぼ別個のサブシステムに送られ、解釈されます。したがって、Perlの操作には2つの異なるフェーズがあります。
- バイトコードへのコンパイルと 
- バイトコードの解釈。 
これはPerlに固有のものではありません。この設計に従った他の言語には、Python、Ruby、Tcl、さらにはJavaが含まれます。
また、Javaバイトコードを機械語に変換して実行するプラットフォームに依存しない実行環境であるJava仮想マシン(JVM)があることもわかっています。この概念を理解すれば、オウムを理解できます。
Parrotは、インタープリター言語のバイトコードを効率的にコンパイルおよび実行するように設計された仮想マシンです。Parrotは最終的なPerl6コンパイラーのターゲットであり、Pugsや、Tcl、Ruby、Pythonなどの他のさまざまな言語のバックエンドとして使用されます。
オウムは最も人気のある言語「C」を使用して書かれています。
始める前に、Parrotの最新のコピーを1つダウンロードして、マシンにインストールしましょう。
Parrotのダウンロードリンクは、ParrotCVSスナップショットで利用できます。最新バージョンのParrotをダウンロードしてインストールするには、次の手順に従います。
- ダウンロードしたファイルを解凍して解凍します。 
- マシンにPerl5がすでにインストールされていることを確認してください。 
- 次に、以下を実行します。 
% 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ディレクトリを確認することもできます。
オウムは現在、4つの形式で実行する命令を受け入れることができます。PIR(Parrot Intermediate Representation)は、人が作成し、コンパイラーが生成するように設計されています。これは、パラメーターが関数に渡される方法など、いくつかの低レベルの詳細を隠します。
PASM(Parrot Assembly)はPIRより下のレベルです。人間が読み取り/書き込み可能であり、コンパイラーで生成できますが、作成者は呼び出し規約やレジスタ割り当てなどの詳細に注意する必要があります。PAST(Parrot Abstract Syntax Tree)を使用すると、Parrotは抽象構文ツリースタイルの入力を受け入れることができます。これは、コンパイラを作成する場合に便利です。
上記の入力形式はすべて、Parrot内でPBC(Parrot Bytecode)に自動的に変換されます。これは機械語によく似ていますが、Parrotインタープリターによって理解されます。
人間が読み取ったり書いたりできるようには意図されていませんが、他のフォームとは異なり、アセンブリフェーズを必要とせずに実行をすぐに開始できます。オウムのバイトコードはプラットフォームに依存しません。
命令セット
Parrot命令セットには、算術演算子と論理演算子、比較と分岐/ジャンプ(ループの実装、if ... then構文など)、グローバル変数と字句変数の検索と保存、クラスとオブジェクトの操作、サブルーチンとメソッドの呼び出しが含まれます。パラメータ、I / O、スレッドなどを使用します。
Java仮想マシンと同様に、Parrotもメモリの割り当て解除について心配する必要がありません。
- オウムはガベージコレクションを提供します。 
- オウムプログラムは、メモリを明示的に解放する必要はありません。 
- 割り当てられたメモリは、使用されなくなったとき、つまり参照されなくなったときに解放されます。 
- Parrot Garbage Collectorは定期的に実行され、不要なメモリを処理します。 
Parrot CPUには、次の4つの基本的なデータ型があります。
- IV - 整数型。ポインタを保持するのに十分な幅が保証されています。 
- NV - アーキテクチャに依存しない浮動小数点型。 
- STRING - 抽象化された、エンコーディングに依存しない文字列型。 
- PMC - スカラー。 
最初の3つのタイプは、ほとんど自明です。最後のタイプであるオウムのマジッククッキーは、理解するのが少し難しいです。
PMCとは何ですか?
PMCはParrotMagicCookieの略です。PMCは、集約データ型(配列、ハッシュテーブルなど)を含む、複雑なデータ構造または型を表します。PMCは、PMCに対して実行される算術、論理、および文字列操作に対して独自の動作を実装できるため、言語固有の動作を導入できます。PMCは、Parrot実行可能ファイルに組み込むことも、必要に応じて動的にロードすることもできます。
現在のPerl5仮想マシンはスタックマシンです。値をスタックに保持することにより、操作間で値を伝達します。操作は値をスタックにロードし、必要なことをすべて実行して、結果をスタックに戻します。これは操作が簡単ですが、時間がかかります。
2つの数値を加算するには、3つのスタックプッシュと2つのスタックポップを実行する必要があります。さらに悪いことに、スタックは実行時に大きくなる必要があります。つまり、メモリを割り当てたくないときにメモリを割り当てるということです。
そのため、Parrotは、仮想マシンの確立された伝統を打ち破り、実際のハードウェアCPUのアーキテクチャに似たレジスタアーキテクチャを使用します。これには別の利点があります。ソフトウェアCPU用のレジスタベースのCPU用のコンパイラとオプティマイザを作成する方法に関する既存のすべての文献を使用できます。
Parrotには、タイプごとに特殊なレジスタがあります。32個のIVレジスタ、32個のNVレジスタ、32個の文字列レジスタ、および32個のPMCレジスタです。Parrotアセンブラでは、これらの名前はそれぞれI1 ... I32、N1 ... N32、S1 ... S32、P1 ... P32です。
それでは、いくつかのアセンブラを見てみましょう。これらのレジスタは、集合演算子を使用して設定できます。
set I1, 10
	set N1, 3.1415
	set S1, "Hello, Parrot"すべてのParrotopsの形式は同じです。つまり、演算子の名前、宛先レジスタ、そしてオペランドです。
実行できる操作にはさまざまなものがあります。たとえば、レジスタまたは定数の内容を出力できます。
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をopに追加する必要がある場合もありますが、これを読むまでに、アセンブラーはこれを神聖にする必要があります。
オウムのプログラミングはアセンブリ言語のプログラミングに似ており、下位レベルで作業する機会があります。これは、Parrotプログラミングのさまざまな側面を理解するためのプログラミング例のリストです。
- クラシックHelloworld!
- レジスタの使用
- 正方形の合計
- フィボナッチ数
- 階乗の計算
- PBCへのコンパイル
- PIRとPASM
クラシックHelloworld!
次のコードを含むhello.pirというファイルを作成します。
.sub _main
      print "Hello world!\n"
      end
  .end次に、次のように入力して実行します。
parrot hello.pir予想どおり、「Helloworld!」というテキストが表示されます。コンソールで、その後に改行が続きます(\ nのため)。
上記の例では、「。sub _main」は、「。end」が検出されるまで、後続の命令が「_main」という名前のサブルーチンを構成することを示しています。2行目には、印刷命令が含まれています。この場合、定数文字列を受け入れる命令のバリアントを呼び出しています。アセンブラは、命令のどのバリアントを使用するかを決定します。3行目には、インタプリタを終了させる「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
  .endPIRをさらに読みやすくするために、名前付きレジスタを使用できます。これらは後で実際の番号のレジスタにマップされます。
.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
  .endPIRは、アセンブリよりも高レベルに見えるようにする糖衣構文を少し提供します。例えば:
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の2つの数を取ります。次に、シリーズの最後の2つの数を繰り返し加算して、次の数を作成します:1、1、2、3、5、8、13など。 。フィボナッチ数fib(n)は、シリーズのn番目の数です。これは、最初の20個のフィボナッチ数を見つける簡単なParrotアセンブラープログラムです。
# 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++;
        }NOTE:興味深い点として、Perlでフィボナッチ数列を印刷する最も短くて確かに最も美しい方法の1つは、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サブを見てみましょう。以前に説明した点は、サブルーチンの名前がすべてアンダースコアで始まる理由です。これは、ラベルが特定のサブルーチンにスコープされているのではなく、グローバルであることを示す方法として単純に行われます。ラベルは他のサブルーチンに表示されるため、これは重要です。
最初の行.paramint nは、このサブルーチンが1つの整数パラメーターを取り、残りのサブルーチンで名前nで渡されたレジスターを参照することを指定します。
以下の内容の多くは、行の読み取りを除いて、前の例で見られました。
result = _fact($I0)この1行のPIRは、実際にはかなりの数のPASM行を表しています。まず、レジスタ$ I0の値が適切なレジスタに移動され、_fact関数によって整数パラメータとして受信されます。次に、他の呼び出し関連レジスタが設定され、続いて_factが呼び出されます。次に、_factが返されると、_factによって返された値が、resultという名前でレジスタに配置されます。
_factサブの.endの直前で、.returnディレクティブを使用して、レジスタに保持されている値を確認します。名前付きの結果は、subを呼び出すコードによって戻り値として表示されるように、正しいレジスタに配置されます。
mainでの_factの呼び出しは、sub_fact自体での_factへの再帰的な呼び出しとまったく同じように機能します。新しい構文の残りのビットは、.sub _mainの後に書かれた:mainだけです。デフォルトでは、PIRは実行がファイルの最初のサブから始まると想定しています。この動作は、subを:mainで開始するようにマークすることで変更できます。
PBCへのコンパイル
PIRをバイトコードにコンパイルするには、-oフラグを使用して、拡張子が.pbcの出力ファイルを指定します。
parrot -o factorial.pbc factorial.pirPIRとPASM
PIRは、以下を実行することでPASMに変えることができます。
parrot -o hello.pasm hello.pir最後の例のPASMは次のようになります。
_main:
      set S30, "Hello world!\n"
      print S30
      endPASMは、レジスタ割り当てを処理したり、名前付きレジスタのサポートを提供したりしません。また、.subおよび.endディレクティブはなく、代わりに、命令の開始時にラベルに置き換えられます。