descartes-src (ソースパッケージ descartes-src-0.26.0.tar.gz) | 2012-09-09 20:57 |
descartes-win (Windows用バイナリパッケージ descartes-win-0.26.0.zip) | 2012-09-09 20:52 |
会話キャラクター: ツンデレ アプリケーション (会話キャラ:ツンデレ v1.0 for Windows) | 2010-04-29 13:41 |
会話キャラクター: 2人の女の子 ダブルキャラクター (会話キャラクター 2人の女の子 ダブルキャラクター 1.0 for Windows) | 2011-10-02 22:23 |
会話キャラクター: Eliza風英語版 (会話キャラ:Eliza風英語版 v1.0 for Windows) | 2010-05-11 01:06 |
会話キャラクター: 猫耳メイド アプリケーション (会話キャラ:猫耳メイド v1.0 for Windows) | 2010-04-27 21:15 |
会話キャラクター: イライザ風日本語版 (会話キャラ:イライザ風日本語版 v1.0 for Windows) | 2010-04-30 21:53 |
経済指標表示プログラム for Windows (経済指標表示プログラム V1.0) | 2011-08-18 22:04 |
ニュースヘッドライン表示プログラム (ニュースヘッドライン表示プログラム V1.0 for Windows) | 2011-08-16 12:31 |
デカルト言語 example (デカルト言語の例題 example-0.7.0.zip) | 2009-03-01 19:47 |
電力状況表示プログラム for Windows (2011年夏版 全国電力供給状況表示プログラム V1.0) | 2011-08-15 13:25 |
--
← 前のページに戻る
さて、ここからは、Closure BasicのVM(バーチャル・マシン)の仕様について説明していきましょう。 JAVAやC#(.Net)も、VMのコードにコンパイルされてから、実行されますね。 Closure Basicも同様にVMを使って、プログラムのコンパイル結果を実行します。
以降では、このClosure Basicのコンパイル結果を実行するVMをCLVMと呼ぶこととします。
Closure Basicのプログラムは、コンパイルされるとCLVMの命令コードに変換されます。 そのCLVM命令コードは、CLVMの上で実際のコンピューターの命令として実行されることになるのです。
Closure BasicのCLVMのアーキテクチャーの構成要素は、大きく分けるとスタックとクロージャに分かれます。 クロージャは、Closure0から始まり、定義された関数一つごとにコンパイル時に生成されます。
+CLVM-------------------+ | | | Stack | | | | Closure0 | | Closure1 | | ... | | | | | | | +-----------------------+
命令コードや変数の値を保持するメモリ領域は、すべてクロージャー内にのみ存在します。
メモリからの値の読み込み・書き込み、演算はスタックで、すべて行います。
Closure Basicのバーチャル・マシンであるCLVMのアーキテクチャは、スタック・マシンです。
最近はリアルなマシンであるx86とかRISCとかでは、レジスタ・マシンが全盛です。 深いパイプラインを持ち、次々に先行命令をスーパースカラーで実行していくには、それが必然なのでしょう。
しかし、中間コード方式のJAVAや.NETはスタック・マシンですね。
それは、ソフトで実現する中間コード・インタプリターでは、スタック・マシンに充分なメリットがあるからだと考えます。
Closure Basicでスタック・マシンを採用したのは次のような理由からです。
- スタック・マシンは、コンパクトなアーキテクチャであり、中間コードのインタプリタを効率よく作成できます。 レジスタを持たず、システムに一つあるだけの演算実行用のスタックを使ってすべての処理を実行できます。
- 少ない命令数でも、多彩なプログラミングが可能です。 メモリを経由しなくても、スタック上で演算を次々に実行していくことによりプログラムが進んでいきます。
- 命令数が少ないうえに、さらに命令コードにレジスタ番号を指定する必要がなくなるので、同じプログラムでも命令コードを短くすることが可能です。
Closure Basicのスタック(演算用)はシステムに1本だけ存在し、全クロージャから共通に使われます。
スタックの長さにはとくに制限を設けていません。複雑な演算でもOKです。 スタックから値を取り出しつくてアンダーフローした場合には、CLVMに異常イベントが上がってプログラムの実行は異常終了します。
CLVMには、一般のコンピューター・アーキテクチャーと異なり、共通のアドレス番地は持ちません。 また使われる領域ごとにまったく別領域であり、使われ方も厳密に区別されます。
命令コード・メモリ領域と変数の値を保持するデータ領域・メモリ領域は、すべてクロージャ内に存在します。 加えて、クロージャ内にはクロージャを呼び出したときにパラメターに対応する変数を保存するパラメター・スタックも 含まれます。パラメター・スタックは前項で説明したスタック(演算用)とは別のスタックです。
このあたりのクロージャについての説明は次の項から詳しく説明する予定なので楽しみにしていてください。
クロージャ 命令コード・メモリ領域 データ領域・メモリ領域 パラメター・スタック
メモリモデルとしては、命令コード、データ領域とパラメター・スタックは厳密に区別された領域に分けて格納します。
命令コード領域とパラメター・スタックは、所属するクロージャでしか使われません。 クロージャ外の他のクロージャからはアクセスする手段はありません。
データ領域については、クロージャ名、アドレス番号の組の値によって、クロージャ外の他のクロージャからアクセスできるようにします。 アドレス番号は、データ領域の先頭0からのオフセットです。クロージャ内で使われる領域サイズ分確保され、変数が増えた場合には動的に拡張できます。
データのアクセスのための対組 [クロージャ名、アドレス番号]
CLVMの命令では、データ領域のメモリをアクセスするときには必ずこの対組によってアクセスします。
Closure Basicのクロージャは以下のような要素で構成されています。
クロージャの要素 PC(プログラム・カウンター) パラメター数 命令コード・メモリ領域 データ領域・メモリ領域 パラメター・スタック
Closure Basicでは、定義された関数がコンパイルされると、必ず対応するクロージャが新たに生成され、上に示すセットが設置されます。
コンパイル時には、次の処理が行われます。
1) 命令コード・メモリ領域には、コンパイルされた結果である関数内の処理の命令コードが設定されます。
2) パラメター数には、関数の引数パラメターの数がコンパイル時に設定されます。
実行時には、次の処理が行われます。
1) プログラム実行時に、この関数が呼び出されると、まずPCに命令コード・アドレスとして0が設定されます。
2) 関数(クロージャ)への呼び出し引数は、スタック(演算用)に、積んでおいて呼び出します。 パラメター・スタックではないことに注意してください。
3) そして、次にパラメター変数に相当する変数の値がパラメター・スタックに退避されます。 パラメター・スタックは、パラメター変数の退避に使われ、クロージャが呼ばれるたびに、どんどんと値が退避されて詰まれて生きます。
4) システム共通のスタック(演算用)から、クロージャに設定されているパラメター数分の値をパラメター変数に設定する命令コードが実行されます。 このパラメター変数への引数の設定処理は、コンパイル時に処理する命令コードとしてクロージャの命令コード・メモリ領域の最初の領域に格納してあります。
5) データ領域の値を使うだけ領域を拡張する命令コードが実行されます。 この拡張命令はクロージャが最初に呼び出されたときに実行されるとデータ領域を拡張します。 すでにクロージャが1度呼び出された後には、この命令コードは実行されても何もしないで次の処理を行います。 この拡張命令も、コンパイル時に命令コードとして格納されたものです。
6) 命令コードにしたがって、処理を続行していきます。 CALL命令により、他のクロージャを呼ぶときには、このクロージャの状態はそのまま保持されます。
7) クロージャの処理が完了するときには、スタック(演算用)に返り値を設定してRET命令を実行します。 復帰するときには、パラメタースタックから、このクロージャが呼び出されたときに保存した値をリストアして呼び出し元に返ります。
クロージャは、処理が完了して復帰しても、内部に含む要素はそのまま保持されます。 次に同じクロージャが呼び出されると、データ領域上の変数はそのまま残っているので、以前に呼び出されたときの値のままです。 これが、他のクロージャを採用しないプログラム言語との大きな相違となっています。
Closure Basicをデカルト言語で実装するにあたり、クロージャの実体はデカルト言語のオブジェクトで実装します。
このクロージャ・オブジェクトの中では、PC、パラメター数、命令コード・メモリ領域、データ領域・メモリ領域、およびパラメター・スタックは、クロージャ・オブジェクトのインヘリタンス変数です。
すべてのクロージャのベースとなるClosureオブジェクトは、大元のClosureのオブジェクトとしてシステム内に定義されています。
::<closure <pc 0>; <code ()>; // 命令コード領域 <ncall 0>; // パラメター数 <parm_length 0>; // パラメター・スタックの長さ <parm_stack ()>; // パラメター・スタック <data (0 0 0 0 0 0 0 0)>; // データ領域 >;
このClosureオブジェクトをベースとして、新しいクロージャが作成されるときには、このClosureオブジェクトがクローンされます。 この場合は、継承するのではないことに注意してください。 新たに作成されたクロージャは独立して独自のコードと環境を持ったクロージャとなります。
実行中の命令コードのアドレス位置を示す整数値を保存します。
関数(クロージャ)の引数の数を整数値で保存しています。 実際に引数としてスタック(演算用)に渡された引数の数と比較され、異なっている場合にはエラーになります。
関数(クロージャ)が呼ばれるたびに、引数の変数の値を保存していきます。
関数(クロージャ)が復帰するときには、保存された引数の値が復元されます。
命令コードを、リスト形式で保存しています。
命令コードは、文字列アトムで表現されています。
a = 123 bc = 456 + 789 c = a+bc print a,bc,c
例えば、上のようなプログラムは次に示すようにコンパイルされます。
(PUSHI 8 BRK PUSHI closure0 PUSHI 8 PUSHI 123 ROT POP PUSHI 9 BRK PUSHI closure0 PUSHI 9 PUSHI 456 PUSHI 789 ADD ROT POP PUSHI 10 BRK PUSHI closure0 PUSHI 10 PUSHI closure0 PUSHI 8 PUSH PUSHI closure0 PUSHI 9 PUSH ADD ROT POP PUSHI closure0 PUSHI 8 PUSH PR PUSHI PR PUSHI closure0 PUSHI 9 PUSH PR PUSHI PR PUSHI closure0 PUSHI 10 PUSH PR NL STOP)
命令コードの詳細は、"4.6 CLVMの命令コード"で説明します。
変数のデータをリスト形式で保存します。
0番地から7番地までの値はシステムのワーク用に使われます。 クロージャ内のローカル変数としては、8番地から順に使われます。
前項で示したプログラムの実行後には、データ領域は次に示すような値になっています。
(0 0 0 0 0 0 0 0 123 1245 1368)
8番地が、変数aに相当し、123が入っています。 9番地は、変数bcに相当し、456+789である1245が入っています。 10番地は、変数cに相当し、a+bcの値である1368が入っています。
Closure Basicでは関数が新たに定義されるたびに新しいクロージャが生成されます。
生成されるクロージャは順にclosure1, closure2, closure3, ...という名前で自動的に生成されます。
closure1 closure2 closure3 ...
このクロージャ名は便宜的にCLVMが管理するためのものであり、プログラマーはこのclosure1, closure2, closure3の名前については 特に気にする必要はありません。
このように関数生成されることによってclosure1から順に作成されるのですが、実は、closure0がCLVMには存在します。
closure0は、必ずプログラムの最初から生成されます。関数が一つも定義されていなくても存在します。
そのclosure0は、Closure Basicのプログラムの上では、関数内ではないトップ・レベルのプログラム・コードやグローバル・変数を受け持つのです。
abc = 0 print abc
上の処理で、変数abcは、closure0の変数として設定され、値0を変数abcに代入し、その値をプリントアウトする処理のプログラム・コードはclosure0に設定されます。
このように、Closure Basicのプログラムは、CLVMバーチャル・マシン上ではすべてクロージャ上の処理とデータとして統一して扱われるようになっています。
Closure Basicでは、関数内で関数を定義できます。
func1 = {fun(x) func2 = {fun(y) return x*y} return func2 }
上の例では、グローバル変数func1は、closure0に所属し、 func2はfunc1に対応するclosure1に所属し、 func2の中の処理はclosure2に所属します。
変数func1はグローバル変数であるので、closure0, closure1とclosure2からアクセスできます。
変数func2はclosure1の変数なので、closure1とclosure2からはアクセスできますが、closure0からはアクセスできません。
このようにアクセスできるクロージャのスコープは、階層となります。
しかし、CLVMから各クロージャを見たときは、すべてが同一階層の並列なクロージャとして実装します。
closure0 closure1 closure2
closure1の中にclosure2があり、その中にclosure3が存在するわけではありません。
各クロージャのスコープの制御はClosure Basicのコンパイラで行います。 Closure Basicのコンパイラ内に各クロージャと所属する変数のスコープを表現するデータ構造を持ち、それに従ってアクセスできる変数と出来ない変数を制御します。 このスコープの制御については、後のClosure Basicのコンパイラの説明「5. コンパイラの仕様」で詳しく解説します。
CLVMのバーチャル・マシンの全命令コードを、スタック操作命令、演算命令、制御命令、call/return/save/restore命令、動的メモリ拡張命令、input/output命令、およびその他命令に分類して説明していきます。
なお、命令コードには引数(オペランド)を持たない命令とオペランドを1つ持つ命令の2種類があります。
命令コード 命令コード オペランド
オペランドには数値、文字列、アドレス、あるいはクロージャなどを指定することになります。
オペランドがない命令は、スタックの上の値をもとに操作するものや、引数の値が不要な命令です。
PUSHI オペランド
使用例 PUSHI 10 PUSHI "ABC" PUSHI closure1
PUSH
使用例 PUSHI closure1 PUSHI 10 PUSH closure1のアドレス10の指すデータ領域の値をスタックにプッシュしています。
POP
使用例 PUSHI 123 PUSHI closure1 PUSHI 10 POP closure1のアドレス10の指すデータ領域に値123をスタックから取り出して設定しています。
DUP
使用例 PUSHI 3 DUP スタックの上には、値3が二つ入ります。
DUP2
使用例 PUSHI closure1 PUSHI 11 DUP2 スタックの上には、closure1, 11, closure1, 11 と入ります。
DROP
使用例 PUSHI 11 DROP スタックの上にPUSHI命令で積まれた値11は、DROP命令によって捨てられ、スタック上から消えます。
SWAP
使用例 PUSHI 11 PUSHI 22 SWAP スタックの上にPUSHI命令で積まれた値11と値22は、SWAP命令によって入れ替えられます。 11, 22とスタックに積まれていた値は、SWAP命令実行後には、22, 11と順番が入れ替えられます。
ROT
使用例 PUSHI 11 PUSHI 22 PUSHI 33 ROT 11, 22, 33とスタックに積まれていた値は、ROT命令実行後には、33, 11, 22と順番が入れ替えられます。
ADD
使用例 PUSHI 11 PUSHI 22 ADD スタックの上にPUSHI命令で積まれた値11と値22は、ADD命令によって足されます。 結果の33がスタックに積まれます。
SUB
使用例 PUSHI 11 PUSHI 22 SUB スタックの上にPUSHI命令で積まれた値11と値22は、SUB命令によって引き算されます。 結果の-11がスタックに積まれます。
MUL
使用例 PUSHI 11 PUSHI 22 MUL スタックの上にPUSHI命令で積まれた値11と値22は、MUL命令によって足されます。 結果の242がスタックに積まれます。
DIV
使用例 PUSHI 11 PUSHI 22 DIV スタックの上にPUSHI命令で積まれた値11と値22は、DIV命令によって引き算されます。 結果の0.5がスタックに積まれます。
INV
使用例 PUSHI 11 INV スタックの上にPUSHI命令で積まれた値11は、INV命令によって-11と鳴ります。
CMPE
使用例 PUSHI 11 PUSHI 22 CMPE スタックの上にPUSHI命令で積まれた値が比較され、一致しないので0がスタックに積まれます。
CMPNE
使用例 PUSHI 11 PUSHI 22 CMPNE スタックの上にPUSHI命令で積まれた値が比較され、一致しないので1がスタックに積まれます。
CMPGT
使用例 PUSHI 11 PUSHI 22 CMPGT スタックの上にPUSHI命令で積まれた値が比較され、(11 > 22)なので0がスタックに積まれます。
CMPGE
使用例 PUSHI 11 PUSHI 22 CMPGE スタックの上にPUSHI命令で積まれた値が比較され、(11 >= 22)なので0がスタックに積まれます。
CMPLT
使用例 PUSHI 11 PUSHI 22 CMPLT スタックの上にPUSHI命令で積まれた値が比較され、(11 < 22)なので1がスタックに積まれます。
CMPLE
使用例 PUSHI 11 PUSHI 22 CMPLE スタックの上にPUSHI命令で積まれた値が比較され、(11 <= 22)なので1がスタックに積まれます。
AND
使用例 PUSHI 11 PUSHI 22 DUP2 CMPLE CMPNE AND スタックの上にPUSHI命令で積まれた対の値がDUP2で複製され、(11 <= 22) AND (11 != 22)の結果の値1がスタックに積まれます。、
OR
使用例 PUSHI 11 PUSHI 22 DUP2 CMPLE CMPE OR スタックの上にPUSHI命令で積まれた対の値がDUP2で複製され、(11 <= 22) OR (11 == 22)の結果の値1がスタックに積まれます。、
NOT
使用例 PUSHI 11 PUSHI 22 CMPLE NOT スタックの上にPUSHI命令で積まれた対の値がDUP2で複製され、(11 <= 22) の結果を反転させた値0がスタックに積まれます。、
BR オペランド
使用例 BR 100 アドレス100番地に飛びます。
BRZ オペランド
使用例 PUSHI 11 PUSHI 22 CMPE BRZ 100 値11と値22を比較して、一致しないため100番地に飛びます。
STOP
使用例 STOP プログラム実行が終了します。
CALL
使用例 PUSHI closure1 CALL closure1のコード領域の0番地を呼び出して実行します。
RET
使用例 RET RET命令の実行後に呼び出し元に戻ります。
SAVE
1) スタックから最初の(アドレス, クロージャ, 新しい値)を取り出します。 2) 取り出したアドレスとクロージャの指す元の値を取り出します。 3) 取り出したアドレスとクロージャと元の値を、パラメター・スタックに保存します。 4) 取り出したアドレスとクロージャの指す領域に新しい値を書き込みます。 5) パラメター数だけ上記1)から4)を繰り返します。
RESTR
1) パラメター・スタックから最初の(アドレス, クロージャ, 元の値)を取り出します。 2) 取り出したアドレスとクロージャの指す領域に元の値を書き込みます。 3) パラメター数だけ上記1)から2)を繰り返します。
BRK
使用例 PUSHI 20 BRK PUSHI命令でスタックにプッシュした値20まで、データ領域を拡張します。
CLR
使用例 CLR CLR命令でデータ領域をクリアします。
INPUT
使用例 INPUT INPUT命令では、入力した数値、または文字列がスタックにプッシュされます。
PR
使用例 PUSHI 3.1415926 PR PUSHI "I think about it" PR PUSHI命令でスタック上にプッシュされた値が、画面にプリントアウトされます。
NL
使用例 PUSHI 3.1415926 PR NL PUSHI "I think about it" PR NL PUSHI命令でスタック上にプッシュされた値が、画面にプリントアウトされた後に改行されます。
ADDSTR
使用例 PUSHI "STR1" PUSHI "STR2" ADDSTR PR ADDSTR命令でスタック上にプッシュされた"STR1", "STR2"が合わされた"STR1STR2"が表示されます。
SUBSTR
使用例 PUSHI "abcdefghijklmnopqrstuvwxyz" PUSHI 3 PUSHI 3 SUBSTR PR 文字列"abc..."から、3文字目から3文字の長さの"def"が取り出されます。
ISNUM
使用例 PUSHI "abc" ISNUM PR PUSHI 3.14 ISNUM PR 文字列"abc"は数でないので0が表示され、3.14は数なので1が表示されます。
RAND
使用例 PUSHI 10 RAND PR 0からプッシュされた10より小さい範囲の整数の乱数が表示されます。
ERR
error :[実行アドレス] メッセージ
使用例 PUSHI "system error occurs" ERR エラーメッセージ"error:[23] system error occurs"が表示されて終了します。
イベントは、VM内で命令を実行している場合に発生します。 以下にイベントの一覧を示しましょう。
RETURNおよびSTOPイベントは正常な動作の中で発生するイベントであり、その他のイベントは異常時に発生するものです。
EPARM : CALL命令またはRAND命令で、引数の値が誤まっている場合に発生するイベント。 ERROR : 不明なエラーの場合に発生するイベント。通常は発生せずVMの内部エラーで発生する。 ILLADDR : メモリアクセス時に存在しないアドレスをアクセスすると発生するイベント。 ILLAREA : 誤まった領域(コード領域またはデータ領域)へのアクセスで発生するイベント。 ILLCODE : 存在しない命令コードを実行すると発生するイベント。 ILLCLOSURE : 存在しないクロージャにアクセスすると発生するイベント。 NOTADDR : アドレスが必要な命令で、数字以外の値が指定された場合に発生するイベント。 NOTANUM : 浮動小数点数値が必要な場合にそれ以外の値が指定された場合に発生するイベント。 RETURN : クロージャから復帰する場合に発生するイベント。エラーにはならないで呼び出し元のクロージャの処理に復帰する。 STOP : STOP命令が実行されると発生するイベント。VMを正常終了させる。 USTKFLOW : スタックがアンダーフローした場合に発生するイベント。
[PageInfo]
LastUpdate: 2011-03-16 21:40:05, ModifiedBy: hniwa
[License]
Creative Commons 2.1 Attribution
[Permissions]
view:all, edit:login users, delete/config:login users